diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index cd31df316a..2079f136d2 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -16,6 +16,7 @@ using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Screens.Menu; using osu.Game.Updater; +using osu.Desktop.Windows; namespace osu.Desktop { @@ -98,6 +99,9 @@ namespace osu.Desktop LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, Add); LoadComponentAsync(new DiscordRichPresence(), Add); + + if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) + LoadComponentAsync(new GameplayWinKeyBlocker(), Add); } protected override void ScreenChanged(IScreen lastScreen, IScreen newScreen) diff --git a/osu.Desktop/Windows/GameplayWinKeyBlocker.cs b/osu.Desktop/Windows/GameplayWinKeyBlocker.cs new file mode 100644 index 0000000000..86174ceb90 --- /dev/null +++ b/osu.Desktop/Windows/GameplayWinKeyBlocker.cs @@ -0,0 +1,41 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Platform; +using osu.Game.Configuration; + +namespace osu.Desktop.Windows +{ + public class GameplayWinKeyBlocker : Component + { + private Bindable allowScreenSuspension; + private Bindable disableWinKey; + + private GameHost host; + + [BackgroundDependencyLoader] + private void load(GameHost host, OsuConfigManager config) + { + this.host = host; + + allowScreenSuspension = host.AllowScreenSuspension.GetBoundCopy(); + allowScreenSuspension.BindValueChanged(_ => updateBlocking()); + + disableWinKey = config.GetBindable(OsuSetting.GameplayDisableWinKey); + disableWinKey.BindValueChanged(_ => updateBlocking(), true); + } + + private void updateBlocking() + { + bool shouldDisable = disableWinKey.Value && !allowScreenSuspension.Value; + + if (shouldDisable) + host.InputThread.Scheduler.Add(WindowsKey.Disable); + else + host.InputThread.Scheduler.Add(WindowsKey.Enable); + } + } +} diff --git a/osu.Desktop/Windows/WindowsKey.cs b/osu.Desktop/Windows/WindowsKey.cs new file mode 100644 index 0000000000..f19d741107 --- /dev/null +++ b/osu.Desktop/Windows/WindowsKey.cs @@ -0,0 +1,80 @@ +// 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; + +namespace osu.Desktop.Windows +{ + internal class WindowsKey + { + private delegate int LowLevelKeyboardProcDelegate(int nCode, int wParam, ref KdDllHookStruct lParam); + + private static bool isBlocked; + + private const int wh_keyboard_ll = 13; + private const int wm_keydown = 256; + private const int wm_syskeyup = 261; + + //Resharper disable once NotAccessedField.Local + private static LowLevelKeyboardProcDelegate keyboardHookDelegate; // keeping a reference alive for the GC + private static IntPtr keyHook; + + [StructLayout(LayoutKind.Explicit)] + private readonly struct KdDllHookStruct + { + [FieldOffset(0)] + public readonly int VkCode; + + [FieldOffset(8)] + public readonly int Flags; + } + + private static int lowLevelKeyboardProc(int nCode, int wParam, ref KdDllHookStruct lParam) + { + if (wParam >= wm_keydown && wParam <= wm_syskeyup) + { + switch (lParam.VkCode) + { + case 0x5B: // left windows key + case 0x5C: // right windows key + return 1; + } + } + + return callNextHookEx(0, nCode, wParam, ref lParam); + } + + internal static void Disable() + { + if (keyHook != IntPtr.Zero || isBlocked) + return; + + keyHook = setWindowsHookEx(wh_keyboard_ll, (keyboardHookDelegate = lowLevelKeyboardProc), Marshal.GetHINSTANCE(System.Reflection.Assembly.GetExecutingAssembly().GetModules()[0]), 0); + + isBlocked = true; + } + + internal static void Enable() + { + if (keyHook == IntPtr.Zero || !isBlocked) + return; + + keyHook = unhookWindowsHookEx(keyHook); + keyboardHookDelegate = null; + + keyHook = IntPtr.Zero; + + isBlocked = false; + } + + [DllImport(@"user32.dll", EntryPoint = @"SetWindowsHookExA")] + private static extern IntPtr setWindowsHookEx(int idHook, LowLevelKeyboardProcDelegate lpfn, IntPtr hMod, int dwThreadId); + + [DllImport(@"user32.dll", EntryPoint = @"UnhookWindowsHookEx")] + private static extern IntPtr unhookWindowsHookEx(IntPtr hHook); + + [DllImport(@"user32.dll", EntryPoint = @"CallNextHookEx")] + private static extern int callNextHookEx(int hHook, int nCode, int wParam, ref KdDllHookStruct lParam); + } +} diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 268328272c..a8a8794320 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -99,6 +99,7 @@ namespace osu.Game.Configuration Set(OsuSetting.ScoreDisplayMode, ScoringMode.Standardised); Set(OsuSetting.IncreaseFirstObjectVisibility, true); + Set(OsuSetting.GameplayDisableWinKey, true); // Update Set(OsuSetting.ReleaseStream, ReleaseStream.Lazer); @@ -229,6 +230,7 @@ namespace osu.Game.Configuration IntroSequence, UIHoldActivationDelay, HitLighting, - MenuBackgroundSource + MenuBackgroundSource, + GameplayDisableWinKey } } diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 93a02ea0e4..0149e6c3a6 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Configuration; @@ -78,6 +79,15 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay Bindable = config.GetBindable(OsuSetting.ScoreDisplayMode) } }; + + if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) + { + Add(new SettingsCheckbox + { + LabelText = "Disable Windows key during gameplay", + Bindable = config.GetBindable(OsuSetting.GameplayDisableWinKey) + }); + } } } }