Allow left/right modifier keys to be used separately (no UI)

Should allow for any key to be used as a modifier (see #2981), simply by
populating `Config.ModifierKeys`. Flip the assignment on `Input.cs:45` to try
out this change specifically.
This commit is contained in:
YoshiRulz 2021-11-24 08:12:58 +10:00
parent 3f9fb0eaef
commit accf0f038c
No known key found for this signature in database
GPG Key ID: C4DE31C245353FB7
11 changed files with 123 additions and 75 deletions

View File

@ -5,6 +5,8 @@ using BizHawk.Common;
using BizHawk.Common.PathExtensions;
using BizHawk.Emulation.Common;
using BizHawk.Emulation.Cores;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace BizHawk.Client.Common
@ -332,5 +334,12 @@ namespace BizHawk.Client.Common
public EHostInputMethod HostInputMethod { get; set; } = HostCapabilityDetector.HasDirectX ? EHostInputMethod.DirectInput : EHostInputMethod.OpenTK;
public bool UseStaticWindowTitles { get; set; }
public List<string> ModifierKeys { get; set; } = new();
[JsonIgnore]
public IReadOnlyList<string> ModifierKeysEffective;
public bool MergeLAndRModifierKeys { get; set; } = true;
}
}

View File

@ -1,6 +1,8 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Text;
namespace BizHawk.Client.Common
{
@ -29,25 +31,31 @@ namespace BizHawk.Client.Common
Press, Release
}
public readonly struct LogicalButton
public struct LogicalButton
{
public const uint MASK_ALT = 1U << 2;
public const uint MASK_CTRL = 1U << 1;
public const uint MASK_SHIFT = 1U << 3;
public const uint MASK_WIN = 1U << 0;
public static bool operator ==(LogicalButton lhs, LogicalButton rhs)
=> lhs.Button == rhs.Button && lhs.Modifiers == rhs.Modifiers;
public static bool operator !=(LogicalButton lhs, LogicalButton rhs) => !(lhs == rhs);
public bool Alt => (Modifiers & ModifierKey.Alt) != 0;
/// <remarks>pretty sure these are always consumed during the same iteration of the main program loop, but ¯\_(ツ)_/¯ better safe than sorry --yoshi</remarks>
private readonly Func<IReadOnlyList<string>> _getEffectiveModListCallback;
public readonly string Button;
public bool Control => (Modifiers & ModifierKey.Control) != 0;
public readonly uint Modifiers;
public readonly ModifierKey Modifiers;
public bool Shift => (Modifiers & ModifierKey.Shift) != 0;
public LogicalButton(string button, ModifierKey modifiers)
public LogicalButton(string button, uint modifiers, Func<IReadOnlyList<string>> getEffectiveModListCallback)
{
_getEffectiveModListCallback = getEffectiveModListCallback;
Button = button;
Modifiers = modifiers;
}
@ -58,12 +66,19 @@ namespace BizHawk.Client.Common
public override readonly string ToString()
{
var ret = "";
if (Control) ret += "Ctrl+";
if (Alt) ret += "Alt+";
if (Shift) ret += "Shift+";
ret += Button;
return ret;
if (Modifiers is 0U) return Button;
var allMods = _getEffectiveModListCallback();
StringBuilder ret = new();
for (var i = 0; i < allMods.Count; i++)
{
var b = 1U << i;
if ((Modifiers & b) is not 0U)
{
ret.Append(allMods[i]);
ret.Append('+');
}
}
return ret + Button;
}
}
}

View File

@ -1,5 +1,3 @@
using System;
namespace BizHawk.Client.Common
{
public enum AllowInput
@ -8,19 +6,4 @@ namespace BizHawk.Client.Common
All = 1,
OnlyController = 2
}
[Flags]
public enum ModifierKey
{
/// <summary>The bitmask to extract modifiers from a key value.</summary>
Modifiers = -65536,
/// <summary>No key pressed.</summary>
None = 0,
/// <summary>The SHIFT modifier key.</summary>
Shift = 65536,
/// <summary>The CTRL modifier key.</summary>
Control = 131072,
/// <summary>The ALT modifier key.</summary>
Alt = 262144,
}
}

View File

