-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Clear pre-existing bindings of same key combination to single action #25152
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
79273b8
Add stub of method to deduplicate bindings (and failing test)
bdach 90c44ce
Implement method to deduplicate keybindings
bdach 639c96e
Prevent ruleset input managers from reading duplicate bindings
bdach 9c6166e
Add migration to remove duplicate bindings
bdach 3aae07d
Add failing case for two bindings of single action bound to same key
bdach 828cede
Fix bindings being cleared if multiple bindings for same action have …
bdach 79a4b98
Use left mouse as alternative default binding for catch dash
bdach 4cfc95c
Add backwards migration of catch dash binding
bdach 4885c55
Merge branch 'master' into key-binding-deduplication
peppy File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. | ||
// See the LICENCE file in the repository root for full licence text. | ||
|
||
using System.Collections.Generic; | ||
using NUnit.Framework; | ||
using osu.Framework.Input.Bindings; | ||
using osu.Game.Input; | ||
using osu.Game.Input.Bindings; | ||
using osuTK.Input; | ||
|
||
namespace osu.Game.Tests.Input | ||
{ | ||
[TestFixture] | ||
public class RealmKeyBindingStoreTest | ||
{ | ||
[Test] | ||
public void TestBindingsWithoutDuplicatesAreNotModified() | ||
{ | ||
var bindings = new List<RealmKeyBinding> | ||
{ | ||
new RealmKeyBinding(GlobalAction.Back, KeyCombination.FromKey(Key.Escape)), | ||
new RealmKeyBinding(GlobalAction.Back, KeyCombination.FromMouseButton(MouseButton.Button1)), | ||
new RealmKeyBinding(GlobalAction.MusicPrev, KeyCombination.FromKey(Key.F1)), | ||
new RealmKeyBinding(GlobalAction.MusicNext, KeyCombination.FromKey(Key.F5)) | ||
}; | ||
|
||
int countCleared = RealmKeyBindingStore.ClearDuplicateBindings(bindings); | ||
|
||
Assert.Multiple(() => | ||
{ | ||
Assert.That(countCleared, Is.Zero); | ||
|
||
Assert.That(bindings[0].Action, Is.EqualTo((int)GlobalAction.Back)); | ||
Assert.That(bindings[0].KeyCombination, Is.EqualTo(new KeyCombination(InputKey.Escape))); | ||
|
||
Assert.That(bindings[1].Action, Is.EqualTo((int)GlobalAction.Back)); | ||
Assert.That(bindings[1].KeyCombination, Is.EqualTo(new KeyCombination(InputKey.ExtraMouseButton1))); | ||
|
||
Assert.That(bindings[2].Action, Is.EqualTo((int)GlobalAction.MusicPrev)); | ||
Assert.That(bindings[2].KeyCombination, Is.EqualTo(new KeyCombination(InputKey.F1))); | ||
|
||
Assert.That(bindings[3].Action, Is.EqualTo((int)GlobalAction.MusicNext)); | ||
Assert.That(bindings[3].KeyCombination, Is.EqualTo(new KeyCombination(InputKey.F5))); | ||
}); | ||
} | ||
|
||
[Test] | ||
public void TestDuplicateBindingsAreCleared() | ||
{ | ||
var bindings = new List<RealmKeyBinding> | ||
{ | ||
new RealmKeyBinding(GlobalAction.Back, KeyCombination.FromKey(Key.Escape)), | ||
new RealmKeyBinding(GlobalAction.Back, KeyCombination.FromMouseButton(MouseButton.Button1)), | ||
new RealmKeyBinding(GlobalAction.MusicPrev, KeyCombination.FromKey(Key.F1)), | ||
new RealmKeyBinding(GlobalAction.IncreaseVolume, KeyCombination.FromKey(Key.Escape)), | ||
new RealmKeyBinding(GlobalAction.MusicNext, KeyCombination.FromKey(Key.F5)), | ||
new RealmKeyBinding(GlobalAction.ExportReplay, KeyCombination.FromKey(Key.F1)), | ||
new RealmKeyBinding(GlobalAction.TakeScreenshot, KeyCombination.FromKey(Key.PrintScreen)), | ||
}; | ||
|
||
int countCleared = RealmKeyBindingStore.ClearDuplicateBindings(bindings); | ||
|
||
Assert.Multiple(() => | ||
{ | ||
Assert.That(countCleared, Is.EqualTo(4)); | ||
|
||
Assert.That(bindings[0].Action, Is.EqualTo((int)GlobalAction.Back)); | ||
Assert.That(bindings[0].KeyCombination, Is.EqualTo(new KeyCombination(InputKey.None))); | ||
|
||
Assert.That(bindings[1].Action, Is.EqualTo((int)GlobalAction.Back)); | ||
Assert.That(bindings[1].KeyCombination, Is.EqualTo(new KeyCombination(InputKey.ExtraMouseButton1))); | ||
|
||
Assert.That(bindings[2].Action, Is.EqualTo((int)GlobalAction.MusicPrev)); | ||
Assert.That(bindings[2].KeyCombination, Is.EqualTo(new KeyCombination(InputKey.None))); | ||
|
||
Assert.That(bindings[3].Action, Is.EqualTo((int)GlobalAction.IncreaseVolume)); | ||
Assert.That(bindings[3].KeyCombination, Is.EqualTo(new KeyCombination(InputKey.None))); | ||
|
||
Assert.That(bindings[4].Action, Is.EqualTo((int)GlobalAction.MusicNext)); | ||
Assert.That(bindings[4].KeyCombination, Is.EqualTo(new KeyCombination(InputKey.F5))); | ||
|
||
Assert.That(bindings[5].Action, Is.EqualTo((int)GlobalAction.ExportReplay)); | ||
Assert.That(bindings[5].KeyCombination, Is.EqualTo(new KeyCombination(InputKey.None))); | ||
|
||
Assert.That(bindings[6].Action, Is.EqualTo((int)GlobalAction.TakeScreenshot)); | ||
Assert.That(bindings[6].KeyCombination, Is.EqualTo(new KeyCombination(InputKey.PrintScreen))); | ||
}); | ||
} | ||
|
||
[Test] | ||
public void TestDuplicateBindingsAllowedIfBoundToSameAction() | ||
{ | ||
var bindings = new List<RealmKeyBinding> | ||
{ | ||
new RealmKeyBinding(GlobalAction.Back, KeyCombination.FromKey(Key.Escape)), | ||
new RealmKeyBinding(GlobalAction.Back, KeyCombination.FromKey(Key.Escape)), | ||
new RealmKeyBinding(GlobalAction.MusicPrev, KeyCombination.FromKey(Key.F1)), | ||
}; | ||
|
||
int countCleared = RealmKeyBindingStore.ClearDuplicateBindings(bindings); | ||
|
||
Assert.Multiple(() => | ||
{ | ||
Assert.That(countCleared, Is.EqualTo(0)); | ||
|
||
Assert.That(bindings[0].Action, Is.EqualTo((int)GlobalAction.Back)); | ||
Assert.That(bindings[0].KeyCombination, Is.EqualTo(new KeyCombination(InputKey.Escape))); | ||
|
||
Assert.That(bindings[1].Action, Is.EqualTo((int)GlobalAction.Back)); | ||
Assert.That(bindings[1].KeyCombination, Is.EqualTo(new KeyCombination(InputKey.Escape))); | ||
|
||
Assert.That(bindings[2].Action, Is.EqualTo((int)GlobalAction.MusicPrev)); | ||
Assert.That(bindings[2].KeyCombination, Is.EqualTo(new KeyCombination(InputKey.F1))); | ||
}); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,6 +25,7 @@ | |
using osu.Game.Beatmaps.Legacy; | ||
using osu.Game.Configuration; | ||
using osu.Game.Extensions; | ||
using osu.Game.Input; | ||
using osu.Game.Input.Bindings; | ||
using osu.Game.Models; | ||
using osu.Game.Online.API; | ||
|
@@ -34,6 +35,7 @@ | |
using osu.Game.Scoring; | ||
using osu.Game.Scoring.Legacy; | ||
using osu.Game.Skinning; | ||
using osuTK.Input; | ||
using Realms; | ||
using Realms.Exceptions; | ||
|
||
|
@@ -84,8 +86,9 @@ public class RealmAccess : IDisposable | |
/// 32 2023-07-09 Populate legacy scores with the ScoreV2 mod (and restore TotalScore to the legacy total for such scores) using replay files. | ||
/// 33 2023-08-16 Reset default chat toggle key binding to avoid conflict with newly added leaderboard toggle key binding. | ||
/// 34 2023-08-21 Add BackgroundReprocessingFailed flag to ScoreInfo to track upgrade failures. | ||
/// 35 2023-10-16 Clear key combinations of keybindings that are assigned to more than one action in a given settings section. | ||
/// </summary> | ||
private const int schema_version = 34; | ||
private const int schema_version = 35; | ||
|
||
/// <summary> | ||
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods. | ||
|
@@ -1031,6 +1034,47 @@ private void applyMigrationsForVersion(Migration migration, ulong targetVersion) | |
|
||
break; | ||
} | ||
|
||
case 35: | ||
{ | ||
// catch used `Shift` twice as a default key combination for dash, which generally was bothersome and causes issues elsewhere. | ||
// the duplicate binding logic below had to account for it, it could also break keybinding conflict resolution on revert-to-default. | ||
// as such, detect this situation and fix it before proceeding further. | ||
var catchDashBindings = migration.NewRealm.All<RealmKeyBinding>() | ||
.Where(kb => kb.RulesetName == @"fruits" && kb.ActionInt == 2) | ||
.ToList(); | ||
|
||
if (catchDashBindings.All(kb => kb.KeyCombination.Equals(new KeyCombination(InputKey.Shift)))) | ||
{ | ||
Debug.Assert(catchDashBindings.Count == 2); | ||
catchDashBindings.Last().KeyCombination = KeyCombination.FromMouseButton(MouseButton.Left); | ||
} | ||
|
||
// with the catch case dealt with, de-duplicate the remaining bindings. | ||
int countCleared = 0; | ||
|
||
var globalBindings = migration.NewRealm.All<RealmKeyBinding>().Where(kb => kb.RulesetName == null).ToList(); | ||
|
||
foreach (var category in Enum.GetValues<GlobalActionCategory>()) | ||
{ | ||
var categoryActions = GlobalActionContainer.GetGlobalActionsFor(category).Cast<int>().ToHashSet(); | ||
var categoryBindings = globalBindings.Where(kb => categoryActions.Contains(kb.ActionInt)); | ||
countCleared += RealmKeyBindingStore.ClearDuplicateBindings(categoryBindings); | ||
} | ||
|
||
var rulesetBindings = migration.NewRealm.All<RealmKeyBinding>().Where(kb => kb.RulesetName != null).ToList(); | ||
|
||
foreach (var variantGroup in rulesetBindings.GroupBy(kb => (kb.RulesetName, kb.Variant))) | ||
countCleared += RealmKeyBindingStore.ClearDuplicateBindings(variantGroup); | ||
|
||
if (countCleared > 0) | ||
{ | ||
Logger.Log($"{countCleared} of your keybinding(s) have been cleared due to being bound to multiple actions. " | ||
+ "Please choose new unique ones in the settings panel.", level: LogLevel.Important); | ||
Comment on lines
+1072
to
+1073
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
|
||
break; | ||
} | ||
} | ||
|
||
Logger.Log($"Migration completed in {stopwatch.ElapsedMilliseconds}ms"); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test could seem like it's exercising an edge case, except for the part where catch actually would get caught on this:
osu/osu.Game.Rulesets.Catch/CatchRuleset.cs
Lines 56 to 57 in 75011db
which would cause default dash bindings to become spontaneously completely unbound.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do wonder if the default for catch should be
InputKey.None
for the second (to create an extra binding but have no default)...There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, but I believe such a change would not apply retroactively to existing configs (unless I specifically include it in the migration I guess).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does seem to work on a quick test. Shall we change it while we're here?
Or alternatively,
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As pointed out on twitch chat, we should likely have the defaults match stable, which would make it:
I think it's fine to leave your test and logic in place as it's probably not going to harm anything. Up to you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've applied the last diff, and also added backwards migration on top (4cfc95c). I initially wasn't gonna bother but not doing so breaks revert-to-default for reasons I'd rather not have to fix so I decided that I might as well migrate over.