diff --git a/osu.Framework.Tests/Visual/Platform/TestSceneBorderless.cs b/osu.Framework.Tests/Visual/Platform/TestSceneBorderless.cs index 5508623b5e..7c26a30830 100644 --- a/osu.Framework.Tests/Visual/Platform/TestSceneBorderless.cs +++ b/osu.Framework.Tests/Visual/Platform/TestSceneBorderless.cs @@ -24,7 +24,7 @@ public partial class TestSceneBorderless : FrameworkTestScene private readonly SpriteText currentWindowMode = new SpriteText(); private readonly SpriteText currentDisplay = new SpriteText(); - private SDL2Window? window; + private SDL3Window? window; private readonly Bindable windowMode = new Bindable(); public TestSceneBorderless() @@ -57,7 +57,7 @@ public TestSceneBorderless() [BackgroundDependencyLoader] private void load(FrameworkConfigManager config, GameHost host) { - window = host.Window as SDL2Window; + window = host.Window as SDL3Window; config.BindWith(FrameworkSetting.WindowMode, windowMode); windowMode.BindValueChanged(mode => currentWindowMode.Text = $"Window Mode: {mode.NewValue}", true); diff --git a/osu.Framework.Tests/Visual/Platform/TestSceneCurrentDisplay.cs b/osu.Framework.Tests/Visual/Platform/TestSceneCurrentDisplay.cs index a123e2d8eb..70bae1da46 100644 --- a/osu.Framework.Tests/Visual/Platform/TestSceneCurrentDisplay.cs +++ b/osu.Framework.Tests/Visual/Platform/TestSceneCurrentDisplay.cs @@ -44,7 +44,7 @@ public void TestChangeCurrentDisplay(WindowState startingState) WindowMode startingMode = getWindowModeForState(startingState); - // this shouldn't be necessary, but SDL2DesktopWindow doesn't set the config WindowMode when changing the WindowState only. + // this shouldn't be necessary, but SDL3DesktopWindow doesn't set the config WindowMode when changing the WindowState only. AddStep($"switch to {startingMode}", () => window.WindowMode.Value = startingMode); AddStep($"switch to {startingState}", () => window.WindowState = startingState); diff --git a/osu.Framework.Tests/Visual/Platform/TestSceneFullscreen.cs b/osu.Framework.Tests/Visual/Platform/TestSceneFullscreen.cs index 4f095d921f..e1907944b1 100644 --- a/osu.Framework.Tests/Visual/Platform/TestSceneFullscreen.cs +++ b/osu.Framework.Tests/Visual/Platform/TestSceneFullscreen.cs @@ -129,7 +129,7 @@ public void TestScreenModeSwitch() if (window.SupportedWindowModes.Contains(WindowMode.Fullscreen)) { AddStep("change to fullscreen", () => windowMode.Value = WindowMode.Fullscreen); - AddAssert("window position updated", () => ((SDL2Window)window).Position, () => Is.EqualTo(window.CurrentDisplayBindable.Value.Bounds.Location)); + AddAssert("window position updated", () => ((SDL3Window)window).Position, () => Is.EqualTo(window.CurrentDisplayBindable.Value.Bounds.Location)); testResolution(1920, 1080); testResolution(1280, 960); testResolution(9999, 9999); diff --git a/osu.Framework.Tests/Visual/Platform/TestSceneWindowed.cs b/osu.Framework.Tests/Visual/Platform/TestSceneWindowed.cs index 54c4d087b4..5015a1e4ff 100644 --- a/osu.Framework.Tests/Visual/Platform/TestSceneWindowed.cs +++ b/osu.Framework.Tests/Visual/Platform/TestSceneWindowed.cs @@ -31,12 +31,12 @@ public partial class TestSceneWindowed : FrameworkTestScene [Resolved] private FrameworkConfigManager config { get; set; } - private SDL2Window sdlWindow; + private SDL3Window sdlWindow; [BackgroundDependencyLoader] private void load() { - sdlWindow = (SDL2Window)host.Window; + sdlWindow = (SDL3Window)host.Window; Children = new Drawable[] { new FillFlowContainer diff --git a/osu.Framework.Tests/Visual/Platform/WindowDisplaysPreview.cs b/osu.Framework.Tests/Visual/Platform/WindowDisplaysPreview.cs index 716ab243b6..abb03eae3d 100644 --- a/osu.Framework.Tests/Visual/Platform/WindowDisplaysPreview.cs +++ b/osu.Framework.Tests/Visual/Platform/WindowDisplaysPreview.cs @@ -36,7 +36,7 @@ public partial class WindowDisplaysPreview : Container private static readonly Color4 window_fill = new Color4(95, 113, 197, 255); private static readonly Color4 window_stroke = new Color4(36, 59, 166, 255); - private SDL2Window? window; + private SDL3Window? window; private readonly Bindable windowMode = new Bindable(); private readonly Bindable currentDisplay = new Bindable(); @@ -90,7 +90,7 @@ public WindowDisplaysPreview() [BackgroundDependencyLoader] private void load(FrameworkConfigManager config, GameHost host) { - window = host.Window as SDL2Window; + window = host.Window as SDL3Window; config.BindWith(FrameworkSetting.WindowMode, windowMode); if (window != null) diff --git a/osu.Framework.iOS/GameApplication.cs b/osu.Framework.iOS/GameApplication.cs index e5d8b00905..fa63f297e1 100644 --- a/osu.Framework.iOS/GameApplication.cs +++ b/osu.Framework.iOS/GameApplication.cs @@ -2,14 +2,14 @@ // 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; using ManagedBass; using ManagedBass.Fx; using ManagedBass.Mix; -using ObjCRuntime; -using SDL2; +using SDL; namespace osu.Framework.iOS { @@ -22,20 +22,20 @@ public static class GameApplication private static readonly OutputVolumeObserver output_volume_observer = new OutputVolumeObserver(); - public static void Main(Game target) + 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)); game = target; - SDL.PrepareLibraryForIOS(); - SDL.SDL_UIKitRunApp(0, IntPtr.Zero, main); + SDL3.SDL_RunApp(0, null, &main, IntPtr.Zero); } - [MonoPInvokeCallback(typeof(SDL.SDL_main_func))] - private static int main(int argc, IntPtr argv) + [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); diff --git a/osu.Framework.iOS/IOSGameHost.cs b/osu.Framework.iOS/IOSGameHost.cs index af41bf85c1..4f5c7a3f6b 100644 --- a/osu.Framework.iOS/IOSGameHost.cs +++ b/osu.Framework.iOS/IOSGameHost.cs @@ -21,7 +21,7 @@ namespace osu.Framework.iOS { - public class IOSGameHost : SDL2GameHost + public class IOSGameHost : SDL3GameHost { public IOSGameHost() : base(string.Empty) diff --git a/osu.Framework.iOS/IOSWindow.cs b/osu.Framework.iOS/IOSWindow.cs index 1e0e404ff3..15b2b7683d 100644 --- a/osu.Framework.iOS/IOSWindow.cs +++ b/osu.Framework.iOS/IOSWindow.cs @@ -4,16 +4,18 @@ using System; using System.Diagnostics; using System.Drawing; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using ObjCRuntime; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Platform; -using SDL2; +using SDL; using UIKit; namespace osu.Framework.iOS { - internal class IOSWindow : SDL2Window + internal class IOSWindow : SDL3Window { private UIWindow? window; @@ -34,10 +36,10 @@ public IOSWindow(GraphicsSurfaceType surfaceType) { } - protected override void UpdateWindowStateAndSize(WindowState state, Display display, DisplayMode displayMode) + protected override unsafe void UpdateWindowStateAndSize(WindowState state, Display display, DisplayMode displayMode) { // This sets the status bar to hidden. - SDL.SDL_SetWindowFullscreen(SDLWindowHandle, (uint)SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN); + SDL3.SDL_SetWindowFullscreen(SDLWindowHandle, SDL_bool.SDL_TRUE); // Don't run base logic at all. Let's keep things simple. } @@ -50,7 +52,7 @@ public override void Create() updateSafeArea(); } - protected override void RunMainLoop() + protected override unsafe void RunMainLoop() { // Delegate running the main loop to CADisplayLink. // @@ -60,11 +62,11 @@ protected override 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.SDL_iPhoneSetEventPump(SDL.SDL_bool.SDL_FALSE); - SDL.SDL_iPhoneSetAnimationCallback(SDLWindowHandle, 1, runFrame, ObjectHandle.Handle); + SDL3.SDL_iPhoneSetEventPump(SDL_bool.SDL_FALSE); + SDL3.SDL_iPhoneSetAnimationCallback(SDLWindowHandle, 1, &runFrame, ObjectHandle.Handle); } - [ObjCRuntime.MonoPInvokeCallback(typeof(SDL.SDL_iPhoneAnimationCallback))] + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] private static void runFrame(IntPtr userdata) { var handle = new ObjectHandle(userdata); diff --git a/osu.Framework/Extensions/BridgingExtensions.cs b/osu.Framework/Extensions/BridgingExtensions.cs index 802de679a2..43a50fa64e 100644 --- a/osu.Framework/Extensions/BridgingExtensions.cs +++ b/osu.Framework/Extensions/BridgingExtensions.cs @@ -12,7 +12,7 @@ namespace osu.Framework.Extensions { /// /// Temporary extension functions for bridging between osuTK, System.Drawing, and System.Numerics - /// Can be removed when the SDL2 migration is complete. + /// Can be removed when the SDL3 migration is complete. /// public static class BridgingExtensions { diff --git a/osu.Framework/Extensions/ExtensionMethods.cs b/osu.Framework/Extensions/ExtensionMethods.cs index 43a44877a2..6f9d093742 100644 --- a/osu.Framework/Extensions/ExtensionMethods.cs +++ b/osu.Framework/Extensions/ExtensionMethods.cs @@ -350,7 +350,7 @@ public static string TrimDirectorySeparator(this string path) /// The to convert. /// A structure populated with the corresponding properties. internal static DisplayMode ToDisplayMode(this DisplayResolution resolution) => - new DisplayMode(null, new Size(resolution.Width, resolution.Height), resolution.BitsPerPixel, (int)Math.Round(resolution.RefreshRate), 0); + new DisplayMode(null, new Size(resolution.Width, resolution.Height), resolution.BitsPerPixel, resolution.RefreshRate, 0); /// /// Checks whether the provided URL is a safe protocol to execute a system call with. diff --git a/osu.Framework/Input/Handlers/Joystick/JoystickHandler.cs b/osu.Framework/Input/Handlers/Joystick/JoystickHandler.cs index 997475bfa1..0a0ca30b13 100644 --- a/osu.Framework/Input/Handlers/Joystick/JoystickHandler.cs +++ b/osu.Framework/Input/Handlers/Joystick/JoystickHandler.cs @@ -29,7 +29,7 @@ public override bool Initialize(GameHost host) if (!base.Initialize(host)) return false; - if (!(host.Window is SDL2Window window)) + if (!(host.Window is SDL3Window window)) return false; Enabled.BindValueChanged(e => diff --git a/osu.Framework/Input/Handlers/Keyboard/KeyboardHandler.cs b/osu.Framework/Input/Handlers/Keyboard/KeyboardHandler.cs index 169cc4543c..afe149b698 100644 --- a/osu.Framework/Input/Handlers/Keyboard/KeyboardHandler.cs +++ b/osu.Framework/Input/Handlers/Keyboard/KeyboardHandler.cs @@ -21,7 +21,7 @@ public override bool Initialize(GameHost host) if (!base.Initialize(host)) return false; - if (!(host.Window is SDL2Window window)) + if (!(host.Window is SDL3Window window)) return false; Enabled.BindValueChanged(e => diff --git a/osu.Framework/Input/Handlers/Mouse/MouseHandler.cs b/osu.Framework/Input/Handlers/Mouse/MouseHandler.cs index 9b2c6bff5d..7150404e0d 100644 --- a/osu.Framework/Input/Handlers/Mouse/MouseHandler.cs +++ b/osu.Framework/Input/Handlers/Mouse/MouseHandler.cs @@ -15,7 +15,7 @@ namespace osu.Framework.Input.Handlers.Mouse { /// - /// Handles mouse events from an . + /// Handles mouse events from an . /// Will use relative mouse mode where possible. /// public class MouseHandler : InputHandler, IHasCursorSensitivity, INeedsMousePositionFeedback @@ -41,7 +41,7 @@ public class MouseHandler : InputHandler, IHasCursorSensitivity, INeedsMousePosi public override bool IsActive => true; - private SDL2Window window; + private SDL3Window window; private Vector2? lastPosition; @@ -76,7 +76,7 @@ public override bool Initialize(GameHost host) if (!base.Initialize(host)) return false; - if (!(host.Window is SDL2Window desktopWindow)) + if (!(host.Window is SDL3Window desktopWindow)) return false; window = desktopWindow; diff --git a/osu.Framework/Input/Handlers/Touch/TouchHandler.cs b/osu.Framework/Input/Handlers/Touch/TouchHandler.cs index 7d19eda1c9..372f802729 100644 --- a/osu.Framework/Input/Handlers/Touch/TouchHandler.cs +++ b/osu.Framework/Input/Handlers/Touch/TouchHandler.cs @@ -18,7 +18,7 @@ public override bool Initialize(GameHost host) if (!base.Initialize(host)) return false; - if (!(host.Window is SDL2Window window)) + if (!(host.Window is SDL3Window window)) return false; Enabled.BindValueChanged(enabled => diff --git a/osu.Framework/Input/SDL2WindowTextInput.cs b/osu.Framework/Input/SDL3WindowTextInput.cs similarity index 92% rename from osu.Framework/Input/SDL2WindowTextInput.cs rename to osu.Framework/Input/SDL3WindowTextInput.cs index 21323e0d11..641724f114 100644 --- a/osu.Framework/Input/SDL2WindowTextInput.cs +++ b/osu.Framework/Input/SDL3WindowTextInput.cs @@ -6,11 +6,11 @@ namespace osu.Framework.Input { - internal class SDL2WindowTextInput : TextInputSource + internal class SDL3WindowTextInput : TextInputSource { - private readonly SDL2Window window; + private readonly SDL3Window window; - public SDL2WindowTextInput(SDL2Window window) + public SDL3WindowTextInput(SDL3Window window) { this.window = window; } diff --git a/osu.Framework/Input/UserInputManager.cs b/osu.Framework/Input/UserInputManager.cs index 231d882ff3..a066de1d25 100644 --- a/osu.Framework/Input/UserInputManager.cs +++ b/osu.Framework/Input/UserInputManager.cs @@ -90,7 +90,7 @@ private bool mouseOutsideAllDisplays(Vector2 mousePosition) switch (Host.Window.WindowMode.Value) { case WindowMode.Windowed: - windowLocation = Host.Window is SDL2Window sdlWindow ? sdlWindow.Position : Point.Empty; + windowLocation = Host.Window is SDL3Window sdlWindow ? sdlWindow.Position : Point.Empty; break; default: diff --git a/osu.Framework/Platform/DesktopGameHost.cs b/osu.Framework/Platform/DesktopGameHost.cs index fdacf0d357..7eb308c2fc 100644 --- a/osu.Framework/Platform/DesktopGameHost.cs +++ b/osu.Framework/Platform/DesktopGameHost.cs @@ -13,7 +13,7 @@ namespace osu.Framework.Platform { - public abstract class DesktopGameHost : SDL2GameHost + public abstract class DesktopGameHost : SDL3GameHost { private TcpIpcProvider ipcProvider; private readonly int? ipcPort; diff --git a/osu.Framework/Platform/DisplayMode.cs b/osu.Framework/Platform/DisplayMode.cs index 23e3e2b4f3..902cbc308d 100644 --- a/osu.Framework/Platform/DisplayMode.cs +++ b/osu.Framework/Platform/DisplayMode.cs @@ -29,14 +29,14 @@ namespace osu.Framework.Platform /// /// The refresh rate in hertz. /// - public readonly int RefreshRate; + public readonly float RefreshRate; /// /// The index of the display this mode belongs to as determined by the windowing backend. /// public readonly int DisplayIndex; - public DisplayMode(string? format, Size size, int bitsPerPixel, int refreshRate, int displayIndex) + public DisplayMode(string? format, Size size, int bitsPerPixel, float refreshRate, int displayIndex) { Format = format ?? "Unknown"; Size = size; diff --git a/osu.Framework/Platform/GameHost.cs b/osu.Framework/Platform/GameHost.cs index 7c5b9717d5..43d12550c1 100644 --- a/osu.Framework/Platform/GameHost.cs +++ b/osu.Framework/Platform/GameHost.cs @@ -732,7 +732,7 @@ public void Run(Game game) ChooseAndSetupRenderer(); - // Window creation may fail in the case of a catastrophic failure (ie. graphics driver or SDL2 level). + // Window creation may fail in the case of a catastrophic failure (ie. graphics driver or SDL3 level). // In such cases, we want to throw here to immediately mark this renderer setup as failed. if (RequireWindowExists && Window == null) { @@ -780,7 +780,7 @@ public void Run(Game game) { switch (Window) { - case SDL2Window window: + case SDL3Window window: window.Update += windowUpdate; break; @@ -1323,7 +1323,7 @@ private void updateFrameSyncMode() if (Window == null) return; - int refreshRate = Window.CurrentDisplayMode.Value.RefreshRate; + int refreshRate = (int)MathF.Round(Window.CurrentDisplayMode.Value.RefreshRate); // For invalid refresh rates let's assume 60 Hz as it is most common. if (refreshRate <= 0) diff --git a/osu.Framework/Platform/Linux/LinuxGameHost.cs b/osu.Framework/Platform/Linux/LinuxGameHost.cs index 03a94ab3f9..070c13de75 100644 --- a/osu.Framework/Platform/Linux/LinuxGameHost.cs +++ b/osu.Framework/Platform/Linux/LinuxGameHost.cs @@ -3,10 +3,10 @@ using System.Collections.Generic; using System.Linq; -using SDL2; using osu.Framework.Input; using osu.Framework.Input.Handlers; using osu.Framework.Input.Handlers.Mouse; +using SDL; namespace osu.Framework.Platform.Linux { @@ -29,11 +29,11 @@ internal LinuxGameHost(string gameName, HostOptions? options) protected override void SetupForRun() { - SDL.SDL_SetHint(SDL.SDL_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, BypassCompositor ? "1" : "0"); + SDL3.SDL_SetHint(SDL3.SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, BypassCompositor ? "1"u8 : "0"u8); base.SetupForRun(); } - protected override IWindow CreateWindow(GraphicsSurfaceType preferredSurface) => new SDL2DesktopWindow(preferredSurface); + protected override IWindow CreateWindow(GraphicsSurfaceType preferredSurface) => new SDL3DesktopWindow(preferredSurface); protected override ReadableKeyCombinationProvider CreateReadableKeyCombinationProvider() => new LinuxReadableKeyCombinationProvider(); @@ -43,7 +43,7 @@ protected override IEnumerable CreateAvailableInputHandlers() foreach (var h in handlers.OfType()) { - // There are several bugs we need to fix with Linux / SDL2 cursor handling before switching this on. + // There are several bugs we need to fix with Linux / SDL3 cursor handling before switching this on. h.UseRelativeMode.Value = false; h.UseRelativeMode.Default = false; } diff --git a/osu.Framework/Platform/Linux/LinuxReadableKeyCombinationProvider.cs b/osu.Framework/Platform/Linux/LinuxReadableKeyCombinationProvider.cs index 532ac01df9..36cb71d42c 100644 --- a/osu.Framework/Platform/Linux/LinuxReadableKeyCombinationProvider.cs +++ b/osu.Framework/Platform/Linux/LinuxReadableKeyCombinationProvider.cs @@ -2,12 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Input.Bindings; -using osu.Framework.Platform.SDL2; -using SDL2; +using osu.Framework.Platform.SDL; +using SDL; namespace osu.Framework.Platform.Linux { - public class LinuxReadableKeyCombinationProvider : SDL2ReadableKeyCombinationProvider + public class LinuxReadableKeyCombinationProvider : SDL3ReadableKeyCombinationProvider { protected override string GetReadableKey(InputKey key) { @@ -21,15 +21,15 @@ protected override string GetReadableKey(InputKey key) } } - protected override bool TryGetNameFromKeycode(SDL.SDL_Keycode keycode, out string name) + protected override bool TryGetNameFromKeycode(SDL_Keycode keycode, out string name) { switch (keycode) { - case SDL.SDL_Keycode.SDLK_LGUI: + case SDL_Keycode.SDLK_LGUI: name = "LSuper"; return true; - case SDL.SDL_Keycode.SDLK_RGUI: + case SDL_Keycode.SDLK_RGUI: name = "RSuper"; return true; diff --git a/osu.Framework/Platform/MacOS/MacOSGameHost.cs b/osu.Framework/Platform/MacOS/MacOSGameHost.cs index 9b39b725d8..0cb6f23628 100644 --- a/osu.Framework/Platform/MacOS/MacOSGameHost.cs +++ b/osu.Framework/Platform/MacOS/MacOSGameHost.cs @@ -54,7 +54,7 @@ protected override IEnumerable CreateAvailableInputHandlers() foreach (var h in handlers.OfType()) { - // There are several bugs we need to fix with macOS / SDL2 cursor handling before switching this on. + // There are several bugs we need to fix with macOS / SDL3 cursor handling before switching this on. h.UseRelativeMode.Value = false; h.UseRelativeMode.Default = false; } diff --git a/osu.Framework/Platform/MacOS/MacOSReadableKeyCombinationProvider.cs b/osu.Framework/Platform/MacOS/MacOSReadableKeyCombinationProvider.cs index cf118531a0..de321c54c4 100644 --- a/osu.Framework/Platform/MacOS/MacOSReadableKeyCombinationProvider.cs +++ b/osu.Framework/Platform/MacOS/MacOSReadableKeyCombinationProvider.cs @@ -2,12 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Input.Bindings; -using osu.Framework.Platform.SDL2; -using SDL2; +using osu.Framework.Platform.SDL; +using SDL; namespace osu.Framework.Platform.MacOS { - public class MacOSReadableKeyCombinationProvider : SDL2ReadableKeyCombinationProvider + public class MacOSReadableKeyCombinationProvider : SDL3ReadableKeyCombinationProvider { protected override string GetReadableKey(InputKey key) { @@ -24,23 +24,23 @@ protected override string GetReadableKey(InputKey key) } } - protected override bool TryGetNameFromKeycode(SDL.SDL_Keycode keycode, out string name) + protected override bool TryGetNameFromKeycode(SDL_Keycode keycode, out string name) { switch (keycode) { - case SDL.SDL_Keycode.SDLK_LGUI: + case SDL_Keycode.SDLK_LGUI: name = "LCmd"; return true; - case SDL.SDL_Keycode.SDLK_RGUI: + case SDL_Keycode.SDLK_RGUI: name = "RCmd"; return true; - case SDL.SDL_Keycode.SDLK_LALT: + case SDL_Keycode.SDLK_LALT: name = "LOpt"; return true; - case SDL.SDL_Keycode.SDLK_RALT: + case SDL_Keycode.SDLK_RALT: name = "ROpt"; return true; diff --git a/osu.Framework/Platform/MacOS/MacOSWindow.cs b/osu.Framework/Platform/MacOS/MacOSWindow.cs index 2a17287bfc..d441d4bbc3 100644 --- a/osu.Framework/Platform/MacOS/MacOSWindow.cs +++ b/osu.Framework/Platform/MacOS/MacOSWindow.cs @@ -10,9 +10,9 @@ namespace osu.Framework.Platform.MacOS { /// - /// macOS-specific subclass of . + /// macOS-specific subclass of . /// - internal class MacOSWindow : SDL2DesktopWindow + internal class MacOSWindow : SDL3DesktopWindow { private static readonly IntPtr sel_hasprecisescrollingdeltas = Selector.Get("hasPreciseScrollingDeltas"); private static readonly IntPtr sel_scrollingdeltax = Selector.Get("scrollingDeltaX"); diff --git a/osu.Framework/Platform/OsuTKWindow.cs b/osu.Framework/Platform/OsuTKWindow.cs index 8ae89c9d28..08ae7915f2 100644 --- a/osu.Framework/Platform/OsuTKWindow.cs +++ b/osu.Framework/Platform/OsuTKWindow.cs @@ -130,14 +130,14 @@ public void CancelFlash() /// osuTK's reference to the current instance is private. /// Instead we construct a based on the metrics of , /// as it defers to the current resolution. Note that we round the refresh rate, as osuTK can sometimes - /// report refresh rates such as 59.992863 where SDL2 will report 60. + /// report refresh rates such as 59.992863 where SDL3 will report 60. /// public virtual IBindable CurrentDisplayMode { get { var display = CurrentDisplayDevice; - return new Bindable(new DisplayMode(null, new Size(display.Width, display.Height), display.BitsPerPixel, (int)Math.Round(display.RefreshRate), 0)); + return new Bindable(new DisplayMode(null, new Size(display.Width, display.Height), display.BitsPerPixel, display.RefreshRate, 0)); } } diff --git a/osu.Framework/Platform/SDL2/SDL2Clipboard.cs b/osu.Framework/Platform/SDL/SDL3Clipboard.cs similarity index 64% rename from osu.Framework/Platform/SDL2/SDL2Clipboard.cs rename to osu.Framework/Platform/SDL/SDL3Clipboard.cs index c49cd048fb..a992876388 100644 --- a/osu.Framework/Platform/SDL2/SDL2Clipboard.cs +++ b/osu.Framework/Platform/SDL/SDL3Clipboard.cs @@ -1,19 +1,20 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using SDL2; +using System.Text; +using SDL; using SixLabors.ImageSharp; -namespace osu.Framework.Platform.SDL2 +namespace osu.Framework.Platform.SDL { - public class SDL2Clipboard : Clipboard + public class SDL3Clipboard : Clipboard { // SDL cannot differentiate between string.Empty and no text (eg. empty clipboard or an image) // doesn't matter as text editors don't really allow copying empty strings. // assume that empty text means no text. - public override string? GetText() => SDL.SDL_HasClipboardText() == SDL.SDL_bool.SDL_TRUE ? SDL.SDL_GetClipboardText() : null; + public override string? GetText() => SDL3.SDL_HasClipboardText() == SDL_bool.SDL_TRUE ? SDL3.SDL_GetClipboardText() : null; - public override void SetText(string text) => SDL.SDL_SetClipboardText(text); + public override void SetText(string text) => SDL3.SDL_SetClipboardText(Encoding.UTF8.GetBytes(text)); public override Image? GetImage() { diff --git a/osu.Framework/Platform/SDL/SDL3ControllerBindings.cs b/osu.Framework/Platform/SDL/SDL3ControllerBindings.cs new file mode 100644 index 0000000000..15bbdb55d4 --- /dev/null +++ b/osu.Framework/Platform/SDL/SDL3ControllerBindings.cs @@ -0,0 +1,78 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System; +using SDL; + +namespace osu.Framework.Platform.SDL +{ + /// + /// Maintain a copy of the SDL-provided bindings for the given controller. + /// Used to determine whether a given event's joystick button or axis is unmapped. + /// + internal unsafe class SDL3ControllerBindings + { + public readonly SDL_Joystick* JoystickHandle; + public readonly SDL_Gamepad* GamepadHandle; + + /// + /// Bindings returned from . + /// Empty if the joystick does not have a corresponding GamepadHandle. + /// + public SDL_GamepadBinding[] Bindings; + + public SDL3ControllerBindings(SDL_Joystick* joystickHandle, SDL_Gamepad* gamepadHandle) + { + JoystickHandle = joystickHandle; + GamepadHandle = gamepadHandle; + + PopulateBindings(); + } + + public void PopulateBindings() + { + if (GamepadHandle == null) + { + Bindings = Array.Empty(); + return; + } + + using var bindings = SDL3.SDL_GetGamepadBindings(GamepadHandle); + + if (bindings == null) + { + Bindings = Array.Empty(); + return; + } + + Bindings = new SDL_GamepadBinding[bindings.Count]; + + for (int i = 0; i < bindings.Count; i++) + Bindings[i] = bindings[i]; + } + + public bool IsJoystickButtonBound(byte buttonIndex) + { + for (int i = 0; i < Bindings.Length; i++) + { + if (Bindings[i].input_type == SDL_GamepadBindingType.SDL_GAMEPAD_BINDTYPE_BUTTON && Bindings[i].input.button == buttonIndex) + return true; + } + + return false; + } + + public bool IsJoystickAxisBound(byte axisIndex) + { + for (int i = 0; i < Bindings.Length; i++) + { + if (Bindings[i].input_type == SDL_GamepadBindingType.SDL_GAMEPAD_BINDTYPE_AXIS && Bindings[i].input.axis.axis == axisIndex) + return true; + } + + return false; + } + } +} diff --git a/osu.Framework/Platform/SDL/SDL3Extensions.cs b/osu.Framework/Platform/SDL/SDL3Extensions.cs new file mode 100644 index 0000000000..abf451ce40 --- /dev/null +++ b/osu.Framework/Platform/SDL/SDL3Extensions.cs @@ -0,0 +1,1113 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Diagnostics.CodeAnalysis; +using System.Drawing; +using osu.Framework.Extensions.EnumExtensions; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; +using osuTK.Input; +using SDL; + +namespace osu.Framework.Platform.SDL +{ + public static class SDL3Extensions + { + public static Key ToKey(this SDL_Keysym sdlKeysym) + { + // Apple devices don't have the notion of NumLock (they have a Clear key instead). + // treat them as if they always have NumLock on (the numpad always performs its primary actions). + bool numLockOn = sdlKeysym.Mod.HasFlagFast(SDL_Keymod.SDL_KMOD_NUM) || RuntimeInfo.IsApple; + + switch (sdlKeysym.scancode) + { + default: + case SDL_Scancode.SDL_SCANCODE_UNKNOWN: + return Key.Unknown; + + case SDL_Scancode.SDL_SCANCODE_KP_COMMA: + return Key.Comma; + + case SDL_Scancode.SDL_SCANCODE_KP_TAB: + return Key.Tab; + + case SDL_Scancode.SDL_SCANCODE_KP_BACKSPACE: + return Key.BackSpace; + + case SDL_Scancode.SDL_SCANCODE_KP_A: + return Key.A; + + case SDL_Scancode.SDL_SCANCODE_KP_B: + return Key.B; + + case SDL_Scancode.SDL_SCANCODE_KP_C: + return Key.C; + + case SDL_Scancode.SDL_SCANCODE_KP_D: + return Key.D; + + case SDL_Scancode.SDL_SCANCODE_KP_E: + return Key.E; + + case SDL_Scancode.SDL_SCANCODE_KP_F: + return Key.F; + + case SDL_Scancode.SDL_SCANCODE_KP_SPACE: + return Key.Space; + + case SDL_Scancode.SDL_SCANCODE_KP_CLEAR: + return Key.Clear; + + case SDL_Scancode.SDL_SCANCODE_RETURN: + return Key.Enter; + + case SDL_Scancode.SDL_SCANCODE_ESCAPE: + return Key.Escape; + + case SDL_Scancode.SDL_SCANCODE_BACKSPACE: + return Key.BackSpace; + + case SDL_Scancode.SDL_SCANCODE_TAB: + return Key.Tab; + + case SDL_Scancode.SDL_SCANCODE_SPACE: + return Key.Space; + + case SDL_Scancode.SDL_SCANCODE_APOSTROPHE: + return Key.Quote; + + case SDL_Scancode.SDL_SCANCODE_COMMA: + return Key.Comma; + + case SDL_Scancode.SDL_SCANCODE_MINUS: + return Key.Minus; + + case SDL_Scancode.SDL_SCANCODE_PERIOD: + return Key.Period; + + case SDL_Scancode.SDL_SCANCODE_SLASH: + return Key.Slash; + + case SDL_Scancode.SDL_SCANCODE_0: + return Key.Number0; + + case SDL_Scancode.SDL_SCANCODE_1: + return Key.Number1; + + case SDL_Scancode.SDL_SCANCODE_2: + return Key.Number2; + + case SDL_Scancode.SDL_SCANCODE_3: + return Key.Number3; + + case SDL_Scancode.SDL_SCANCODE_4: + return Key.Number4; + + case SDL_Scancode.SDL_SCANCODE_5: + return Key.Number5; + + case SDL_Scancode.SDL_SCANCODE_6: + return Key.Number6; + + case SDL_Scancode.SDL_SCANCODE_7: + return Key.Number7; + + case SDL_Scancode.SDL_SCANCODE_8: + return Key.Number8; + + case SDL_Scancode.SDL_SCANCODE_9: + return Key.Number9; + + case SDL_Scancode.SDL_SCANCODE_SEMICOLON: + return Key.Semicolon; + + case SDL_Scancode.SDL_SCANCODE_EQUALS: + return Key.Plus; + + case SDL_Scancode.SDL_SCANCODE_LEFTBRACKET: + return Key.BracketLeft; + + case SDL_Scancode.SDL_SCANCODE_BACKSLASH: + return Key.BackSlash; + + case SDL_Scancode.SDL_SCANCODE_RIGHTBRACKET: + return Key.BracketRight; + + case SDL_Scancode.SDL_SCANCODE_GRAVE: + return Key.Tilde; + + case SDL_Scancode.SDL_SCANCODE_A: + return Key.A; + + case SDL_Scancode.SDL_SCANCODE_B: + return Key.B; + + case SDL_Scancode.SDL_SCANCODE_C: + return Key.C; + + case SDL_Scancode.SDL_SCANCODE_D: + return Key.D; + + case SDL_Scancode.SDL_SCANCODE_E: + return Key.E; + + case SDL_Scancode.SDL_SCANCODE_F: + return Key.F; + + case SDL_Scancode.SDL_SCANCODE_G: + return Key.G; + + case SDL_Scancode.SDL_SCANCODE_H: + return Key.H; + + case SDL_Scancode.SDL_SCANCODE_I: + return Key.I; + + case SDL_Scancode.SDL_SCANCODE_J: + return Key.J; + + case SDL_Scancode.SDL_SCANCODE_K: + return Key.K; + + case SDL_Scancode.SDL_SCANCODE_L: + return Key.L; + + case SDL_Scancode.SDL_SCANCODE_M: + return Key.M; + + case SDL_Scancode.SDL_SCANCODE_N: + return Key.N; + + case SDL_Scancode.SDL_SCANCODE_O: + return Key.O; + + case SDL_Scancode.SDL_SCANCODE_P: + return Key.P; + + case SDL_Scancode.SDL_SCANCODE_Q: + return Key.Q; + + case SDL_Scancode.SDL_SCANCODE_R: + return Key.R; + + case SDL_Scancode.SDL_SCANCODE_S: + return Key.S; + + case SDL_Scancode.SDL_SCANCODE_T: + return Key.T; + + case SDL_Scancode.SDL_SCANCODE_U: + return Key.U; + + case SDL_Scancode.SDL_SCANCODE_V: + return Key.V; + + case SDL_Scancode.SDL_SCANCODE_W: + return Key.W; + + case SDL_Scancode.SDL_SCANCODE_X: + return Key.X; + + case SDL_Scancode.SDL_SCANCODE_Y: + return Key.Y; + + case SDL_Scancode.SDL_SCANCODE_Z: + return Key.Z; + + case SDL_Scancode.SDL_SCANCODE_CAPSLOCK: + return Key.CapsLock; + + case SDL_Scancode.SDL_SCANCODE_F1: + return Key.F1; + + case SDL_Scancode.SDL_SCANCODE_F2: + return Key.F2; + + case SDL_Scancode.SDL_SCANCODE_F3: + return Key.F3; + + case SDL_Scancode.SDL_SCANCODE_F4: + return Key.F4; + + case SDL_Scancode.SDL_SCANCODE_F5: + return Key.F5; + + case SDL_Scancode.SDL_SCANCODE_F6: + return Key.F6; + + case SDL_Scancode.SDL_SCANCODE_F7: + return Key.F7; + + case SDL_Scancode.SDL_SCANCODE_F8: + return Key.F8; + + case SDL_Scancode.SDL_SCANCODE_F9: + return Key.F9; + + case SDL_Scancode.SDL_SCANCODE_F10: + return Key.F10; + + case SDL_Scancode.SDL_SCANCODE_F11: + return Key.F11; + + case SDL_Scancode.SDL_SCANCODE_F12: + return Key.F12; + + case SDL_Scancode.SDL_SCANCODE_PRINTSCREEN: + return Key.PrintScreen; + + case SDL_Scancode.SDL_SCANCODE_SCROLLLOCK: + return Key.ScrollLock; + + case SDL_Scancode.SDL_SCANCODE_PAUSE: + return Key.Pause; + + case SDL_Scancode.SDL_SCANCODE_INSERT: + return Key.Insert; + + case SDL_Scancode.SDL_SCANCODE_HOME: + return Key.Home; + + case SDL_Scancode.SDL_SCANCODE_PAGEUP: + return Key.PageUp; + + case SDL_Scancode.SDL_SCANCODE_DELETE: + return Key.Delete; + + case SDL_Scancode.SDL_SCANCODE_END: + return Key.End; + + case SDL_Scancode.SDL_SCANCODE_PAGEDOWN: + return Key.PageDown; + + case SDL_Scancode.SDL_SCANCODE_RIGHT: + return Key.Right; + + case SDL_Scancode.SDL_SCANCODE_LEFT: + return Key.Left; + + case SDL_Scancode.SDL_SCANCODE_DOWN: + return Key.Down; + + case SDL_Scancode.SDL_SCANCODE_UP: + return Key.Up; + + case SDL_Scancode.SDL_SCANCODE_NUMLOCKCLEAR: + return Key.NumLock; + + case SDL_Scancode.SDL_SCANCODE_KP_DIVIDE: + return Key.KeypadDivide; + + case SDL_Scancode.SDL_SCANCODE_KP_MULTIPLY: + return Key.KeypadMultiply; + + case SDL_Scancode.SDL_SCANCODE_KP_MINUS: + return Key.KeypadMinus; + + case SDL_Scancode.SDL_SCANCODE_KP_PLUS: + return Key.KeypadPlus; + + case SDL_Scancode.SDL_SCANCODE_KP_ENTER: + return Key.KeypadEnter; + + case SDL_Scancode.SDL_SCANCODE_KP_1: + return numLockOn ? Key.Keypad1 : Key.End; + + case SDL_Scancode.SDL_SCANCODE_KP_2: + return numLockOn ? Key.Keypad2 : Key.Down; + + case SDL_Scancode.SDL_SCANCODE_KP_3: + return numLockOn ? Key.Keypad3 : Key.PageDown; + + case SDL_Scancode.SDL_SCANCODE_KP_4: + return numLockOn ? Key.Keypad4 : Key.Left; + + case SDL_Scancode.SDL_SCANCODE_KP_5: + return numLockOn ? Key.Keypad5 : Key.Clear; + + case SDL_Scancode.SDL_SCANCODE_KP_6: + return numLockOn ? Key.Keypad6 : Key.Right; + + case SDL_Scancode.SDL_SCANCODE_KP_7: + return numLockOn ? Key.Keypad7 : Key.Home; + + case SDL_Scancode.SDL_SCANCODE_KP_8: + return numLockOn ? Key.Keypad8 : Key.Up; + + case SDL_Scancode.SDL_SCANCODE_KP_9: + return numLockOn ? Key.Keypad9 : Key.PageUp; + + case SDL_Scancode.SDL_SCANCODE_KP_0: + return numLockOn ? Key.Keypad0 : Key.Insert; + + case SDL_Scancode.SDL_SCANCODE_KP_PERIOD: + return numLockOn ? Key.KeypadPeriod : Key.Delete; + + case SDL_Scancode.SDL_SCANCODE_NONUSBACKSLASH: + return Key.NonUSBackSlash; + + case SDL_Scancode.SDL_SCANCODE_F13: + return Key.F13; + + case SDL_Scancode.SDL_SCANCODE_F14: + return Key.F14; + + case SDL_Scancode.SDL_SCANCODE_F15: + return Key.F15; + + case SDL_Scancode.SDL_SCANCODE_F16: + return Key.F16; + + case SDL_Scancode.SDL_SCANCODE_F17: + return Key.F17; + + case SDL_Scancode.SDL_SCANCODE_F18: + return Key.F18; + + case SDL_Scancode.SDL_SCANCODE_F19: + return Key.F19; + + case SDL_Scancode.SDL_SCANCODE_F20: + return Key.F20; + + case SDL_Scancode.SDL_SCANCODE_F21: + return Key.F21; + + case SDL_Scancode.SDL_SCANCODE_F22: + return Key.F22; + + case SDL_Scancode.SDL_SCANCODE_F23: + return Key.F23; + + case SDL_Scancode.SDL_SCANCODE_F24: + return Key.F24; + + case SDL_Scancode.SDL_SCANCODE_MENU: + case SDL_Scancode.SDL_SCANCODE_APPLICATION: + return Key.Menu; + + case SDL_Scancode.SDL_SCANCODE_STOP: + return Key.Stop; + + case SDL_Scancode.SDL_SCANCODE_MUTE: + return Key.Mute; + + case SDL_Scancode.SDL_SCANCODE_VOLUMEUP: + return Key.VolumeUp; + + case SDL_Scancode.SDL_SCANCODE_VOLUMEDOWN: + return Key.VolumeDown; + + case SDL_Scancode.SDL_SCANCODE_CLEAR: + return Key.Clear; + + case SDL_Scancode.SDL_SCANCODE_DECIMALSEPARATOR: + return Key.KeypadDecimal; + + case SDL_Scancode.SDL_SCANCODE_LCTRL: + return Key.ControlLeft; + + case SDL_Scancode.SDL_SCANCODE_LSHIFT: + return Key.ShiftLeft; + + case SDL_Scancode.SDL_SCANCODE_LALT: + return Key.AltLeft; + + case SDL_Scancode.SDL_SCANCODE_LGUI: + return Key.WinLeft; + + case SDL_Scancode.SDL_SCANCODE_RCTRL: + return Key.ControlRight; + + case SDL_Scancode.SDL_SCANCODE_RSHIFT: + return Key.ShiftRight; + + case SDL_Scancode.SDL_SCANCODE_RALT: + return Key.AltRight; + + case SDL_Scancode.SDL_SCANCODE_RGUI: + return Key.WinRight; + + case SDL_Scancode.SDL_SCANCODE_AUDIONEXT: + return Key.TrackNext; + + case SDL_Scancode.SDL_SCANCODE_AUDIOPREV: + return Key.TrackPrevious; + + case SDL_Scancode.SDL_SCANCODE_AUDIOSTOP: + return Key.Stop; + + case SDL_Scancode.SDL_SCANCODE_AUDIOPLAY: + return Key.PlayPause; + + case SDL_Scancode.SDL_SCANCODE_AUDIOMUTE: + return Key.Mute; + + case SDL_Scancode.SDL_SCANCODE_SLEEP: + return Key.Sleep; + } + } + + /// + /// Returns the corresponding for a given . + /// + /// + /// Should be a keyboard key. + /// + /// + /// The corresponding if the is valid. + /// otherwise. + /// + public static SDL_Scancode ToScancode(this InputKey inputKey) + { + switch (inputKey) + { + default: + case InputKey.Shift: + case InputKey.Control: + case InputKey.Alt: + case InputKey.Super: + case InputKey.F25: + case InputKey.F26: + case InputKey.F27: + case InputKey.F28: + case InputKey.F29: + case InputKey.F30: + case InputKey.F31: + case InputKey.F32: + case InputKey.F33: + case InputKey.F34: + case InputKey.F35: + case InputKey.Clear: + return SDL_Scancode.SDL_SCANCODE_UNKNOWN; + + case InputKey.Menu: + return SDL_Scancode.SDL_SCANCODE_MENU; + + case InputKey.F1: + return SDL_Scancode.SDL_SCANCODE_F1; + + case InputKey.F2: + return SDL_Scancode.SDL_SCANCODE_F2; + + case InputKey.F3: + return SDL_Scancode.SDL_SCANCODE_F3; + + case InputKey.F4: + return SDL_Scancode.SDL_SCANCODE_F4; + + case InputKey.F5: + return SDL_Scancode.SDL_SCANCODE_F5; + + case InputKey.F6: + return SDL_Scancode.SDL_SCANCODE_F6; + + case InputKey.F7: + return SDL_Scancode.SDL_SCANCODE_F7; + + case InputKey.F8: + return SDL_Scancode.SDL_SCANCODE_F8; + + case InputKey.F9: + return SDL_Scancode.SDL_SCANCODE_F9; + + case InputKey.F10: + return SDL_Scancode.SDL_SCANCODE_F10; + + case InputKey.F11: + return SDL_Scancode.SDL_SCANCODE_F11; + + case InputKey.F12: + return SDL_Scancode.SDL_SCANCODE_F12; + + case InputKey.F13: + return SDL_Scancode.SDL_SCANCODE_F13; + + case InputKey.F14: + return SDL_Scancode.SDL_SCANCODE_F14; + + case InputKey.F15: + return SDL_Scancode.SDL_SCANCODE_F15; + + case InputKey.F16: + return SDL_Scancode.SDL_SCANCODE_F16; + + case InputKey.F17: + return SDL_Scancode.SDL_SCANCODE_F17; + + case InputKey.F18: + return SDL_Scancode.SDL_SCANCODE_F18; + + case InputKey.F19: + return SDL_Scancode.SDL_SCANCODE_F19; + + case InputKey.F20: + return SDL_Scancode.SDL_SCANCODE_F20; + + case InputKey.F21: + return SDL_Scancode.SDL_SCANCODE_F21; + + case InputKey.F22: + return SDL_Scancode.SDL_SCANCODE_F22; + + case InputKey.F23: + return SDL_Scancode.SDL_SCANCODE_F23; + + case InputKey.F24: + return SDL_Scancode.SDL_SCANCODE_F24; + + case InputKey.Up: + return SDL_Scancode.SDL_SCANCODE_UP; + + case InputKey.Down: + return SDL_Scancode.SDL_SCANCODE_DOWN; + + case InputKey.Left: + return SDL_Scancode.SDL_SCANCODE_LEFT; + + case InputKey.Right: + return SDL_Scancode.SDL_SCANCODE_RIGHT; + + case InputKey.Enter: + return SDL_Scancode.SDL_SCANCODE_RETURN; + + case InputKey.Escape: + return SDL_Scancode.SDL_SCANCODE_ESCAPE; + + case InputKey.Space: + return SDL_Scancode.SDL_SCANCODE_SPACE; + + case InputKey.Tab: + return SDL_Scancode.SDL_SCANCODE_TAB; + + case InputKey.BackSpace: + return SDL_Scancode.SDL_SCANCODE_BACKSPACE; + + case InputKey.Insert: + return SDL_Scancode.SDL_SCANCODE_INSERT; + + case InputKey.Delete: + return SDL_Scancode.SDL_SCANCODE_DELETE; + + case InputKey.PageUp: + return SDL_Scancode.SDL_SCANCODE_PAGEUP; + + case InputKey.PageDown: + return SDL_Scancode.SDL_SCANCODE_PAGEDOWN; + + case InputKey.Home: + return SDL_Scancode.SDL_SCANCODE_HOME; + + case InputKey.End: + return SDL_Scancode.SDL_SCANCODE_END; + + case InputKey.CapsLock: + return SDL_Scancode.SDL_SCANCODE_CAPSLOCK; + + case InputKey.ScrollLock: + return SDL_Scancode.SDL_SCANCODE_SCROLLLOCK; + + case InputKey.PrintScreen: + return SDL_Scancode.SDL_SCANCODE_PRINTSCREEN; + + case InputKey.Pause: + return SDL_Scancode.SDL_SCANCODE_PAUSE; + + case InputKey.NumLock: + return SDL_Scancode.SDL_SCANCODE_NUMLOCKCLEAR; + + case InputKey.Sleep: + return SDL_Scancode.SDL_SCANCODE_SLEEP; + + case InputKey.Keypad0: + return SDL_Scancode.SDL_SCANCODE_KP_0; + + case InputKey.Keypad1: + return SDL_Scancode.SDL_SCANCODE_KP_1; + + case InputKey.Keypad2: + return SDL_Scancode.SDL_SCANCODE_KP_2; + + case InputKey.Keypad3: + return SDL_Scancode.SDL_SCANCODE_KP_3; + + case InputKey.Keypad4: + return SDL_Scancode.SDL_SCANCODE_KP_4; + + case InputKey.Keypad5: + return SDL_Scancode.SDL_SCANCODE_KP_5; + + case InputKey.Keypad6: + return SDL_Scancode.SDL_SCANCODE_KP_6; + + case InputKey.Keypad7: + return SDL_Scancode.SDL_SCANCODE_KP_7; + + case InputKey.Keypad8: + return SDL_Scancode.SDL_SCANCODE_KP_8; + + case InputKey.Keypad9: + return SDL_Scancode.SDL_SCANCODE_KP_9; + + case InputKey.KeypadDivide: + return SDL_Scancode.SDL_SCANCODE_KP_DIVIDE; + + case InputKey.KeypadMultiply: + return SDL_Scancode.SDL_SCANCODE_KP_MULTIPLY; + + case InputKey.KeypadMinus: + return SDL_Scancode.SDL_SCANCODE_KP_MINUS; + + case InputKey.KeypadPlus: + return SDL_Scancode.SDL_SCANCODE_KP_PLUS; + + case InputKey.KeypadPeriod: + return SDL_Scancode.SDL_SCANCODE_KP_PERIOD; + + case InputKey.KeypadEnter: + return SDL_Scancode.SDL_SCANCODE_KP_ENTER; + + case InputKey.A: + return SDL_Scancode.SDL_SCANCODE_A; + + case InputKey.B: + return SDL_Scancode.SDL_SCANCODE_B; + + case InputKey.C: + return SDL_Scancode.SDL_SCANCODE_C; + + case InputKey.D: + return SDL_Scancode.SDL_SCANCODE_D; + + case InputKey.E: + return SDL_Scancode.SDL_SCANCODE_E; + + case InputKey.F: + return SDL_Scancode.SDL_SCANCODE_F; + + case InputKey.G: + return SDL_Scancode.SDL_SCANCODE_G; + + case InputKey.H: + return SDL_Scancode.SDL_SCANCODE_H; + + case InputKey.I: + return SDL_Scancode.SDL_SCANCODE_I; + + case InputKey.J: + return SDL_Scancode.SDL_SCANCODE_J; + + case InputKey.K: + return SDL_Scancode.SDL_SCANCODE_K; + + case InputKey.L: + return SDL_Scancode.SDL_SCANCODE_L; + + case InputKey.M: + return SDL_Scancode.SDL_SCANCODE_M; + + case InputKey.N: + return SDL_Scancode.SDL_SCANCODE_N; + + case InputKey.O: + return SDL_Scancode.SDL_SCANCODE_O; + + case InputKey.P: + return SDL_Scancode.SDL_SCANCODE_P; + + case InputKey.Q: + return SDL_Scancode.SDL_SCANCODE_Q; + + case InputKey.R: + return SDL_Scancode.SDL_SCANCODE_R; + + case InputKey.S: + return SDL_Scancode.SDL_SCANCODE_S; + + case InputKey.T: + return SDL_Scancode.SDL_SCANCODE_T; + + case InputKey.U: + return SDL_Scancode.SDL_SCANCODE_U; + + case InputKey.V: + return SDL_Scancode.SDL_SCANCODE_V; + + case InputKey.W: + return SDL_Scancode.SDL_SCANCODE_W; + + case InputKey.X: + return SDL_Scancode.SDL_SCANCODE_X; + + case InputKey.Y: + return SDL_Scancode.SDL_SCANCODE_Y; + + case InputKey.Z: + return SDL_Scancode.SDL_SCANCODE_Z; + + case InputKey.Number0: + return SDL_Scancode.SDL_SCANCODE_0; + + case InputKey.Number1: + return SDL_Scancode.SDL_SCANCODE_1; + + case InputKey.Number2: + return SDL_Scancode.SDL_SCANCODE_2; + + case InputKey.Number3: + return SDL_Scancode.SDL_SCANCODE_3; + + case InputKey.Number4: + return SDL_Scancode.SDL_SCANCODE_4; + + case InputKey.Number5: + return SDL_Scancode.SDL_SCANCODE_5; + + case InputKey.Number6: + return SDL_Scancode.SDL_SCANCODE_6; + + case InputKey.Number7: + return SDL_Scancode.SDL_SCANCODE_7; + + case InputKey.Number8: + return SDL_Scancode.SDL_SCANCODE_8; + + case InputKey.Number9: + return SDL_Scancode.SDL_SCANCODE_9; + + case InputKey.Grave: + return SDL_Scancode.SDL_SCANCODE_GRAVE; + + case InputKey.Minus: + return SDL_Scancode.SDL_SCANCODE_MINUS; + + case InputKey.Plus: + return SDL_Scancode.SDL_SCANCODE_EQUALS; + + case InputKey.BracketLeft: + return SDL_Scancode.SDL_SCANCODE_LEFTBRACKET; + + case InputKey.BracketRight: + return SDL_Scancode.SDL_SCANCODE_RIGHTBRACKET; + + case InputKey.Semicolon: + return SDL_Scancode.SDL_SCANCODE_SEMICOLON; + + case InputKey.Quote: + return SDL_Scancode.SDL_SCANCODE_APOSTROPHE; + + case InputKey.Comma: + return SDL_Scancode.SDL_SCANCODE_COMMA; + + case InputKey.Period: + return SDL_Scancode.SDL_SCANCODE_PERIOD; + + case InputKey.Slash: + return SDL_Scancode.SDL_SCANCODE_SLASH; + + case InputKey.BackSlash: + return SDL_Scancode.SDL_SCANCODE_BACKSLASH; + + case InputKey.NonUSBackSlash: + return SDL_Scancode.SDL_SCANCODE_NONUSBACKSLASH; + + case InputKey.Mute: + return SDL_Scancode.SDL_SCANCODE_AUDIOMUTE; + + case InputKey.PlayPause: + return SDL_Scancode.SDL_SCANCODE_AUDIOPLAY; + + case InputKey.Stop: + return SDL_Scancode.SDL_SCANCODE_AUDIOSTOP; + + case InputKey.VolumeUp: + return SDL_Scancode.SDL_SCANCODE_VOLUMEUP; + + case InputKey.VolumeDown: + return SDL_Scancode.SDL_SCANCODE_VOLUMEDOWN; + + case InputKey.TrackPrevious: + return SDL_Scancode.SDL_SCANCODE_AUDIOPREV; + + case InputKey.TrackNext: + return SDL_Scancode.SDL_SCANCODE_AUDIONEXT; + + case InputKey.LShift: + return SDL_Scancode.SDL_SCANCODE_LSHIFT; + + case InputKey.RShift: + return SDL_Scancode.SDL_SCANCODE_RSHIFT; + + case InputKey.LControl: + return SDL_Scancode.SDL_SCANCODE_LCTRL; + + case InputKey.RControl: + return SDL_Scancode.SDL_SCANCODE_RCTRL; + + case InputKey.LAlt: + return SDL_Scancode.SDL_SCANCODE_LALT; + + case InputKey.RAlt: + return SDL_Scancode.SDL_SCANCODE_RALT; + + case InputKey.LSuper: + return SDL_Scancode.SDL_SCANCODE_LGUI; + + case InputKey.RSuper: + return SDL_Scancode.SDL_SCANCODE_RGUI; + } + } + + public static WindowState ToWindowState(this SDL_WindowFlags windowFlags) + { + if (windowFlags.HasFlagFast(SDL_WindowFlags.SDL_WINDOW_BORDERLESS)) + return WindowState.FullscreenBorderless; + + if (windowFlags.HasFlagFast(SDL_WindowFlags.SDL_WINDOW_MINIMIZED)) + return WindowState.Minimised; + + if (windowFlags.HasFlagFast(SDL_WindowFlags.SDL_WINDOW_FULLSCREEN)) + return WindowState.Fullscreen; + + if (windowFlags.HasFlagFast(SDL_WindowFlags.SDL_WINDOW_MAXIMIZED)) + return WindowState.Maximised; + + return WindowState.Normal; + } + + public static SDL_WindowFlags ToFlags(this WindowState state) + { + switch (state) + { + case WindowState.Normal: + return 0; + + case WindowState.Fullscreen: + return SDL_WindowFlags.SDL_WINDOW_FULLSCREEN; + + case WindowState.Maximised: + return SDL_WindowFlags.SDL_WINDOW_MAXIMIZED; + + case WindowState.Minimised: + return SDL_WindowFlags.SDL_WINDOW_MINIMIZED; + + case WindowState.FullscreenBorderless: + return SDL_WindowFlags.SDL_WINDOW_BORDERLESS; + } + + return 0; + } + + public static SDL_WindowFlags ToFlags(this GraphicsSurfaceType surfaceType) + { + switch (surfaceType) + { + case GraphicsSurfaceType.OpenGL: + return SDL_WindowFlags.SDL_WINDOW_OPENGL; + + case GraphicsSurfaceType.Vulkan when !RuntimeInfo.IsApple: + return SDL_WindowFlags.SDL_WINDOW_VULKAN; + + case GraphicsSurfaceType.Metal: + case GraphicsSurfaceType.Vulkan when RuntimeInfo.IsApple: + return SDL_WindowFlags.SDL_WINDOW_METAL; + } + + return 0; + } + + public static JoystickAxisSource ToJoystickAxisSource(this SDL_GamepadAxis axis) + { + switch (axis) + { + default: + case SDL_GamepadAxis.SDL_GAMEPAD_AXIS_INVALID: + return 0; + + case SDL_GamepadAxis.SDL_GAMEPAD_AXIS_LEFTX: + return JoystickAxisSource.GamePadLeftStickX; + + case SDL_GamepadAxis.SDL_GAMEPAD_AXIS_LEFTY: + return JoystickAxisSource.GamePadLeftStickY; + + case SDL_GamepadAxis.SDL_GAMEPAD_AXIS_LEFT_TRIGGER: + return JoystickAxisSource.GamePadLeftTrigger; + + case SDL_GamepadAxis.SDL_GAMEPAD_AXIS_RIGHTX: + return JoystickAxisSource.GamePadRightStickX; + + case SDL_GamepadAxis.SDL_GAMEPAD_AXIS_RIGHTY: + return JoystickAxisSource.GamePadRightStickY; + + case SDL_GamepadAxis.SDL_GAMEPAD_AXIS_RIGHT_TRIGGER: + return JoystickAxisSource.GamePadRightTrigger; + } + } + + public static JoystickButton ToJoystickButton(this SDL_GamepadButton button) + { + switch (button) + { + default: + case SDL_GamepadButton.SDL_GAMEPAD_BUTTON_INVALID: + return 0; + + case SDL_GamepadButton.SDL_GAMEPAD_BUTTON_SOUTH: + return JoystickButton.GamePadA; + + case SDL_GamepadButton.SDL_GAMEPAD_BUTTON_EAST: + return JoystickButton.GamePadB; + + case SDL_GamepadButton.SDL_GAMEPAD_BUTTON_WEST: + return JoystickButton.GamePadX; + + case SDL_GamepadButton.SDL_GAMEPAD_BUTTON_NORTH: + return JoystickButton.GamePadY; + + case SDL_GamepadButton.SDL_GAMEPAD_BUTTON_BACK: + return JoystickButton.GamePadBack; + + case SDL_GamepadButton.SDL_GAMEPAD_BUTTON_GUIDE: + return JoystickButton.GamePadGuide; + + case SDL_GamepadButton.SDL_GAMEPAD_BUTTON_START: + return JoystickButton.GamePadStart; + + case SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_STICK: + return JoystickButton.GamePadLeftStick; + + case SDL_GamepadButton.SDL_GAMEPAD_BUTTON_RIGHT_STICK: + return JoystickButton.GamePadRightStick; + + case SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_SHOULDER: + return JoystickButton.GamePadLeftShoulder; + + case SDL_GamepadButton.SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER: + return JoystickButton.GamePadRightShoulder; + + case SDL_GamepadButton.SDL_GAMEPAD_BUTTON_DPAD_UP: + return JoystickButton.GamePadDPadUp; + + case SDL_GamepadButton.SDL_GAMEPAD_BUTTON_DPAD_DOWN: + return JoystickButton.GamePadDPadDown; + + case SDL_GamepadButton.SDL_GAMEPAD_BUTTON_DPAD_LEFT: + return JoystickButton.GamePadDPadLeft; + + case SDL_GamepadButton.SDL_GAMEPAD_BUTTON_DPAD_RIGHT: + return JoystickButton.GamePadDPadRight; + } + } + + public static SDL_Rect ToSDLRect(this RectangleI rectangle) => + new SDL_Rect + { + x = rectangle.X, + y = rectangle.Y, + h = rectangle.Height, + w = rectangle.Width, + }; + + public static unsafe DisplayMode ToDisplayMode(this SDL_DisplayMode mode, int displayIndex) + { + int bpp; + uint unused; + SDL3.SDL_GetMasksForPixelFormatEnum(mode.format, &bpp, &unused, &unused, &unused, &unused); + return new DisplayMode(SDL3.SDL_GetPixelFormatName(mode.format), new Size(mode.w, mode.h), bpp, mode.refresh_rate, displayIndex); + } + + public static string ReadableName(this SDL_LogCategory category) + { + switch (category) + { + case SDL_LogCategory.SDL_LOG_CATEGORY_APPLICATION: + return "application"; + + case SDL_LogCategory.SDL_LOG_CATEGORY_ERROR: + return "error"; + + case SDL_LogCategory.SDL_LOG_CATEGORY_ASSERT: + return "assert"; + + case SDL_LogCategory.SDL_LOG_CATEGORY_SYSTEM: + return "system"; + + case SDL_LogCategory.SDL_LOG_CATEGORY_AUDIO: + return "audio"; + + case SDL_LogCategory.SDL_LOG_CATEGORY_VIDEO: + return "video"; + + case SDL_LogCategory.SDL_LOG_CATEGORY_RENDER: + return "render"; + + case SDL_LogCategory.SDL_LOG_CATEGORY_INPUT: + return "input"; + + case SDL_LogCategory.SDL_LOG_CATEGORY_TEST: + return "test"; + + default: + return "unknown"; + } + } + + public static string ReadableName(this SDL_LogPriority priority) + { + switch (priority) + { + case SDL_LogPriority.SDL_LOG_PRIORITY_VERBOSE: + return "verbose"; + + case SDL_LogPriority.SDL_LOG_PRIORITY_DEBUG: + return "debug"; + + case SDL_LogPriority.SDL_LOG_PRIORITY_INFO: + return "info"; + + case SDL_LogPriority.SDL_LOG_PRIORITY_WARN: + return "warn"; + + case SDL_LogPriority.SDL_LOG_PRIORITY_ERROR: + return "error"; + + case SDL_LogPriority.SDL_LOG_PRIORITY_CRITICAL: + return "critical"; + + default: + return "unknown"; + } + } + + /// + /// Gets the readable string for this . + /// + /// + /// string in the format of 1920x1080@60. + /// + public static string ReadableString(this SDL_DisplayMode mode) => $"{mode.w}x{mode.h}@{mode.refresh_rate}"; + + /// + /// Gets the SDL error, and then clears it. + /// + public static string? GetAndClearError() + { + string? error = SDL3.SDL_GetError(); + SDL3.SDL_ClearError(); + return error; + } + + /// + /// Gets the of the touch device for this . + /// + /// + /// On Windows, this will return "touch" for touchscreen events or "pen" for pen/tablet events. + /// + public static bool TryGetTouchName(this SDL_TouchFingerEvent e, [NotNullWhen(true)] out string? name) + { + name = SDL3.SDL_GetTouchDeviceName(e.touchID); + return name != null; + } + } +} diff --git a/osu.Framework/Platform/SDL2/SDL2GraphicsSurface.cs b/osu.Framework/Platform/SDL/SDL3GraphicsSurface.cs similarity index 58% rename from osu.Framework/Platform/SDL2/SDL2GraphicsSurface.cs rename to osu.Framework/Platform/SDL/SDL3GraphicsSurface.cs index c5bc15842f..f77776a7e4 100644 --- a/osu.Framework/Platform/SDL2/SDL2GraphicsSurface.cs +++ b/osu.Framework/Platform/SDL/SDL3GraphicsSurface.cs @@ -7,15 +7,16 @@ using System.Linq; using System.Reflection; using System.Runtime.InteropServices; +using System.Text; using osuTK.Graphics; using osuTK.Graphics.ES30; -using SDL2; +using SDL; -namespace osu.Framework.Platform.SDL2 +namespace osu.Framework.Platform.SDL { - internal class SDL2GraphicsSurface : IGraphicsSurface, IOpenGLGraphicsSurface, IMetalGraphicsSurface, ILinuxGraphicsSurface + internal unsafe class SDL3GraphicsSurface : IGraphicsSurface, IOpenGLGraphicsSurface, IMetalGraphicsSurface, ILinuxGraphicsSurface { - private readonly SDL2Window window; + private readonly SDL3Window window; private IntPtr context; @@ -24,7 +25,7 @@ internal class SDL2GraphicsSurface : IGraphicsSurface, IOpenGLGraphicsSurface, I public GraphicsSurfaceType Type { get; } - public SDL2GraphicsSurface(SDL2Window window, GraphicsSurfaceType surfaceType) + public SDL3GraphicsSurface(SDL3Window window, GraphicsSurfaceType surfaceType) { this.window = window; Type = surfaceType; @@ -32,12 +33,12 @@ public SDL2GraphicsSurface(SDL2Window window, GraphicsSurfaceType surfaceType) switch (surfaceType) { case GraphicsSurfaceType.OpenGL: - SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_RED_SIZE, 8); - SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_GREEN_SIZE, 8); - SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_BLUE_SIZE, 8); - SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_ACCUM_ALPHA_SIZE, 0); - SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_DEPTH_SIZE, 16); - SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_STENCIL_SIZE, 8); + SDL3.SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_RED_SIZE, 8); + SDL3.SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_GREEN_SIZE, 8); + SDL3.SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_BLUE_SIZE, 8); + SDL3.SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_ACCUM_ALPHA_SIZE, 0); + SDL3.SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_DEPTH_SIZE, 16); + SDL3.SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_STENCIL_SIZE, 8); break; case GraphicsSurfaceType.Vulkan: @@ -58,7 +59,8 @@ public void Initialise() public Size GetDrawableSize() { - SDL.SDL_GetWindowSizeInPixels(window.SDLWindowHandle, out int width, out int height); + int width, height; + SDL3.SDL_GetWindowSizeInPixels(window.SDLWindowHandle, &width, &height); return new Size(width, height); } @@ -68,27 +70,27 @@ private void initialiseOpenGL() { if (RuntimeInfo.IsMobile) { - SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_PROFILE_MASK, SDL.SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_ES); + SDL3.SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_PROFILE_MASK, (int)SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_ES); // Minimum OpenGL version for ES profile: - SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_MAJOR_VERSION, 3); - SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_MINOR_VERSION, 0); + SDL3.SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL3.SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_MINOR_VERSION, 0); } else { - SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_PROFILE_MASK, SDL.SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_CORE); + SDL3.SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_PROFILE_MASK, (int)SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_CORE); // Minimum OpenGL version for core profile: - SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_MAJOR_VERSION, 3); - SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_MINOR_VERSION, 2); + SDL3.SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL3.SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_MINOR_VERSION, 2); } - context = SDL.SDL_GL_CreateContext(window.SDLWindowHandle); + context = SDL3.SDL_GL_CreateContext(window.SDLWindowHandle); if (context == IntPtr.Zero) - throw new InvalidOperationException($"Failed to create an SDL2 GL context ({SDL.SDL_GetError()})"); + throw new InvalidOperationException($"Failed to create an SDL3 GL context ({SDL3.SDL_GetError()})"); - SDL.SDL_GL_MakeCurrent(window.SDLWindowHandle, context); + SDL3.SDL_GL_MakeCurrent(window.SDLWindowHandle, context); loadBindings(); } @@ -102,7 +104,7 @@ private void loadBindings() loadEntryPoints(new GL()); } - private unsafe void loadEntryPoints(GraphicsBindingsBase bindings) + private void loadEntryPoints(GraphicsBindingsBase bindings) { var type = bindings.GetType(); var pointsInfo = type.GetRuntimeFields().First(x => x.Name == "_EntryPointsInstance"); @@ -133,16 +135,16 @@ private unsafe void loadEntryPoints(GraphicsBindingsBase bindings) private IntPtr getProcAddress(string symbol) { - const int error_category = (int)SDL.SDL_LogCategory.SDL_LOG_CATEGORY_ERROR; - SDL.SDL_LogPriority oldPriority = SDL.SDL_LogGetPriority(error_category); + const SDL_LogCategory error_category = SDL_LogCategory.SDL_LOG_CATEGORY_ERROR; + SDL_LogPriority oldPriority = SDL3.SDL_LogGetPriority(error_category); // Prevent logging calls to SDL_GL_GetProcAddress() that fail on systems which don't have the requested symbol (typically macOS). - SDL.SDL_LogSetPriority(error_category, SDL.SDL_LogPriority.SDL_LOG_PRIORITY_INFO); + SDL3.SDL_LogSetPriority(error_category, SDL_LogPriority.SDL_LOG_PRIORITY_INFO); - IntPtr ret = SDL.SDL_GL_GetProcAddress(symbol); + IntPtr ret = SDL3.SDL_GL_GetProcAddress(Encoding.UTF8.GetBytes(symbol)); // Reset the logging behaviour. - SDL.SDL_LogSetPriority(error_category, oldPriority); + SDL3.SDL_LogSetPriority(error_category, oldPriority); return ret; } @@ -151,16 +153,17 @@ private IntPtr getProcAddress(string symbol) { get { - if (window.SDLWindowHandle == IntPtr.Zero) + if (window.SDLWindowHandle == null) return null; - var wmInfo = window.GetWindowSystemInformation(); - - switch (wmInfo.subsystem) - { - case SDL.SDL_SYSWM_TYPE.SDL_SYSWM_UIKIT: - return (int)wmInfo.info.uikit.framebuffer; - } + // TODO: Migrate to SDL3 when https://github.com/libsdl-org/SDL/issues/9430 is resolved + // var wmInfo = window.GetWindowSystemInformation(); + // + // switch (wmInfo.subsystem) + // { + // case SDL_SYSWM_TYPE.SDL_SYSWM_UIKIT: + // return (int)wmInfo.info.uikit.framebuffer; + // } return null; } @@ -177,33 +180,35 @@ bool IOpenGLGraphicsSurface.VerticalSync if (verticalSync != null) return verticalSync.Value; - return (verticalSync = SDL.SDL_GL_GetSwapInterval() != 0).Value; + int interval; + SDL3.SDL_GL_GetSwapInterval(&interval); + return (verticalSync = interval != 0).Value; } set { if (RuntimeInfo.IsDesktop) { - SDL.SDL_GL_SetSwapInterval(value ? 1 : 0); + SDL3.SDL_GL_SetSwapInterval(value ? 1 : 0); verticalSync = value; } } } IntPtr IOpenGLGraphicsSurface.WindowContext => context; - IntPtr IOpenGLGraphicsSurface.CurrentContext => SDL.SDL_GL_GetCurrentContext(); + IntPtr IOpenGLGraphicsSurface.CurrentContext => SDL3.SDL_GL_GetCurrentContext(); - void IOpenGLGraphicsSurface.SwapBuffers() => SDL.SDL_GL_SwapWindow(window.SDLWindowHandle); - void IOpenGLGraphicsSurface.CreateContext() => SDL.SDL_GL_CreateContext(window.SDLWindowHandle); - void IOpenGLGraphicsSurface.DeleteContext(IntPtr context) => SDL.SDL_GL_DeleteContext(context); - void IOpenGLGraphicsSurface.MakeCurrent(IntPtr context) => SDL.SDL_GL_MakeCurrent(window.SDLWindowHandle, context); - void IOpenGLGraphicsSurface.ClearCurrent() => SDL.SDL_GL_MakeCurrent(window.SDLWindowHandle, IntPtr.Zero); + void IOpenGLGraphicsSurface.SwapBuffers() => SDL3.SDL_GL_SwapWindow(window.SDLWindowHandle); + void IOpenGLGraphicsSurface.CreateContext() => SDL3.SDL_GL_CreateContext(window.SDLWindowHandle); + void IOpenGLGraphicsSurface.DeleteContext(IntPtr context) => SDL3.SDL_GL_DeleteContext(context); + void IOpenGLGraphicsSurface.MakeCurrent(IntPtr context) => SDL3.SDL_GL_MakeCurrent(window.SDLWindowHandle, context); + void IOpenGLGraphicsSurface.ClearCurrent() => SDL3.SDL_GL_MakeCurrent(window.SDLWindowHandle, IntPtr.Zero); IntPtr IOpenGLGraphicsSurface.GetProcAddress(string symbol) => getProcAddress(symbol); #endregion #region Metal-specific implementation - IntPtr IMetalGraphicsSurface.CreateMetalView() => SDL.SDL_Metal_CreateView(window.SDLWindowHandle); + IntPtr IMetalGraphicsSurface.CreateMetalView() => SDL3.SDL_Metal_CreateView(window.SDLWindowHandle); #endregion diff --git a/osu.Framework/Platform/SDL2/SDL2ReadableKeyCombinationProvider.cs b/osu.Framework/Platform/SDL/SDL3ReadableKeyCombinationProvider.cs similarity index 66% rename from osu.Framework/Platform/SDL2/SDL2ReadableKeyCombinationProvider.cs rename to osu.Framework/Platform/SDL/SDL3ReadableKeyCombinationProvider.cs index 538dbf9b29..bb2763fced 100644 --- a/osu.Framework/Platform/SDL2/SDL2ReadableKeyCombinationProvider.cs +++ b/osu.Framework/Platform/SDL/SDL3ReadableKeyCombinationProvider.cs @@ -4,27 +4,27 @@ using System.Globalization; using osu.Framework.Input; using osu.Framework.Input.Bindings; -using SDL2; +using SDL; -namespace osu.Framework.Platform.SDL2 +namespace osu.Framework.Platform.SDL { - public class SDL2ReadableKeyCombinationProvider : ReadableKeyCombinationProvider + public class SDL3ReadableKeyCombinationProvider : ReadableKeyCombinationProvider { protected override string GetReadableKey(InputKey key) { - var keycode = SDL.SDL_GetKeyFromScancode(key.ToScancode()); + var keycode = SDL3.SDL_GetKeyFromScancode(key.ToScancode()); // early return if unknown. probably because key isn't a keyboard key, or doesn't map to an `SDL_Scancode`. - if (keycode == SDL.SDL_Keycode.SDLK_UNKNOWN) + if (keycode == SDL_Keycode.SDLK_UNKNOWN) return base.GetReadableKey(key); - string name; + string? name; // overrides for some keys that we want displayed differently from SDL_GetKeyName(). if (TryGetNameFromKeycode(keycode, out name)) return name; - name = SDL.SDL_GetKeyName(keycode); + name = SDL3.SDL_GetKeyName(keycode); // fall back if SDL couldn't find a name. if (string.IsNullOrEmpty(name)) @@ -32,7 +32,7 @@ protected override string GetReadableKey(InputKey key) // true if SDL_GetKeyName() returned a proper key/scancode name. // see https://github.com/libsdl-org/SDL/blob/release-2.0.16/src/events/SDL_keyboard.c#L1012 - if (((int)keycode & SDL.SDLK_SCANCODE_MASK) != 0) + if (((int)keycode & SDL3.SDLK_SCANCODE_MASK) != 0) return name; // SDL_GetKeyName() returned a unicode character that would be produced if that key was pressed. @@ -49,175 +49,175 @@ protected override string GetReadableKey(InputKey key) /// /// Should be overriden per-platform to provide platform-specific names for applicable keys. /// - protected virtual bool TryGetNameFromKeycode(SDL.SDL_Keycode keycode, out string name) + protected virtual bool TryGetNameFromKeycode(SDL_Keycode keycode, out string name) { switch (keycode) { - case SDL.SDL_Keycode.SDLK_RETURN: + case SDL_Keycode.SDLK_RETURN: name = "Enter"; return true; - case SDL.SDL_Keycode.SDLK_ESCAPE: + case SDL_Keycode.SDLK_ESCAPE: name = "Esc"; return true; - case SDL.SDL_Keycode.SDLK_BACKSPACE: + case SDL_Keycode.SDLK_BACKSPACE: name = "Backsp"; return true; - case SDL.SDL_Keycode.SDLK_TAB: + case SDL_Keycode.SDLK_TAB: name = "Tab"; return true; - case SDL.SDL_Keycode.SDLK_SPACE: + case SDL_Keycode.SDLK_SPACE: name = "Space"; return true; - case SDL.SDL_Keycode.SDLK_PLUS: + case SDL_Keycode.SDLK_PLUS: name = "Plus"; return true; - case SDL.SDL_Keycode.SDLK_MINUS: + case SDL_Keycode.SDLK_MINUS: name = "Minus"; return true; - case SDL.SDL_Keycode.SDLK_DELETE: + case SDL_Keycode.SDLK_DELETE: name = "Del"; return true; - case SDL.SDL_Keycode.SDLK_CAPSLOCK: + case SDL_Keycode.SDLK_CAPSLOCK: name = "Caps"; return true; - case SDL.SDL_Keycode.SDLK_INSERT: + case SDL_Keycode.SDLK_INSERT: name = "Ins"; return true; - case SDL.SDL_Keycode.SDLK_PAGEUP: + case SDL_Keycode.SDLK_PAGEUP: name = "PgUp"; return true; - case SDL.SDL_Keycode.SDLK_PAGEDOWN: + case SDL_Keycode.SDLK_PAGEDOWN: name = "PgDn"; return true; - case SDL.SDL_Keycode.SDLK_NUMLOCKCLEAR: + case SDL_Keycode.SDLK_NUMLOCKCLEAR: name = "NumLock"; return true; - case SDL.SDL_Keycode.SDLK_KP_DIVIDE: + case SDL_Keycode.SDLK_KP_DIVIDE: name = "NumpadDivide"; return true; - case SDL.SDL_Keycode.SDLK_KP_MULTIPLY: + case SDL_Keycode.SDLK_KP_MULTIPLY: name = "NumpadMultiply"; return true; - case SDL.SDL_Keycode.SDLK_KP_MINUS: + case SDL_Keycode.SDLK_KP_MINUS: name = "NumpadMinus"; return true; - case SDL.SDL_Keycode.SDLK_KP_PLUS: + case SDL_Keycode.SDLK_KP_PLUS: name = "NumpadPlus"; return true; - case SDL.SDL_Keycode.SDLK_KP_ENTER: + case SDL_Keycode.SDLK_KP_ENTER: name = "NumpadEnter"; return true; - case SDL.SDL_Keycode.SDLK_KP_PERIOD: + case SDL_Keycode.SDLK_KP_PERIOD: name = "NumpadDecimal"; return true; - case SDL.SDL_Keycode.SDLK_KP_0: + case SDL_Keycode.SDLK_KP_0: name = "Numpad0"; return true; - case SDL.SDL_Keycode.SDLK_KP_1: + case SDL_Keycode.SDLK_KP_1: name = "Numpad1"; return true; - case SDL.SDL_Keycode.SDLK_KP_2: + case SDL_Keycode.SDLK_KP_2: name = "Numpad2"; return true; - case SDL.SDL_Keycode.SDLK_KP_3: + case SDL_Keycode.SDLK_KP_3: name = "Numpad3"; return true; - case SDL.SDL_Keycode.SDLK_KP_4: + case SDL_Keycode.SDLK_KP_4: name = "Numpad4"; return true; - case SDL.SDL_Keycode.SDLK_KP_5: + case SDL_Keycode.SDLK_KP_5: name = "Numpad5"; return true; - case SDL.SDL_Keycode.SDLK_KP_6: + case SDL_Keycode.SDLK_KP_6: name = "Numpad6"; return true; - case SDL.SDL_Keycode.SDLK_KP_7: + case SDL_Keycode.SDLK_KP_7: name = "Numpad7"; return true; - case SDL.SDL_Keycode.SDLK_KP_8: + case SDL_Keycode.SDLK_KP_8: name = "Numpad8"; return true; - case SDL.SDL_Keycode.SDLK_KP_9: + case SDL_Keycode.SDLK_KP_9: name = "Numpad9"; return true; - case SDL.SDL_Keycode.SDLK_LCTRL: + case SDL_Keycode.SDLK_LCTRL: name = "LCtrl"; return true; - case SDL.SDL_Keycode.SDLK_LSHIFT: + case SDL_Keycode.SDLK_LSHIFT: name = "LShift"; return true; - case SDL.SDL_Keycode.SDLK_LALT: + case SDL_Keycode.SDLK_LALT: name = "LAlt"; return true; - case SDL.SDL_Keycode.SDLK_RCTRL: + case SDL_Keycode.SDLK_RCTRL: name = "RCtrl"; return true; - case SDL.SDL_Keycode.SDLK_RSHIFT: + case SDL_Keycode.SDLK_RSHIFT: name = "RShift"; return true; - case SDL.SDL_Keycode.SDLK_RALT: + case SDL_Keycode.SDLK_RALT: name = "RAlt"; return true; - case SDL.SDL_Keycode.SDLK_VOLUMEUP: + case SDL_Keycode.SDLK_VOLUMEUP: name = "Vol. Up"; return true; - case SDL.SDL_Keycode.SDLK_VOLUMEDOWN: + case SDL_Keycode.SDLK_VOLUMEDOWN: name = "Vol. Down"; return true; - case SDL.SDL_Keycode.SDLK_AUDIONEXT: + case SDL_Keycode.SDLK_AUDIONEXT: name = "Media Next"; return true; - case SDL.SDL_Keycode.SDLK_AUDIOPREV: + case SDL_Keycode.SDLK_AUDIOPREV: name = "Media Previous"; return true; - case SDL.SDL_Keycode.SDLK_AUDIOSTOP: + case SDL_Keycode.SDLK_AUDIOSTOP: name = "Media Stop"; return true; - case SDL.SDL_Keycode.SDLK_AUDIOPLAY: + case SDL_Keycode.SDLK_AUDIOPLAY: name = "Media Play"; return true; - case SDL.SDL_Keycode.SDLK_AUDIOMUTE: + case SDL_Keycode.SDLK_AUDIOMUTE: name = "Mute"; return true; diff --git a/osu.Framework/Platform/SDL2/SDL2ControllerBindings.cs b/osu.Framework/Platform/SDL2/SDL2ControllerBindings.cs deleted file mode 100644 index b364c4031f..0000000000 --- a/osu.Framework/Platform/SDL2/SDL2ControllerBindings.cs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System; -using System.Linq; -using SDL2; - -namespace osu.Framework.Platform.SDL2 -{ - /// - /// Maintain a copy of the SDL-provided bindings for the given controller. - /// Used to determine whether a given event's joystick button or axis is unmapped. - /// - internal class SDL2ControllerBindings - { - public readonly IntPtr JoystickHandle; - public readonly IntPtr ControllerHandle; - - /// - /// Bindings returned from , indexed by . - /// Empty if the joystick does not have a corresponding ControllerHandle. - /// - public SDL.SDL_GameControllerButtonBind[] ButtonBindings; - - /// - /// Bindings returned from , indexed by . - /// Empty if the joystick does not have a corresponding ControllerHandle. - /// - public SDL.SDL_GameControllerButtonBind[] AxisBindings; - - public SDL2ControllerBindings(IntPtr joystickHandle, IntPtr controllerHandle) - { - JoystickHandle = joystickHandle; - ControllerHandle = controllerHandle; - - PopulateBindings(); - } - - public void PopulateBindings() - { - if (ControllerHandle == IntPtr.Zero) - { - ButtonBindings = Array.Empty(); - AxisBindings = Array.Empty(); - return; - } - - ButtonBindings = Enumerable.Range(0, (int)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_MAX) - .Select(i => SDL.SDL_GameControllerGetBindForButton(ControllerHandle, (SDL.SDL_GameControllerButton)i)).ToArray(); - - AxisBindings = Enumerable.Range(0, (int)SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_MAX) - .Select(i => SDL.SDL_GameControllerGetBindForAxis(ControllerHandle, (SDL.SDL_GameControllerAxis)i)).ToArray(); - } - - public bool IsJoystickButtonBound(byte buttonIndex) - { - for (int i = 0; i < ButtonBindings.Length; i++) - { - if (ButtonBindings[i].bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE && ButtonBindings[i].value.button == buttonIndex) - return true; - } - - return false; - } - - public bool IsJoystickAxisBound(byte axisIndex) - { - for (int i = 0; i < AxisBindings.Length; i++) - { - if (AxisBindings[i].bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE && AxisBindings[i].value.axis == axisIndex) - return true; - } - - return false; - } - } -} diff --git a/osu.Framework/Platform/SDL2/SDL2Extensions.cs b/osu.Framework/Platform/SDL2/SDL2Extensions.cs deleted file mode 100644 index 0f154a20ea..0000000000 --- a/osu.Framework/Platform/SDL2/SDL2Extensions.cs +++ /dev/null @@ -1,1161 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System; -using System.Drawing; -using System.Runtime.InteropServices; -using osu.Framework.Extensions.EnumExtensions; -using osu.Framework.Graphics.Primitives; -using osu.Framework.Input; -using osu.Framework.Input.Bindings; -using osuTK.Input; -using SDL2; - -namespace osu.Framework.Platform.SDL2 -{ - public static class SDL2Extensions - { - public static Key ToKey(this SDL.SDL_Keysym sdlKeysym) - { - // Apple devices don't have the notion of NumLock (they have a Clear key instead). - // treat them as if they always have NumLock on (the numpad always performs its primary actions). - bool numLockOn = sdlKeysym.mod.HasFlagFast(SDL.SDL_Keymod.KMOD_NUM) || RuntimeInfo.IsApple; - - switch (sdlKeysym.scancode) - { - default: - case SDL.SDL_Scancode.SDL_SCANCODE_UNKNOWN: - return Key.Unknown; - - case SDL.SDL_Scancode.SDL_SCANCODE_KP_COMMA: - return Key.Comma; - - case SDL.SDL_Scancode.SDL_SCANCODE_KP_TAB: - return Key.Tab; - - case SDL.SDL_Scancode.SDL_SCANCODE_KP_BACKSPACE: - return Key.BackSpace; - - case SDL.SDL_Scancode.SDL_SCANCODE_KP_A: - return Key.A; - - case SDL.SDL_Scancode.SDL_SCANCODE_KP_B: - return Key.B; - - case SDL.SDL_Scancode.SDL_SCANCODE_KP_C: - return Key.C; - - case SDL.SDL_Scancode.SDL_SCANCODE_KP_D: - return Key.D; - - case SDL.SDL_Scancode.SDL_SCANCODE_KP_E: - return Key.E; - - case SDL.SDL_Scancode.SDL_SCANCODE_KP_F: - return Key.F; - - case SDL.SDL_Scancode.SDL_SCANCODE_KP_SPACE: - return Key.Space; - - case SDL.SDL_Scancode.SDL_SCANCODE_KP_CLEAR: - return Key.Clear; - - case SDL.SDL_Scancode.SDL_SCANCODE_RETURN: - return Key.Enter; - - case SDL.SDL_Scancode.SDL_SCANCODE_ESCAPE: - return Key.Escape; - - case SDL.SDL_Scancode.SDL_SCANCODE_BACKSPACE: - return Key.BackSpace; - - case SDL.SDL_Scancode.SDL_SCANCODE_TAB: - return Key.Tab; - - case SDL.SDL_Scancode.SDL_SCANCODE_SPACE: - return Key.Space; - - case SDL.SDL_Scancode.SDL_SCANCODE_APOSTROPHE: - return Key.Quote; - - case SDL.SDL_Scancode.SDL_SCANCODE_COMMA: - return Key.Comma; - - case SDL.SDL_Scancode.SDL_SCANCODE_MINUS: - return Key.Minus; - - case SDL.SDL_Scancode.SDL_SCANCODE_PERIOD: - return Key.Period; - - case SDL.SDL_Scancode.SDL_SCANCODE_SLASH: - return Key.Slash; - - case SDL.SDL_Scancode.SDL_SCANCODE_0: - return Key.Number0; - - case SDL.SDL_Scancode.SDL_SCANCODE_1: - return Key.Number1; - - case SDL.SDL_Scancode.SDL_SCANCODE_2: - return Key.Number2; - - case SDL.SDL_Scancode.SDL_SCANCODE_3: - return Key.Number3; - - case SDL.SDL_Scancode.SDL_SCANCODE_4: - return Key.Number4; - - case SDL.SDL_Scancode.SDL_SCANCODE_5: - return Key.Number5; - - case SDL.SDL_Scancode.SDL_SCANCODE_6: - return Key.Number6; - - case SDL.SDL_Scancode.SDL_SCANCODE_7: - return Key.Number7; - - case SDL.SDL_Scancode.SDL_SCANCODE_8: - return Key.Number8; - - case SDL.SDL_Scancode.SDL_SCANCODE_9: - return Key.Number9; - - case SDL.SDL_Scancode.SDL_SCANCODE_SEMICOLON: - return Key.Semicolon; - - case SDL.SDL_Scancode.SDL_SCANCODE_EQUALS: - return Key.Plus; - - case SDL.SDL_Scancode.SDL_SCANCODE_LEFTBRACKET: - return Key.BracketLeft; - - case SDL.SDL_Scancode.SDL_SCANCODE_BACKSLASH: - return Key.BackSlash; - - case SDL.SDL_Scancode.SDL_SCANCODE_RIGHTBRACKET: - return Key.BracketRight; - - case SDL.SDL_Scancode.SDL_SCANCODE_GRAVE: - return Key.Tilde; - - case SDL.SDL_Scancode.SDL_SCANCODE_A: - return Key.A; - - case SDL.SDL_Scancode.SDL_SCANCODE_B: - return Key.B; - - case SDL.SDL_Scancode.SDL_SCANCODE_C: - return Key.C; - - case SDL.SDL_Scancode.SDL_SCANCODE_D: - return Key.D; - - case SDL.SDL_Scancode.SDL_SCANCODE_E: - return Key.E; - - case SDL.SDL_Scancode.SDL_SCANCODE_F: - return Key.F; - - case SDL.SDL_Scancode.SDL_SCANCODE_G: - return Key.G; - - case SDL.SDL_Scancode.SDL_SCANCODE_H: - return Key.H; - - case SDL.SDL_Scancode.SDL_SCANCODE_I: - return Key.I; - - case SDL.SDL_Scancode.SDL_SCANCODE_J: - return Key.J; - - case SDL.SDL_Scancode.SDL_SCANCODE_K: - return Key.K; - - case SDL.SDL_Scancode.SDL_SCANCODE_L: - return Key.L; - - case SDL.SDL_Scancode.SDL_SCANCODE_M: - return Key.M; - - case SDL.SDL_Scancode.SDL_SCANCODE_N: - return Key.N; - - case SDL.SDL_Scancode.SDL_SCANCODE_O: - return Key.O; - - case SDL.SDL_Scancode.SDL_SCANCODE_P: - return Key.P; - - case SDL.SDL_Scancode.SDL_SCANCODE_Q: - return Key.Q; - - case SDL.SDL_Scancode.SDL_SCANCODE_R: - return Key.R; - - case SDL.SDL_Scancode.SDL_SCANCODE_S: - return Key.S; - - case SDL.SDL_Scancode.SDL_SCANCODE_T: - return Key.T; - - case SDL.SDL_Scancode.SDL_SCANCODE_U: - return Key.U; - - case SDL.SDL_Scancode.SDL_SCANCODE_V: - return Key.V; - - case SDL.SDL_Scancode.SDL_SCANCODE_W: - return Key.W; - - case SDL.SDL_Scancode.SDL_SCANCODE_X: - return Key.X; - - case SDL.SDL_Scancode.SDL_SCANCODE_Y: - return Key.Y; - - case SDL.SDL_Scancode.SDL_SCANCODE_Z: - return Key.Z; - - case SDL.SDL_Scancode.SDL_SCANCODE_CAPSLOCK: - return Key.CapsLock; - - case SDL.SDL_Scancode.SDL_SCANCODE_F1: - return Key.F1; - - case SDL.SDL_Scancode.SDL_SCANCODE_F2: - return Key.F2; - - case SDL.SDL_Scancode.SDL_SCANCODE_F3: - return Key.F3; - - case SDL.SDL_Scancode.SDL_SCANCODE_F4: - return Key.F4; - - case SDL.SDL_Scancode.SDL_SCANCODE_F5: - return Key.F5; - - case SDL.SDL_Scancode.SDL_SCANCODE_F6: - return Key.F6; - - case SDL.SDL_Scancode.SDL_SCANCODE_F7: - return Key.F7; - - case SDL.SDL_Scancode.SDL_SCANCODE_F8: - return Key.F8; - - case SDL.SDL_Scancode.SDL_SCANCODE_F9: - return Key.F9; - - case SDL.SDL_Scancode.SDL_SCANCODE_F10: - return Key.F10; - - case SDL.SDL_Scancode.SDL_SCANCODE_F11: - return Key.F11; - - case SDL.SDL_Scancode.SDL_SCANCODE_F12: - return Key.F12; - - case SDL.SDL_Scancode.SDL_SCANCODE_PRINTSCREEN: - return Key.PrintScreen; - - case SDL.SDL_Scancode.SDL_SCANCODE_SCROLLLOCK: - return Key.ScrollLock; - - case SDL.SDL_Scancode.SDL_SCANCODE_PAUSE: - return Key.Pause; - - case SDL.SDL_Scancode.SDL_SCANCODE_INSERT: - return Key.Insert; - - case SDL.SDL_Scancode.SDL_SCANCODE_HOME: - return Key.Home; - - case SDL.SDL_Scancode.SDL_SCANCODE_PAGEUP: - return Key.PageUp; - - case SDL.SDL_Scancode.SDL_SCANCODE_DELETE: - return Key.Delete; - - case SDL.SDL_Scancode.SDL_SCANCODE_END: - return Key.End; - - case SDL.SDL_Scancode.SDL_SCANCODE_PAGEDOWN: - return Key.PageDown; - - case SDL.SDL_Scancode.SDL_SCANCODE_RIGHT: - return Key.Right; - - case SDL.SDL_Scancode.SDL_SCANCODE_LEFT: - return Key.Left; - - case SDL.SDL_Scancode.SDL_SCANCODE_DOWN: - return Key.Down; - - case SDL.SDL_Scancode.SDL_SCANCODE_UP: - return Key.Up; - - case SDL.SDL_Scancode.SDL_SCANCODE_NUMLOCKCLEAR: - return Key.NumLock; - - case SDL.SDL_Scancode.SDL_SCANCODE_KP_DIVIDE: - return Key.KeypadDivide; - - case SDL.SDL_Scancode.SDL_SCANCODE_KP_MULTIPLY: - return Key.KeypadMultiply; - - case SDL.SDL_Scancode.SDL_SCANCODE_KP_MINUS: - return Key.KeypadMinus; - - case SDL.SDL_Scancode.SDL_SCANCODE_KP_PLUS: - return Key.KeypadPlus; - - case SDL.SDL_Scancode.SDL_SCANCODE_KP_ENTER: - return Key.KeypadEnter; - - case SDL.SDL_Scancode.SDL_SCANCODE_KP_1: - return numLockOn ? Key.Keypad1 : Key.End; - - case SDL.SDL_Scancode.SDL_SCANCODE_KP_2: - return numLockOn ? Key.Keypad2 : Key.Down; - - case SDL.SDL_Scancode.SDL_SCANCODE_KP_3: - return numLockOn ? Key.Keypad3 : Key.PageDown; - - case SDL.SDL_Scancode.SDL_SCANCODE_KP_4: - return numLockOn ? Key.Keypad4 : Key.Left; - - case SDL.SDL_Scancode.SDL_SCANCODE_KP_5: - return numLockOn ? Key.Keypad5 : Key.Clear; - - case SDL.SDL_Scancode.SDL_SCANCODE_KP_6: - return numLockOn ? Key.Keypad6 : Key.Right; - - case SDL.SDL_Scancode.SDL_SCANCODE_KP_7: - return numLockOn ? Key.Keypad7 : Key.Home; - - case SDL.SDL_Scancode.SDL_SCANCODE_KP_8: - return numLockOn ? Key.Keypad8 : Key.Up; - - case SDL.SDL_Scancode.SDL_SCANCODE_KP_9: - return numLockOn ? Key.Keypad9 : Key.PageUp; - - case SDL.SDL_Scancode.SDL_SCANCODE_KP_0: - return numLockOn ? Key.Keypad0 : Key.Insert; - - case SDL.SDL_Scancode.SDL_SCANCODE_KP_PERIOD: - return numLockOn ? Key.KeypadPeriod : Key.Delete; - - case SDL.SDL_Scancode.SDL_SCANCODE_NONUSBACKSLASH: - return Key.NonUSBackSlash; - - case SDL.SDL_Scancode.SDL_SCANCODE_F13: - return Key.F13; - - case SDL.SDL_Scancode.SDL_SCANCODE_F14: - return Key.F14; - - case SDL.SDL_Scancode.SDL_SCANCODE_F15: - return Key.F15; - - case SDL.SDL_Scancode.SDL_SCANCODE_F16: - return Key.F16; - - case SDL.SDL_Scancode.SDL_SCANCODE_F17: - return Key.F17; - - case SDL.SDL_Scancode.SDL_SCANCODE_F18: - return Key.F18; - - case SDL.SDL_Scancode.SDL_SCANCODE_F19: - return Key.F19; - - case SDL.SDL_Scancode.SDL_SCANCODE_F20: - return Key.F20; - - case SDL.SDL_Scancode.SDL_SCANCODE_F21: - return Key.F21; - - case SDL.SDL_Scancode.SDL_SCANCODE_F22: - return Key.F22; - - case SDL.SDL_Scancode.SDL_SCANCODE_F23: - return Key.F23; - - case SDL.SDL_Scancode.SDL_SCANCODE_F24: - return Key.F24; - - case SDL.SDL_Scancode.SDL_SCANCODE_MENU: - case SDL.SDL_Scancode.SDL_SCANCODE_APPLICATION: - return Key.Menu; - - case SDL.SDL_Scancode.SDL_SCANCODE_STOP: - return Key.Stop; - - case SDL.SDL_Scancode.SDL_SCANCODE_MUTE: - return Key.Mute; - - case SDL.SDL_Scancode.SDL_SCANCODE_VOLUMEUP: - return Key.VolumeUp; - - case SDL.SDL_Scancode.SDL_SCANCODE_VOLUMEDOWN: - return Key.VolumeDown; - - case SDL.SDL_Scancode.SDL_SCANCODE_CLEAR: - return Key.Clear; - - case SDL.SDL_Scancode.SDL_SCANCODE_DECIMALSEPARATOR: - return Key.KeypadDecimal; - - case SDL.SDL_Scancode.SDL_SCANCODE_LCTRL: - return Key.ControlLeft; - - case SDL.SDL_Scancode.SDL_SCANCODE_LSHIFT: - return Key.ShiftLeft; - - case SDL.SDL_Scancode.SDL_SCANCODE_LALT: - return Key.AltLeft; - - case SDL.SDL_Scancode.SDL_SCANCODE_LGUI: - return Key.WinLeft; - - case SDL.SDL_Scancode.SDL_SCANCODE_RCTRL: - return Key.ControlRight; - - case SDL.SDL_Scancode.SDL_SCANCODE_RSHIFT: - return Key.ShiftRight; - - case SDL.SDL_Scancode.SDL_SCANCODE_RALT: - return Key.AltRight; - - case SDL.SDL_Scancode.SDL_SCANCODE_RGUI: - return Key.WinRight; - - case SDL.SDL_Scancode.SDL_SCANCODE_AUDIONEXT: - return Key.TrackNext; - - case SDL.SDL_Scancode.SDL_SCANCODE_AUDIOPREV: - return Key.TrackPrevious; - - case SDL.SDL_Scancode.SDL_SCANCODE_AUDIOSTOP: - return Key.Stop; - - case SDL.SDL_Scancode.SDL_SCANCODE_AUDIOPLAY: - return Key.PlayPause; - - case SDL.SDL_Scancode.SDL_SCANCODE_AUDIOMUTE: - return Key.Mute; - - case SDL.SDL_Scancode.SDL_SCANCODE_SLEEP: - return Key.Sleep; - } - } - - /// - /// Returns the corresponding for a given . - /// - /// - /// Should be a keyboard key. - /// - /// - /// The corresponding if the is valid. - /// otherwise. - /// - public static SDL.SDL_Scancode ToScancode(this InputKey inputKey) - { - switch (inputKey) - { - default: - case InputKey.Shift: - case InputKey.Control: - case InputKey.Alt: - case InputKey.Super: - case InputKey.F25: - case InputKey.F26: - case InputKey.F27: - case InputKey.F28: - case InputKey.F29: - case InputKey.F30: - case InputKey.F31: - case InputKey.F32: - case InputKey.F33: - case InputKey.F34: - case InputKey.F35: - case InputKey.Clear: - return SDL.SDL_Scancode.SDL_SCANCODE_UNKNOWN; - - case InputKey.Menu: - return SDL.SDL_Scancode.SDL_SCANCODE_MENU; - - case InputKey.F1: - return SDL.SDL_Scancode.SDL_SCANCODE_F1; - - case InputKey.F2: - return SDL.SDL_Scancode.SDL_SCANCODE_F2; - - case InputKey.F3: - return SDL.SDL_Scancode.SDL_SCANCODE_F3; - - case InputKey.F4: - return SDL.SDL_Scancode.SDL_SCANCODE_F4; - - case InputKey.F5: - return SDL.SDL_Scancode.SDL_SCANCODE_F5; - - case InputKey.F6: - return SDL.SDL_Scancode.SDL_SCANCODE_F6; - - case InputKey.F7: - return SDL.SDL_Scancode.SDL_SCANCODE_F7; - - case InputKey.F8: - return SDL.SDL_Scancode.SDL_SCANCODE_F8; - - case InputKey.F9: - return SDL.SDL_Scancode.SDL_SCANCODE_F9; - - case InputKey.F10: - return SDL.SDL_Scancode.SDL_SCANCODE_F10; - - case InputKey.F11: - return SDL.SDL_Scancode.SDL_SCANCODE_F11; - - case InputKey.F12: - return SDL.SDL_Scancode.SDL_SCANCODE_F12; - - case InputKey.F13: - return SDL.SDL_Scancode.SDL_SCANCODE_F13; - - case InputKey.F14: - return SDL.SDL_Scancode.SDL_SCANCODE_F14; - - case InputKey.F15: - return SDL.SDL_Scancode.SDL_SCANCODE_F15; - - case InputKey.F16: - return SDL.SDL_Scancode.SDL_SCANCODE_F16; - - case InputKey.F17: - return SDL.SDL_Scancode.SDL_SCANCODE_F17; - - case InputKey.F18: - return SDL.SDL_Scancode.SDL_SCANCODE_F18; - - case InputKey.F19: - return SDL.SDL_Scancode.SDL_SCANCODE_F19; - - case InputKey.F20: - return SDL.SDL_Scancode.SDL_SCANCODE_F20; - - case InputKey.F21: - return SDL.SDL_Scancode.SDL_SCANCODE_F21; - - case InputKey.F22: - return SDL.SDL_Scancode.SDL_SCANCODE_F22; - - case InputKey.F23: - return SDL.SDL_Scancode.SDL_SCANCODE_F23; - - case InputKey.F24: - return SDL.SDL_Scancode.SDL_SCANCODE_F24; - - case InputKey.Up: - return SDL.SDL_Scancode.SDL_SCANCODE_UP; - - case InputKey.Down: - return SDL.SDL_Scancode.SDL_SCANCODE_DOWN; - - case InputKey.Left: - return SDL.SDL_Scancode.SDL_SCANCODE_LEFT; - - case InputKey.Right: - return SDL.SDL_Scancode.SDL_SCANCODE_RIGHT; - - case InputKey.Enter: - return SDL.SDL_Scancode.SDL_SCANCODE_RETURN; - - case InputKey.Escape: - return SDL.SDL_Scancode.SDL_SCANCODE_ESCAPE; - - case InputKey.Space: - return SDL.SDL_Scancode.SDL_SCANCODE_SPACE; - - case InputKey.Tab: - return SDL.SDL_Scancode.SDL_SCANCODE_TAB; - - case InputKey.BackSpace: - return SDL.SDL_Scancode.SDL_SCANCODE_BACKSPACE; - - case InputKey.Insert: - return SDL.SDL_Scancode.SDL_SCANCODE_INSERT; - - case InputKey.Delete: - return SDL.SDL_Scancode.SDL_SCANCODE_DELETE; - - case InputKey.PageUp: - return SDL.SDL_Scancode.SDL_SCANCODE_PAGEUP; - - case InputKey.PageDown: - return SDL.SDL_Scancode.SDL_SCANCODE_PAGEDOWN; - - case InputKey.Home: - return SDL.SDL_Scancode.SDL_SCANCODE_HOME; - - case InputKey.End: - return SDL.SDL_Scancode.SDL_SCANCODE_END; - - case InputKey.CapsLock: - return SDL.SDL_Scancode.SDL_SCANCODE_CAPSLOCK; - - case InputKey.ScrollLock: - return SDL.SDL_Scancode.SDL_SCANCODE_SCROLLLOCK; - - case InputKey.PrintScreen: - return SDL.SDL_Scancode.SDL_SCANCODE_PRINTSCREEN; - - case InputKey.Pause: - return SDL.SDL_Scancode.SDL_SCANCODE_PAUSE; - - case InputKey.NumLock: - return SDL.SDL_Scancode.SDL_SCANCODE_NUMLOCKCLEAR; - - case InputKey.Sleep: - return SDL.SDL_Scancode.SDL_SCANCODE_SLEEP; - - case InputKey.Keypad0: - return SDL.SDL_Scancode.SDL_SCANCODE_KP_0; - - case InputKey.Keypad1: - return SDL.SDL_Scancode.SDL_SCANCODE_KP_1; - - case InputKey.Keypad2: - return SDL.SDL_Scancode.SDL_SCANCODE_KP_2; - - case InputKey.Keypad3: - return SDL.SDL_Scancode.SDL_SCANCODE_KP_3; - - case InputKey.Keypad4: - return SDL.SDL_Scancode.SDL_SCANCODE_KP_4; - - case InputKey.Keypad5: - return SDL.SDL_Scancode.SDL_SCANCODE_KP_5; - - case InputKey.Keypad6: - return SDL.SDL_Scancode.SDL_SCANCODE_KP_6; - - case InputKey.Keypad7: - return SDL.SDL_Scancode.SDL_SCANCODE_KP_7; - - case InputKey.Keypad8: - return SDL.SDL_Scancode.SDL_SCANCODE_KP_8; - - case InputKey.Keypad9: - return SDL.SDL_Scancode.SDL_SCANCODE_KP_9; - - case InputKey.KeypadDivide: - return SDL.SDL_Scancode.SDL_SCANCODE_KP_DIVIDE; - - case InputKey.KeypadMultiply: - return SDL.SDL_Scancode.SDL_SCANCODE_KP_MULTIPLY; - - case InputKey.KeypadMinus: - return SDL.SDL_Scancode.SDL_SCANCODE_KP_MINUS; - - case InputKey.KeypadPlus: - return SDL.SDL_Scancode.SDL_SCANCODE_KP_PLUS; - - case InputKey.KeypadPeriod: - return SDL.SDL_Scancode.SDL_SCANCODE_KP_PERIOD; - - case InputKey.KeypadEnter: - return SDL.SDL_Scancode.SDL_SCANCODE_KP_ENTER; - - case InputKey.A: - return SDL.SDL_Scancode.SDL_SCANCODE_A; - - case InputKey.B: - return SDL.SDL_Scancode.SDL_SCANCODE_B; - - case InputKey.C: - return SDL.SDL_Scancode.SDL_SCANCODE_C; - - case InputKey.D: - return SDL.SDL_Scancode.SDL_SCANCODE_D; - - case InputKey.E: - return SDL.SDL_Scancode.SDL_SCANCODE_E; - - case InputKey.F: - return SDL.SDL_Scancode.SDL_SCANCODE_F; - - case InputKey.G: - return SDL.SDL_Scancode.SDL_SCANCODE_G; - - case InputKey.H: - return SDL.SDL_Scancode.SDL_SCANCODE_H; - - case InputKey.I: - return SDL.SDL_Scancode.SDL_SCANCODE_I; - - case InputKey.J: - return SDL.SDL_Scancode.SDL_SCANCODE_J; - - case InputKey.K: - return SDL.SDL_Scancode.SDL_SCANCODE_K; - - case InputKey.L: - return SDL.SDL_Scancode.SDL_SCANCODE_L; - - case InputKey.M: - return SDL.SDL_Scancode.SDL_SCANCODE_M; - - case InputKey.N: - return SDL.SDL_Scancode.SDL_SCANCODE_N; - - case InputKey.O: - return SDL.SDL_Scancode.SDL_SCANCODE_O; - - case InputKey.P: - return SDL.SDL_Scancode.SDL_SCANCODE_P; - - case InputKey.Q: - return SDL.SDL_Scancode.SDL_SCANCODE_Q; - - case InputKey.R: - return SDL.SDL_Scancode.SDL_SCANCODE_R; - - case InputKey.S: - return SDL.SDL_Scancode.SDL_SCANCODE_S; - - case InputKey.T: - return SDL.SDL_Scancode.SDL_SCANCODE_T; - - case InputKey.U: - return SDL.SDL_Scancode.SDL_SCANCODE_U; - - case InputKey.V: - return SDL.SDL_Scancode.SDL_SCANCODE_V; - - case InputKey.W: - return SDL.SDL_Scancode.SDL_SCANCODE_W; - - case InputKey.X: - return SDL.SDL_Scancode.SDL_SCANCODE_X; - - case InputKey.Y: - return SDL.SDL_Scancode.SDL_SCANCODE_Y; - - case InputKey.Z: - return SDL.SDL_Scancode.SDL_SCANCODE_Z; - - case InputKey.Number0: - return SDL.SDL_Scancode.SDL_SCANCODE_0; - - case InputKey.Number1: - return SDL.SDL_Scancode.SDL_SCANCODE_1; - - case InputKey.Number2: - return SDL.SDL_Scancode.SDL_SCANCODE_2; - - case InputKey.Number3: - return SDL.SDL_Scancode.SDL_SCANCODE_3; - - case InputKey.Number4: - return SDL.SDL_Scancode.SDL_SCANCODE_4; - - case InputKey.Number5: - return SDL.SDL_Scancode.SDL_SCANCODE_5; - - case InputKey.Number6: - return SDL.SDL_Scancode.SDL_SCANCODE_6; - - case InputKey.Number7: - return SDL.SDL_Scancode.SDL_SCANCODE_7; - - case InputKey.Number8: - return SDL.SDL_Scancode.SDL_SCANCODE_8; - - case InputKey.Number9: - return SDL.SDL_Scancode.SDL_SCANCODE_9; - - case InputKey.Grave: - return SDL.SDL_Scancode.SDL_SCANCODE_GRAVE; - - case InputKey.Minus: - return SDL.SDL_Scancode.SDL_SCANCODE_MINUS; - - case InputKey.Plus: - return SDL.SDL_Scancode.SDL_SCANCODE_EQUALS; - - case InputKey.BracketLeft: - return SDL.SDL_Scancode.SDL_SCANCODE_LEFTBRACKET; - - case InputKey.BracketRight: - return SDL.SDL_Scancode.SDL_SCANCODE_RIGHTBRACKET; - - case InputKey.Semicolon: - return SDL.SDL_Scancode.SDL_SCANCODE_SEMICOLON; - - case InputKey.Quote: - return SDL.SDL_Scancode.SDL_SCANCODE_APOSTROPHE; - - case InputKey.Comma: - return SDL.SDL_Scancode.SDL_SCANCODE_COMMA; - - case InputKey.Period: - return SDL.SDL_Scancode.SDL_SCANCODE_PERIOD; - - case InputKey.Slash: - return SDL.SDL_Scancode.SDL_SCANCODE_SLASH; - - case InputKey.BackSlash: - return SDL.SDL_Scancode.SDL_SCANCODE_BACKSLASH; - - case InputKey.NonUSBackSlash: - return SDL.SDL_Scancode.SDL_SCANCODE_NONUSBACKSLASH; - - case InputKey.Mute: - return SDL.SDL_Scancode.SDL_SCANCODE_AUDIOMUTE; - - case InputKey.PlayPause: - return SDL.SDL_Scancode.SDL_SCANCODE_AUDIOPLAY; - - case InputKey.Stop: - return SDL.SDL_Scancode.SDL_SCANCODE_AUDIOSTOP; - - case InputKey.VolumeUp: - return SDL.SDL_Scancode.SDL_SCANCODE_VOLUMEUP; - - case InputKey.VolumeDown: - return SDL.SDL_Scancode.SDL_SCANCODE_VOLUMEDOWN; - - case InputKey.TrackPrevious: - return SDL.SDL_Scancode.SDL_SCANCODE_AUDIOPREV; - - case InputKey.TrackNext: - return SDL.SDL_Scancode.SDL_SCANCODE_AUDIONEXT; - - case InputKey.LShift: - return SDL.SDL_Scancode.SDL_SCANCODE_LSHIFT; - - case InputKey.RShift: - return SDL.SDL_Scancode.SDL_SCANCODE_RSHIFT; - - case InputKey.LControl: - return SDL.SDL_Scancode.SDL_SCANCODE_LCTRL; - - case InputKey.RControl: - return SDL.SDL_Scancode.SDL_SCANCODE_RCTRL; - - case InputKey.LAlt: - return SDL.SDL_Scancode.SDL_SCANCODE_LALT; - - case InputKey.RAlt: - return SDL.SDL_Scancode.SDL_SCANCODE_RALT; - - case InputKey.LSuper: - return SDL.SDL_Scancode.SDL_SCANCODE_LGUI; - - case InputKey.RSuper: - return SDL.SDL_Scancode.SDL_SCANCODE_RGUI; - } - } - - public static WindowState ToWindowState(this SDL.SDL_WindowFlags windowFlags) - { - if (windowFlags.HasFlagFast(SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP) || - windowFlags.HasFlagFast(SDL.SDL_WindowFlags.SDL_WINDOW_BORDERLESS)) - return WindowState.FullscreenBorderless; - - if (windowFlags.HasFlagFast(SDL.SDL_WindowFlags.SDL_WINDOW_MINIMIZED)) - return WindowState.Minimised; - - if (windowFlags.HasFlagFast(SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN)) - return WindowState.Fullscreen; - - if (windowFlags.HasFlagFast(SDL.SDL_WindowFlags.SDL_WINDOW_MAXIMIZED)) - return WindowState.Maximised; - - return WindowState.Normal; - } - - public static SDL.SDL_WindowFlags ToFlags(this WindowState state) - { - switch (state) - { - case WindowState.Normal: - return 0; - - case WindowState.Fullscreen: - return SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN; - - case WindowState.Maximised: - return SDL.SDL_WindowFlags.SDL_WINDOW_MAXIMIZED; - - case WindowState.Minimised: - return SDL.SDL_WindowFlags.SDL_WINDOW_MINIMIZED; - - case WindowState.FullscreenBorderless: - return SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP; - } - - return 0; - } - - public static SDL.SDL_WindowFlags ToFlags(this GraphicsSurfaceType surfaceType) - { - switch (surfaceType) - { - case GraphicsSurfaceType.OpenGL: - return SDL.SDL_WindowFlags.SDL_WINDOW_OPENGL; - - case GraphicsSurfaceType.Vulkan when !RuntimeInfo.IsApple: - return SDL.SDL_WindowFlags.SDL_WINDOW_VULKAN; - - case GraphicsSurfaceType.Metal: - case GraphicsSurfaceType.Vulkan when RuntimeInfo.IsApple: - return SDL.SDL_WindowFlags.SDL_WINDOW_METAL; - } - - return 0; - } - - public static JoystickAxisSource ToJoystickAxisSource(this SDL.SDL_GameControllerAxis axis) - { - switch (axis) - { - default: - case SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_INVALID: - return 0; - - case SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTX: - return JoystickAxisSource.GamePadLeftStickX; - - case SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTY: - return JoystickAxisSource.GamePadLeftStickY; - - case SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERLEFT: - return JoystickAxisSource.GamePadLeftTrigger; - - case SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTX: - return JoystickAxisSource.GamePadRightStickX; - - case SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTY: - return JoystickAxisSource.GamePadRightStickY; - - case SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERRIGHT: - return JoystickAxisSource.GamePadRightTrigger; - } - } - - public static JoystickButton ToJoystickButton(this SDL.SDL_GameControllerButton button) - { - switch (button) - { - default: - case SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID: - return 0; - - case SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A: - return JoystickButton.GamePadA; - - case SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_B: - return JoystickButton.GamePadB; - - case SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X: - return JoystickButton.GamePadX; - - case SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y: - return JoystickButton.GamePadY; - - case SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_BACK: - return JoystickButton.GamePadBack; - - case SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_GUIDE: - return JoystickButton.GamePadGuide; - - case SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START: - return JoystickButton.GamePadStart; - - case SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSTICK: - return JoystickButton.GamePadLeftStick; - - case SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSTICK: - return JoystickButton.GamePadRightStick; - - case SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER: - return JoystickButton.GamePadLeftShoulder; - - case SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: - return JoystickButton.GamePadRightShoulder; - - case SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_UP: - return JoystickButton.GamePadDPadUp; - - case SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_DOWN: - return JoystickButton.GamePadDPadDown; - - case SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_LEFT: - return JoystickButton.GamePadDPadLeft; - - case SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_RIGHT: - return JoystickButton.GamePadDPadRight; - } - } - - public static SDL.SDL_Rect ToSDLRect(this RectangleI rectangle) => - new SDL.SDL_Rect - { - x = rectangle.X, - y = rectangle.Y, - h = rectangle.Height, - w = rectangle.Width, - }; - - /// - /// Converts a UTF-8 byte pointer to a string. - /// - /// Most commonly used with SDL text events. - /// Pointer to UTF-8 encoded byte array. - /// The resulting string - /// true if the was successfully converted to a string. - public static unsafe bool TryGetStringFromBytePointer(byte* bytePointer, out string str) - { - IntPtr ptr = new IntPtr(bytePointer); - - if (ptr == IntPtr.Zero) - { - str = null; - return false; - } - - str = Marshal.PtrToStringUTF8(ptr) ?? string.Empty; - return true; - } - - public static DisplayMode ToDisplayMode(this SDL.SDL_DisplayMode mode, int displayIndex) - { - SDL.SDL_PixelFormatEnumToMasks(mode.format, out int bpp, out _, out _, out _, out _); - return new DisplayMode(SDL.SDL_GetPixelFormatName(mode.format), new Size(mode.w, mode.h), bpp, mode.refresh_rate, displayIndex); - } - - public static string ReadableName(this SDL.SDL_LogCategory category) - { - switch (category) - { - case SDL.SDL_LogCategory.SDL_LOG_CATEGORY_APPLICATION: - return "application"; - - case SDL.SDL_LogCategory.SDL_LOG_CATEGORY_ERROR: - return "error"; - - case SDL.SDL_LogCategory.SDL_LOG_CATEGORY_ASSERT: - return "assert"; - - case SDL.SDL_LogCategory.SDL_LOG_CATEGORY_SYSTEM: - return "system"; - - case SDL.SDL_LogCategory.SDL_LOG_CATEGORY_AUDIO: - return "audio"; - - case SDL.SDL_LogCategory.SDL_LOG_CATEGORY_VIDEO: - return "video"; - - case SDL.SDL_LogCategory.SDL_LOG_CATEGORY_RENDER: - return "render"; - - case SDL.SDL_LogCategory.SDL_LOG_CATEGORY_INPUT: - return "input"; - - case SDL.SDL_LogCategory.SDL_LOG_CATEGORY_TEST: - return "test"; - - default: - return "unknown"; - } - } - - public static string ReadableName(this SDL.SDL_LogPriority priority) - { - switch (priority) - { - case SDL.SDL_LogPriority.SDL_LOG_PRIORITY_VERBOSE: - return "verbose"; - - case SDL.SDL_LogPriority.SDL_LOG_PRIORITY_DEBUG: - return "debug"; - - case SDL.SDL_LogPriority.SDL_LOG_PRIORITY_INFO: - return "info"; - - case SDL.SDL_LogPriority.SDL_LOG_PRIORITY_WARN: - return "warn"; - - case SDL.SDL_LogPriority.SDL_LOG_PRIORITY_ERROR: - return "error"; - - case SDL.SDL_LogPriority.SDL_LOG_PRIORITY_CRITICAL: - return "critical"; - - default: - return "unknown"; - } - } - - /// - /// Gets the readable string for this . - /// - /// - /// string in the format of 1920x1080@60. - /// - public static string ReadableString(this SDL.SDL_DisplayMode mode) => $"{mode.w}x{mode.h}@{mode.refresh_rate}"; - - /// - /// Gets the SDL error, and then clears it. - /// - public static string GetAndClearError() - { - string error = SDL.SDL_GetError(); - SDL.SDL_ClearError(); - return error; - } - - private static bool tryGetTouchDeviceIndex(long touchId, out int index) - { - int n = SDL.SDL_GetNumTouchDevices(); - - for (int i = 0; i < n; i++) - { - long currentTouchId = SDL.SDL_GetTouchDevice(i); - - if (touchId == currentTouchId) - { - index = i; - return true; - } - } - - index = -1; - return false; - } - - /// - /// Gets the of the touch device for this . - /// - /// - /// On Windows, this will return "touch" for touchscreen events or "pen" for pen/tablet events. - /// - public static bool TryGetTouchName(this SDL.SDL_TouchFingerEvent e, out string name) - { - if (tryGetTouchDeviceIndex(e.touchId, out int index)) - { - name = SDL.SDL_GetTouchName(index); - return name != null; - } - - name = null; - return false; - } - } -} diff --git a/osu.Framework/Platform/SDL2/SDL2Structs.cs b/osu.Framework/Platform/SDL2/SDL2Structs.cs deleted file mode 100644 index 32c7ffaf9d..0000000000 --- a/osu.Framework/Platform/SDL2/SDL2Structs.cs +++ /dev/null @@ -1,50 +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 System; -using System.Runtime.InteropServices; -using SDL2; - -// ReSharper disable MemberCanBePrivate.Global -// (Some members not currently used) - -// ReSharper disable InconsistentNaming -// ReSharper disable IdentifierTypo -// (Mimics SDL and SDL2-CS naming) - -#pragma warning disable IDE1006 // Naming style - -namespace osu.Framework.Platform.SDL2 -{ - internal static class SDL2Structs - { - [StructLayout(LayoutKind.Sequential)] - internal struct INTERNAL_windows_wmmsg - { - public IntPtr hwnd; - public uint msg; - public ulong wParam; - public long lParam; - } - - [StructLayout(LayoutKind.Explicit)] - internal struct INTERNAL_SysWMmsgUnion - { - [FieldOffset(0)] - public INTERNAL_windows_wmmsg win; - - // could add more native events here if required - } - - /// - /// Member msg of . - /// - [StructLayout(LayoutKind.Sequential)] - public struct SDL_SysWMmsg - { - public SDL.SDL_version version; - public SDL.SDL_SYSWM_TYPE subsystem; - public INTERNAL_SysWMmsgUnion msg; - } - } -} diff --git a/osu.Framework/Platform/SDL2DesktopWindow.cs b/osu.Framework/Platform/SDL3DesktopWindow.cs similarity index 61% rename from osu.Framework/Platform/SDL2DesktopWindow.cs rename to osu.Framework/Platform/SDL3DesktopWindow.cs index 56807f19fb..84e5480a7d 100644 --- a/osu.Framework/Platform/SDL2DesktopWindow.cs +++ b/osu.Framework/Platform/SDL3DesktopWindow.cs @@ -1,25 +1,25 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using SDL2; +using SDL; namespace osu.Framework.Platform { - internal class SDL2DesktopWindow : SDL2Window + internal class SDL3DesktopWindow : SDL3Window { - public SDL2DesktopWindow(GraphicsSurfaceType surfaceType) + public SDL3DesktopWindow(GraphicsSurfaceType surfaceType) : base(surfaceType) { } - protected override void UpdateWindowStateAndSize(WindowState state, Display display, DisplayMode displayMode) + protected override unsafe void UpdateWindowStateAndSize(WindowState state, Display display, DisplayMode displayMode) { // this reset is required even on changing from one fullscreen resolution to another. // if it is not included, the GL context will not get the correct size. // this is mentioned by multiple sources as an SDL issue, which seems to resolve by similar means (see https://discourse.libsdl.org/t/sdl-setwindowsize-does-not-work-in-fullscreen/20711/4). - SDL.SDL_SetWindowBordered(SDLWindowHandle, SDL.SDL_bool.SDL_TRUE); - SDL.SDL_SetWindowFullscreen(SDLWindowHandle, (uint)SDL.SDL_bool.SDL_FALSE); - SDL.SDL_RestoreWindow(SDLWindowHandle); + SDL3.SDL_SetWindowBordered(SDLWindowHandle, SDL_bool.SDL_TRUE); + SDL3.SDL_SetWindowFullscreen(SDLWindowHandle, SDL_bool.SDL_FALSE); + SDL3.SDL_RestoreWindow(SDLWindowHandle); base.UpdateWindowStateAndSize(state, display, displayMode); } diff --git a/osu.Framework/Platform/SDL2GameHost.cs b/osu.Framework/Platform/SDL3GameHost.cs similarity index 80% rename from osu.Framework/Platform/SDL2GameHost.cs rename to osu.Framework/Platform/SDL3GameHost.cs index b59e2be00a..345c06a13b 100644 --- a/osu.Framework/Platform/SDL2GameHost.cs +++ b/osu.Framework/Platform/SDL3GameHost.cs @@ -10,28 +10,28 @@ using osu.Framework.Input.Handlers.Mouse; using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Input.Handlers.Touch; -using osu.Framework.Platform.SDL2; +using osu.Framework.Platform.SDL; namespace osu.Framework.Platform { - public abstract class SDL2GameHost : GameHost + public abstract class SDL3GameHost : GameHost { - public override bool CapsLockEnabled => (Window as SDL2Window)?.CapsLockPressed == true; + public override bool CapsLockEnabled => (Window as SDL3Window)?.CapsLockPressed == true; - protected SDL2GameHost(string gameName, HostOptions? options = null) + protected SDL3GameHost(string gameName, HostOptions? options = null) : base(gameName, options) { } protected override TextInputSource CreateTextInput() { - if (Window is SDL2Window window) - return new SDL2WindowTextInput(window); + if (Window is SDL3Window window) + return new SDL3WindowTextInput(window); return base.CreateTextInput(); } - protected override Clipboard CreateClipboard() => new SDL2Clipboard(); + protected override Clipboard CreateClipboard() => new SDL3Clipboard(); protected override IEnumerable CreateAvailableInputHandlers() => new InputHandler[] diff --git a/osu.Framework/Platform/SDL2Window.cs b/osu.Framework/Platform/SDL3Window.cs similarity index 54% rename from osu.Framework/Platform/SDL2Window.cs rename to osu.Framework/Platform/SDL3Window.cs index 028431b631..2212e946d1 100644 --- a/osu.Framework/Platform/SDL2Window.cs +++ b/osu.Framework/Platform/SDL3Window.cs @@ -3,17 +3,18 @@ using System; using System.IO; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using JetBrains.Annotations; +using System.Text; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Configuration; using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Extensions.ImageExtensions; using osu.Framework.Logging; -using osu.Framework.Platform.SDL2; +using osu.Framework.Platform.SDL; using osu.Framework.Threading; -using SDL2; +using SDL; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; using Image = SixLabors.ImageSharp.Image; @@ -24,11 +25,11 @@ namespace osu.Framework.Platform /// /// Default implementation of a window, using SDL for windowing and graphics support. /// - internal abstract partial class SDL2Window : IWindow + internal abstract unsafe partial class SDL3Window : IWindow { - internal IntPtr SDLWindowHandle { get; private set; } = IntPtr.Zero; + internal SDL_Window* SDLWindowHandle { get; private set; } = null; - private readonly SDL2GraphicsSurface graphicsSurface; + private readonly SDL3GraphicsSurface graphicsSurface; IGraphicsSurface IWindow.GraphicsSurface => graphicsSurface; /// @@ -69,23 +70,14 @@ public string Title set { title = value; - ScheduleCommand(() => SDL.SDL_SetWindowTitle(SDLWindowHandle, title)); + ScheduleCommand(() => SDL3.SDL_SetWindowTitle(SDLWindowHandle, Encoding.UTF8.GetBytes(title))); } } /// /// Whether the current display server is Wayland. /// - internal bool IsWayland - { - get - { - if (SDLWindowHandle == IntPtr.Zero) - return false; - - return GetWindowSystemInformation().subsystem == SDL.SDL_SYSWM_TYPE.SDL_SYSWM_WAYLAND; - } - } + internal bool IsWayland => SDL3.SDL_GetCurrentVideoDriver() == "wayland"; /// /// Gets the native window handle as provided by the operating system. @@ -94,38 +86,36 @@ public IntPtr WindowHandle { get { - if (SDLWindowHandle == IntPtr.Zero) + if (SDLWindowHandle == null) return IntPtr.Zero; - var wmInfo = GetWindowSystemInformation(); + var props = SDL3.SDL_GetWindowProperties(SDLWindowHandle); - // Window handle is selected per subsystem as defined at: - // https://wiki.libsdl.org/SDL_SysWMinfo - switch (wmInfo.subsystem) + switch (RuntimeInfo.OS) { - case SDL.SDL_SYSWM_TYPE.SDL_SYSWM_WINDOWS: - return wmInfo.info.win.window; + case RuntimeInfo.Platform.Windows: + return SDL3.SDL_GetProperty(props, SDL3.SDL_PROP_WINDOW_WIN32_HWND_POINTER, IntPtr.Zero); - case SDL.SDL_SYSWM_TYPE.SDL_SYSWM_X11: - return wmInfo.info.x11.window; + case RuntimeInfo.Platform.Linux: + if (IsWayland) + return SDL3.SDL_GetProperty(props, SDL3.SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, IntPtr.Zero); - case SDL.SDL_SYSWM_TYPE.SDL_SYSWM_DIRECTFB: - return wmInfo.info.dfb.window; + if (SDL3.SDL_GetCurrentVideoDriver() == "x11") + return new IntPtr(SDL3.SDL_GetNumberProperty(props, SDL3.SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0)); - case SDL.SDL_SYSWM_TYPE.SDL_SYSWM_COCOA: - return wmInfo.info.cocoa.window; + return IntPtr.Zero; - case SDL.SDL_SYSWM_TYPE.SDL_SYSWM_UIKIT: - return wmInfo.info.uikit.window; + case RuntimeInfo.Platform.macOS: + return SDL3.SDL_GetProperty(props, SDL3.SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, IntPtr.Zero); - case SDL.SDL_SYSWM_TYPE.SDL_SYSWM_WAYLAND: - return wmInfo.info.wl.surface; + case RuntimeInfo.Platform.iOS: + return SDL3.SDL_GetProperty(props, SDL3.SDL_PROP_WINDOW_UIKIT_WINDOW_POINTER, IntPtr.Zero); - case SDL.SDL_SYSWM_TYPE.SDL_SYSWM_ANDROID: - return wmInfo.info.android.window; + case RuntimeInfo.Platform.Android: + return SDL3.SDL_GetProperty(props, SDL3.SDL_PROP_WINDOW_ANDROID_WINDOW_POINTER, IntPtr.Zero); default: - return IntPtr.Zero; + throw new ArgumentOutOfRangeException(); } } } @@ -134,67 +124,41 @@ public IntPtr DisplayHandle { get { - if (SDLWindowHandle == IntPtr.Zero) + if (SDLWindowHandle == null) return IntPtr.Zero; - var wmInfo = GetWindowSystemInformation(); + var props = SDL3.SDL_GetWindowProperties(SDLWindowHandle); - switch (wmInfo.subsystem) - { - case SDL.SDL_SYSWM_TYPE.SDL_SYSWM_X11: - return wmInfo.info.x11.display; + if (IsWayland) + return SDL3.SDL_GetProperty(props, SDL3.SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, IntPtr.Zero); - case SDL.SDL_SYSWM_TYPE.SDL_SYSWM_WAYLAND: - return wmInfo.info.wl.display; + if (SDL3.SDL_GetCurrentVideoDriver() == "x11") + return SDL3.SDL_GetProperty(props, SDL3.SDL_PROP_WINDOW_X11_DISPLAY_POINTER, IntPtr.Zero); - default: - return IntPtr.Zero; - } + return IntPtr.Zero; } } - internal SDL.SDL_SysWMinfo GetWindowSystemInformation() - { - if (SDLWindowHandle == IntPtr.Zero) - return default; - - var wmInfo = new SDL.SDL_SysWMinfo(); - SDL.SDL_GetVersion(out wmInfo.version); - SDL.SDL_GetWindowWMInfo(SDLWindowHandle, ref wmInfo); - return wmInfo; - } - - public bool CapsLockPressed => SDL.SDL_GetModState().HasFlagFast(SDL.SDL_Keymod.KMOD_CAPS); - - // references must be kept to avoid GC, see https://stackoverflow.com/a/6193914 - - [UsedImplicitly] - private SDL.SDL_LogOutputFunction logOutputDelegate; - - [UsedImplicitly] - private SDL.SDL_EventFilter? eventFilterDelegate; - - [UsedImplicitly] - private SDL.SDL_EventFilter? eventWatchDelegate; + public bool CapsLockPressed => SDL3.SDL_GetModState().HasFlagFast(SDL_Keymod.SDL_KMOD_CAPS); /// - /// Represents a handle to this instance, used for unmanaged callbacks. + /// Represents a handle to this instance, used for unmanaged callbacks. /// - protected ObjectHandle ObjectHandle { get; private set; } + protected ObjectHandle ObjectHandle { get; private set; } - protected SDL2Window(GraphicsSurfaceType surfaceType) + protected SDL3Window(GraphicsSurfaceType surfaceType) { - ObjectHandle = new ObjectHandle(this, GCHandleType.Normal); + ObjectHandle = new ObjectHandle(this, GCHandleType.Normal); - if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_GAMECONTROLLER) < 0) + if (SDL3.SDL_Init(SDL_InitFlags.SDL_INIT_VIDEO | SDL_InitFlags.SDL_INIT_GAMEPAD) < 0) { - throw new InvalidOperationException($"Failed to initialise SDL: {SDL.SDL_GetError()}"); + throw new InvalidOperationException($"Failed to initialise SDL: {SDL3.SDL_GetError()}"); } - SDL.SDL_LogSetPriority((int)SDL.SDL_LogCategory.SDL_LOG_CATEGORY_ERROR, SDL.SDL_LogPriority.SDL_LOG_PRIORITY_DEBUG); - SDL.SDL_LogSetOutputFunction(logOutputDelegate = logOutput, IntPtr.Zero); + SDL3.SDL_LogSetPriority(SDL_LogCategory.SDL_LOG_CATEGORY_ERROR, SDL_LogPriority.SDL_LOG_PRIORITY_DEBUG); + SDL3.SDL_SetLogOutputFunction(&logOutput, IntPtr.Zero); - graphicsSurface = new SDL2GraphicsSurface(this, surfaceType); + graphicsSurface = new SDL3GraphicsSurface(this, surfaceType); CursorStateBindable.ValueChanged += evt => { @@ -205,12 +169,10 @@ protected SDL2Window(GraphicsSurfaceType surfaceType) populateJoysticks(); } - [MonoPInvokeCallback(typeof(SDL.SDL_LogOutputFunction))] - private static void logOutput(IntPtr _, int categoryInt, SDL.SDL_LogPriority priority, IntPtr messagePtr) + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] + private static void logOutput(IntPtr _, SDL_LogCategory category, SDL_LogPriority priority, byte* messagePtr) { - var category = (SDL.SDL_LogCategory)categoryInt; - string? message = Marshal.PtrToStringUTF8(messagePtr); - + string? message = SDL3.PtrToStringUTF8(messagePtr); Logger.Log($@"SDL {category.ReadableName()} log [{priority.ReadableName()}]: {message}"); } @@ -222,28 +184,28 @@ public void SetupWindow(FrameworkConfigManager config) public virtual void Create() { - SDL.SDL_WindowFlags flags = SDL.SDL_WindowFlags.SDL_WINDOW_RESIZABLE | - SDL.SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI | - SDL.SDL_WindowFlags.SDL_WINDOW_HIDDEN; // shown after first swap to avoid white flash on startup (windows) + SDL_WindowFlags flags = SDL_WindowFlags.SDL_WINDOW_RESIZABLE | + SDL_WindowFlags.SDL_WINDOW_HIGH_PIXEL_DENSITY | + SDL_WindowFlags.SDL_WINDOW_HIDDEN; // shown after first swap to avoid white flash on startup (windows) flags |= WindowState.ToFlags(); flags |= graphicsSurface.Type.ToFlags(); - SDL.SDL_SetHint(SDL.SDL_HINT_WINDOWS_NO_CLOSE_ON_ALT_F4, "1"); - SDL.SDL_SetHint(SDL.SDL_HINT_IME_SHOW_UI, "1"); - SDL.SDL_SetHint(SDL.SDL_HINT_MOUSE_RELATIVE_MODE_CENTER, "0"); - SDL.SDL_SetHint(SDL.SDL_HINT_TOUCH_MOUSE_EVENTS, "0"); // disable touch events generating synthetic mouse events on desktop platforms - SDL.SDL_SetHint(SDL.SDL_HINT_MOUSE_TOUCH_EVENTS, "0"); // disable mouse events generating synthetic touch events on mobile platforms + SDL3.SDL_SetHint(SDL3.SDL_HINT_WINDOWS_CLOSE_ON_ALT_F4, "0"u8); + SDL3.SDL_SetHint(SDL3.SDL_HINT_IME_SHOW_UI, "1"u8); + SDL3.SDL_SetHint(SDL3.SDL_HINT_MOUSE_RELATIVE_MODE_CENTER, "0"u8); + SDL3.SDL_SetHint(SDL3.SDL_HINT_TOUCH_MOUSE_EVENTS, "0"u8); // disable touch events generating synthetic mouse events on desktop platforms + SDL3.SDL_SetHint(SDL3.SDL_HINT_MOUSE_TOUCH_EVENTS, "0"u8); // disable mouse events generating synthetic touch events on mobile platforms - // we want text input to only be active when SDL2DesktopWindowTextInput is active. + // we want text input to only be active when SDL3DesktopWindowTextInput is active. // SDL activates it by default on some platforms: https://github.com/libsdl-org/SDL/blob/release-2.0.16/src/video/SDL_video.c#L573-L582 // so we deactivate it on startup. - SDL.SDL_StopTextInput(); + SDL3.SDL_StopTextInput(); - SDLWindowHandle = SDL.SDL_CreateWindow(title, Position.X, Position.Y, Size.Width, Size.Height, flags); + SDLWindowHandle = SDL3.SDL_CreateWindow(Encoding.UTF8.GetBytes(title), Size.Width, Size.Height, flags); - if (SDLWindowHandle == IntPtr.Zero) - throw new InvalidOperationException($"Failed to create SDL window. SDL Error: {SDL.SDL_GetError()}"); + if (SDLWindowHandle == null) + throw new InvalidOperationException($"Failed to create SDL window. SDL Error: {SDL3.SDL_GetError()}"); graphicsSurface.Initialise(); @@ -254,10 +216,10 @@ public virtual void Create() /// /// Starts the window's run loop. /// - public void Run() + public virtual void Run() { - SDL.SDL_SetEventFilter(eventFilterDelegate = eventFilter, ObjectHandle.Handle); - SDL.SDL_AddEventWatch(eventWatchDelegate = eventWatch, ObjectHandle.Handle); + SDL3.SDL_SetEventFilter(&eventFilter, ObjectHandle.Handle); + SDL3.SDL_AddEventWatch(&eventWatch, ObjectHandle.Handle); RunMainLoop(); } @@ -279,7 +241,7 @@ protected virtual void RunMainLoop() Exited?.Invoke(); Close(); - SDL.SDL_Quit(); + SDL3.SDL_Quit(); } /// @@ -305,63 +267,63 @@ protected void RunFrame() } /// - /// Handles s fired from the SDL event filter. + /// Handles s fired from the SDL event filter. /// /// /// As per SDL's recommendation, application events should always be handled via the event filter. - /// See: https://wiki.libsdl.org/SDL2/SDL_EventType#android_ios_and_winrt_events + /// See: https://wiki.libsdl.org/SDL3/SDL_EventType#android_ios_and_winrt_events /// - protected virtual void HandleEventFromFilter(SDL.SDL_Event evt) + protected virtual void HandleEventFromFilter(SDL_Event evt) { switch (evt.type) { - case SDL.SDL_EventType.SDL_APP_TERMINATING: + case SDL_EventType.SDL_EVENT_TERMINATING: handleQuitEvent(evt.quit); break; - case SDL.SDL_EventType.SDL_APP_DIDENTERBACKGROUND: + case SDL_EventType.SDL_EVENT_DID_ENTER_BACKGROUND: Suspended?.Invoke(); break; - case SDL.SDL_EventType.SDL_APP_WILLENTERFOREGROUND: + case SDL_EventType.SDL_EVENT_WILL_ENTER_FOREGROUND: Resumed?.Invoke(); break; - case SDL.SDL_EventType.SDL_APP_LOWMEMORY: + case SDL_EventType.SDL_EVENT_LOW_MEMORY: LowOnMemory?.Invoke(); break; } } - protected void HandleEventFromWatch(SDL.SDL_Event evt) + protected void HandleEventFromWatch(SDL_Event evt) { switch (evt.type) { - case SDL.SDL_EventType.SDL_WINDOWEVENT: + case SDL_EventType.SDL_EVENT_WINDOW_RESIZED: // polling via SDL_PollEvent blocks on resizes (https://stackoverflow.com/a/50858339) - if (evt.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_RESIZED && !updatingWindowStateAndSize) + if (!updatingWindowStateAndSize) fetchWindowSize(); break; } } - [MonoPInvokeCallback(typeof(SDL.SDL_EventFilter))] - private static int eventFilter(IntPtr userdata, IntPtr eventPtr) + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] + private static int eventFilter(IntPtr userdata, SDL_Event* eventPtr) { - var handle = new ObjectHandle(userdata); - if (handle.GetTarget(out SDL2Window window)) - window.HandleEventFromFilter(Marshal.PtrToStructure(eventPtr)); + var handle = new ObjectHandle(userdata); + if (handle.GetTarget(out SDL3Window window)) + window.HandleEventFromFilter(*eventPtr); return 1; } - [MonoPInvokeCallback(typeof(SDL.SDL_EventFilter))] - private static int eventWatch(IntPtr userdata, IntPtr eventPtr) + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] + private static int eventWatch(IntPtr userdata, SDL_Event* eventPtr) { - var handle = new ObjectHandle(userdata); - if (handle.GetTarget(out SDL2Window window)) - window.HandleEventFromWatch(Marshal.PtrToStructure(eventPtr)); + var handle = new ObjectHandle(userdata); + if (handle.GetTarget(out SDL3Window window)) + window.HandleEventFromWatch(*eventPtr); return 1; } @@ -389,32 +351,32 @@ public void Close() } else { - if (SDLWindowHandle != IntPtr.Zero) + if (SDLWindowHandle != null) { - SDL.SDL_DestroyWindow(SDLWindowHandle); - SDLWindowHandle = IntPtr.Zero; + SDL3.SDL_DestroyWindow(SDLWindowHandle); + SDLWindowHandle = null; } } } public void Raise() => ScheduleCommand(() => { - var flags = (SDL.SDL_WindowFlags)SDL.SDL_GetWindowFlags(SDLWindowHandle); + var flags = SDL3.SDL_GetWindowFlags(SDLWindowHandle); - if (flags.HasFlagFast(SDL.SDL_WindowFlags.SDL_WINDOW_MINIMIZED)) - SDL.SDL_RestoreWindow(SDLWindowHandle); + if (flags.HasFlagFast(SDL_WindowFlags.SDL_WINDOW_MINIMIZED)) + SDL3.SDL_RestoreWindow(SDLWindowHandle); - SDL.SDL_RaiseWindow(SDLWindowHandle); + SDL3.SDL_RaiseWindow(SDLWindowHandle); }); public void Hide() => ScheduleCommand(() => { - SDL.SDL_HideWindow(SDLWindowHandle); + SDL3.SDL_HideWindow(SDLWindowHandle); }); public void Show() => ScheduleCommand(() => { - SDL.SDL_ShowWindow(SDLWindowHandle); + SDL3.SDL_ShowWindow(SDLWindowHandle); }); public void Flash(bool flashUntilFocused = false) => ScheduleCommand(() => @@ -425,9 +387,9 @@ public void Close() if (!RuntimeInfo.IsDesktop) return; - SDL.SDL_FlashWindow(SDLWindowHandle, flashUntilFocused - ? SDL.SDL_FlashOperation.SDL_FLASH_UNTIL_FOCUSED - : SDL.SDL_FlashOperation.SDL_FLASH_BRIEFLY); + SDL3.SDL_FlashWindow(SDLWindowHandle, flashUntilFocused + ? SDL_FlashOperation.SDL_FLASH_UNTIL_FOCUSED + : SDL_FlashOperation.SDL_FLASH_BRIEFLY); }); public void CancelFlash() => ScheduleCommand(() => @@ -435,14 +397,14 @@ public void Close() if (!RuntimeInfo.IsDesktop) return; - SDL.SDL_FlashWindow(SDLWindowHandle, SDL.SDL_FlashOperation.SDL_FLASH_CANCEL); + SDL3.SDL_FlashWindow(SDLWindowHandle, SDL_FlashOperation.SDL_FLASH_CANCEL); }); /// /// Attempts to set the window's icon to the specified image. /// /// An to set as the window icon. - private unsafe void setSDLIcon(Image image) + private void setSDLIcon(Image image) { var pixelMemory = image.CreateReadOnlyPixelMemory(); var imageSize = image.Size; @@ -451,12 +413,16 @@ private unsafe void setSDLIcon(Image image) { var pixelSpan = pixelMemory.Span; - IntPtr surface; + SDL_Surface* surface; + fixed (Rgba32* ptr = pixelSpan) - surface = SDL.SDL_CreateRGBSurfaceFrom(new IntPtr(ptr), imageSize.Width, imageSize.Height, 32, imageSize.Width * 4, 0xff, 0xff00, 0xff0000, 0xff000000); + { + var pixelFormat = SDL3.SDL_GetPixelFormatEnumForMasks(32, 0xff, 0xff00, 0xff0000, 0xff000000); + surface = SDL3.SDL_CreateSurfaceFrom(new IntPtr(ptr), imageSize.Width, imageSize.Height, imageSize.Width * 4, pixelFormat); + } - SDL.SDL_SetWindowIcon(SDLWindowHandle, surface); - SDL.SDL_FreeSurface(surface); + SDL3.SDL_SetWindowIcon(SDLWindowHandle, surface); + SDL3.SDL_DestroySurface(surface); }); } @@ -471,128 +437,133 @@ private unsafe void setSDLIcon(Image image) protected void ScheduleCommand(Action action) => commandScheduler.Add(action, false); private const int events_per_peep = 64; - private readonly SDL.SDL_Event[] events = new SDL.SDL_Event[events_per_peep]; + private readonly SDL_Event[] events = new SDL_Event[events_per_peep]; /// /// Poll for all pending events. /// private void pollSDLEvents() { - SDL.SDL_PumpEvents(); + SDL3.SDL_PumpEvents(); int eventsRead; do { - eventsRead = SDL.SDL_PeepEvents(events, events_per_peep, SDL.SDL_eventaction.SDL_GETEVENT, SDL.SDL_EventType.SDL_FIRSTEVENT, SDL.SDL_EventType.SDL_LASTEVENT); + fixed (SDL_Event* buf = events) + eventsRead = SDL3.SDL_PeepEvents(buf, events_per_peep, SDL_eventaction.SDL_GETEVENT, SDL_EventType.SDL_EVENT_FIRST, SDL_EventType.SDL_EVENT_LAST); for (int i = 0; i < eventsRead; i++) HandleEvent(events[i]); } while (eventsRead == events_per_peep); } /// - /// Handles s polled on the main thread. + /// Handles s polled on the main thread. /// - protected virtual void HandleEvent(SDL.SDL_Event e) + protected virtual void HandleEvent(SDL_Event e) { - switch (e.type) + if (e.type >= SDL_EventType.SDL_EVENT_DISPLAY_FIRST && e.type <= SDL_EventType.SDL_EVENT_DISPLAY_LAST) { - case SDL.SDL_EventType.SDL_QUIT: - handleQuitEvent(e.quit); - break; + handleDisplayEvent(e.display); + return; + } - case SDL.SDL_EventType.SDL_DISPLAYEVENT: - handleDisplayEvent(e.display); - break; + if (e.type >= SDL_EventType.SDL_EVENT_WINDOW_FIRST && e.type <= SDL_EventType.SDL_EVENT_WINDOW_LAST) + { + handleWindowEvent(e.window); + return; + } - case SDL.SDL_EventType.SDL_WINDOWEVENT: - handleWindowEvent(e.window); + switch (e.type) + { + case SDL_EventType.SDL_EVENT_QUIT: + handleQuitEvent(e.quit); break; - case SDL.SDL_EventType.SDL_KEYDOWN: - case SDL.SDL_EventType.SDL_KEYUP: + case SDL_EventType.SDL_EVENT_KEY_DOWN: + case SDL_EventType.SDL_EVENT_KEY_UP: handleKeyboardEvent(e.key); break; - case SDL.SDL_EventType.SDL_TEXTEDITING: + case SDL_EventType.SDL_EVENT_TEXT_EDITING: HandleTextEditingEvent(e.edit); break; - case SDL.SDL_EventType.SDL_TEXTINPUT: + case SDL_EventType.SDL_EVENT_TEXT_INPUT: HandleTextInputEvent(e.text); break; - case SDL.SDL_EventType.SDL_KEYMAPCHANGED: + case SDL_EventType.SDL_EVENT_KEYMAP_CHANGED: handleKeymapChangedEvent(); break; - case SDL.SDL_EventType.SDL_MOUSEMOTION: + case SDL_EventType.SDL_EVENT_MOUSE_MOTION: handleMouseMotionEvent(e.motion); break; - case SDL.SDL_EventType.SDL_MOUSEBUTTONDOWN: - case SDL.SDL_EventType.SDL_MOUSEBUTTONUP: + case SDL_EventType.SDL_EVENT_MOUSE_BUTTON_DOWN: + case SDL_EventType.SDL_EVENT_MOUSE_BUTTON_UP: handleMouseButtonEvent(e.button); break; - case SDL.SDL_EventType.SDL_MOUSEWHEEL: + case SDL_EventType.SDL_EVENT_MOUSE_WHEEL: handleMouseWheelEvent(e.wheel); break; - case SDL.SDL_EventType.SDL_JOYAXISMOTION: + case SDL_EventType.SDL_EVENT_JOYSTICK_AXIS_MOTION: handleJoyAxisEvent(e.jaxis); break; - case SDL.SDL_EventType.SDL_JOYBALLMOTION: + case SDL_EventType.SDL_EVENT_JOYSTICK_BALL_MOTION: handleJoyBallEvent(e.jball); break; - case SDL.SDL_EventType.SDL_JOYHATMOTION: + case SDL_EventType.SDL_EVENT_JOYSTICK_HAT_MOTION: handleJoyHatEvent(e.jhat); break; - case SDL.SDL_EventType.SDL_JOYBUTTONDOWN: - case SDL.SDL_EventType.SDL_JOYBUTTONUP: + case SDL_EventType.SDL_EVENT_JOYSTICK_BUTTON_DOWN: + case SDL_EventType.SDL_EVENT_JOYSTICK_BUTTON_UP: handleJoyButtonEvent(e.jbutton); break; - case SDL.SDL_EventType.SDL_JOYDEVICEADDED: - case SDL.SDL_EventType.SDL_JOYDEVICEREMOVED: + case SDL_EventType.SDL_EVENT_JOYSTICK_ADDED: + case SDL_EventType.SDL_EVENT_JOYSTICK_REMOVED: handleJoyDeviceEvent(e.jdevice); break; - case SDL.SDL_EventType.SDL_CONTROLLERAXISMOTION: - handleControllerAxisEvent(e.caxis); + case SDL_EventType.SDL_EVENT_GAMEPAD_AXIS_MOTION: + handleControllerAxisEvent(e.gaxis); break; - case SDL.SDL_EventType.SDL_CONTROLLERBUTTONDOWN: - case SDL.SDL_EventType.SDL_CONTROLLERBUTTONUP: - handleControllerButtonEvent(e.cbutton); + case SDL_EventType.SDL_EVENT_GAMEPAD_BUTTON_DOWN: + case SDL_EventType.SDL_EVENT_GAMEPAD_BUTTON_UP: + handleControllerButtonEvent(e.gbutton); break; - case SDL.SDL_EventType.SDL_CONTROLLERDEVICEADDED: - case SDL.SDL_EventType.SDL_CONTROLLERDEVICEREMOVED: - case SDL.SDL_EventType.SDL_CONTROLLERDEVICEREMAPPED: - handleControllerDeviceEvent(e.cdevice); + case SDL_EventType.SDL_EVENT_GAMEPAD_ADDED: + case SDL_EventType.SDL_EVENT_GAMEPAD_REMOVED: + case SDL_EventType.SDL_EVENT_GAMEPAD_REMAPPED: + handleControllerDeviceEvent(e.gdevice); break; - case SDL.SDL_EventType.SDL_FINGERDOWN: - case SDL.SDL_EventType.SDL_FINGERUP: - case SDL.SDL_EventType.SDL_FINGERMOTION: + case SDL_EventType.SDL_EVENT_FINGER_DOWN: + case SDL_EventType.SDL_EVENT_FINGER_UP: + case SDL_EventType.SDL_EVENT_FINGER_MOTION: HandleTouchFingerEvent(e.tfinger); break; - case SDL.SDL_EventType.SDL_DROPFILE: - case SDL.SDL_EventType.SDL_DROPTEXT: - case SDL.SDL_EventType.SDL_DROPBEGIN: - case SDL.SDL_EventType.SDL_DROPCOMPLETE: + case SDL_EventType.SDL_EVENT_DROP_FILE: + case SDL_EventType.SDL_EVENT_DROP_TEXT: + case SDL_EventType.SDL_EVENT_DROP_BEGIN: + case SDL_EventType.SDL_EVENT_DROP_COMPLETE: handleDropEvent(e.drop); break; } } // ReSharper disable once UnusedParameter.Local - private void handleQuitEvent(SDL.SDL_QuitEvent evtQuit) => ExitRequested?.Invoke(); + private void handleQuitEvent(SDL_QuitEvent evtQuit) => ExitRequested?.Invoke(); #endregion @@ -669,7 +640,7 @@ internal virtual void SetIconFromGroup(IconGroup iconGroup) public void Dispose() { Close(); - SDL.SDL_Quit(); + SDL3.SDL_Quit(); ObjectHandle.Dispose(); } diff --git a/osu.Framework/Platform/SDL2Window_Input.cs b/osu.Framework/Platform/SDL3Window_Input.cs similarity index 68% rename from osu.Framework/Platform/SDL2Window_Input.cs rename to osu.Framework/Platform/SDL3Window_Input.cs index e09842121d..b48e35110e 100644 --- a/osu.Framework/Platform/SDL2Window_Input.cs +++ b/osu.Framework/Platform/SDL3Window_Input.cs @@ -12,15 +12,15 @@ using osu.Framework.Input; using osu.Framework.Input.States; using osu.Framework.Logging; -using osu.Framework.Platform.SDL2; +using osu.Framework.Platform.SDL; using osuTK; using osuTK.Input; -using SDL2; +using SDL; using RectangleF = osu.Framework.Graphics.Primitives.RectangleF; namespace osu.Framework.Platform { - internal partial class SDL2Window + internal partial class SDL3Window { private void setupInput(FrameworkConfigManager config) { @@ -33,7 +33,7 @@ private void setupInput(FrameworkConfigManager config) private bool relativeMouseMode; /// - /// Set the state of SDL2's RelativeMouseMode (https://wiki.libsdl.org/SDL_SetRelativeMouseMode). + /// Set the state of SDL3's RelativeMouseMode (https://wiki.libsdl.org/SDL_SetRelativeMouseMode). /// On all platforms, this will lock the mouse to the window (although escaping by setting is still possible via a local implementation). /// On windows, this will use raw input if available. /// @@ -49,7 +49,7 @@ public bool RelativeMouseMode throw new InvalidOperationException($"Cannot set {nameof(RelativeMouseMode)} to true when the cursor is not hidden via {nameof(CursorState)}."); relativeMouseMode = value; - ScheduleCommand(() => SDL.SDL_SetRelativeMouseMode(value ? SDL.SDL_bool.SDL_TRUE : SDL.SDL_bool.SDL_FALSE)); + ScheduleCommand(() => SDL3.SDL_SetRelativeMouseMode(value ? SDL_bool.SDL_TRUE : SDL_bool.SDL_FALSE)); updateCursorConfinement(); } } @@ -59,15 +59,15 @@ public bool RelativeMouseMode /// Only works with disabled. /// /// - /// If the cursor leaves the window while it's captured, is not sent until the button(s) are released. - /// And if the cursor leaves and enters the window while captured, is not sent either. - /// We disable relative mode when the cursor exits window bounds (not on the event), but we only enable it again on . + /// If the cursor leaves the window while it's captured, is not sent until the button(s) are released. + /// And if the cursor leaves and enters the window while captured, is not sent either. + /// We disable relative mode when the cursor exits window bounds (not on the event), but we only enable it again on . /// The above culminate in staying off when the cursor leaves and enters the window bounds when any buttons are pressed. /// This is an invalid state, as the cursor is inside the window, and is off. /// internal bool MouseAutoCapture { - set => ScheduleCommand(() => SDL.SDL_SetHint(SDL.SDL_HINT_MOUSE_AUTO_CAPTURE, value ? "1" : "0")); + set => ScheduleCommand(() => SDL3.SDL_SetHint(SDL3.SDL_HINT_MOUSE_AUTO_CAPTURE, value ? "1"u8 : "0"u8)); } /// @@ -93,30 +93,39 @@ public CursorState CursorState } } - private readonly Dictionary controllers = new Dictionary(); + private readonly Dictionary controllers = new Dictionary(); private void updateCursorVisibility(bool cursorVisible) => - ScheduleCommand(() => SDL.SDL_ShowCursor(cursorVisible ? SDL.SDL_ENABLE : SDL.SDL_DISABLE)); + ScheduleCommand(() => + { + if (cursorVisible) + SDL3.SDL_ShowCursor(); + else + SDL3.SDL_HideCursor(); + }); /// /// Updates OS cursor confinement based on the current , and . /// - private void updateCursorConfinement() + private unsafe void updateCursorConfinement() { bool confined = CursorState.HasFlagFast(CursorState.Confined); - ScheduleCommand(() => SDL.SDL_SetWindowGrab(SDLWindowHandle, confined ? SDL.SDL_bool.SDL_TRUE : SDL.SDL_bool.SDL_FALSE)); + ScheduleCommand(() => SDL3.SDL_SetWindowMouseGrab(SDLWindowHandle, confined ? SDL_bool.SDL_TRUE : SDL_bool.SDL_FALSE)); // Don't use SDL_SetWindowMouseRect when relative mode is enabled, as relative mode already confines the OS cursor to the window. // This is fine for our use case, as UserInputManager will clamp the mouse position. if (CursorConfineRect != null && confined && !RelativeMouseMode) { - var rect = ((RectangleI)(CursorConfineRect / Scale)).ToSDLRect(); - ScheduleCommand(() => SDL.SDL_SetWindowMouseRect(SDLWindowHandle, ref rect)); + ScheduleCommand(() => + { + var rect = ((RectangleI)(CursorConfineRect / Scale)).ToSDLRect(); + SDL3.SDL_SetWindowMouseRect(SDLWindowHandle, &rect); + }); } else { - ScheduleCommand(() => SDL.SDL_SetWindowMouseRect(SDLWindowHandle, IntPtr.Zero)); + ScheduleCommand(() => SDL3.SDL_SetWindowMouseRect(SDLWindowHandle, null)); } } @@ -140,21 +149,22 @@ private void enqueueJoystickButtonInput(JoystickButton button, bool isPressed) JoystickButtonUp?.Invoke(button); } - private Point previousPolledPoint = Point.Empty; + private PointF previousPolledPoint = PointF.Empty; private SDLButtonMask pressedButtons; - private void pollMouse() + private unsafe void pollMouse() { - SDLButtonMask globalButtons = (SDLButtonMask)SDL.SDL_GetGlobalMouseState(out int x, out int y); + float x, y; + SDLButtonMask globalButtons = (SDLButtonMask)SDL3.SDL_GetGlobalMouseState(&x, &y); if (previousPolledPoint.X != x || previousPolledPoint.Y != y) { - previousPolledPoint = new Point(x, y); + previousPolledPoint = new PointF(x, y); var pos = WindowMode.Value == Configuration.WindowMode.Windowed ? Position : windowDisplayBounds.Location; - int rx = x - pos.X; - int ry = y - pos.Y; + float rx = x - pos.X; + float ry = y - pos.Y; MouseMove?.Invoke(new Vector2(rx * Scale, ry * Scale)); } @@ -163,19 +173,19 @@ private void pollMouse() SDLButtonMask buttonsToRelease = pressedButtons & (globalButtons ^ pressedButtons); // the outer if just optimises for the common case that there are no buttons to release. - if (buttonsToRelease != SDLButtonMask.None) + if (buttonsToRelease != 0) { - if (buttonsToRelease.HasFlagFast(SDLButtonMask.Left)) MouseUp?.Invoke(MouseButton.Left); - if (buttonsToRelease.HasFlagFast(SDLButtonMask.Middle)) MouseUp?.Invoke(MouseButton.Middle); - if (buttonsToRelease.HasFlagFast(SDLButtonMask.Right)) MouseUp?.Invoke(MouseButton.Right); - if (buttonsToRelease.HasFlagFast(SDLButtonMask.X1)) MouseUp?.Invoke(MouseButton.Button1); - if (buttonsToRelease.HasFlagFast(SDLButtonMask.X2)) MouseUp?.Invoke(MouseButton.Button2); + if (buttonsToRelease.HasFlagFast(SDLButtonMask.SDL_BUTTON_LMASK)) MouseUp?.Invoke(MouseButton.Left); + if (buttonsToRelease.HasFlagFast(SDLButtonMask.SDL_BUTTON_MMASK)) MouseUp?.Invoke(MouseButton.Middle); + if (buttonsToRelease.HasFlagFast(SDLButtonMask.SDL_BUTTON_RMASK)) MouseUp?.Invoke(MouseButton.Right); + if (buttonsToRelease.HasFlagFast(SDLButtonMask.SDL_BUTTON_X1MASK)) MouseUp?.Invoke(MouseButton.Button1); + if (buttonsToRelease.HasFlagFast(SDLButtonMask.SDL_BUTTON_X2MASK)) MouseUp?.Invoke(MouseButton.Button2); } } - public virtual void StartTextInput(bool allowIme) => ScheduleCommand(SDL.SDL_StartTextInput); + public virtual void StartTextInput(bool allowIme) => ScheduleCommand(SDL3.SDL_StartTextInput); - public void StopTextInput() => ScheduleCommand(SDL.SDL_StopTextInput); + public void StopTextInput() => ScheduleCommand(SDL3.SDL_StopTextInput); /// /// Resets internal state of the platform-native IME. @@ -183,24 +193,24 @@ private void pollMouse() /// public virtual void ResetIme() => ScheduleCommand(() => { - SDL.SDL_StopTextInput(); - SDL.SDL_StartTextInput(); + SDL3.SDL_StopTextInput(); + SDL3.SDL_StartTextInput(); }); - public void SetTextInputRect(RectangleF rect) => ScheduleCommand(() => + public unsafe void SetTextInputRect(RectangleF rect) => ScheduleCommand(() => { var sdlRect = ((RectangleI)(rect / Scale)).ToSDLRect(); - SDL.SDL_SetTextInputRect(ref sdlRect); + SDL3.SDL_SetTextInputRect(&sdlRect); }); #region SDL Event Handling - private void handleDropEvent(SDL.SDL_DropEvent evtDrop) + private void handleDropEvent(SDL_DropEvent evtDrop) { switch (evtDrop.type) { - case SDL.SDL_EventType.SDL_DROPFILE: - string str = SDL.UTF8_ToManaged(evtDrop.file, true); + case SDL_EventType.SDL_EVENT_DROP_FILE: + string? str = evtDrop.GetData(); if (str != null) DragDrop?.Invoke(str); @@ -208,9 +218,9 @@ private void handleDropEvent(SDL.SDL_DropEvent evtDrop) } } - private readonly long?[] activeTouches = new long?[TouchState.MAX_TOUCH_COUNT]; + private readonly SDL_FingerID?[] activeTouches = new SDL_FingerID?[TouchState.MAX_TOUCH_COUNT]; - private TouchSource? getTouchSource(long fingerId) + private TouchSource? getTouchSource(SDL_FingerID fingerId) { for (int i = 0; i < activeTouches.Length; i++) { @@ -221,7 +231,7 @@ private void handleDropEvent(SDL.SDL_DropEvent evtDrop) return null; } - private TouchSource? assignNextAvailableTouchSource(long fingerId) + private TouchSource? assignNextAvailableTouchSource(SDL_FingerID fingerId) { for (int i = 0; i < activeTouches.Length; i++) { @@ -235,14 +245,14 @@ private void handleDropEvent(SDL.SDL_DropEvent evtDrop) return null; } - protected virtual void HandleTouchFingerEvent(SDL.SDL_TouchFingerEvent evtTfinger) + protected virtual void HandleTouchFingerEvent(SDL_TouchFingerEvent evtTfinger) { - var existingSource = getTouchSource(evtTfinger.fingerId); + var existingSource = getTouchSource(evtTfinger.fingerID); - if (evtTfinger.type == SDL.SDL_EventType.SDL_FINGERDOWN) + if (evtTfinger.type == SDL_EventType.SDL_EVENT_FINGER_DOWN) { Debug.Assert(existingSource == null); - existingSource = assignNextAvailableTouchSource(evtTfinger.fingerId); + existingSource = assignNextAvailableTouchSource(evtTfinger.fingerID); } if (existingSource == null) @@ -255,32 +265,32 @@ protected virtual void HandleTouchFingerEvent(SDL.SDL_TouchFingerEvent evtTfinge switch (evtTfinger.type) { - case SDL.SDL_EventType.SDL_FINGERDOWN: - case SDL.SDL_EventType.SDL_FINGERMOTION: + case SDL_EventType.SDL_EVENT_FINGER_DOWN: + case SDL_EventType.SDL_EVENT_FINGER_MOTION: TouchDown?.Invoke(touch); break; - case SDL.SDL_EventType.SDL_FINGERUP: + case SDL_EventType.SDL_EVENT_FINGER_UP: TouchUp?.Invoke(touch); activeTouches[(int)existingSource] = null; break; } } - private void handleControllerDeviceEvent(SDL.SDL_ControllerDeviceEvent evtCdevice) + private unsafe void handleControllerDeviceEvent(SDL_GamepadDeviceEvent evtCdevice) { switch (evtCdevice.type) { - case SDL.SDL_EventType.SDL_CONTROLLERDEVICEADDED: + case SDL_EventType.SDL_EVENT_GAMEPAD_ADDED: addJoystick(evtCdevice.which); break; - case SDL.SDL_EventType.SDL_CONTROLLERDEVICEREMOVED: - SDL.SDL_GameControllerClose(controllers[evtCdevice.which].ControllerHandle); + case SDL_EventType.SDL_EVENT_GAMEPAD_REMOVED: + SDL3.SDL_CloseGamepad(controllers[evtCdevice.which].GamepadHandle); controllers.Remove(evtCdevice.which); break; - case SDL.SDL_EventType.SDL_CONTROLLERDEVICEREMAPPED: + case SDL_EventType.SDL_EVENT_GAMEPAD_REMAPPED: if (controllers.TryGetValue(evtCdevice.which, out var state)) state.PopulateBindings(); @@ -288,40 +298,38 @@ private void handleControllerDeviceEvent(SDL.SDL_ControllerDeviceEvent evtCdevic } } - private void handleControllerButtonEvent(SDL.SDL_ControllerButtonEvent evtCbutton) + private void handleControllerButtonEvent(SDL_GamepadButtonEvent evtCbutton) { - var button = ((SDL.SDL_GameControllerButton)evtCbutton.button).ToJoystickButton(); + var button = evtCbutton.Button.ToJoystickButton(); switch (evtCbutton.type) { - case SDL.SDL_EventType.SDL_CONTROLLERBUTTONDOWN: + case SDL_EventType.SDL_EVENT_GAMEPAD_BUTTON_DOWN: enqueueJoystickButtonInput(button, true); break; - case SDL.SDL_EventType.SDL_CONTROLLERBUTTONUP: + case SDL_EventType.SDL_EVENT_GAMEPAD_BUTTON_UP: enqueueJoystickButtonInput(button, false); break; } } - private void handleControllerAxisEvent(SDL.SDL_ControllerAxisEvent evtCaxis) => - enqueueJoystickAxisInput(((SDL.SDL_GameControllerAxis)evtCaxis.axis).ToJoystickAxisSource(), evtCaxis.axisValue); + private void handleControllerAxisEvent(SDL_GamepadAxisEvent evtCaxis) => + enqueueJoystickAxisInput(evtCaxis.Axis.ToJoystickAxisSource(), evtCaxis.value); - private void addJoystick(int which) + private unsafe void addJoystick(SDL_JoystickID instanceID) { - int instanceID = SDL.SDL_JoystickGetDeviceInstanceID(which); - // if the joystick is already opened, ignore it if (controllers.ContainsKey(instanceID)) return; - IntPtr joystick = SDL.SDL_JoystickOpen(which); + SDL_Joystick* joystick = SDL3.SDL_OpenJoystick(instanceID); - IntPtr controller = IntPtr.Zero; - if (SDL.SDL_IsGameController(which) == SDL.SDL_bool.SDL_TRUE) - controller = SDL.SDL_GameControllerOpen(which); + SDL_Gamepad* controller = null; + if (SDL3.SDL_IsGamepad(instanceID) == SDL_bool.SDL_TRUE) + controller = SDL3.SDL_OpenGamepad(instanceID); - controllers[instanceID] = new SDL2ControllerBindings(joystick, controller); + controllers[instanceID] = new SDL3ControllerBindings(joystick, controller); } /// @@ -329,32 +337,37 @@ private void addJoystick(int which) /// private void populateJoysticks() { - for (int i = 0; i < SDL.SDL_NumJoysticks(); i++) + using var joysticks = SDL3.SDL_GetJoysticks(); + + if (joysticks == null) + return; + + for (int i = 0; i < joysticks.Count; i++) { - addJoystick(i); + addJoystick(joysticks[i]); } } - private void handleJoyDeviceEvent(SDL.SDL_JoyDeviceEvent evtJdevice) + private unsafe void handleJoyDeviceEvent(SDL_JoyDeviceEvent evtJdevice) { switch (evtJdevice.type) { - case SDL.SDL_EventType.SDL_JOYDEVICEADDED: + case SDL_EventType.SDL_EVENT_JOYSTICK_ADDED: addJoystick(evtJdevice.which); break; - case SDL.SDL_EventType.SDL_JOYDEVICEREMOVED: + case SDL_EventType.SDL_EVENT_JOYSTICK_REMOVED: // if the joystick is already closed, ignore it if (!controllers.ContainsKey(evtJdevice.which)) break; - SDL.SDL_JoystickClose(controllers[evtJdevice.which].JoystickHandle); + SDL3.SDL_CloseJoystick(controllers[evtJdevice.which].JoystickHandle); controllers.Remove(evtJdevice.which); break; } } - private void handleJoyButtonEvent(SDL.SDL_JoyButtonEvent evtJbutton) + private void handleJoyButtonEvent(SDL_JoyButtonEvent evtJbutton) { // if this button exists in the controller bindings, skip it if (controllers.TryGetValue(evtJbutton.which, out var state) && state.IsJoystickButtonBound(evtJbutton.button)) @@ -364,96 +377,94 @@ private void handleJoyButtonEvent(SDL.SDL_JoyButtonEvent evtJbutton) switch (evtJbutton.type) { - case SDL.SDL_EventType.SDL_JOYBUTTONDOWN: + case SDL_EventType.SDL_EVENT_JOYSTICK_BUTTON_DOWN: enqueueJoystickButtonInput(button, true); break; - case SDL.SDL_EventType.SDL_JOYBUTTONUP: + case SDL_EventType.SDL_EVENT_JOYSTICK_BUTTON_UP: enqueueJoystickButtonInput(button, false); break; } } // ReSharper disable once UnusedParameter.Local - private void handleJoyHatEvent(SDL.SDL_JoyHatEvent evtJhat) + private void handleJoyHatEvent(SDL_JoyHatEvent evtJhat) { } // ReSharper disable once UnusedParameter.Local - private void handleJoyBallEvent(SDL.SDL_JoyBallEvent evtJball) + private void handleJoyBallEvent(SDL_JoyBallEvent evtJball) { } - private void handleJoyAxisEvent(SDL.SDL_JoyAxisEvent evtJaxis) + private void handleJoyAxisEvent(SDL_JoyAxisEvent evtJaxis) { // if this axis exists in the controller bindings, skip it if (controllers.TryGetValue(evtJaxis.which, out var state) && state.IsJoystickAxisBound(evtJaxis.axis)) return; - enqueueJoystickAxisInput(JoystickAxisSource.Axis1 + evtJaxis.axis, evtJaxis.axisValue); + enqueueJoystickAxisInput(JoystickAxisSource.Axis1 + evtJaxis.axis, evtJaxis.axis); } - private uint lastPreciseScroll; + private ulong lastPreciseScroll; private const uint precise_scroll_debounce = 100; - private void handleMouseWheelEvent(SDL.SDL_MouseWheelEvent evtWheel) + private void handleMouseWheelEvent(SDL_MouseWheelEvent evtWheel) { bool isPrecise(float f) => f % 1 != 0; - if (isPrecise(evtWheel.preciseX) || isPrecise(evtWheel.preciseY)) + if (isPrecise(evtWheel.x) || isPrecise(evtWheel.y)) lastPreciseScroll = evtWheel.timestamp; bool precise = evtWheel.timestamp < lastPreciseScroll + precise_scroll_debounce; // SDL reports horizontal scroll opposite of what framework expects (in non-"natural" mode, scrolling to the right gives positive deltas while we want negative). - TriggerMouseWheel(new Vector2(-evtWheel.preciseX, evtWheel.preciseY), precise); + TriggerMouseWheel(new Vector2(-evtWheel.x, evtWheel.y), precise); } - private void handleMouseButtonEvent(SDL.SDL_MouseButtonEvent evtButton) + private void handleMouseButtonEvent(SDL_MouseButtonEvent evtButton) { - MouseButton button = mouseButtonFromEvent(evtButton.button); - SDLButtonMask mask = (SDLButtonMask)SDL.SDL_BUTTON(evtButton.button); + MouseButton button = mouseButtonFromEvent(evtButton.Button); + SDLButtonMask mask = SDL3.SDL_BUTTON(evtButton.Button); Debug.Assert(Enum.IsDefined(mask)); switch (evtButton.type) { - case SDL.SDL_EventType.SDL_MOUSEBUTTONDOWN: + case SDL_EventType.SDL_EVENT_MOUSE_BUTTON_DOWN: pressedButtons |= mask; MouseDown?.Invoke(button); break; - case SDL.SDL_EventType.SDL_MOUSEBUTTONUP: + case SDL_EventType.SDL_EVENT_MOUSE_BUTTON_UP: pressedButtons &= ~mask; MouseUp?.Invoke(button); break; } } - private void handleMouseMotionEvent(SDL.SDL_MouseMotionEvent evtMotion) + private void handleMouseMotionEvent(SDL_MouseMotionEvent evtMotion) { - if (SDL.SDL_GetRelativeMouseMode() == SDL.SDL_bool.SDL_FALSE) + if (SDL3.SDL_GetRelativeMouseMode() == SDL_bool.SDL_FALSE) MouseMove?.Invoke(new Vector2(evtMotion.x * Scale, evtMotion.y * Scale)); else MouseMoveRelative?.Invoke(new Vector2(evtMotion.xrel * Scale, evtMotion.yrel * Scale)); } - protected virtual unsafe void HandleTextInputEvent(SDL.SDL_TextInputEvent evtText) + protected virtual void HandleTextInputEvent(SDL_TextInputEvent evtText) { - if (!SDL2Extensions.TryGetStringFromBytePointer(evtText.text, out string text)) - return; - + string? text = evtText.GetText(); + Debug.Assert(text != null); TriggerTextInput(text); } - protected virtual unsafe void HandleTextEditingEvent(SDL.SDL_TextEditingEvent evtEdit) + protected virtual void HandleTextEditingEvent(SDL_TextEditingEvent evtEdit) { - if (!SDL2Extensions.TryGetStringFromBytePointer(evtEdit.text, out string text)) - return; - + string? text = evtEdit.GetText(); + Debug.Assert(text != null); TriggerTextEditing(text, evtEdit.start, evtEdit.length); } - private void handleKeyboardEvent(SDL.SDL_KeyboardEvent evtKey) + private void handleKeyboardEvent(SDL_KeyboardEvent evtKey) { Key key = evtKey.keysym.ToKey(); @@ -465,11 +476,11 @@ private void handleKeyboardEvent(SDL.SDL_KeyboardEvent evtKey) switch (evtKey.type) { - case SDL.SDL_EventType.SDL_KEYDOWN: + case SDL_EventType.SDL_EVENT_KEY_DOWN: KeyDown?.Invoke(key); break; - case SDL.SDL_EventType.SDL_KEYUP: + case SDL_EventType.SDL_EVENT_KEY_UP: KeyUp?.Invoke(key); break; } @@ -477,50 +488,29 @@ private void handleKeyboardEvent(SDL.SDL_KeyboardEvent evtKey) private void handleKeymapChangedEvent() => KeymapChanged?.Invoke(); - private MouseButton mouseButtonFromEvent(byte button) + private MouseButton mouseButtonFromEvent(SDLButton button) { - switch ((uint)button) + switch (button) { - default: - case SDL.SDL_BUTTON_LEFT: + case SDLButton.SDL_BUTTON_LEFT: return MouseButton.Left; - case SDL.SDL_BUTTON_RIGHT: + case SDLButton.SDL_BUTTON_RIGHT: return MouseButton.Right; - case SDL.SDL_BUTTON_MIDDLE: + case SDLButton.SDL_BUTTON_MIDDLE: return MouseButton.Middle; - case SDL.SDL_BUTTON_X1: + case SDLButton.SDL_BUTTON_X1: return MouseButton.Button1; - case SDL.SDL_BUTTON_X2: + case SDLButton.SDL_BUTTON_X2: return MouseButton.Button2; - } - } - /// - /// Button mask as returned from and . - /// - [Flags] - private enum SDLButtonMask - { - None = 0, - - /// - Left = 1 << 0, - - /// - Middle = 1 << 1, - - /// - Right = 1 << 2, - - /// - X1 = 1 << 3, - - /// - X2 = 1 << 4 + default: + Logger.Log($"unknown mouse button: {button}, defaulting to left button"); + return MouseButton.Left; + } } #endregion @@ -529,8 +519,7 @@ private enum SDLButtonMask /// Update the host window manager's cursor position based on a location relative to window coordinates. /// /// A position inside the window. - public void UpdateMousePosition(Vector2 mousePosition) => ScheduleCommand(() => - SDL.SDL_WarpMouseInWindow(SDLWindowHandle, (int)(mousePosition.X / Scale), (int)(mousePosition.Y / Scale))); + public unsafe void UpdateMousePosition(Vector2 mousePosition) => ScheduleCommand(() => SDL3.SDL_WarpMouseInWindow(SDLWindowHandle, mousePosition.X / Scale, mousePosition.Y / Scale)); private void updateConfineMode() { diff --git a/osu.Framework/Platform/SDL2Window_Windowing.cs b/osu.Framework/Platform/SDL3Window_Windowing.cs similarity index 72% rename from osu.Framework/Platform/SDL2Window_Windowing.cs rename to osu.Framework/Platform/SDL3Window_Windowing.cs index 4652f8823b..12150fda19 100644 --- a/osu.Framework/Platform/SDL2Window_Windowing.cs +++ b/osu.Framework/Platform/SDL3Window_Windowing.cs @@ -11,20 +11,20 @@ using osu.Framework.Bindables; using osu.Framework.Configuration; using osu.Framework.Logging; -using osu.Framework.Platform.SDL2; +using osu.Framework.Platform.SDL; using osuTK; -using SDL2; +using SDL; namespace osu.Framework.Platform { - internal partial class SDL2Window + internal partial class SDL3Window { - private void setupWindowing(FrameworkConfigManager config) + private unsafe void setupWindowing(FrameworkConfigManager config) { config.BindWith(FrameworkSetting.MinimiseOnFocusLossInFullscreen, minimiseOnFocusLoss); minimiseOnFocusLoss.BindValueChanged(e => { - ScheduleCommand(() => SDL.SDL_SetHint(SDL.SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, e.NewValue ? "1" : "0")); + ScheduleCommand(() => SDL3.SDL_SetHint(SDL3.SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, e.NewValue ? "1"u8 : "0"u8)); }, true); fetchDisplays(); @@ -69,7 +69,7 @@ private void setupWindowing(FrameworkConfigManager config) if (min.Width > sizeWindowed.MaxValue.Width || min.Height > sizeWindowed.MaxValue.Height) throw new InvalidOperationException($"Expected a size less than max window size ({sizeWindowed.MaxValue}), got {min}"); - ScheduleCommand(() => SDL.SDL_SetWindowMinimumSize(SDLWindowHandle, min.Width, min.Height)); + ScheduleCommand(() => SDL3.SDL_SetWindowMinimumSize(SDLWindowHandle, min.Width, min.Height)); }; sizeWindowed.MaxValueChanged += max => @@ -80,7 +80,7 @@ private void setupWindowing(FrameworkConfigManager config) if (max.Width < sizeWindowed.MinValue.Width || max.Height < sizeWindowed.MinValue.Height) throw new InvalidOperationException($"Expected a size greater than min window size ({sizeWindowed.MinValue}), got {max}"); - ScheduleCommand(() => SDL.SDL_SetWindowMaximumSize(SDLWindowHandle, max.Width, max.Height)); + ScheduleCommand(() => SDL3.SDL_SetWindowMaximumSize(SDLWindowHandle, max.Width, max.Height)); }; config.BindWith(FrameworkSetting.SizeFullscreen, sizeFullscreen); @@ -146,7 +146,10 @@ public virtual IEnumerable SupportedWindowModes if (RuntimeInfo.IsMobile) return new[] { Configuration.WindowMode.Fullscreen }; - return Enum.GetValues(); + if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) + return Enum.GetValues(); + + return new[] { Configuration.WindowMode.Windowed, Configuration.WindowMode.Fullscreen }; } } @@ -155,13 +158,13 @@ public virtual IEnumerable SupportedWindowModes /// /// Returns or sets the window's position in screen space. Only valid when in /// - public Point Position + public unsafe Point Position { get => position; set { position = value; - ScheduleCommand(() => SDL.SDL_SetWindowPosition(SDLWindowHandle, value.X, value.Y)); + ScheduleCommand(() => SDL3.SDL_SetWindowPosition(SDLWindowHandle, value.X, value.Y)); } } @@ -170,7 +173,7 @@ public Point Position /// /// Returns or sets whether the window is resizable or not. Only valid when in . /// - public bool Resizable + public unsafe bool Resizable { get => resizable; set @@ -179,7 +182,7 @@ public bool Resizable return; resizable = value; - ScheduleCommand(() => SDL.SDL_SetWindowResizable(SDLWindowHandle, value ? SDL.SDL_bool.SDL_TRUE : SDL.SDL_bool.SDL_FALSE)); + ScheduleCommand(() => SDL3.SDL_SetWindowResizable(SDLWindowHandle, value ? SDL_bool.SDL_TRUE : SDL_bool.SDL_FALSE)); } } @@ -232,7 +235,7 @@ public Size MaxSize /// /// Enables or disables the window visibility. /// - public bool Visible + public unsafe bool Visible { get => visible; set @@ -241,9 +244,9 @@ public bool Visible ScheduleCommand(() => { if (value) - SDL.SDL_ShowWindow(SDLWindowHandle); + SDL3.SDL_ShowWindow(SDLWindowHandle); else - SDL.SDL_HideWindow(SDLWindowHandle); + SDL3.SDL_HideWindow(SDLWindowHandle); }); } } @@ -290,7 +293,7 @@ public WindowState WindowState public event Action>? DisplaysChanged; // ReSharper disable once UnusedParameter.Local - private void handleDisplayEvent(SDL.SDL_DisplayEvent evtDisplay) => fetchDisplays(); + private void handleDisplayEvent(SDL_DisplayEvent evtDisplay) => fetchDisplays(); /// /// Updates with the latest display information reported by SDL. @@ -326,16 +329,16 @@ private void assertDisplaysMatchSDL() private static ImmutableArray getSDLDisplays() { - int numDisplays = SDL.SDL_GetNumVideoDisplays(); + using var displays = SDL3.SDL_GetDisplays(); - if (numDisplays <= 0) - throw new InvalidOperationException($"Failed to get number of SDL displays. Return code: {numDisplays}. SDL Error: {SDL.SDL_GetError()}"); + if (displays == null) + throw new InvalidOperationException($"Failed to get number of SDL displays. SDL Error: {SDL3.SDL_GetError()}"); - var builder = ImmutableArray.CreateBuilder(numDisplays); + var builder = ImmutableArray.CreateBuilder(displays.Count); - for (int i = 0; i < numDisplays; i++) + for (int i = 0; i < displays.Count; i++) { - if (tryGetDisplayFromSDL(i, out Display? display)) + if (tryGetDisplayFromSDL(i, displays[i], out Display? display)) builder.Add(display); else Logger.Log($"Failed to retrieve SDL display at index ({i})", level: LogLevel.Error); @@ -344,13 +347,15 @@ private static ImmutableArray getSDLDisplays() return builder.MoveToImmutable(); } - private static bool tryGetDisplayFromSDL(int displayIndex, [NotNullWhen(true)] out Display? display) + private static unsafe bool tryGetDisplayFromSDL(int displayIndex, SDL_DisplayID displayID, [NotNullWhen(true)] out Display? display) { ArgumentOutOfRangeException.ThrowIfNegative(displayIndex); - if (SDL.SDL_GetDisplayBounds(displayIndex, out var rect) < 0) + SDL_Rect rect; + + if (SDL3.SDL_GetDisplayBounds(displayID, &rect) < 0) { - Logger.Log($"Failed to get display bounds for display at index ({displayIndex}). SDL Error: {SDL.SDL_GetError()}"); + Logger.Log($"Failed to get display bounds for display at index ({displayIndex}). SDL Error: {SDL3.SDL_GetError()}"); display = null; return false; } @@ -359,28 +364,25 @@ private static bool tryGetDisplayFromSDL(int displayIndex, [NotNullWhen(true)] o if (RuntimeInfo.IsDesktop) { - int numModes = SDL.SDL_GetNumDisplayModes(displayIndex); + using var modes = SDL3.SDL_GetFullscreenDisplayModes(displayID); - if (numModes < 0) + if (modes == null) { - Logger.Log($"Failed to get display modes for display at index ({displayIndex}) ({rect.w}x{rect.h}). SDL Error: {SDL.SDL_GetError()} ({numModes})"); + Logger.Log($"Failed to get display modes for display at index ({displayIndex}) ({rect.w}x{rect.h}). SDL Error: {SDL3.SDL_GetError()}"); display = null; return false; } - if (numModes == 0) + if (modes.Count == 0) Logger.Log($"Display at index ({displayIndex}) ({rect.w}x{rect.h}) has no display modes. Fullscreen might not work."); - displayModes = Enumerable.Range(0, numModes) - .Select(modeIndex => - { - SDL.SDL_GetDisplayMode(displayIndex, modeIndex, out var mode); - return mode.ToDisplayMode(displayIndex); - }) - .ToArray(); + displayModes = new DisplayMode[modes.Count]; + + for (int i = 0; i < modes.Count; i++) + displayModes[i] = modes[i].ToDisplayMode(displayIndex); } - display = new Display(displayIndex, SDL.SDL_GetDisplayName(displayIndex), new Rectangle(rect.x, rect.y, rect.w, rect.h), displayModes); + display = new Display(displayIndex, SDL3.SDL_GetDisplayName(displayID), new Rectangle(rect.x, rect.y, rect.w, rect.h), displayModes); return true; } @@ -392,7 +394,7 @@ private static bool tryGetDisplayFromSDL(int displayIndex, [NotNullWhen(true)] o public virtual Display PrimaryDisplay => Displays.First(); private Display currentDisplay = null!; - private int displayIndex = -1; + private SDL_DisplayID displayID; private readonly Bindable currentDisplayMode = new Bindable(); @@ -401,11 +403,12 @@ private static bool tryGetDisplayFromSDL(int displayIndex, [NotNullWhen(true)] o /// public IBindable CurrentDisplayMode => currentDisplayMode; - private Rectangle windowDisplayBounds + private unsafe Rectangle windowDisplayBounds { get { - SDL.SDL_GetDisplayBounds(displayIndex, out var rect); + SDL_Rect rect; + SDL3.SDL_GetDisplayBounds(displayID, &rect); return new Rectangle(rect.x, rect.y, rect.w, rect.h); } } @@ -441,9 +444,10 @@ private Rectangle windowDisplayBounds /// Updates and according to SDL state. /// /// Whether the window size has been changed after updating. - private void fetchWindowSize() + private unsafe void fetchWindowSize() { - SDL.SDL_GetWindowSize(SDLWindowHandle, out int w, out int h); + int w, h; + SDL3.SDL_GetWindowSize(SDLWindowHandle, &w, &h); int drawableW = graphicsSurface.GetDrawableSize().Width; @@ -460,15 +464,16 @@ private void fetchWindowSize() #region SDL Event Handling - private void handleWindowEvent(SDL.SDL_WindowEvent evtWindow) + private unsafe void handleWindowEvent(SDL_WindowEvent evtWindow) { updateAndFetchWindowSpecifics(); - switch (evtWindow.windowEvent) + switch (evtWindow.type) { - case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_MOVED: + case SDL_EventType.SDL_EVENT_WINDOW_MOVED: // explicitly requery as there are occasions where what SDL has provided us with is not up-to-date. - SDL.SDL_GetWindowPosition(SDLWindowHandle, out int x, out int y); + int x, y; + SDL3.SDL_GetWindowPosition(SDLWindowHandle, &x, &y); var newPosition = new Point(x, y); if (!newPosition.Equals(Position)) @@ -482,45 +487,45 @@ private void handleWindowEvent(SDL.SDL_WindowEvent evtWindow) break; - case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_SIZE_CHANGED: + case SDL_EventType.SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: fetchWindowSize(); break; - case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_ENTER: + case SDL_EventType.SDL_EVENT_WINDOW_MOUSE_ENTER: cursorInWindow.Value = true; MouseEntered?.Invoke(); break; - case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_LEAVE: + case SDL_EventType.SDL_EVENT_WINDOW_MOUSE_LEAVE: cursorInWindow.Value = false; MouseLeft?.Invoke(); break; - case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_RESTORED: - case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_FOCUS_GAINED: + case SDL_EventType.SDL_EVENT_WINDOW_RESTORED: + case SDL_EventType.SDL_EVENT_WINDOW_FOCUS_GAINED: Focused = true; break; - case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_MINIMIZED: - case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_FOCUS_LOST: + case SDL_EventType.SDL_EVENT_WINDOW_MINIMIZED: + case SDL_EventType.SDL_EVENT_WINDOW_FOCUS_LOST: Focused = false; break; - case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_CLOSE: + case SDL_EventType.SDL_EVENT_WINDOW_CLOSE_REQUESTED: break; } // displays can change without a SDL_DISPLAYEVENT being sent, eg. changing resolution. // force update displays when gaining keyboard focus to always have up-to-date information. // eg. this covers scenarios when changing resolution outside of the game, and then tabbing in. - switch (evtWindow.windowEvent) - { - case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_RESTORED: - case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_FOCUS_GAINED: - case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_MINIMIZED: - case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_FOCUS_LOST: - case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_SHOWN: - case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_HIDDEN: + switch (evtWindow.type) + { + case SDL_EventType.SDL_EVENT_WINDOW_RESTORED: + case SDL_EventType.SDL_EVENT_WINDOW_FOCUS_GAINED: + case SDL_EventType.SDL_EVENT_WINDOW_MINIMIZED: + case SDL_EventType.SDL_EVENT_WINDOW_FOCUS_LOST: + case SDL_EventType.SDL_EVENT_WINDOW_SHOWN: + case SDL_EventType.SDL_EVENT_WINDOW_HIDDEN: fetchDisplays(); break; } @@ -542,10 +547,10 @@ private void invalidateWindowSpecifics() /// /// Should be run on a regular basis to check for external window state changes. /// - private void updateAndFetchWindowSpecifics() + private unsafe void updateAndFetchWindowSpecifics() { // don't attempt to run before the window is initialised, as Create() will do so anyway. - if (SDLWindowHandle == IntPtr.Zero) + if (SDLWindowHandle == null) return; var stateBefore = windowState; @@ -572,7 +577,7 @@ private void updateAndFetchWindowSpecifics() } else { - windowState = ((SDL.SDL_WindowFlags)SDL.SDL_GetWindowFlags(SDLWindowHandle)).ToWindowState(); + windowState = SDL3.SDL_GetWindowFlags(SDLWindowHandle).ToWindowState(); } if (windowState != stateBefore) @@ -583,32 +588,57 @@ private void updateAndFetchWindowSpecifics() windowMaximised = maximized; } - int newDisplayIndex = SDL.SDL_GetWindowDisplayIndex(SDLWindowHandle); + var newDisplayID = SDL3.SDL_GetDisplayForWindow(SDLWindowHandle); - if (displayIndex != newDisplayIndex) + if (displayID != newDisplayID) { - displayIndex = newDisplayIndex; - currentDisplay = Displays.ElementAtOrDefault(displayIndex) ?? PrimaryDisplay; + displayID = newDisplayID; + + if (tryGetDisplayIndex(newDisplayID, out int index) && tryGetDisplayFromSDL(index, newDisplayID, out var display)) + currentDisplay = display; + else + currentDisplay = PrimaryDisplay; + CurrentDisplayBindable.Value = currentDisplay; } } + private static bool tryGetDisplayIndex(SDL_DisplayID id, out int index) + { + using var displays = SDL3.SDL_GetDisplays(); + + if (displays == null) + throw new InvalidOperationException($"Failed to get SDL displays. SDL error: {SDL3.SDL_GetError()}"); + + for (int i = 0; i < displays.Count; i++) + { + if (displays[i] == id) + { + index = i; + return true; + } + } + + index = default; + return false; + } + /// /// Should be run after a local window state change, to propagate the correct SDL actions. /// /// /// Call sites need to set appropriately. /// - protected virtual void UpdateWindowStateAndSize(WindowState state, Display display, DisplayMode displayMode) + protected virtual unsafe void UpdateWindowStateAndSize(WindowState state, Display display, DisplayMode displayMode) { switch (state) { case WindowState.Normal: Size = sizeWindowed.Value; - SDL.SDL_RestoreWindow(SDLWindowHandle); - SDL.SDL_SetWindowSize(SDLWindowHandle, Size.Width, Size.Height); - SDL.SDL_SetWindowResizable(SDLWindowHandle, Resizable ? SDL.SDL_bool.SDL_TRUE : SDL.SDL_bool.SDL_FALSE); + SDL3.SDL_RestoreWindow(SDLWindowHandle); + SDL3.SDL_SetWindowSize(SDLWindowHandle, Size.Width, Size.Height); + SDL3.SDL_SetWindowResizable(SDLWindowHandle, Resizable ? SDL_bool.SDL_TRUE : SDL_bool.SDL_FALSE); readWindowPositionFromConfig(state, display); break; @@ -620,8 +650,8 @@ protected virtual void UpdateWindowStateAndSize(WindowState state, Display displ ensureWindowOnDisplay(display); - SDL.SDL_SetWindowDisplayMode(SDLWindowHandle, ref closestMode); - SDL.SDL_SetWindowFullscreen(SDLWindowHandle, (uint)SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN); + SDL3.SDL_SetWindowFullscreenMode(SDLWindowHandle, &closestMode); + SDL3.SDL_SetWindowFullscreen(SDLWindowHandle, SDL_bool.SDL_TRUE); break; case WindowState.FullscreenBorderless: @@ -629,49 +659,40 @@ protected virtual void UpdateWindowStateAndSize(WindowState state, Display displ break; case WindowState.Maximised: - SDL.SDL_RestoreWindow(SDLWindowHandle); + SDL3.SDL_RestoreWindow(SDLWindowHandle); ensureWindowOnDisplay(display); - SDL.SDL_MaximizeWindow(SDLWindowHandle); + SDL3.SDL_MaximizeWindow(SDLWindowHandle); break; case WindowState.Minimised: ensureWindowOnDisplay(display); - SDL.SDL_MinimizeWindow(SDLWindowHandle); + SDL3.SDL_MinimizeWindow(SDLWindowHandle); break; } } - private static bool tryFetchDisplayMode(IntPtr windowHandle, WindowState windowState, Display display, out DisplayMode displayMode) + private static unsafe bool tryFetchDisplayMode(SDL_Window* windowHandle, WindowState windowState, Display display, out DisplayMode displayMode) { - // TODO: displayIndex should be valid here at all times. - // on startup, the displayIndex will be invalid (-1) due to it being set later in the startup sequence. - // related to order of operations in `updateWindowSpecifics()`. - int localIndex = SDL.SDL_GetWindowDisplayIndex(windowHandle); - - if (localIndex != display.Index) - Logger.Log($"Stored display index ({display.Index}) doesn't match current index ({localIndex})"); - - bool success; - SDL.SDL_DisplayMode mode; - - if (windowState == WindowState.Fullscreen) - success = SDL.SDL_GetWindowDisplayMode(windowHandle, out mode) >= 0; - else - success = SDL.SDL_GetCurrentDisplayMode(localIndex, out mode) >= 0; + if (!tryGetDisplayAtIndex(display.Index, out var displayID)) + { + displayMode = default; + return false; + } + var mode = windowState == WindowState.Fullscreen ? SDL3.SDL_GetWindowFullscreenMode(windowHandle) : SDL3.SDL_GetDesktopDisplayMode(displayID); string type = windowState == WindowState.Fullscreen ? "fullscreen" : "desktop"; - if (success) + if (mode != null) { - displayMode = mode.ToDisplayMode(localIndex); - Logger.Log($"Updated display mode to {type} resolution: {mode.w}x{mode.h}@{mode.refresh_rate}, {displayMode.Format}"); + displayMode = mode->ToDisplayMode(display.Index); + Logger.Log($"Updated display mode to {type} resolution: {mode->w}x{mode->h}@{mode->refresh_rate}, {displayMode.Format}"); return true; } else { - Logger.Log($"Failed to get {type} display mode. Display index: {localIndex}. SDL error: {SDL.SDL_GetError()}"); + Logger.Log($"Failed to get {type} display mode. Display index: {display.Index}. SDL error: {SDL3.SDL_GetError()}"); displayMode = default; return false; } @@ -703,10 +724,13 @@ private void readWindowPositionFromConfig(WindowState state, Display display) /// Ensures that the window is located on the provided . /// /// The to center the window on. - private void ensureWindowOnDisplay(Display display) + private unsafe void ensureWindowOnDisplay(Display display) { - if (display.Index == SDL.SDL_GetWindowDisplayIndex(SDLWindowHandle)) - return; + if (tryGetDisplayAtIndex(display.Index, out var requestedID)) + { + if (requestedID == SDL3.SDL_GetDisplayForWindow(SDLWindowHandle)) + return; + } moveWindowTo(display, new Vector2(0.5f)); } @@ -775,15 +799,7 @@ private void storeWindowSizeToConfig() /// /// The size of the borderless window's draw area. /// - protected virtual Size SetBorderless(Display display) - { - ensureWindowOnDisplay(display); - - // this is a generally sane method of handling borderless, and works well on macOS and linux. - SDL.SDL_SetWindowFullscreen(SDLWindowHandle, (uint)SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP); - - return display.Bounds.Size; - } + protected virtual Size SetBorderless(Display display) => throw new PlatformNotSupportedException(); #endregion @@ -814,54 +830,68 @@ public void CycleMode() #region Helper functions - private static SDL.SDL_DisplayMode getClosestDisplayMode(IntPtr windowHandle, Size size, Display display, DisplayMode requestedMode) + /// + /// Gets the of the display at the specified index. + /// + /// Index of the display. + /// The of the display at the specified index. + /// true if the display at the requested index is available, false otherwise. + private static bool tryGetDisplayAtIndex(int index, out SDL_DisplayID displayID) { - SDL.SDL_ClearError(); // clear any stale error. + ArgumentOutOfRangeException.ThrowIfNegative(index); + + using var displays = SDL3.SDL_GetDisplays(); + + if (displays == null) + throw new InvalidOperationException($"Unable to get displays. SDL error: {SDL3.SDL_GetError()}"); + + if (index >= displays.Count) + { + displayID = default; + return false; + } + + displayID = displays[index]; + return true; + } + + private static unsafe SDL_DisplayMode getClosestDisplayMode(SDL_Window* windowHandle, Size size, Display display, DisplayMode requestedMode) + { + SDL3.SDL_ClearError(); // clear any stale error. + + if (!tryGetDisplayAtIndex(display.Index, out var displayID)) + throw new ArgumentException($"Requested display index ({display}) is invalid.", nameof(display)); // default size means to use the display's native size. if (size.Width == 9999 && size.Height == 9999) size = display.Bounds.Size; - var targetMode = new SDL.SDL_DisplayMode { w = size.Width, h = size.Height, refresh_rate = requestedMode.RefreshRate }; - - if (SDL.SDL_GetClosestDisplayMode(display.Index, ref targetMode, out var mode) != IntPtr.Zero) - return mode; + var mode = SDL3.SDL_GetClosestFullscreenDisplayMode(displayID, size.Width, size.Height, requestedMode.RefreshRate, SDL_bool.SDL_TRUE); + if (mode != null) + return *mode; else - Logger.Log($"Unable to get preferred display mode (try #1/2). Target display: {display.Index}, mode: {targetMode.ReadableString()}. SDL error: {SDL2Extensions.GetAndClearError()}"); + Logger.Log($"Unable to get preferred display mode (try #1/2). Target display: {display.Index}, mode: {size.Width}x{size.Height}@{requestedMode.RefreshRate}. SDL error: {SDL3Extensions.GetAndClearError()}"); // fallback to current display's native bounds - targetMode.w = display.Bounds.Width; - targetMode.h = display.Bounds.Height; - targetMode.refresh_rate = 0; - - if (SDL.SDL_GetClosestDisplayMode(display.Index, ref targetMode, out mode) != IntPtr.Zero) - return mode; + mode = SDL3.SDL_GetClosestFullscreenDisplayMode(displayID, display.Bounds.Width, display.Bounds.Height, 0f, SDL_bool.SDL_TRUE); + if (mode != null) + return *mode; else - Logger.Log($"Unable to get preferred display mode (try #2/2). Target display: {display.Index}, mode: {targetMode.ReadableString()}. SDL error: {SDL2Extensions.GetAndClearError()}"); + Logger.Log($"Unable to get preferred display mode (try #2/2). Target display: {display.Index}, mode: {display.Bounds.Width}x{display.Bounds.Height}@default. SDL error: {SDL3Extensions.GetAndClearError()}"); // try the display's native display mode. - if (SDL.SDL_GetDesktopDisplayMode(display.Index, out mode) == 0) - return mode; - else - Logger.Log($"Failed to get desktop display mode (try #1/3). Target display: {display.Index}. SDL error: {SDL2Extensions.GetAndClearError()}", level: LogLevel.Error); - - // try the primary display mode. - if (SDL.SDL_GetDisplayMode(display.Index, 0, out mode) == 0) - return mode; - else - Logger.Log($"Failed to get desktop display mode (try #2/3). Target display: {display.Index}. SDL error: {SDL2Extensions.GetAndClearError()}", level: LogLevel.Error); - - // try the primary display's primary display mode. - if (SDL.SDL_GetDisplayMode(0, 0, out mode) == 0) - return mode; + mode = SDL3.SDL_GetDesktopDisplayMode(displayID); + if (mode != null) + return *mode; else - Logger.Log($"Failed to get desktop display mode (try #3/3). Target display: primary. SDL error: {SDL2Extensions.GetAndClearError()}", level: LogLevel.Error); + Logger.Log($"Failed to get desktop display mode (try #1/1). Target display: {display.Index}. SDL error: {SDL3Extensions.GetAndClearError()}", level: LogLevel.Error); // finally return the current mode if everything else fails. - if (SDL.SDL_GetWindowDisplayMode(windowHandle, out mode) >= 0) - return mode; + mode = SDL3.SDL_GetWindowFullscreenMode(windowHandle); + if (mode != null) + return *mode; else - Logger.Log($"Failed to get window display mode. SDL error: {SDL2Extensions.GetAndClearError()}", level: LogLevel.Error); + Logger.Log($"Failed to get window display mode. SDL error: {SDL3Extensions.GetAndClearError()}", level: LogLevel.Error); throw new InvalidOperationException("couldn't retrieve valid display mode"); } diff --git a/osu.Framework/Platform/Windows/Native/Input.cs b/osu.Framework/Platform/Windows/Native/Input.cs index 851172654f..fcb28d5634 100644 --- a/osu.Framework/Platform/Windows/Native/Input.cs +++ b/osu.Framework/Platform/Windows/Native/Input.cs @@ -2,72 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Drawing; using System.Runtime.InteropServices; namespace osu.Framework.Platform.Windows.Native { internal static class Input { - [DllImport("user32.dll")] - public static extern bool RegisterRawInputDevices( - [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] - RawInputDevice[] pRawInputDevices, - int uiNumDevices, - int cbSize); - - [DllImport("user32.dll")] - public static extern int GetRawInputData(IntPtr hRawInput, RawInputCommand uiCommand, out RawInputData pData, ref int pcbSize, int cbSizeHeader); - - internal static Rectangle VirtualScreenRect => new Rectangle( - GetSystemMetrics(SM_XVIRTUALSCREEN), - GetSystemMetrics(SM_YVIRTUALSCREEN), - GetSystemMetrics(SM_CXVIRTUALSCREEN), - GetSystemMetrics(SM_CYVIRTUALSCREEN)); - - internal const int WM_INPUT = 0x00FF; - - [DllImport("user32.dll")] - public static extern int GetSystemMetrics(int nIndex); - - internal static Rectangle GetVirtualScreenRect() => new Rectangle( - GetSystemMetrics(SM_XVIRTUALSCREEN), - GetSystemMetrics(SM_YVIRTUALSCREEN), - GetSystemMetrics(SM_CXVIRTUALSCREEN), - GetSystemMetrics(SM_CYVIRTUALSCREEN) - ); - - public const int SM_XVIRTUALSCREEN = 76; - public const int SM_YVIRTUALSCREEN = 77; - public const int SM_CXVIRTUALSCREEN = 78; - public const int SM_CYVIRTUALSCREEN = 79; - - public const long MI_WP_SIGNATURE = 0xFF515700; - public const long MI_WP_SIGNATURE_MASK = 0xFFFFFF00; - - /// - /// Flag distinguishing touch input from mouse input in events. - /// - /// - /// https://docs.microsoft.com/en-us/windows/win32/tablet/system-events-and-mouse-messages - /// Additionally, the eighth bit, masked by 0x80, is used to differentiate touch input from pen input (0 = pen, 1 = touch). - /// - private const long touch_flag = 0x80; - - /// - /// https://docs.microsoft.com/en-us/windows/win32/tablet/system-events-and-mouse-messages - /// - /// for the current event. - /// true if this event is from a finger touch, false if it's from mouse or pen input. - public static bool IsTouchEvent(long dw) => (dw & MI_WP_SIGNATURE_MASK) == MI_WP_SIGNATURE && HasTouchFlag(dw); - - /// or - /// Whether has the set. - public static bool HasTouchFlag(long extraInformation) => (extraInformation & touch_flag) == touch_flag; - - [DllImport("user32.dll", SetLastError = false)] - public static extern long GetMessageExtraInfo(); - [DllImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern unsafe bool SetWindowFeedbackSetting(IntPtr hwnd, FeedbackType feedback, ulong flags, uint size, int* configuration); @@ -87,277 +27,6 @@ public static unsafe void SetWindowFeedbackSetting(IntPtr hwnd, FeedbackType fee } } - /// - /// Value type for raw input. - /// - public struct RawInputData - { - /// Header for the data. - public RawInputHeader Header; - - /// Mouse raw input data. - public RawMouse Mouse; - - // This struct is a lot larger but the remaining elements have been omitted until required (Keyboard / HID / Touch). - } - - /// - /// Contains information about the state of the mouse. - /// - public struct RawMouse - { - /// - /// The mouse state. - /// - public RawMouseFlags Flags; - - /// - /// Flags for the event. - /// - public RawMouseButtons ButtonFlags; - - /// - /// If the mouse wheel is moved, this will contain the delta amount. - /// - public short ButtonData; - - /// - /// Raw button data. - /// - public uint RawButtons; - - /// - /// The motion in the X direction. This is signed relative motion or - /// absolute motion, depending on the value of usFlags. - /// - public int LastX; - - /// - /// The motion in the Y direction. This is signed relative motion or absolute motion, - /// depending on the value of usFlags. - /// - public int LastY; - - /// - /// The device-specific additional information for the event. - /// - public uint ExtraInformation; - } - - /// - /// Enumeration containing the flags for raw mouse data. - /// - [Flags] - public enum RawMouseFlags : ushort - { - /// Relative to the last position. - MoveRelative = 0, - - /// Absolute positioning. - MoveAbsolute = 1, - - /// Coordinate data is mapped to a virtual desktop. - VirtualDesktop = 2, - - /// Attributes for the mouse have changed. - AttributesChanged = 4, - - /// WM_MOUSEMOVE and WM_INPUT don't coalesce - MoveNoCoalesce = 8, - } - - /// - /// Enumeration containing the button data for raw mouse input. - /// - public enum RawMouseButtons : ushort - { - /// No button. - None = 0, - - /// Left (button 1) down. - LeftDown = 0x0001, - - /// Left (button 1) up. - LeftUp = 0x0002, - - /// Right (button 2) down. - RightDown = 0x0004, - - /// Right (button 2) up. - RightUp = 0x0008, - - /// Middle (button 3) down. - MiddleDown = 0x0010, - - /// Middle (button 3) up. - MiddleUp = 0x0020, - - /// Button 4 down. - Button4Down = 0x0040, - - /// Button 4 up. - Button4Up = 0x0080, - - /// Button 5 down. - Button5Down = 0x0100, - - /// Button 5 up. - Button5Up = 0x0200, - - /// Mouse wheel moved. - MouseWheel = 0x0400 - } - - /// - /// Enumeration contanining the command types to issue. - /// - public enum RawInputCommand - { - /// - /// Get input data. - /// - Input = 0x10000003, - - /// - /// Get header data. - /// - Header = 0x10000005 - } - - /// - /// Enumeration containing the type device the raw input is coming from. - /// - public enum RawInputType - { - /// - /// Mouse input. - /// - Mouse = 0, - - /// - /// Keyboard input. - /// - Keyboard = 1, - - /// - /// Another device that is not the keyboard or the mouse. - /// - HID = 2 - } - -#pragma warning disable IDE1006 // Naming style - - /// - /// Value type for a raw input header. - /// - [StructLayout(LayoutKind.Sequential)] - public struct RawInputHeader - { - /// Type of device the input is coming from. - public RawInputType Type; - - /// Size of the packet of data. - public int Size; - - /// Handle to the device sending the data. - public IntPtr Device; - - /// wParam from the window message. - public IntPtr wParam; - } - - [StructLayout(LayoutKind.Sequential)] - public struct RawInputDevice - { - /// Top level collection Usage page for the raw input device. - public HIDUsagePage UsagePage; - - /// Top level collection Usage for the raw input device. - public HIDUsage Usage; - - /// Mode flag that specifies how to interpret the information provided by UsagePage and Usage. - public RawInputDeviceFlags Flags; - - /// Handle to the target device. If NULL, it follows the keyboard focus. - public IntPtr WindowHandle; - } - -#pragma warning restore IDE1006 - - /// Enumeration containing flags for a raw input device. - public enum RawInputDeviceFlags - { - /// No flags. - None = 0, - - /// If set, this removes the top level collection from the inclusion list. This tells the operating system to stop reading from a device which matches the top level collection. - Remove = 0x00000001, - - /// If set, this specifies the top level collections to exclude when reading a complete usage page. This flag only affects a TLC whose usage page is already specified with PageOnly. - Exclude = 0x00000010, - - /// If set, this specifies all devices whose top level collection is from the specified usUsagePage. Note that Usage must be zero. To exclude a particular top level collection, use Exclude. - PageOnly = 0x00000020, - - /// If set, this prevents any devices specified by UsagePage or Usage from generating legacy messages. This is only for the mouse and keyboard. - NoLegacy = 0x00000030, - - /// If set, this enables the caller to receive the input even when the caller is not in the foreground. Note that WindowHandle must be specified. - InputSink = 0x00000100, - - /// If set, the mouse button click does not activate the other window. - CaptureMouse = 0x00000200, - - /// If set, the application-defined keyboard device hotkeys are not handled. However, the system hotkeys; for example, ALT+TAB and CTRL+ALT+DEL, are still handled. By default, all keyboard hotkeys are handled. NoHotKeys can be specified even if NoLegacy is not specified and WindowHandle is NULL. - NoHotKeys = 0x00000200, - - /// If set, application keys are handled. NoLegacy must be specified. Keyboard only. - AppKeys = 0x00000400 - } - - public enum HIDUsage : ushort - { - Pointer = 0x01, - Mouse = 0x02, - Joystick = 0x04, - Gamepad = 0x05, - Keyboard = 0x06, - Keypad = 0x07, - SystemControl = 0x80, - } - - public enum HIDUsagePage : ushort - { - Undefined = 0x00, - Generic = 0x01, - Simulation = 0x02, - VR = 0x03, - Sport = 0x04, - Game = 0x05, - Keyboard = 0x07, - LED = 0x08, - Button = 0x09, - Ordinal = 0x0A, - Telephony = 0x0B, - Consumer = 0x0C, - Digitizer = 0x0D, - PID = 0x0F, - Unicode = 0x10, - AlphaNumeric = 0x14, - Medical = 0x40, - MonitorPage0 = 0x80, - MonitorPage1 = 0x81, - MonitorPage2 = 0x82, - MonitorPage3 = 0x83, - PowerPage0 = 0x84, - PowerPage1 = 0x85, - PowerPage2 = 0x86, - PowerPage3 = 0x87, - BarCode = 0x8C, - Scale = 0x8D, - MSR = 0x8E - } - public enum FeedbackType { TouchContactVisualization = 1, diff --git a/osu.Framework/Platform/Windows/WindowsMouseHandler.cs b/osu.Framework/Platform/Windows/WindowsMouseHandler.cs index ae8070e11e..b74535b787 100644 --- a/osu.Framework/Platform/Windows/WindowsMouseHandler.cs +++ b/osu.Framework/Platform/Windows/WindowsMouseHandler.cs @@ -1,52 +1,27 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using System.Drawing; using System.Runtime.Versioning; -using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Input.Handlers.Mouse; -using osu.Framework.Input.StateChanges; -using osu.Framework.Platform.Windows.Native; -using osu.Framework.Statistics; using osuTK; -using SDL2; namespace osu.Framework.Platform.Windows { /// - /// A windows specific mouse input handler which overrides the SDL2 implementation of raw input. + /// A windows specific mouse input handler which overrides the SDL3 implementation of raw input. /// This is done to better handle quirks of some devices. /// [SupportedOSPlatform("windows")] - internal unsafe class WindowsMouseHandler : MouseHandler + internal class WindowsMouseHandler : MouseHandler { - private static readonly GlobalStatistic statistic_relative_events = GlobalStatistics.Get(StatisticGroupFor(), "Relative events"); - private static readonly GlobalStatistic statistic_absolute_events = GlobalStatistics.Get(StatisticGroupFor(), "Absolute events"); - private static readonly GlobalStatistic statistic_dropped_touch_inputs = GlobalStatistics.Get(StatisticGroupFor(), "Dropped native touch inputs"); - private static readonly GlobalStatistic statistic_inputs_with_extra_information = GlobalStatistics.Get(StatisticGroupFor(), "Native inputs with ExtraInformation"); - - private const int raw_input_coordinate_space = 65535; - - private SDL.SDL_WindowsMessageHook callback = null!; private WindowsWindow window = null!; - public override bool IsActive => Enabled.Value; - public override bool Initialize(GameHost host) { if (!(host.Window is WindowsWindow desktopWindow)) return false; window = desktopWindow; - // ReSharper disable once ConvertClosureToMethodGroup - callback = (ptr, wnd, u, param, l) => onWndProc(ptr, wnd, u, param, l); - - Enabled.BindValueChanged(enabled => - { - host.InputThread.Scheduler.Add(() => SDL.SDL_SetWindowsMessageHook(enabled.NewValue ? callback : null, IntPtr.Zero)); - }, true); - return base.Initialize(host); } @@ -55,102 +30,5 @@ public override void FeedbackMousePositionChange(Vector2 position, bool isSelfFe window.LastMousePosition = position; base.FeedbackMousePositionChange(position, isSelfFeedback); } - - protected override void HandleMouseMoveRelative(Vector2 delta) - { - // handled via custom logic below. - } - - private IntPtr onWndProc(IntPtr userData, IntPtr hWnd, uint message, ulong wParam, long lParam) - { - if (!Enabled.Value) - return IntPtr.Zero; - - if (message != Native.Input.WM_INPUT) - return IntPtr.Zero; - - if (Native.Input.IsTouchEvent(Native.Input.GetMessageExtraInfo())) - { - // sometimes GetMessageExtraInfo returns 0, so additionally, mouse.ExtraInformation is checked below. - // touch events are handled by TouchHandler - statistic_dropped_touch_inputs.Value++; - return IntPtr.Zero; - } - - int payloadSize = sizeof(RawInputData); - - Native.Input.GetRawInputData((IntPtr)lParam, RawInputCommand.Input, out var data, ref payloadSize, sizeof(RawInputHeader)); - - if (data.Header.Type != RawInputType.Mouse) - return IntPtr.Zero; - - var mouse = data.Mouse; - - // `ExtraInformation` doesn't have the MI_WP_SIGNATURE set, so we have to rely solely on the touch flag. - if (Native.Input.HasTouchFlag(mouse.ExtraInformation)) - { - statistic_dropped_touch_inputs.Value++; - return IntPtr.Zero; - } - - //TODO: this isn't correct. - if (mouse.ExtraInformation > 0) - { - statistic_inputs_with_extra_information.Value++; - - // i'm not sure if there is a valid case where we need to handle packets with this present - // but the osu!tablet fires noise events with non-zero values, which we want to ignore. - // return IntPtr.Zero; - } - - var position = new Vector2(mouse.LastX, mouse.LastY); - float sensitivity = (float)Sensitivity.Value; - - if (mouse.Flags.HasFlagFast(RawMouseFlags.MoveAbsolute)) - { - var screenRect = mouse.Flags.HasFlagFast(RawMouseFlags.VirtualDesktop) ? Native.Input.VirtualScreenRect : new Rectangle(window.Position, window.ClientSize); - - Vector2 screenSize = new Vector2(screenRect.Width, screenRect.Height); - - if (mouse.LastX == 0 && mouse.LastY == 0) - { - // not sure if this is the case for all tablets, but on osu!tablet these can appear and are noise. - return IntPtr.Zero; - } - - // i am not sure what this 64 flag is, but it's set on the osu!tablet at very least. - // using it here as a method of determining where the coordinate space is incorrect. - if (((int)mouse.Flags & 64) == 0) - { - position /= raw_input_coordinate_space; - position *= screenSize; - } - - if (Sensitivity.Value != 1) - { - // apply absolute sensitivity adjustment from the centre of the screen area. - Vector2 halfScreenSize = (screenSize / 2); - - position -= halfScreenSize; - position *= (float)Sensitivity.Value; - position += halfScreenSize; - } - - // map from screen to client coordinate space. - // not using Window's PointToClient implementation to keep floating point precision here. - position -= new Vector2(window.Position.X, window.Position.Y); - position *= window.Scale; - - PendingInputs.Enqueue(new MousePositionAbsoluteInput { Position = position }); - statistic_absolute_events.Value++; - } - else - { - PendingInputs.Enqueue(new MousePositionRelativeInput { Delta = new Vector2(mouse.LastX, mouse.LastY) * sensitivity }); - statistic_relative_events.Value++; - } - - return IntPtr.Zero; - } } } diff --git a/osu.Framework/Platform/Windows/WindowsReadableKeyCombinationProvider.cs b/osu.Framework/Platform/Windows/WindowsReadableKeyCombinationProvider.cs index a97a06e4d9..fffe665478 100644 --- a/osu.Framework/Platform/Windows/WindowsReadableKeyCombinationProvider.cs +++ b/osu.Framework/Platform/Windows/WindowsReadableKeyCombinationProvider.cs @@ -2,12 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Input.Bindings; -using osu.Framework.Platform.SDL2; -using SDL2; +using osu.Framework.Platform.SDL; +using SDL; namespace osu.Framework.Platform.Windows { - public class WindowsReadableKeyCombinationProvider : SDL2ReadableKeyCombinationProvider + public class WindowsReadableKeyCombinationProvider : SDL3ReadableKeyCombinationProvider { protected override string GetReadableKey(InputKey key) { @@ -21,15 +21,15 @@ protected override string GetReadableKey(InputKey key) } } - protected override bool TryGetNameFromKeycode(SDL.SDL_Keycode keycode, out string name) + protected override bool TryGetNameFromKeycode(SDL_Keycode keycode, out string name) { switch (keycode) { - case SDL.SDL_Keycode.SDLK_LGUI: + case SDL_Keycode.SDLK_LGUI: name = "LWin"; return true; - case SDL.SDL_Keycode.SDLK_RGUI: + case SDL_Keycode.SDLK_RGUI: name = "RWin"; return true; diff --git a/osu.Framework/Platform/Windows/WindowsWindow.cs b/osu.Framework/Platform/Windows/WindowsWindow.cs index 72041de6d2..25560f43bc 100644 --- a/osu.Framework/Platform/Windows/WindowsWindow.cs +++ b/osu.Framework/Platform/Windows/WindowsWindow.cs @@ -2,21 +2,24 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using System.Drawing; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; +using osu.Framework.Allocation; using osu.Framework.Input.Handlers.Mouse; -using osu.Framework.Platform.SDL2; +using osu.Framework.Platform.SDL; using osu.Framework.Platform.Windows.Native; using osuTK; using osuTK.Input; -using SDL2; +using SDL; using Icon = osu.Framework.Platform.Windows.Native.Icon; namespace osu.Framework.Platform.Windows { [SupportedOSPlatform("windows")] - internal class WindowsWindow : SDL2DesktopWindow + internal class WindowsWindow : SDL3DesktopWindow { private const int seticon_message = 0x0080; private const int icon_big = 1; @@ -28,8 +31,6 @@ internal class WindowsWindow : SDL2DesktopWindow private Icon? smallIcon; private Icon? largeIcon; - private const int wm_killfocus = 8; - /// /// Whether to apply the . /// @@ -49,33 +50,6 @@ public WindowsWindow(GraphicsSurfaceType surfaceType) applyBorderlessWindowHack = false; break; } - - if (!declareDpiAwareV2()) - declareDpiAware(); - } - - private bool declareDpiAwareV2() - { - try - { - return SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT.DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); - } - catch - { - return false; - } - } - - private bool declareDpiAware() - { - try - { - return SetProcessDpiAwareness(ProcessDpiAwareness.Process_Per_Monitor_DPI_Aware); - } - catch - { - return false; - } } public override void Create() @@ -85,33 +59,48 @@ public override void Create() // disable all pen and touch feedback as this causes issues when running "optimised" fullscreen under Direct3D11. foreach (var feedbackType in Enum.GetValues()) Native.Input.SetWindowFeedbackSetting(WindowHandle, feedbackType, false); + } + + public override unsafe void Run() + { + SDL3.SDL_SetWindowsMessageHook(&messageHook, ObjectHandle.Handle); + base.Run(); + } + + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] + private static unsafe SDL_bool messageHook(IntPtr userdata, MSG* msg) + { + var handle = new ObjectHandle(userdata); + if (handle.GetTarget(out WindowsWindow window)) + return window.handleEventFromHook(*msg); - // enable window message events to use with `OnSDLEvent` below. - SDL.SDL_EventState(SDL.SDL_EventType.SDL_SYSWMEVENT, SDL.SDL_ENABLE); + return SDL_bool.SDL_TRUE; } - protected override void HandleEventFromFilter(SDL.SDL_Event e) + private SDL_bool handleEventFromHook(MSG msg) { - if (e.type == SDL.SDL_EventType.SDL_SYSWMEVENT) + switch (msg.message) { - var wmMsg = Marshal.PtrToStructure(e.syswm.msg); - var m = wmMsg.msg.win; + case Imm.WM_IME_STARTCOMPOSITION: + case Imm.WM_IME_COMPOSITION: + case Imm.WM_IME_ENDCOMPOSITION: + handleImeMessage(msg.hwnd, msg.message, msg.lParam); + break; + } - switch (m.msg) - { - case wm_killfocus: - warpCursorFromFocusLoss(); - break; + return SDL_bool.SDL_TRUE; + } - case Imm.WM_IME_STARTCOMPOSITION: - case Imm.WM_IME_COMPOSITION: - case Imm.WM_IME_ENDCOMPOSITION: - handleImeMessage(m.hwnd, m.msg, m.lParam); - break; - } + protected override void HandleEventFromFilter(SDL_Event evt) + { + switch (evt.type) + { + case SDL_EventType.SDL_EVENT_WINDOW_FOCUS_LOST: + warpCursorFromFocusLoss(); + break; } - base.HandleEventFromFilter(e); + base.HandleEventFromFilter(evt); } /// @@ -125,7 +114,7 @@ protected override void HandleEventFromFilter(SDL.SDL_Event e) /// /// The normal warp in doesn't work in fullscreen, /// as it is called when the window has already lost focus and is minimized. - /// So we do an out-of-band warp, immediately after receiving the message. + /// So we do an out-of-band warp, immediately after receiving the message. /// private void warpCursorFromFocusLoss() { @@ -134,7 +123,7 @@ private void warpCursorFromFocusLoss() && RelativeMouseMode) { var pt = PointToScreen(new Point((int)LastMousePosition.Value.X, (int)LastMousePosition.Value.Y)); - SDL.SDL_WarpMouseGlobal(pt.X, pt.Y); // this directly calls the SetCursorPos win32 API + SDL3.SDL_WarpMouseGlobal(pt.X, pt.Y); // this directly calls the SetCursorPos win32 API } } @@ -148,10 +137,10 @@ public override void StartTextInput(bool allowIme) public override void ResetIme() => ScheduleCommand(() => Imm.CancelComposition(WindowHandle)); - protected override unsafe void HandleTextInputEvent(SDL.SDL_TextInputEvent evtText) + protected override void HandleTextInputEvent(SDL_TextInputEvent evtText) { - if (!SDL2Extensions.TryGetStringFromBytePointer(evtText.text, out string sdlResult)) - return; + string? sdlResult = evtText.GetText(); + Debug.Assert(sdlResult != null); // Block SDL text input if it was already handled by `handleImeMessage()`. // SDL truncates text over 32 bytes and sends it as multiple events. @@ -169,7 +158,7 @@ protected override unsafe void HandleTextInputEvent(SDL.SDL_TextInputEvent evtTe base.HandleTextInputEvent(evtText); } - protected override void HandleTextEditingEvent(SDL.SDL_TextEditingEvent evtEdit) + protected override void HandleTextEditingEvent(SDL_TextEditingEvent evtEdit) { // handled by custom logic below } @@ -224,9 +213,9 @@ private void handleImeMessage(IntPtr hWnd, uint uMsg, long lParam) #endregion - protected override void HandleTouchFingerEvent(SDL.SDL_TouchFingerEvent evtTfinger) + protected override void HandleTouchFingerEvent(SDL_TouchFingerEvent evtTfinger) { - if (evtTfinger.TryGetTouchName(out string name) && name == "pen") + if (evtTfinger.TryGetTouchName(out string? name) && name == "pen") { // Windows Ink tablet/pen handling // InputManager expects to receive this as mouse events, to have proper `mouseSource` input priority (see InputManager.GetPendingInputs) @@ -236,11 +225,11 @@ protected override void HandleTouchFingerEvent(SDL.SDL_TouchFingerEvent evtTfing switch (evtTfinger.type) { - case SDL.SDL_EventType.SDL_FINGERDOWN: + case SDL_EventType.SDL_EVENT_FINGER_DOWN: TriggerMouseDown(MouseButton.Left); break; - case SDL.SDL_EventType.SDL_FINGERUP: + case SDL_EventType.SDL_EVENT_FINGER_UP: TriggerMouseUp(MouseButton.Left); break; } @@ -270,9 +259,9 @@ protected set /// Used on and . private const int windows_borderless_width_hack = 1; - protected override Size SetBorderless(Display display) + protected override unsafe Size SetBorderless(Display display) { - SDL.SDL_SetWindowBordered(SDLWindowHandle, SDL.SDL_bool.SDL_FALSE); + SDL3.SDL_SetWindowBordered(SDLWindowHandle, SDL_bool.SDL_FALSE); var newSize = display.Bounds.Size; @@ -281,7 +270,7 @@ protected override Size SetBorderless(Display display) // we also trick the game into thinking the window has normal size: see Size setter override newSize += new Size(windows_borderless_width_hack, 0); - SDL.SDL_SetWindowSize(SDLWindowHandle, newSize.Width, newSize.Height); + SDL3.SDL_SetWindowSize(SDLWindowHandle, newSize.Width, newSize.Height); Position = display.Bounds.Location; return newSize; @@ -321,30 +310,6 @@ public override Point PointToScreen(Point point) return point; } - [DllImport("SHCore.dll", SetLastError = true)] - internal static extern bool SetProcessDpiAwareness(ProcessDpiAwareness awareness); - - internal enum ProcessDpiAwareness - { - Process_DPI_Unaware = 0, - Process_System_DPI_Aware = 1, - Process_Per_Monitor_DPI_Aware = 2 - } - - [DllImport("User32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT value); - - // ReSharper disable once InconsistentNaming - internal enum DPI_AWARENESS_CONTEXT - { - DPI_AWARENESS_CONTEXT_UNAWARE = -1, - DPI_AWARENESS_CONTEXT_SYSTEM_AWARE = -2, - DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE = -3, - DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = -4, - DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED = -5, - } - [DllImport("user32.dll", SetLastError = true)] internal static extern bool ScreenToClient(IntPtr hWnd, ref Point point); diff --git a/osu.Framework/osu.Framework.csproj b/osu.Framework/osu.Framework.csproj index 732b4a8d27..ad33a6496d 100644 --- a/osu.Framework/osu.Framework.csproj +++ b/osu.Framework/osu.Framework.csproj @@ -36,10 +36,10 @@ - + - +