@ -8,6 +8,7 @@ using BizHawk.Bizware.DirectX;
using BizHawk.Bizware.OpenTK3;
using BizHawk.Common;
using BizHawk.Client.Common;
using BizHawk.Common.CollectionExtensions;
namespace BizHawk.Client.EmuHawk
{
@ -33,15 +34,20 @@ namespace BizHawk.Client.EmuHawk
public readonly IHostInputAdapter Adapter;
private Config _currentConfig;
private readonly Func<Config> _getConfigCallback;
internal Input(IntPtr mainFormHandle, Func<Config> getConfigCallback, Func<bool, AllowInput> mainFormInputAllowedCallback)
{
_getConfigCallback = getConfigCallback;
_currentConfig = _getConfigCallback();
_currentConfig.MergeLAndRModifierKeys = true; // for debugging
UpdateModifierKeysEffective();
MainFormInputAllowedCallback = mainFormInputAllowedCallback;
var config = _getConfigCallback();
Adapter = config.HostInputMethod switch
Adapter = _currentConfig.HostInputMethod switch
{
EHostInputMethod.OpenTK => new OpenTKInputAdapter(),
_ when OSTailoredCode.IsUnixHost => new OpenTKInputAdapter(),
@ -49,7 +55,7 @@ namespace BizHawk.Client.EmuHawk
_ => throw new Exception()
};
Console.WriteLine($"Using {Adapter.Desc} for host input (keyboard + gamepads)");
Adapter.UpdateConfig(config);
Adapter.UpdateConfig(_currentConfig);
Adapter.FirstInitAll(mainFormHandle);
_updateThread = new Thread(UpdateThreadProc)
{
@ -66,26 +72,36 @@ namespace BizHawk.Client.EmuHawk
private bool _trackDeltas;
private bool _ignoreEventsNextPoll;
private static readonly IReadOnlyList<string> ModifierKeysBase = new[] { "Win", "Ctrl", "Alt", "Shift" };
private static readonly IReadOnlyList<string> ModifierKeysBaseUnmerged = new[] { "Win", "Ctrl", "Alt", "Shift", "LeftWin", "RightWin", "LeftCtrl", "RightCtrl", "LeftAlt", "RightAlt", "LeftShift", "RightShift" };
public void UpdateModifierKeysEffective()
=> _currentConfig.ModifierKeysEffective = (_currentConfig.MergeLAndRModifierKeys ? ModifierKeysBase : ModifierKeysBaseUnmerged)
.Concat(_currentConfig.ModifierKeys)
.Take(32).ToArray();
private readonly IReadOnlyDictionary<string, string> _modifierKeyPreMap = new Dictionary<string, string>
{
["LeftWin"] = "Win",
["RightWin"] = "Win",
["LeftCtrl"] = "Ctrl",
["RightCtrl"] = "Ctrl",
["LeftAlt"] = "Alt",
["RightAlt"] = "Alt",
["LeftShift"] = "Shift",
["RightShift"] = "Shift",
};
private void HandleButton(string button, bool newState, ClientInputFocus source)
{
var currentModifier = button switch
{
// "LeftWin" => ModifierKey.Win,
// "RightWin" => ModifierKey.Win,
"LeftShift" => ModifierKey.Shift,
"RightShift" => ModifierKey.Shift,
"LeftCtrl" => ModifierKey.Control,
"RightCtrl" => ModifierKey.Control,
"LeftAlt" => ModifierKey.Alt,
"RightAlt" => ModifierKey.Alt,
_ => ModifierKey.None
};
if (EnableIgnoreModifiers && currentModifier != ModifierKey.None) return;
if (_lastState[button] == newState) return;
if (!(_currentConfig.MergeLAndRModifierKeys &&_modifierKeyPreMap.TryGetValue(button, out var button1))) button1 = button;
var modIndex = _currentConfig.ModifierKeysEffective.IndexOf(button1);
var currentModifier = modIndex is -1 ? 0U : 1U << modIndex;
if (EnableIgnoreModifiers && currentModifier is not 0U) return;
if (newState == _lastState[button1]) return;
// apply
// NOTE: this is not quite right. if someone held leftshift+rightshift it would be broken. seems unlikely, though.
if (currentModifier != ModifierKey.None)
if (currentModifier is not 0U)
{
if (newState)
_modifiers |= currentModifier;
@ -94,17 +110,17 @@ namespace BizHawk.Client.EmuHawk
}
// don't generate events for things like Ctrl+LeftControl
ModifierKey mods = _modifiers;
if (currentModifier != ModifierKey.None)
var mods = _modifiers;
if (currentModifier is not 0U)
mods &= ~currentModifier;
var ie = new InputEvent
{
EventType = newState ? InputEventType.Press : InputEventType.Release,
LogicalButton = new LogicalButton(button, mods),
LogicalButton = new(button1, mods, () => _getConfigCallback().ModifierKeysEffective),
Source = source
};
_lastState[button] = newState;
_lastState[button1] = newState;
// track the pressed events with modifiers that we send so that we can send corresponding unpresses with modifiers
// this is an interesting idea, which we may need later, but not yet.
@ -115,11 +131,11 @@ namespace BizHawk.Client.EmuHawk
// so, i am adding it as of 11-sep-2011
if (newState)
{
_modifierState[button] = ie.LogicalButton;
_modifierState[button1] = ie.LogicalButton;
}
else
{
if (_modifierState.TryGetValue(button, out var buttonModifierState))
if (_modifierState.TryGetValue(button1, out var buttonModifierState))
{
if (buttonModifierState != ie.LogicalButton && !_ignoreEventsNextPoll)
{
@ -131,7 +147,7 @@ namespace BizHawk.Client.EmuHawk
Source = source
});
}
_modifierState.Remove(button);
_modifierState.Remove(button1);
}
}
@ -147,7 +163,7 @@ namespace BizHawk.Client.EmuHawk
_axisValues[axis] = newValue;
}
private ModifierKey _modifiers;
private uint _modifiers;
private readonly List<InputEvent> _newEvents = new List<InputEvent>();
public void ClearEvents()
@ -245,7 +261,8 @@ namespace BizHawk.Client.EmuHawk
};
while (true)
{
Adapter.UpdateConfig(_getConfigCallback());
_currentConfig = _getConfigCallback();
Adapter.UpdateConfig(_currentConfig);
var keyEvents = Adapter.ProcessHostKeyboards();
Adapter.PreprocessHostGamepads();
@ -307,7 +324,7 @@ namespace BizHawk.Client.EmuHawk
foreach (var ie in _newEvents)
{
//events are swallowed in some cases:
if (ie.LogicalButton.Alt && ShouldSwallow(MainFormInputAllowedCallback(true), ie))
if ((ie.LogicalButton.Modifiers & LogicalButton.MASK_ALT) is not 0U && ShouldSwallow(MainFormInputAllowedCallback(true), ie))
continue;
if (ie.EventType == InputEventType.Press && ShouldSwallow(allowInput, ie))
continue;

View File

@ -992,9 +992,9 @@ namespace BizHawk.Client.EmuHawk
if (triggers.Count == 0)
{
// Maybe it is a system alt-key which hasn't been overridden
if (ie.EventType is InputEventType.Press)
if (ie.EventType is InputEventType.Press && (ie.LogicalButton.Modifiers & LogicalButton.MASK_ALT) is not 0U)
{
if (ie.LogicalButton.Alt && ie.LogicalButton.Button.Length == 1)
if (ie.LogicalButton.Button.Length == 1)
{
var c = ie.LogicalButton.Button.ToLower()[0];
if ((c >= 'a' && c <= 'z') || c == ' ')
@ -1002,8 +1002,7 @@ namespace BizHawk.Client.EmuHawk
SendAltKeyChar(c);
}
}
if (ie.LogicalButton.Alt && ie.LogicalButton.Button == "Space")
else if (ie.LogicalButton.Button == "Space")
{
SendPlainAltKey(32);
}

View File

@ -81,7 +81,7 @@ namespace BizHawk.Client.EmuHawk
private Control CreateNormalPanel(Dictionary<string, string> settings, List<string> buttons, Size size)
{
var cp = new ControllerConfigPanel { Dock = DockStyle.Fill, AutoScroll = true, Tooltip = toolTip1 };
ControllerConfigPanel cp = new(_config.ModifierKeysEffective) { Dock = DockStyle.Fill, AutoScroll = true, Tooltip = toolTip1 };
cp.LoadSettings(settings, checkBoxAutoTab.Checked, buttons, size.Width, size.Height);
return cp;
}

View File

@ -4,6 +4,8 @@ using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using BizHawk.Client.Common;
namespace BizHawk.Client.EmuHawk
{
// this is a little messy right now because of remnants of the old config system
@ -33,8 +35,11 @@ namespace BizHawk.Client.EmuHawk
private bool _autoTab;
public ControllerConfigPanel()
private readonly IReadOnlyList<string> _effectiveModList;
public ControllerConfigPanel(IReadOnlyList<string> effectiveModList)
{
_effectiveModList = effectiveModList;
InitializeComponent();
}
@ -121,7 +126,7 @@ namespace BizHawk.Client.EmuHawk
x += columnWidth;
}
var iw = new InputCompositeWidget
var iw = new InputCompositeWidget(_effectiveModList)
{
Location = new Point(x, y),
Size = new Size(_inputSize, UIHelper.ScaleY(23)),

View File

@ -110,7 +110,7 @@ namespace BizHawk.Client.EmuHawk
Size = new Size(iwOffsetX - UIHelper.ScaleX(2), UIHelper.ScaleY(15))
};
var w = new InputCompositeWidget
var w = new InputCompositeWidget(_config.ModifierKeysEffective)
{
Location = new Point(x + iwOffsetX, y + iwOffsetY),
AutoTab = AutoTabCheckBox.Checked,

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using BizHawk.Client.Common;
@ -7,8 +8,12 @@ namespace BizHawk.Client.EmuHawk
{
public partial class InputCompositeWidget : UserControl
{
public InputCompositeWidget()
private readonly IReadOnlyList<string> _effectiveModList;
public InputCompositeWidget(IReadOnlyList<string> effectiveModList)
{
_effectiveModList = effectiveModList;
InitializeComponent();
btnSpecial.Image = Properties.Resources.ArrowBlackDown;
@ -82,24 +87,24 @@ namespace BizHawk.Client.EmuHawk
private void DropdownMenu_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
var mods = ModifierKey.None;
var mods = 0U;
if ((ModifierKeys & Keys.Shift) != 0)
{
mods |= ModifierKey.Shift;
mods |= LogicalButton.MASK_SHIFT;
}
if ((ModifierKeys & Keys.Control) != 0)
{
mods |= ModifierKey.Control;
mods |= LogicalButton.MASK_CTRL;
}
if ((ModifierKeys & Keys.Alt) != 0)
{
mods |= ModifierKey.Alt;
mods |= LogicalButton.MASK_ALT;
}
LogicalButton lb = new(e.ClickedItem.Text, mods);
LogicalButton lb = new(e.ClickedItem.Text, mods, () => _effectiveModList);
widget.SetBinding(lb.ToString());
}

View File

@ -120,6 +120,19 @@ namespace BizHawk.Common.CollectionExtensions
foreach (var item in collection) list.Add(item);
}
/// <inheritdoc cref="IList{T}.IndexOf"/>
/// <remarks>
/// (This is an extension method which reimplements <see cref="IList{T}.IndexOf"/> for other <see cref="IReadOnlyList{T}">collections</see>.
/// It defers to the existing <see cref="IList{T}.IndexOf">IndexOf</see> if the receiver's type is <see cref="IList{T}"/> or a subtype.)
/// </remarks>
public static int IndexOf<T>(this IReadOnlyList<T> list, T elem)
where T : IEquatable<T>
{
if (list is IList<T> listImpl) return listImpl.IndexOf(elem);
for (int i = 0, l = list.Count; i < l; i++) if (elem.Equals(list[i])) return i;
return -1;
}
public static T? FirstOrNull<T>(this IEnumerable<T> list, Func<T, bool> predicate)
where T : struct
{

View File

@ -33,6 +33,7 @@ namespace BizHawk.Tests.Client.Common.config
typeof(Dictionary<,>),
typeof(int),
typeof(JToken),
typeof(List<>),
typeof(Nullable<>),
typeof(object),
typeof(float),
@ -78,6 +79,7 @@ namespace BizHawk.Tests.Client.Common.config
groupDesc ??= t.Name;
foreach (var mi in t.GetMembers(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
{
if (mi.GetCustomAttribute<JsonIgnoreAttribute>() is not null) continue;
if (mi is PropertyInfo pi) CheckMemberAndTypeParams(pi.PropertyType, groupDesc);
else if (mi is FieldInfo fi) CheckMemberAndTypeParams(fi.FieldType, groupDesc);
}