From f21100ddd700e56c070b8a9d059b595d86ea7f79 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 11 Dec 2024 03:19:08 -0500 Subject: [PATCH 1/5] Roll our own iOS application delegates --- SampleGame.iOS/AppDelegate.cs | 15 ++++++ SampleGame.iOS/Application.cs | 13 ------ SampleGame.iOS/Main.cs | 7 +++ osu.Framework.Tests.iOS/AppDelegate.cs | 14 ++++++ osu.Framework.Tests.iOS/Application.cs | 13 ------ osu.Framework.Tests.iOS/Main.cs | 7 +++ ...lication.cs => GameApplicationDelegate.cs} | 46 +++++++++++-------- 7 files changed, 70 insertions(+), 45 deletions(-) create mode 100644 SampleGame.iOS/AppDelegate.cs delete mode 100644 SampleGame.iOS/Application.cs create mode 100644 SampleGame.iOS/Main.cs create mode 100644 osu.Framework.Tests.iOS/AppDelegate.cs delete mode 100644 osu.Framework.Tests.iOS/Application.cs create mode 100644 osu.Framework.Tests.iOS/Main.cs rename osu.Framework.iOS/{GameApplication.cs => GameApplicationDelegate.cs} (69%) diff --git a/SampleGame.iOS/AppDelegate.cs b/SampleGame.iOS/AppDelegate.cs new file mode 100644 index 0000000000..9712408a08 --- /dev/null +++ b/SampleGame.iOS/AppDelegate.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Foundation; +using osu.Framework; +using osu.Framework.iOS; + +namespace SampleGame.iOS +{ + [Register("AppDelegate")] + public class AppDelegate : GameApplicationDelegate + { + protected override Game CreateGame() => new SampleGameGame(); + } +} diff --git a/SampleGame.iOS/Application.cs b/SampleGame.iOS/Application.cs deleted file mode 100644 index c27e589f7a..0000000000 --- a/SampleGame.iOS/Application.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.iOS; - -namespace SampleGame.iOS -{ - public static class Application - { - // This is the main entry point of the application. - public static void Main(string[] args) => GameApplication.Main(new SampleGameGame()); - } -} diff --git a/SampleGame.iOS/Main.cs b/SampleGame.iOS/Main.cs new file mode 100644 index 0000000000..1ea3a59009 --- /dev/null +++ b/SampleGame.iOS/Main.cs @@ -0,0 +1,7 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using SampleGame.iOS; +using UIKit; + +UIApplication.Main(args, null, typeof(AppDelegate)); diff --git a/osu.Framework.Tests.iOS/AppDelegate.cs b/osu.Framework.Tests.iOS/AppDelegate.cs new file mode 100644 index 0000000000..ada14af271 --- /dev/null +++ b/osu.Framework.Tests.iOS/AppDelegate.cs @@ -0,0 +1,14 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Foundation; +using osu.Framework.iOS; + +namespace osu.Framework.Tests +{ + [Register("AppDelegate")] + public class AppDelegate : GameApplicationDelegate + { + protected override Game CreateGame() => new VisualTestGame(); + } +} diff --git a/osu.Framework.Tests.iOS/Application.cs b/osu.Framework.Tests.iOS/Application.cs deleted file mode 100644 index 7305fe41e6..0000000000 --- a/osu.Framework.Tests.iOS/Application.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.iOS; - -namespace osu.Framework.Tests -{ - public static class Application - { - // This is the main entry point of the application. - public static void Main(string[] args) => GameApplication.Main(new VisualTestGame()); - } -} diff --git a/osu.Framework.Tests.iOS/Main.cs b/osu.Framework.Tests.iOS/Main.cs new file mode 100644 index 0000000000..d8bb8495b7 --- /dev/null +++ b/osu.Framework.Tests.iOS/Main.cs @@ -0,0 +1,7 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Tests; +using UIKit; + +UIApplication.Main(args, null, typeof(AppDelegate)); diff --git a/osu.Framework.iOS/GameApplication.cs b/osu.Framework.iOS/GameApplicationDelegate.cs similarity index 69% rename from osu.Framework.iOS/GameApplication.cs rename to osu.Framework.iOS/GameApplicationDelegate.cs index fa63f297e1..c0a797fa97 100644 --- a/osu.Framework.iOS/GameApplication.cs +++ b/osu.Framework.iOS/GameApplicationDelegate.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using AVFoundation; using Foundation; @@ -10,40 +9,49 @@ using ManagedBass.Fx; using ManagedBass.Mix; using SDL; +using UIKit; +using static SDL.SDL3; namespace osu.Framework.iOS { - public static class GameApplication + /// + /// Base implementation for osu!framework applications. + /// + public abstract class GameApplicationDelegate : UIApplicationDelegate { - private const string output_volume = @"outputVolume"; - private static IOSGameHost host = null!; - private static Game game = null!; + private const string output_volume = "outputVolume"; private static readonly OutputVolumeObserver output_volume_observer = new OutputVolumeObserver(); - public static unsafe void Main(Game target) - { - NativeLibrary.SetDllImportResolver(typeof(Bass).Assembly, (_, assembly, path) => NativeLibrary.Load("@rpath/bass.framework/bass", assembly, path)); - NativeLibrary.SetDllImportResolver(typeof(BassFx).Assembly, (_, assembly, path) => NativeLibrary.Load("@rpath/bass_fx.framework/bass_fx", assembly, path)); - NativeLibrary.SetDllImportResolver(typeof(BassMix).Assembly, (_, assembly, path) => NativeLibrary.Load("@rpath/bassmix.framework/bassmix", assembly, path)); - NativeLibrary.SetDllImportResolver(typeof(SDL3).Assembly, (_, assembly, path) => NativeLibrary.Load("@rpath/SDL3.framework/SDL3", assembly, path)); + private IOSGameHost host = null!; - game = target; + public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions) + { + mapLibraryNames(); - SDL3.SDL_RunApp(0, null, &main, IntPtr.Zero); - } + SDL_SetMainReady(); + SDL_SetiOSEventPump(true); - [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] - private static unsafe int main(int argc, byte** argv) - { var audioSession = AVAudioSession.SharedInstance(); audioSession.AddObserver(output_volume_observer, output_volume, NSKeyValueObservingOptions.New, 0); host = new IOSGameHost(); - host.Run(game); + host.Run(CreateGame()); + return true; + } + + /// + /// Creates the class to launch. + /// + protected abstract Game CreateGame(); - return 0; + private static void mapLibraryNames() + { + NativeLibrary.SetDllImportResolver(typeof(Bass).Assembly, (_, assembly, path) => NativeLibrary.Load("@rpath/bass.framework/bass", assembly, path)); + NativeLibrary.SetDllImportResolver(typeof(BassFx).Assembly, (_, assembly, path) => NativeLibrary.Load("@rpath/bass_fx.framework/bass_fx", assembly, path)); + NativeLibrary.SetDllImportResolver(typeof(BassMix).Assembly, (_, assembly, path) => NativeLibrary.Load("@rpath/bassmix.framework/bassmix", assembly, path)); + NativeLibrary.SetDllImportResolver(typeof(SDL3).Assembly, (_, assembly, path) => NativeLibrary.Load("@rpath/SDL3.framework/SDL3", assembly, path)); } private class OutputVolumeObserver : NSObject From 3134295ba69d5906134f42cf035fa4e944dc27a0 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 11 Dec 2024 03:27:57 -0500 Subject: [PATCH 2/5] Copy drag-drop feature back from SDL's appdelegate class --- osu.Framework.iOS/GameApplicationDelegate.cs | 10 ++++++++++ osu.Framework.iOS/IOSWindow.cs | 3 +++ osu.Framework/Platform/SDL3/SDL3Window.cs | 2 ++ osu.Framework/Platform/SDL3/SDL3Window_Input.cs | 2 +- 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Framework.iOS/GameApplicationDelegate.cs b/osu.Framework.iOS/GameApplicationDelegate.cs index c0a797fa97..a223b6327e 100644 --- a/osu.Framework.iOS/GameApplicationDelegate.cs +++ b/osu.Framework.iOS/GameApplicationDelegate.cs @@ -19,6 +19,7 @@ namespace osu.Framework.iOS /// public abstract class GameApplicationDelegate : UIApplicationDelegate { + internal event Action? DragDrop; private const string output_volume = "outputVolume"; @@ -41,6 +42,15 @@ public override bool FinishedLaunching(UIApplication application, NSDictionary l return true; } + public override bool OpenUrl(UIApplication application, NSUrl url, string sourceApplication, NSObject annotation) + { + // copied verbatim from SDL: https://github.com/libsdl-org/SDL/blob/d252a8fe126b998bd1b0f4e4cf52312cd11de378/src/video/uikit/SDL_uikitappdelegate.m#L508-L535 + // the hope is that the SDL app delegate class does not have such handling exist there, but Apple does not provide a corresponding notification to make that possible. + NSUrl? fileUrl = url.FilePathUrl; + DragDrop?.Invoke(fileUrl != null ? fileUrl.Path! : url.AbsoluteString!); + return true; + } + /// /// Creates the class to launch. /// diff --git a/osu.Framework.iOS/IOSWindow.cs b/osu.Framework.iOS/IOSWindow.cs index 66bc29697f..dfb37a3802 100644 --- a/osu.Framework.iOS/IOSWindow.cs +++ b/osu.Framework.iOS/IOSWindow.cs @@ -47,6 +47,9 @@ public override void Create() uiWindow = Runtime.GetNSObject(WindowHandle); updateSafeArea(); + + var appDelegate = (GameApplicationDelegate)UIApplication.SharedApplication.Delegate; + appDelegate.DragDrop += TriggerDragDrop; } protected override unsafe void RunMainLoop() diff --git a/osu.Framework/Platform/SDL3/SDL3Window.cs b/osu.Framework/Platform/SDL3/SDL3Window.cs index 33e509828f..1932911681 100644 --- a/osu.Framework/Platform/SDL3/SDL3Window.cs +++ b/osu.Framework/Platform/SDL3/SDL3Window.cs @@ -666,6 +666,8 @@ internal virtual void SetIconFromGroup(IconGroup iconGroup) /// public event Action? DragDrop; + protected void TriggerDragDrop(string filename) => DragDrop?.Invoke(filename); + #endregion public void Dispose() diff --git a/osu.Framework/Platform/SDL3/SDL3Window_Input.cs b/osu.Framework/Platform/SDL3/SDL3Window_Input.cs index 7709f531eb..4153f78e2e 100644 --- a/osu.Framework/Platform/SDL3/SDL3Window_Input.cs +++ b/osu.Framework/Platform/SDL3/SDL3Window_Input.cs @@ -217,7 +217,7 @@ private void handleDropEvent(SDL_DropEvent evtDrop) case SDL_EventType.SDL_EVENT_DROP_FILE: string? str = evtDrop.GetData(); if (str != null) - DragDrop?.Invoke(str); + TriggerDragDrop(str); break; } From 3ef75423996bba4ca71552bb1ebf34d80e371234 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 11 Dec 2024 03:28:31 -0500 Subject: [PATCH 3/5] Don't disable UIKit event pump ever Disabling UIKit event pump prevents the app from receiving general app notifications. See: https://github.com/libsdl-org/SDL/blob/45fc548562d7313e0066b99ca7279935e90e4fb1/src/video/uikit/SDL_uikitevents.m#L52-L66 --- osu.Framework.iOS/IOSWindow.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Framework.iOS/IOSWindow.cs b/osu.Framework.iOS/IOSWindow.cs index dfb37a3802..012092f224 100644 --- a/osu.Framework.iOS/IOSWindow.cs +++ b/osu.Framework.iOS/IOSWindow.cs @@ -62,8 +62,6 @@ protected override unsafe void RunMainLoop() // iOS may be a good forward direction if this ever comes up, as a user may see a potentially higher // frame rate with multi-threaded mode turned on, but it is going to give them worse input latency // and higher power usage. - - SDL_SetiOSEventPump(false); SDL_SetiOSAnimationCallback(SDLWindowHandle, 1, &runFrame, ObjectHandle.Handle); } From 0edaede15c62ed0fe0e057633de4dd957d200e05 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 11 Dec 2024 04:31:09 -0500 Subject: [PATCH 4/5] Update templates --- .../template-empty/TemplateGame.iOS/AppDelegate.cs | 12 ++++++++++++ .../template-empty/TemplateGame.iOS/Application.cs | 13 ------------- .../template-empty/TemplateGame.iOS/Main.cs | 4 ++++ .../template-flappy/FlappyDon.iOS/AppDelegate.cs | 12 ++++++++++++ .../template-flappy/FlappyDon.iOS/Application.cs | 10 ---------- .../templates/template-flappy/FlappyDon.iOS/Main.cs | 4 ++++ 6 files changed, 32 insertions(+), 23 deletions(-) create mode 100644 osu.Framework.Templates/templates/template-empty/TemplateGame.iOS/AppDelegate.cs delete mode 100644 osu.Framework.Templates/templates/template-empty/TemplateGame.iOS/Application.cs create mode 100644 osu.Framework.Templates/templates/template-empty/TemplateGame.iOS/Main.cs create mode 100644 osu.Framework.Templates/templates/template-flappy/FlappyDon.iOS/AppDelegate.cs delete mode 100644 osu.Framework.Templates/templates/template-flappy/FlappyDon.iOS/Application.cs create mode 100644 osu.Framework.Templates/templates/template-flappy/FlappyDon.iOS/Main.cs diff --git a/osu.Framework.Templates/templates/template-empty/TemplateGame.iOS/AppDelegate.cs b/osu.Framework.Templates/templates/template-empty/TemplateGame.iOS/AppDelegate.cs new file mode 100644 index 0000000000..89ed9edbbc --- /dev/null +++ b/osu.Framework.Templates/templates/template-empty/TemplateGame.iOS/AppDelegate.cs @@ -0,0 +1,12 @@ +using osu.Framework.iOS; +using TemplateGame.Game; + +namespace TemplateGame.iOS +{ + /// + public class AppDelegate : GameApplicationDelegate + { + /// + protected override osu.Framework.Game CreateGame() => new TemplateGameGame(); + } +} diff --git a/osu.Framework.Templates/templates/template-empty/TemplateGame.iOS/Application.cs b/osu.Framework.Templates/templates/template-empty/TemplateGame.iOS/Application.cs deleted file mode 100644 index 00995cb86f..0000000000 --- a/osu.Framework.Templates/templates/template-empty/TemplateGame.iOS/Application.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.iOS; -using TemplateGame.Game; - -namespace TemplateGame.iOS -{ - public static class Application - { - public static void Main(string[] args) => GameApplication.Main(new TemplateGameGame()); - } -} diff --git a/osu.Framework.Templates/templates/template-empty/TemplateGame.iOS/Main.cs b/osu.Framework.Templates/templates/template-empty/TemplateGame.iOS/Main.cs new file mode 100644 index 0000000000..dde4fe0591 --- /dev/null +++ b/osu.Framework.Templates/templates/template-empty/TemplateGame.iOS/Main.cs @@ -0,0 +1,4 @@ +using TemplateGame.iOS; +using UIKit; + +UIApplication.Main(args, null, typeof(AppDelegate)); diff --git a/osu.Framework.Templates/templates/template-flappy/FlappyDon.iOS/AppDelegate.cs b/osu.Framework.Templates/templates/template-flappy/FlappyDon.iOS/AppDelegate.cs new file mode 100644 index 0000000000..acd6f60330 --- /dev/null +++ b/osu.Framework.Templates/templates/template-flappy/FlappyDon.iOS/AppDelegate.cs @@ -0,0 +1,12 @@ +using FlappyDon.Game; +using osu.Framework.iOS; + +namespace FlappyDon.iOS +{ + /// + public class AppDelegate : GameApplicationDelegate + { + /// + protected override osu.Framework.Game CreateGame() => new FlappyDonGame(); + } +} diff --git a/osu.Framework.Templates/templates/template-flappy/FlappyDon.iOS/Application.cs b/osu.Framework.Templates/templates/template-flappy/FlappyDon.iOS/Application.cs deleted file mode 100644 index 46d6870a8b..0000000000 --- a/osu.Framework.Templates/templates/template-flappy/FlappyDon.iOS/Application.cs +++ /dev/null @@ -1,10 +0,0 @@ -using FlappyDon.Game; -using osu.Framework.iOS; - -namespace FlappyDon.iOS -{ - public static class Application - { - public static void Main(string[] args) => GameApplication.Main(new FlappyDonGame()); - } -} diff --git a/osu.Framework.Templates/templates/template-flappy/FlappyDon.iOS/Main.cs b/osu.Framework.Templates/templates/template-flappy/FlappyDon.iOS/Main.cs new file mode 100644 index 0000000000..a3620d860d --- /dev/null +++ b/osu.Framework.Templates/templates/template-flappy/FlappyDon.iOS/Main.cs @@ -0,0 +1,4 @@ +using FlappyDon.iOS; +using UIKit; + +UIApplication.Main(args, null, typeof(AppDelegate)); From dfbf4b272bf38f168369b7e1e701af0e829032c9 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 11 Dec 2024 04:53:02 -0500 Subject: [PATCH 5/5] Name it `Program` and make it non-top-level to appease CFS --- SampleGame.iOS/Main.cs | 7 ------- SampleGame.iOS/Program.cs | 15 +++++++++++++++ .../template-empty/TemplateGame.iOS/Main.cs | 4 ---- .../template-empty/TemplateGame.iOS/Program.cs | 16 ++++++++++++++++ .../template-flappy/FlappyDon.iOS/Main.cs | 4 ---- .../template-flappy/FlappyDon.iOS/Program.cs | 16 ++++++++++++++++ osu.Framework.Tests.iOS/Main.cs | 7 ------- osu.Framework.Tests.iOS/Program.cs | 15 +++++++++++++++ 8 files changed, 62 insertions(+), 22 deletions(-) delete mode 100644 SampleGame.iOS/Main.cs create mode 100644 SampleGame.iOS/Program.cs delete mode 100644 osu.Framework.Templates/templates/template-empty/TemplateGame.iOS/Main.cs create mode 100644 osu.Framework.Templates/templates/template-empty/TemplateGame.iOS/Program.cs delete mode 100644 osu.Framework.Templates/templates/template-flappy/FlappyDon.iOS/Main.cs create mode 100644 osu.Framework.Templates/templates/template-flappy/FlappyDon.iOS/Program.cs delete mode 100644 osu.Framework.Tests.iOS/Main.cs create mode 100644 osu.Framework.Tests.iOS/Program.cs diff --git a/SampleGame.iOS/Main.cs b/SampleGame.iOS/Main.cs deleted file mode 100644 index 1ea3a59009..0000000000 --- a/SampleGame.iOS/Main.cs +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using SampleGame.iOS; -using UIKit; - -UIApplication.Main(args, null, typeof(AppDelegate)); diff --git a/SampleGame.iOS/Program.cs b/SampleGame.iOS/Program.cs new file mode 100644 index 0000000000..b5dbf419e3 --- /dev/null +++ b/SampleGame.iOS/Program.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using UIKit; + +namespace SampleGame.iOS +{ + public static class Program + { + public static void Main(string[] args) + { + UIApplication.Main(args, null, typeof(AppDelegate)); + } + } +} diff --git a/osu.Framework.Templates/templates/template-empty/TemplateGame.iOS/Main.cs b/osu.Framework.Templates/templates/template-empty/TemplateGame.iOS/Main.cs deleted file mode 100644 index dde4fe0591..0000000000 --- a/osu.Framework.Templates/templates/template-empty/TemplateGame.iOS/Main.cs +++ /dev/null @@ -1,4 +0,0 @@ -using TemplateGame.iOS; -using UIKit; - -UIApplication.Main(args, null, typeof(AppDelegate)); diff --git a/osu.Framework.Templates/templates/template-empty/TemplateGame.iOS/Program.cs b/osu.Framework.Templates/templates/template-empty/TemplateGame.iOS/Program.cs new file mode 100644 index 0000000000..66bdcb5cec --- /dev/null +++ b/osu.Framework.Templates/templates/template-empty/TemplateGame.iOS/Program.cs @@ -0,0 +1,16 @@ +using UIKit; + +namespace TemplateGame.iOS +{ + /// + /// + public static class Program + { + /// + /// + public static void Main(string[] args) + { + UIApplication.Main(args, null, typeof(AppDelegate)); + } + } +} diff --git a/osu.Framework.Templates/templates/template-flappy/FlappyDon.iOS/Main.cs b/osu.Framework.Templates/templates/template-flappy/FlappyDon.iOS/Main.cs deleted file mode 100644 index a3620d860d..0000000000 --- a/osu.Framework.Templates/templates/template-flappy/FlappyDon.iOS/Main.cs +++ /dev/null @@ -1,4 +0,0 @@ -using FlappyDon.iOS; -using UIKit; - -UIApplication.Main(args, null, typeof(AppDelegate)); diff --git a/osu.Framework.Templates/templates/template-flappy/FlappyDon.iOS/Program.cs b/osu.Framework.Templates/templates/template-flappy/FlappyDon.iOS/Program.cs new file mode 100644 index 0000000000..b7e7de184f --- /dev/null +++ b/osu.Framework.Templates/templates/template-flappy/FlappyDon.iOS/Program.cs @@ -0,0 +1,16 @@ +using UIKit; + +namespace FlappyDon.iOS +{ + /// + /// + public static class Program + { + /// + /// + public static void Main(string[] args) + { + UIApplication.Main(args, null, typeof(AppDelegate)); + } + } +} diff --git a/osu.Framework.Tests.iOS/Main.cs b/osu.Framework.Tests.iOS/Main.cs deleted file mode 100644 index d8bb8495b7..0000000000 --- a/osu.Framework.Tests.iOS/Main.cs +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Tests; -using UIKit; - -UIApplication.Main(args, null, typeof(AppDelegate)); diff --git a/osu.Framework.Tests.iOS/Program.cs b/osu.Framework.Tests.iOS/Program.cs new file mode 100644 index 0000000000..717b5e161f --- /dev/null +++ b/osu.Framework.Tests.iOS/Program.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using UIKit; + +namespace osu.Framework.Tests +{ + public static class Program + { + public static void Main(string[] args) + { + UIApplication.Main(args, null, typeof(AppDelegate)); + } + } +}