diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
index 18c5b554ec..1007ff2e54 100644
--- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
+++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
@@ -821,6 +821,7 @@
+
diff --git a/OpenRA.Mods.Common/Widgets/Logic/SingleHotkeyBaseLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/SingleHotkeyBaseLogic.cs
new file mode 100644
index 0000000000..d00f2b96d7
--- /dev/null
+++ b/OpenRA.Mods.Common/Widgets/Logic/SingleHotkeyBaseLogic.cs
@@ -0,0 +1,45 @@
+#region Copyright & License Information
+/*
+ * Copyright 2007-2017 The OpenRA Developers (see AUTHORS)
+ * This file is part of OpenRA, which is free software. It is made
+ * available to you under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version. For more
+ * information, see COPYING.
+ */
+#endregion
+
+using System.Collections.Generic;
+using OpenRA.Mods.Common.Lint;
+using OpenRA.Widgets;
+
+namespace OpenRA.Mods.Common.Widgets.Logic
+{
+ public abstract class SingleHotkeyBaseLogic : ChromeLogic
+ {
+ protected SingleHotkeyBaseLogic(Widget widget, string argName, string parentName, Dictionary logicArgs)
+ {
+ var ks = Game.Settings.Keys;
+ MiniYaml yaml;
+
+ var namedKey = new NamedHotkey();
+ if (logicArgs.TryGetValue(argName, out yaml))
+ namedKey = new NamedHotkey(yaml.Value, ks);
+
+ var keyhandler = widget.Get(parentName);
+ keyhandler.AddHandler(e =>
+ {
+ if (e.Event == KeyInputEvent.Down)
+ {
+ var key = Hotkey.FromKeyInput(e);
+ if (key == namedKey.GetValue())
+ return OnHotkeyActivated(e);
+ }
+
+ return false;
+ });
+ }
+
+ protected abstract bool OnHotkeyActivated(KeyInput e);
+ }
+}