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:
parent
3f9fb0eaef
commit
accf0f038c
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue