diff --git a/src/BizHawk.Client.Common/inputAdapters/InputManager.cs b/src/BizHawk.Client.Common/inputAdapters/InputManager.cs
index 04cdbd8cfe7..867e7043c01 100644
--- a/src/BizHawk.Client.Common/inputAdapters/InputManager.cs
+++ b/src/BizHawk.Client.Common/inputAdapters/InputManager.cs
@@ -149,10 +149,11 @@ private static AutofireController BindToDefinitionAF(
///
/// Processes queued inputs and triggers input evets (i.e. hotkeys), but does not update output controllers.
///
- /// Events that did not do anything are forwarded out here.
+ /// All input events are forwarded out here.
/// This allows things like Windows' standard alt hotkeys (for menu items) to be handled by the
- /// caller if the input didn't alrady do something else.
- public void ProcessInput(IPhysicalInputSource source, Func processHotkey, Config config, Action processUnboundInput)
+ /// caller if the input didn't alrady do something else.
+ ///
The second parameter is true if the input already did something (hotkey or controller input).
+ public void ProcessInput(IPhysicalInputSource source, Func processHotkey, Config config, Action processSpecialInput)
{
// loop through all available events
InputEvent ie;
@@ -191,10 +192,7 @@ public void ProcessInput(IPhysicalInputSource source, Func process
}
bool didEmuInput = shouldDoEmuInput && isEmuInput;
- if (!didHotkey && !didEmuInput)
- {
- processUnboundInput(ie);
- }
+ processSpecialInput(ie, didHotkey | didEmuInput);
} // foreach event
// also handle axes
@@ -223,7 +221,7 @@ public void ProcessInput(IPhysicalInputSource source, Func process
}
///
- /// Update output controllers. Call shortly before this.
+ /// Update output controllers. Call shortly before this.
///
public void RunControllerChain(Config config)
{
diff --git a/src/BizHawk.Client.EmuHawk/FormBase.cs b/src/BizHawk.Client.EmuHawk/FormBase.cs
index d663cd47f51..2dc4319dce4 100644
--- a/src/BizHawk.Client.EmuHawk/FormBase.cs
+++ b/src/BizHawk.Client.EmuHawk/FormBase.cs
@@ -46,6 +46,8 @@ public static void FixBackColorOnControls(Control control)
public virtual bool BlocksInputWhenFocused
=> true;
+ public bool MenuIsOpen { get; private set; }
+
public Config? Config { get; set; }
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
@@ -90,6 +92,12 @@ protected override void OnLoad(EventArgs e)
}
if (OSTailoredCode.IsUnixHost) FixBackColorOnControls(this);
UpdateWindowTitle();
+
+ if (MainMenuStrip != null)
+ {
+ MainMenuStrip.MenuActivate += (_, _) => MenuIsOpen = true;
+ MainMenuStrip.MenuDeactivate += (_, _) => MenuIsOpen = false;
+ }
}
public void UpdateWindowTitle()
@@ -98,25 +106,29 @@ public void UpdateWindowTitle()
: WindowTitle;
// Alt key hacks. We need this in order for hotkey bindings with alt to work.
+ private const int WM_SYSCOMMAND = 0x0112;
+ private const int SC_KEYMENU = 0xF100;
+ internal void SendAltCombination(char character)
+ {
+ var m = new Message { WParam = new IntPtr(SC_KEYMENU), LParam = new IntPtr(character), Msg = WM_SYSCOMMAND, HWnd = Handle };
+ if (character == ' ') base.WndProc(ref m);
+ else if (character >= 'a' && character <= 'z') base.ProcessDialogChar(character);
+ }
- /// sends a simulation of a plain alt key keystroke
- internal void SendPlainAltKey(int lparam)
+ internal void FocusToolStipMenu()
{
- var m = new Message { WParam = new IntPtr(0xF100), LParam = new IntPtr(lparam), Msg = 0x0112, HWnd = Handle };
+ var m = new Message { WParam = new IntPtr(SC_KEYMENU), LParam = new IntPtr(0), Msg = WM_SYSCOMMAND, HWnd = Handle };
base.WndProc(ref m);
}
- /// HACK to send an alt+mnemonic combination
- internal void SendAltKeyChar(char c) => ProcessMnemonic(c);
-
protected override void WndProc(ref Message m)
{
if (!BlocksInputWhenFocused)
{
// this is necessary to trap plain alt keypresses so that only our hotkey system gets them
- if (m.Msg == 0x0112) // WM_SYSCOMMAND
+ if (m.Msg == WM_SYSCOMMAND)
{
- if (m.WParam.ToInt32() == 0xF100) // SC_KEYMENU
+ if (m.WParam.ToInt32() == SC_KEYMENU && m.LParam == IntPtr.Zero)
{
return;
}
@@ -128,6 +140,7 @@ protected override void WndProc(ref Message m)
protected override bool ProcessDialogChar(char charCode)
{
+ if (BlocksInputWhenFocused) return base.ProcessDialogChar(charCode);
// this is necessary to trap alt+char combinations so that only our hotkey system gets them
return (ModifierKeys & Keys.Alt) != 0 || base.ProcessDialogChar(charCode);
}
diff --git a/src/BizHawk.Client.EmuHawk/MainForm.cs b/src/BizHawk.Client.EmuHawk/MainForm.cs
index 55fb6f208c9..c377108eb42 100644
--- a/src/BizHawk.Client.EmuHawk/MainForm.cs
+++ b/src/BizHawk.Client.EmuHawk/MainForm.cs
@@ -641,7 +641,7 @@ _argParser.SocketAddress is var (socketIP, socketPort)
? AllowInput.OnlyController
: AllowInput.All
: AllowInput.None,
- FormBase { BlocksInputWhenFocused: false } => AllowInput.All,
+ FormBase { BlocksInputWhenFocused: false, MenuIsOpen: false } => AllowInput.All,
ControllerConfig => AllowInput.All,
HotkeyConfig => AllowInput.All,
LuaWinform { BlocksInputWhenFocused: false } => AllowInput.All,
@@ -899,6 +899,13 @@ private void CheckMayCloseAndCleanup(object/*?*/ closingSender, CancelEventArgs
public override bool BlocksInputWhenFocused { get; } = false;
+ ///
+ /// Windows does tool stip menu focus things when Alt is released, not pressed.
+ /// However, if an alt combination is pressed then those things happen at that time instead.
+ /// So we need to know if a key combination was used, so we can skip the alt release logic.
+ ///
+ private bool _skipNextAltRelease = true;
+
public int ProgramRunLoop()
{
// needs to be done late, after the log console snaps on top
@@ -929,26 +936,40 @@ public int ProgramRunLoop()
InputManager.ActiveController.PrepareHapticsForHost(finalHostController);
Input.Instance.Adapter.SetHaptics(finalHostController.GetHapticsSnapshot());
- InputManager.ProcessInput(Input.Instance, CheckHotkey, Config, (ie) =>
+ InputManager.ProcessInput(Input.Instance, CheckHotkey, Config, (ie, handled) =>
{
if (ActiveForm is not FormBase afb) return;
// Alt key for menu items.
if (ie.EventType is InputEventType.Press && (ie.LogicalButton.Modifiers & LogicalButton.MASK_ALT) is not 0U)
{
+ // Windows will not focus the menu if any other key was pressed while Alt is held. Regardless of whether that key did anything.
+ _skipNextAltRelease = true;
+ if (handled) return;
+
if (ie.LogicalButton.Button.Length == 1)
{
var c = ie.LogicalButton.Button.ToLowerInvariant()[0];
- if ((c >= 'a' && c <= 'z') || c == ' ')
- {
- afb.SendAltKeyChar(c);
- }
+ afb.SendAltCombination(c);
}
else if (ie.LogicalButton.Button == "Space")
{
- afb.SendPlainAltKey(32);
+ afb.SendAltCombination(' ');
}
}
+ else if (handled) return;
+ else if (ie.EventType is InputEventType.Press && ie.LogicalButton.Button == "Alt")
+ {
+ // We will only do the alt release if the alt press itself was not already handled.
+ _skipNextAltRelease = false;
+ }
+ else if (ie.EventType is InputEventType.Release
+ && !afb.BlocksInputWhenFocused
+ && ie.LogicalButton.Button == "Alt"
+ && !_skipNextAltRelease)
+ {
+ afb.FocusToolStipMenu();
+ }
// same as right-click
if (ie.ToString() == "Press:Apps" && Config.ShowContextMenu && ContainsFocus)
diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Designer.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Designer.cs
index 14575f8f6cc..21b9ebca7d4 100644
--- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Designer.cs
+++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.Designer.cs
@@ -218,8 +218,6 @@ private void InitializeComponent()
this.ColumnsSubMenu,
this.HelpSubMenu});
this.TASMenu.TabIndex = 0;
- this.TASMenu.MenuActivate += new System.EventHandler(this.TASMenu_MenuActivate);
- this.TASMenu.MenuDeactivate += new System.EventHandler(this.TASMenu_MenuDeactivate);
//
// FileSubMenu
//
diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs
index 0e7c7fc0987..d52c7f15c30 100644
--- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs
+++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs
@@ -20,7 +20,7 @@ public partial class TAStudio : ToolFormBase, IToolFormAutoConfig, IControlMainf
public static Icon ToolIcon
=> Resources.TAStudioIcon;
- public override bool BlocksInputWhenFocused => IsInMenuLoop;
+ public override bool BlocksInputWhenFocused => false;
public new IMainFormForTools MainForm => base.MainForm;
@@ -29,8 +29,6 @@ public static Icon ToolIcon
// TODO: UI flow that conveniently allows to start from savestate
public ITasMovie CurrentTasMovie => MovieSession.Movie as ITasMovie;
- public bool IsInMenuLoop { get; private set; }
-
private readonly List _tasClipboard = new List();
private const string CursorColumnName = "CursorColumn";
private const string FrameColumnName = "FrameColumn";
@@ -1086,16 +1084,6 @@ private void TasView_CellDropped(object sender, InputRoll.CellEventArgs e)
}
}
- private void TASMenu_MenuActivate(object sender, EventArgs e)
- {
- IsInMenuLoop = true;
- }
-
- private void TASMenu_MenuDeactivate(object sender, EventArgs e)
- {
- IsInMenuLoop = false;
- }
-
// Stupid designer
protected void DragEnterWrapper(object sender, DragEventArgs e)
{
diff --git a/src/BizHawk.Tests.Client.Common/InputManagerTests.cs b/src/BizHawk.Tests.Client.Common/InputManagerTests.cs
index 54ed3d11114..172d4dc6cff 100644
--- a/src/BizHawk.Tests.Client.Common/InputManagerTests.cs
+++ b/src/BizHawk.Tests.Client.Common/InputManagerTests.cs
@@ -48,7 +48,7 @@ public void EmulateFrameAdvance(bool lag = false)
public void BasicInputProcessing()
{
- manager.ProcessInput(source, ProcessHotkey, config, (_) => { });
+ manager.ProcessInput(source, ProcessHotkey, config, (_, _) => { });
manager.RunControllerChain(config);
}
@@ -448,7 +448,7 @@ public void AutofireHotkeyDoesNotRespondToAlreadyHeldButton()
}
[TestMethod]
- public void HotkeyIsNotSeenAsUnbound()
+ public void HotkeyIsNotSeenAsUnhandled()
{
Context context = new(_hotkeys);
InputManager manager = context.manager;
@@ -456,11 +456,11 @@ public void HotkeyIsNotSeenAsUnbound()
manager.ClientControls.BindMulti(_hotkeys[0], "Q");
source.MakePressEvent("Q");
- manager.ProcessInput(source, context.ProcessHotkey, context.config, (_) => Assert.Fail("Bound key was seen as unbound."));
+ manager.ProcessInput(source, context.ProcessHotkey, context.config, (_, handled) => Assert.IsTrue(handled, "Bound key was seen as unbound."));
}
[TestMethod]
- public void InputIsNotSeenAsUnbound()
+ public void InputIsNotSeenAsUnhandled()
{
Context context = new(_hotkeys);
InputManager manager = context.manager;
@@ -468,11 +468,11 @@ public void InputIsNotSeenAsUnbound()
manager.ActiveController.BindMulti("A", "Q");
source.MakePressEvent("Q");
- manager.ProcessInput(source, context.ProcessHotkey, context.config, (_) => Assert.Fail("Bound key was seen as unbound."));
+ manager.ProcessInput(source, context.ProcessHotkey, context.config, (_, handled) => Assert.IsTrue(handled, "Bound key was seen as unbound."));
}
[TestMethod]
- public void UnboundInputIsSeen()
+ public void UnboundInputIsSeenAsUnhandled()
{
Context context = new(_hotkeys);
InputManager manager = context.manager;
@@ -480,10 +480,7 @@ public void UnboundInputIsSeen()
source.MakePressEvent("A");
- bool sawUnboundInput = false;
- manager.ProcessInput(source, context.ProcessHotkey, context.config, (_) => sawUnboundInput = true);
-
- Assert.IsTrue(sawUnboundInput);
+ manager.ProcessInput(source, context.ProcessHotkey, context.config, (_, handled) => Assert.IsFalse(handled, "Unbound key was seen as handled."));
}
[TestMethod]