From 151ae38a568a518458e57ac0c72d6b15172f0462 Mon Sep 17 00:00:00 2001
From: spycrab <spycrab@users.noreply.github.com>
Date: Tue, 6 Jun 2017 13:49:49 +0200
Subject: [PATCH] Qt: Implement hotkeys (+ configuration)

---
 Source/Core/DolphinQt2/CMakeLists.txt         |   8 +
 .../DolphinQt2/Config/Mapping/Hotkey3D.cpp    |  42 ++
 .../Core/DolphinQt2/Config/Mapping/Hotkey3D.h |  25 ++
 .../Config/Mapping/HotkeyGeneral.cpp          |  47 +++
 .../DolphinQt2/Config/Mapping/HotkeyGeneral.h |  25 ++
 .../Config/Mapping/HotkeyGraphics.cpp         |  48 +++
 .../Config/Mapping/HotkeyGraphics.h           |  25 ++
 .../Config/Mapping/HotkeyStates.cpp           |  42 ++
 .../DolphinQt2/Config/Mapping/HotkeyStates.h  |  25 ++
 .../DolphinQt2/Config/Mapping/HotkeyTAS.cpp   |  42 ++
 .../DolphinQt2/Config/Mapping/HotkeyTAS.h     |  25 ++
 .../DolphinQt2/Config/Mapping/HotkeyWii.cpp   |  39 ++
 .../DolphinQt2/Config/Mapping/HotkeyWii.h     |  25 ++
 .../Config/Mapping/MappingWindow.cpp          |  18 +
 .../DolphinQt2/Config/Mapping/MappingWindow.h |   4 +-
 Source/Core/DolphinQt2/DolphinQt2.vcxproj     |  12 +
 Source/Core/DolphinQt2/HotkeyScheduler.cpp    | 363 ++++++++++++++++++
 Source/Core/DolphinQt2/HotkeyScheduler.h      |  37 ++
 Source/Core/DolphinQt2/MainWindow.cpp         |  54 ++-
 Source/Core/DolphinQt2/MainWindow.h           |   6 +
 Source/Core/DolphinQt2/MenuBar.cpp            |   8 +-
 Source/Core/DolphinQt2/MenuBar.h              |   4 +
 .../DolphinQt2/QtUtils/FocusEventFilter.cpp   |  19 +
 .../DolphinQt2/QtUtils/FocusEventFilter.h     |  18 +
 24 files changed, 958 insertions(+), 3 deletions(-)
 create mode 100644 Source/Core/DolphinQt2/Config/Mapping/Hotkey3D.cpp
 create mode 100644 Source/Core/DolphinQt2/Config/Mapping/Hotkey3D.h
 create mode 100644 Source/Core/DolphinQt2/Config/Mapping/HotkeyGeneral.cpp
 create mode 100644 Source/Core/DolphinQt2/Config/Mapping/HotkeyGeneral.h
 create mode 100644 Source/Core/DolphinQt2/Config/Mapping/HotkeyGraphics.cpp
 create mode 100644 Source/Core/DolphinQt2/Config/Mapping/HotkeyGraphics.h
 create mode 100644 Source/Core/DolphinQt2/Config/Mapping/HotkeyStates.cpp
 create mode 100644 Source/Core/DolphinQt2/Config/Mapping/HotkeyStates.h
 create mode 100644 Source/Core/DolphinQt2/Config/Mapping/HotkeyTAS.cpp
 create mode 100644 Source/Core/DolphinQt2/Config/Mapping/HotkeyTAS.h
 create mode 100644 Source/Core/DolphinQt2/Config/Mapping/HotkeyWii.cpp
 create mode 100644 Source/Core/DolphinQt2/Config/Mapping/HotkeyWii.h
 create mode 100644 Source/Core/DolphinQt2/HotkeyScheduler.cpp
 create mode 100644 Source/Core/DolphinQt2/HotkeyScheduler.h
 create mode 100644 Source/Core/DolphinQt2/QtUtils/FocusEventFilter.cpp
 create mode 100644 Source/Core/DolphinQt2/QtUtils/FocusEventFilter.h

diff --git a/Source/Core/DolphinQt2/CMakeLists.txt b/Source/Core/DolphinQt2/CMakeLists.txt
index 42a42bfaa8..e5ff552b6c 100644
--- a/Source/Core/DolphinQt2/CMakeLists.txt
+++ b/Source/Core/DolphinQt2/CMakeLists.txt
@@ -9,6 +9,7 @@ set(CMAKE_AUTOMOC ON)
 
 set(SRCS
   AboutDialog.cpp
+  HotkeyScheduler.cpp
   Host.cpp
   InDevelopmentWarning.cpp
   Main.cpp
@@ -24,6 +25,12 @@ set(SRCS
   Config/Mapping/GCKeyboardEmu.cpp
   Config/Mapping/GCPadEmu.cpp
   Config/Mapping/GCPadWiiU.cpp
+  Config/Mapping/Hotkey3D.cpp
+  Config/Mapping/HotkeyGeneral.cpp
+  Config/Mapping/HotkeyGraphics.cpp
+  Config/Mapping/HotkeyStates.cpp
+  Config/Mapping/HotkeyTAS.cpp
+  Config/Mapping/HotkeyWii.cpp
   Config/Mapping/MappingBool.cpp
   Config/Mapping/MappingButton.cpp
   Config/Mapping/MappingNumeric.cpp
@@ -41,6 +48,7 @@ set(SRCS
   GameList/ListProxyModel.cpp
   QtUtils/DoubleClickEventFilter.cpp
   QtUtils/ElidedButton.cpp
+  QtUtils/FocusEventFilter.cpp
   Settings/GeneralPane.cpp
   Settings/InterfacePane.cpp
   Settings/PathPane.cpp
diff --git a/Source/Core/DolphinQt2/Config/Mapping/Hotkey3D.cpp b/Source/Core/DolphinQt2/Config/Mapping/Hotkey3D.cpp
new file mode 100644
index 0000000000..c144d53f76
--- /dev/null
+++ b/Source/Core/DolphinQt2/Config/Mapping/Hotkey3D.cpp
@@ -0,0 +1,42 @@
+// Copyright 2017 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include "DolphinQt2/Config/Mapping/Hotkey3D.h"
+
+#include <QGroupBox>
+#include <QHBoxLayout>
+
+#include "Core/HotkeyManager.h"
+
+Hotkey3D::Hotkey3D(MappingWindow* window) : MappingWidget(window)
+{
+  CreateMainLayout();
+}
+
+void Hotkey3D::CreateMainLayout()
+{
+  m_main_layout = new QHBoxLayout();
+
+  m_main_layout->addWidget(
+      CreateGroupBox(tr("3D"), HotkeyManagerEmu::GetHotkeyGroup(HKGP_3D_TOGGLE)));
+  m_main_layout->addWidget(
+      CreateGroupBox(tr("3D Depth"), HotkeyManagerEmu::GetHotkeyGroup(HKGP_3D_DEPTH)));
+
+  setLayout(m_main_layout);
+}
+
+InputConfig* Hotkey3D::GetConfig()
+{
+  return HotkeyManagerEmu::GetConfig();
+}
+
+void Hotkey3D::LoadSettings()
+{
+  HotkeyManagerEmu::LoadConfig();
+}
+
+void Hotkey3D::SaveSettings()
+{
+  HotkeyManagerEmu::GetConfig()->SaveConfig();
+}
diff --git a/Source/Core/DolphinQt2/Config/Mapping/Hotkey3D.h b/Source/Core/DolphinQt2/Config/Mapping/Hotkey3D.h
new file mode 100644
index 0000000000..b7e78822f2
--- /dev/null
+++ b/Source/Core/DolphinQt2/Config/Mapping/Hotkey3D.h
@@ -0,0 +1,25 @@
+// Copyright 2017 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "DolphinQt2/Config/Mapping/MappingWidget.h"
+
+class QHBoxLayout;
+
+class Hotkey3D final : public MappingWidget
+{
+public:
+  explicit Hotkey3D(MappingWindow* window);
+
+  InputConfig* GetConfig() override;
+
+private:
+  void LoadSettings() override;
+  void SaveSettings() override;
+  void CreateMainLayout();
+
+  // Main
+  QHBoxLayout* m_main_layout;
+};
diff --git a/Source/Core/DolphinQt2/Config/Mapping/HotkeyGeneral.cpp b/Source/Core/DolphinQt2/Config/Mapping/HotkeyGeneral.cpp
new file mode 100644
index 0000000000..fc111f745c
--- /dev/null
+++ b/Source/Core/DolphinQt2/Config/Mapping/HotkeyGeneral.cpp
@@ -0,0 +1,47 @@
+// Copyright 2017 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include "DolphinQt2/Config/Mapping/HotkeyGeneral.h"
+
+#include <QGroupBox>
+#include <QHBoxLayout>
+#include <QVBoxLayout>
+
+#include "Core/HotkeyManager.h"
+
+HotkeyGeneral::HotkeyGeneral(MappingWindow* window) : MappingWidget(window)
+{
+  CreateMainLayout();
+}
+
+void HotkeyGeneral::CreateMainLayout()
+{
+  m_main_layout = new QHBoxLayout();
+
+  m_main_layout->addWidget(
+      CreateGroupBox(tr("General"), HotkeyManagerEmu::GetHotkeyGroup(HKGP_GENERAL)));
+
+  auto* vbox = new QVBoxLayout();
+  vbox->addWidget(CreateGroupBox(tr("Volume"), HotkeyManagerEmu::GetHotkeyGroup(HKGP_VOLUME)));
+  vbox->addWidget(
+      CreateGroupBox(tr("Emulation Speed"), HotkeyManagerEmu::GetHotkeyGroup(HKGP_SPEED)));
+  m_main_layout->addItem(vbox);
+
+  setLayout(m_main_layout);
+}
+
+InputConfig* HotkeyGeneral::GetConfig()
+{
+  return HotkeyManagerEmu::GetConfig();
+}
+
+void HotkeyGeneral::LoadSettings()
+{
+  HotkeyManagerEmu::LoadConfig();
+}
+
+void HotkeyGeneral::SaveSettings()
+{
+  HotkeyManagerEmu::GetConfig()->SaveConfig();
+}
diff --git a/Source/Core/DolphinQt2/Config/Mapping/HotkeyGeneral.h b/Source/Core/DolphinQt2/Config/Mapping/HotkeyGeneral.h
new file mode 100644
index 0000000000..15c6dd04b3
--- /dev/null
+++ b/Source/Core/DolphinQt2/Config/Mapping/HotkeyGeneral.h
@@ -0,0 +1,25 @@
+// Copyright 2017 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "DolphinQt2/Config/Mapping/MappingWidget.h"
+
+class QHBoxLayout;
+
+class HotkeyGeneral final : public MappingWidget
+{
+public:
+  explicit HotkeyGeneral(MappingWindow* window);
+
+  InputConfig* GetConfig() override;
+
+private:
+  void LoadSettings() override;
+  void SaveSettings() override;
+  void CreateMainLayout();
+
+  // Main
+  QHBoxLayout* m_main_layout;
+};
diff --git a/Source/Core/DolphinQt2/Config/Mapping/HotkeyGraphics.cpp b/Source/Core/DolphinQt2/Config/Mapping/HotkeyGraphics.cpp
new file mode 100644
index 0000000000..fe4cc8cdba
--- /dev/null
+++ b/Source/Core/DolphinQt2/Config/Mapping/HotkeyGraphics.cpp
@@ -0,0 +1,48 @@
+// Copyright 2017 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include "DolphinQt2/Config/Mapping/HotkeyGraphics.h"
+
+#include <QGroupBox>
+#include <QHBoxLayout>
+#include <QVBoxLayout>
+
+#include "Core/HotkeyManager.h"
+
+HotkeyGraphics::HotkeyGraphics(MappingWindow* window) : MappingWidget(window)
+{
+  CreateMainLayout();
+}
+
+void HotkeyGraphics::CreateMainLayout()
+{
+  m_main_layout = new QHBoxLayout();
+
+  m_main_layout->addWidget(
+      CreateGroupBox(tr("Freelook"), HotkeyManagerEmu::GetHotkeyGroup(HKGP_FREELOOK)));
+
+  auto* vbox = new QVBoxLayout();
+  vbox->addWidget(CreateGroupBox(tr("Graphics Toggles"),
+                                 HotkeyManagerEmu::GetHotkeyGroup(HKGP_GRAPHICS_TOGGLES)));
+  vbox->addWidget(
+      CreateGroupBox(tr("Internal Resolution"), HotkeyManagerEmu::GetHotkeyGroup(HKGP_IR)));
+  m_main_layout->addItem(vbox);
+
+  setLayout(m_main_layout);
+}
+
+InputConfig* HotkeyGraphics::GetConfig()
+{
+  return HotkeyManagerEmu::GetConfig();
+}
+
+void HotkeyGraphics::LoadSettings()
+{
+  HotkeyManagerEmu::LoadConfig();
+}
+
+void HotkeyGraphics::SaveSettings()
+{
+  HotkeyManagerEmu::GetConfig()->SaveConfig();
+}
diff --git a/Source/Core/DolphinQt2/Config/Mapping/HotkeyGraphics.h b/Source/Core/DolphinQt2/Config/Mapping/HotkeyGraphics.h
new file mode 100644
index 0000000000..63837202b3
--- /dev/null
+++ b/Source/Core/DolphinQt2/Config/Mapping/HotkeyGraphics.h
@@ -0,0 +1,25 @@
+// Copyright 2017 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "DolphinQt2/Config/Mapping/MappingWidget.h"
+
+class QHBoxLayout;
+
+class HotkeyGraphics final : public MappingWidget
+{
+public:
+  explicit HotkeyGraphics(MappingWindow* window);
+
+  InputConfig* GetConfig() override;
+
+private:
+  void LoadSettings() override;
+  void SaveSettings() override;
+  void CreateMainLayout();
+
+  // Main
+  QHBoxLayout* m_main_layout;
+};
diff --git a/Source/Core/DolphinQt2/Config/Mapping/HotkeyStates.cpp b/Source/Core/DolphinQt2/Config/Mapping/HotkeyStates.cpp
new file mode 100644
index 0000000000..20d85cd544
--- /dev/null
+++ b/Source/Core/DolphinQt2/Config/Mapping/HotkeyStates.cpp
@@ -0,0 +1,42 @@
+// Copyright 2017 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include "DolphinQt2/Config/Mapping/HotkeyStates.h"
+
+#include <QGroupBox>
+#include <QHBoxLayout>
+
+#include "Core/HotkeyManager.h"
+
+HotkeyStates::HotkeyStates(MappingWindow* window) : MappingWidget(window)
+{
+  CreateMainLayout();
+}
+
+void HotkeyStates::CreateMainLayout()
+{
+  m_main_layout = new QHBoxLayout();
+
+  m_main_layout->addWidget(
+      CreateGroupBox(tr("Save"), HotkeyManagerEmu::GetHotkeyGroup(HKGP_SAVE_STATE)));
+  m_main_layout->addWidget(
+      CreateGroupBox(tr("Load"), HotkeyManagerEmu::GetHotkeyGroup(HKGP_LOAD_STATE)));
+
+  setLayout(m_main_layout);
+}
+
+InputConfig* HotkeyStates::GetConfig()
+{
+  return HotkeyManagerEmu::GetConfig();
+}
+
+void HotkeyStates::LoadSettings()
+{
+  HotkeyManagerEmu::LoadConfig();
+}
+
+void HotkeyStates::SaveSettings()
+{
+  HotkeyManagerEmu::GetConfig()->SaveConfig();
+}
diff --git a/Source/Core/DolphinQt2/Config/Mapping/HotkeyStates.h b/Source/Core/DolphinQt2/Config/Mapping/HotkeyStates.h
new file mode 100644
index 0000000000..bf36ec96f3
--- /dev/null
+++ b/Source/Core/DolphinQt2/Config/Mapping/HotkeyStates.h
@@ -0,0 +1,25 @@
+// Copyright 2017 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "DolphinQt2/Config/Mapping/MappingWidget.h"
+
+class QHBoxLayout;
+
+class HotkeyStates final : public MappingWidget
+{
+public:
+  explicit HotkeyStates(MappingWindow* window);
+
+  InputConfig* GetConfig() override;
+
+private:
+  void LoadSettings() override;
+  void SaveSettings() override;
+  void CreateMainLayout();
+
+  // Main
+  QHBoxLayout* m_main_layout;
+};
diff --git a/Source/Core/DolphinQt2/Config/Mapping/HotkeyTAS.cpp b/Source/Core/DolphinQt2/Config/Mapping/HotkeyTAS.cpp
new file mode 100644
index 0000000000..2586ff2d2a
--- /dev/null
+++ b/Source/Core/DolphinQt2/Config/Mapping/HotkeyTAS.cpp
@@ -0,0 +1,42 @@
+// Copyright 2017 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include "DolphinQt2/Config/Mapping/HotkeyTAS.h"
+
+#include <QGroupBox>
+#include <QHBoxLayout>
+
+#include "Core/HotkeyManager.h"
+
+HotkeyTAS::HotkeyTAS(MappingWindow* window) : MappingWidget(window)
+{
+  CreateMainLayout();
+}
+
+void HotkeyTAS::CreateMainLayout()
+{
+  m_main_layout = new QHBoxLayout();
+
+  m_main_layout->addWidget(
+      CreateGroupBox(tr("Frame Advance"), HotkeyManagerEmu::GetHotkeyGroup(HKGP_FRAME_ADVANCE)));
+  m_main_layout->addWidget(
+      CreateGroupBox(tr("Movie"), HotkeyManagerEmu::GetHotkeyGroup(HKGP_MOVIE)));
+
+  setLayout(m_main_layout);
+}
+
+InputConfig* HotkeyTAS::GetConfig()
+{
+  return HotkeyManagerEmu::GetConfig();
+}
+
+void HotkeyTAS::LoadSettings()
+{
+  HotkeyManagerEmu::LoadConfig();
+}
+
+void HotkeyTAS::SaveSettings()
+{
+  HotkeyManagerEmu::GetConfig()->SaveConfig();
+}
diff --git a/Source/Core/DolphinQt2/Config/Mapping/HotkeyTAS.h b/Source/Core/DolphinQt2/Config/Mapping/HotkeyTAS.h
new file mode 100644
index 0000000000..d6fabc1294
--- /dev/null
+++ b/Source/Core/DolphinQt2/Config/Mapping/HotkeyTAS.h
@@ -0,0 +1,25 @@
+// Copyright 2017 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "DolphinQt2/Config/Mapping/MappingWidget.h"
+
+class QHBoxLayout;
+
+class HotkeyTAS final : public MappingWidget
+{
+public:
+  explicit HotkeyTAS(MappingWindow* window);
+
+  InputConfig* GetConfig() override;
+
+private:
+  void LoadSettings() override;
+  void SaveSettings() override;
+  void CreateMainLayout();
+
+  // Main
+  QHBoxLayout* m_main_layout;
+};
diff --git a/Source/Core/DolphinQt2/Config/Mapping/HotkeyWii.cpp b/Source/Core/DolphinQt2/Config/Mapping/HotkeyWii.cpp
new file mode 100644
index 0000000000..7cfebf5705
--- /dev/null
+++ b/Source/Core/DolphinQt2/Config/Mapping/HotkeyWii.cpp
@@ -0,0 +1,39 @@
+// Copyright 2017 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include "DolphinQt2/Config/Mapping/HotkeyWii.h"
+
+#include <QGroupBox>
+#include <QHBoxLayout>
+
+#include "Core/HotkeyManager.h"
+
+HotkeyWii::HotkeyWii(MappingWindow* window) : MappingWidget(window)
+{
+  CreateMainLayout();
+}
+
+void HotkeyWii::CreateMainLayout()
+{
+  m_main_layout = new QHBoxLayout();
+
+  m_main_layout->addWidget(CreateGroupBox(tr("Wii"), HotkeyManagerEmu::GetHotkeyGroup(HKGP_WII)));
+
+  setLayout(m_main_layout);
+}
+
+InputConfig* HotkeyWii::GetConfig()
+{
+  return HotkeyManagerEmu::GetConfig();
+}
+
+void HotkeyWii::LoadSettings()
+{
+  HotkeyManagerEmu::LoadConfig();
+}
+
+void HotkeyWii::SaveSettings()
+{
+  HotkeyManagerEmu::GetConfig()->SaveConfig();
+}
diff --git a/Source/Core/DolphinQt2/Config/Mapping/HotkeyWii.h b/Source/Core/DolphinQt2/Config/Mapping/HotkeyWii.h
new file mode 100644
index 0000000000..56906e8770
--- /dev/null
+++ b/Source/Core/DolphinQt2/Config/Mapping/HotkeyWii.h
@@ -0,0 +1,25 @@
+// Copyright 2017 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "DolphinQt2/Config/Mapping/MappingWidget.h"
+
+class QHBoxLayout;
+
+class HotkeyWii final : public MappingWidget
+{
+public:
+  explicit HotkeyWii(MappingWindow* window);
+
+  InputConfig* GetConfig() override;
+
+private:
+  void LoadSettings() override;
+  void SaveSettings() override;
+  void CreateMainLayout();
+
+  // Main
+  QHBoxLayout* m_main_layout;
+};
diff --git a/Source/Core/DolphinQt2/Config/Mapping/MappingWindow.cpp b/Source/Core/DolphinQt2/Config/Mapping/MappingWindow.cpp
index 04e20485b2..33e30a9b93 100644
--- a/Source/Core/DolphinQt2/Config/Mapping/MappingWindow.cpp
+++ b/Source/Core/DolphinQt2/Config/Mapping/MappingWindow.cpp
@@ -20,6 +20,12 @@
 #include "DolphinQt2/Config/Mapping/GCKeyboardEmu.h"
 #include "DolphinQt2/Config/Mapping/GCPadEmu.h"
 #include "DolphinQt2/Config/Mapping/GCPadWiiU.h"
+#include "DolphinQt2/Config/Mapping/Hotkey3D.h"
+#include "DolphinQt2/Config/Mapping/HotkeyGeneral.h"
+#include "DolphinQt2/Config/Mapping/HotkeyGraphics.h"
+#include "DolphinQt2/Config/Mapping/HotkeyStates.h"
+#include "DolphinQt2/Config/Mapping/HotkeyTAS.h"
+#include "DolphinQt2/Config/Mapping/HotkeyWii.h"
 #include "DolphinQt2/Config/Mapping/WiimoteEmuExtension.h"
 #include "DolphinQt2/Config/Mapping/WiimoteEmuGeneral.h"
 #include "DolphinQt2/Config/Mapping/WiimoteEmuMotionControl.h"
@@ -285,6 +291,18 @@ void MappingWindow::ChangeMappingType(MappingWindow::Type type)
     AddWidget(tr("Extension"), extension);
     break;
   }
+  case Type::MAPPING_HOTKEYS:
+  {
+    widget = new HotkeyGeneral(this);
+    AddWidget(tr("General"), widget);
+    AddWidget(tr("TAS Tools"), new HotkeyTAS(this));
+    AddWidget(tr("Wii (Remote)"), new HotkeyWii(this));
+    AddWidget(tr("Graphics"), new HotkeyGraphics(this));
+    AddWidget(tr("3D"), new Hotkey3D(this));
+    AddWidget(tr("Save States"), new HotkeyStates(this));
+    setWindowTitle(tr("Hotkey Settings"));
+    break;
+  }
   default:
     return;
   }
diff --git a/Source/Core/DolphinQt2/Config/Mapping/MappingWindow.h b/Source/Core/DolphinQt2/Config/Mapping/MappingWindow.h
index c181f642b3..8e6fd82d7c 100644
--- a/Source/Core/DolphinQt2/Config/Mapping/MappingWindow.h
+++ b/Source/Core/DolphinQt2/Config/Mapping/MappingWindow.h
@@ -41,7 +41,9 @@ public:
     MAPPING_GC_STEERINGWHEEL,
     // Wii
     MAPPING_WIIMOTE_EMU,
-    MAPPING_WIIMOTE_HYBRID
+    MAPPING_WIIMOTE_HYBRID,
+    // Hotkeys
+    MAPPING_HOTKEYS
   };
 
   explicit MappingWindow(QWidget* parent, int port_num);
diff --git a/Source/Core/DolphinQt2/DolphinQt2.vcxproj b/Source/Core/DolphinQt2/DolphinQt2.vcxproj
index 3e5ba69f0e..f1bce1701c 100644
--- a/Source/Core/DolphinQt2/DolphinQt2.vcxproj
+++ b/Source/Core/DolphinQt2/DolphinQt2.vcxproj
@@ -83,11 +83,13 @@
     <QtMoc Include="GameList\GameTracker.h" />
     <QtMoc Include="GameList\ListProxyModel.h" />
     <QtMoc Include="Host.h" />
+    <QtMoc Include="HotkeyScheduler.h" />
     <QtMoc Include="InDevelopmentWarning.h" />
     <QtMoc Include="Settings\InterfacePane.h" />
     <QtMoc Include="MainWindow.h" />
     <QtMoc Include="MenuBar.h" />
     <QtMoc Include="QtUtils\DoubleClickEventFilter.h" />
+    <QtMoc Include="QtUtils\FocusEventFilter.h" />
     <QtMoc Include="RenderWidget.h" />
     <QtMoc Include="Settings.h" />
     <QtMoc Include="Settings\GeneralPane.h" />
@@ -98,12 +100,14 @@
     <ClCompile Include="$(QtMocOutPrefix)AboutDialog.cpp" />
     <ClCompile Include="$(QtMocOutPrefix)ControllersWindow.cpp" />
     <ClCompile Include="$(QtMocOutPrefix)FilesystemWidget.cpp" />
+    <ClCompile Include="$(QtMocOutPrefix)FocusEventFilter.cpp" />
     <ClCompile Include="$(QtMocOutPrefix)GameFile.cpp" />
     <ClCompile Include="$(QtMocOutPrefix)GameList.cpp" />
     <ClCompile Include="$(QtMocOutPrefix)GameListModel.cpp" />
     <ClCompile Include="$(QtMocOutPrefix)GameTracker.cpp" />
     <ClCompile Include="$(QtMocOutPrefix)GeneralPane.cpp" />
     <ClCompile Include="$(QtMocOutPrefix)Host.cpp" />
+    <ClCompile Include="$(QtMocOutPrefix)HotkeyScheduler.cpp" />
     <ClCompile Include="$(QtMocOutPrefix)InDevelopmentWarning.cpp" />
     <ClCompile Include="$(QtMocOutPrefix)InfoWidget.cpp" />
     <ClCompile Include="$(QtMocOutPrefix)InterfacePane.cpp" />
@@ -125,6 +129,12 @@
     <ClCompile Include="Config\Mapping\GCKeyboardEmu.cpp" />
     <ClCompile Include="Config\Mapping\GCPadEmu.cpp" />
     <ClCompile Include="Config\Mapping\GCPadWiiU.cpp" />
+    <ClCompile Include="Config\Mapping\Hotkey3D.cpp" />
+    <ClCompile Include="Config\Mapping\HotkeyGeneral.cpp" />
+    <ClCompile Include="Config\Mapping\HotkeyGraphics.cpp" />
+    <ClCompile Include="Config\Mapping\HotkeyStates.cpp" />
+    <ClCompile Include="Config\Mapping\HotkeyTAS.cpp" />
+    <ClCompile Include="Config\Mapping\HotkeyWii.cpp" />
     <ClCompile Include="Config\Mapping\MappingBool.cpp" />
     <ClCompile Include="Config\Mapping\MappingButton.cpp" />
     <ClCompile Include="Config\Mapping\MappingNumeric.cpp" />
@@ -140,6 +150,7 @@
     <ClCompile Include="GameList\GameListModel.cpp" />
     <ClCompile Include="GameList\GameTracker.cpp" />
     <ClCompile Include="GameList\ListProxyModel.cpp" />
+    <ClCompile Include="HotkeyScheduler.cpp" />
     <ClCompile Include="Host.cpp" />
     <ClCompile Include="InDevelopmentWarning.cpp" />
     <ClCompile Include="Main.cpp" />
@@ -147,6 +158,7 @@
     <ClCompile Include="MenuBar.cpp" />
     <ClCompile Include="QtUtils\DoubleClickEventFilter.cpp" />
     <ClCompile Include="QtUtils\ElidedButton.cpp" />
+    <ClCompile Include="QtUtils\FocusEventFilter.cpp" />
     <ClCompile Include="RenderWidget.cpp" />
     <ClCompile Include="Resources.cpp" />
     <ClCompile Include="Settings.cpp" />
diff --git a/Source/Core/DolphinQt2/HotkeyScheduler.cpp b/Source/Core/DolphinQt2/HotkeyScheduler.cpp
new file mode 100644
index 0000000000..3be862f8e1
--- /dev/null
+++ b/Source/Core/DolphinQt2/HotkeyScheduler.cpp
@@ -0,0 +1,363 @@
+// Copyright 2017 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include "DolphinQt2/HotkeyScheduler.h"
+
+#include <algorithm>
+#include <thread>
+
+#include <QCoreApplication>
+
+#include "AudioCommon/AudioCommon.h"
+#include "Common/Thread.h"
+#include "Core/Core.h"
+#include "Core/HotkeyManager.h"
+#include "Core/IOS/IOS.h"
+#include "Core/IOS/USB/Bluetooth/BTBase.h"
+#include "Core/State.h"
+#include "DolphinQt2/MainWindow.h"
+#include "DolphinQt2/Settings.h"
+#include "InputCommon/ControllerInterface/ControllerInterface.h"
+#include "VideoCommon/VertexShaderManager.h"
+#include "VideoCommon/VideoConfig.h"
+
+constexpr const char* DUBOIS_ALGORITHM_SHADER = "dubois";
+
+HotkeyScheduler::HotkeyScheduler() : m_stop_requested(false)
+{
+  HotkeyManagerEmu::Initialize();
+  HotkeyManagerEmu::LoadConfig();
+  HotkeyManagerEmu::Enable(true);
+}
+
+HotkeyScheduler::~HotkeyScheduler()
+{
+  Stop();
+}
+
+void HotkeyScheduler::Start()
+{
+  m_stop_requested.Set(false);
+  m_thread = std::thread(&HotkeyScheduler::Run, this);
+}
+
+void HotkeyScheduler::Stop()
+{
+  m_stop_requested.Set(true);
+
+  if (m_thread.joinable())
+    m_thread.join();
+}
+
+static bool IsHotkey(int id, bool held = false)
+{
+  return HotkeyManagerEmu::IsPressed(id, held);
+}
+
+static void HandleFrameskipHotkeys()
+{
+  constexpr int MAX_FRAME_SKIP_DELAY = 60;
+  constexpr int FRAME_STEP_DELAY = 30;
+
+  static int frame_step_count = 0;
+  static int frame_step_delay = 1;
+  static int frame_step_delay_count = 0;
+  static bool frame_step_hold = false;
+
+  if (IsHotkey(HK_FRAME_ADVANCE_INCREASE_SPEED))
+  {
+    frame_step_delay = std::min(frame_step_delay + 1, MAX_FRAME_SKIP_DELAY);
+    return;
+  }
+
+  if (IsHotkey(HK_FRAME_ADVANCE_DECREASE_SPEED))
+  {
+    frame_step_delay = std::max(frame_step_delay - 1, 0);
+    return;
+  }
+
+  if (IsHotkey(HK_FRAME_ADVANCE_RESET_SPEED))
+  {
+    frame_step_delay = 1;
+    return;
+  }
+
+  if (IsHotkey(HK_FRAME_ADVANCE, true))
+  {
+    if (frame_step_delay_count < frame_step_delay && frame_step_hold)
+      frame_step_delay_count++;
+
+    // TODO GUI Update (Depends on an unimplemented feature)
+    // if ((frame_step_count == 0 || frame_step_count == FRAME_STEP_DELAY) && !frame_step_hold)
+
+    if (frame_step_count < FRAME_STEP_DELAY)
+    {
+      ++frame_step_count;
+      if (frame_step_hold)
+        frame_step_hold = false;
+    }
+
+    if (frame_step_count == FRAME_STEP_DELAY && frame_step_hold &&
+        frame_step_delay_count >= frame_step_delay)
+    {
+      frame_step_hold = false;
+      frame_step_delay_count = 0;
+    }
+
+    return;
+  }
+
+  if (frame_step_count > 0)
+  {
+    // Reset frame advance
+    frame_step_count = 0;
+    frame_step_hold = false;
+    frame_step_delay_count = 0;
+  }
+}
+
+void HotkeyScheduler::Run()
+{
+  while (!m_stop_requested.IsSet())
+  {
+    Common::SleepCurrentThread(1000 / 60);
+
+    if (!HotkeyManagerEmu::IsEnabled())
+      continue;
+
+    if (Core::GetState() == Core::State::Uninitialized || Core::GetState() == Core::State::Paused)
+      g_controller_interface.UpdateInput();
+
+    if (Core::GetState() != Core::State::Stopping)
+    {
+      HotkeyManagerEmu::GetStatus();
+
+      if (!Core::IsRunningAndStarted())
+        continue;
+
+      // Fullscreen
+      if (IsHotkey(HK_FULLSCREEN))
+        emit FullScreenHotkey();
+
+      // Pause and Unpause
+      if (IsHotkey(HK_PLAY_PAUSE))
+        emit PauseHotkey();
+
+      // Stop
+      if (IsHotkey(HK_STOP))
+        emit StopHotkey();
+
+      // Frameskipping
+      HandleFrameskipHotkeys();
+
+      // Screenshot
+      if (IsHotkey(HK_SCREENSHOT))
+        emit ScreenShotHotkey();
+
+      // Exit
+      if (IsHotkey(HK_EXIT))
+        emit ExitHotkey();
+
+      // Volume
+      if (IsHotkey(HK_VOLUME_DOWN))
+        AudioCommon::DecreaseVolume(3);
+
+      if (IsHotkey(HK_VOLUME_UP))
+        AudioCommon::IncreaseVolume(3);
+
+      if (IsHotkey(HK_VOLUME_TOGGLE_MUTE))
+        AudioCommon::ToggleMuteVolume();
+
+      auto& settings = Settings::Instance();
+
+      // Wiimote
+      if (settings.IsBluetoothPassthroughEnabled())
+      {
+        const auto ios = IOS::HLE::GetIOS();
+        auto device = ios ? ios->GetDeviceByName("/dev/usb/oh1/57e/305") : nullptr;
+
+        if (device != nullptr)
+          std::static_pointer_cast<IOS::HLE::Device::BluetoothBase>(device)->UpdateSyncButtonState(
+              IsHotkey(HK_TRIGGER_SYNC_BUTTON, true));
+      }
+
+      // TODO Debugging shortcuts (Separate PR)
+
+      if (settings.IsWiiGameRunning())
+      {
+        int wiimote_id = -1;
+        if (IsHotkey(HK_WIIMOTE1_CONNECT))
+          wiimote_id = 0;
+        if (IsHotkey(HK_WIIMOTE2_CONNECT))
+          wiimote_id = 1;
+        if (IsHotkey(HK_WIIMOTE3_CONNECT))
+          wiimote_id = 2;
+        if (IsHotkey(HK_WIIMOTE4_CONNECT))
+          wiimote_id = 3;
+        if (IsHotkey(HK_BALANCEBOARD_CONNECT))
+          wiimote_id = 4;
+
+        // TODO Implement Wiimote connecting / disconnecting (Separate PR)
+        // if (wiimote_id > -1)
+      }
+
+      // Graphics
+      if (IsHotkey(HK_INCREASE_IR))
+        ++g_Config.iEFBScale;
+      if (IsHotkey(HK_DECREASE_IR))
+        g_Config.iEFBScale = std::max(g_Config.iEFBScale - 1, static_cast<int>(SCALE_AUTO));
+      if (IsHotkey(HK_TOGGLE_CROP))
+        g_Config.bCrop = !g_Config.bCrop;
+      if (IsHotkey(HK_TOGGLE_AR))
+        g_Config.iAspectRatio = (g_Config.iAspectRatio + 1) & 3;
+      if (IsHotkey(HK_TOGGLE_EFBCOPIES))
+        g_Config.bSkipEFBCopyToRam = !g_Config.bSkipEFBCopyToRam;
+      if (IsHotkey(HK_TOGGLE_FOG))
+        g_Config.bDisableFog = !g_Config.bDisableFog;
+      if (IsHotkey(HK_TOGGLE_DUMPTEXTURES))
+        g_Config.bDumpTextures = !g_Config.bDumpTextures;
+      if (IsHotkey(HK_TOGGLE_TEXTURES))
+        g_Config.bHiresTextures = !g_Config.bHiresTextures;
+
+      Core::SetIsThrottlerTempDisabled(IsHotkey(HK_TOGGLE_THROTTLE, true));
+
+      if (IsHotkey(HK_DECREASE_EMULATION_SPEED))
+      {
+        auto speed = settings.GetEmulationSpeed() - 0.1;
+        speed = (speed <= 0 || (speed >= 0.95 && speed <= 1.05)) ? 1.0 : speed;
+        settings.SetEmulationSpeed(speed);
+      }
+
+      if (IsHotkey(HK_INCREASE_EMULATION_SPEED))
+      {
+        auto speed = settings.GetEmulationSpeed() + 0.1;
+        speed = (speed >= 0.95 && speed <= 1.05) ? 1.0 : speed;
+        settings.SetEmulationSpeed(speed);
+      }
+
+      // Slot Saving / Loading
+      if (IsHotkey(HK_SAVE_STATE_SLOT_SELECTED))
+        emit StateSaveSlotHotkey();
+
+      if (IsHotkey(HK_LOAD_STATE_SLOT_SELECTED))
+        emit StateLoadSlotHotkey();
+
+      // Stereoscopy
+      if (IsHotkey(HK_TOGGLE_STEREO_SBS) || IsHotkey(HK_TOGGLE_STEREO_TAB))
+      {
+        if (g_Config.iStereoMode != STEREO_SBS)
+        {
+          // Disable post-processing shader, as stereoscopy itself is currently a shader
+          if (g_Config.sPostProcessingShader == DUBOIS_ALGORITHM_SHADER)
+            g_Config.sPostProcessingShader = "";
+
+          g_Config.iStereoMode = IsHotkey(HK_TOGGLE_STEREO_SBS) ? STEREO_SBS : STEREO_TAB;
+        }
+        else
+        {
+          g_Config.iStereoMode = STEREO_OFF;
+        }
+      }
+
+      if (IsHotkey(HK_TOGGLE_STEREO_ANAGLYPH))
+      {
+        if (g_Config.iStereoMode != STEREO_ANAGLYPH)
+        {
+          g_Config.iStereoMode = STEREO_ANAGLYPH;
+          g_Config.sPostProcessingShader = DUBOIS_ALGORITHM_SHADER;
+        }
+        else
+        {
+          g_Config.iStereoMode = STEREO_OFF;
+          g_Config.sPostProcessingShader = "";
+        }
+      }
+
+      if (IsHotkey(HK_TOGGLE_STEREO_3DVISION))
+      {
+        if (g_Config.iStereoMode != STEREO_3DVISION)
+        {
+          if (g_Config.sPostProcessingShader == DUBOIS_ALGORITHM_SHADER)
+            g_Config.sPostProcessingShader = "";
+
+          g_Config.iStereoMode = STEREO_3DVISION;
+        }
+        else
+        {
+          g_Config.iStereoMode = STEREO_OFF;
+        }
+      }
+    }
+
+    if (IsHotkey(HK_DECREASE_DEPTH, true))
+      g_Config.iStereoDepth = std::max(g_Config.iStereoDepth - 1, 0);
+
+    if (IsHotkey(HK_INCREASE_DEPTH, true))
+      g_Config.iStereoDepth = std::min(g_Config.iStereoDepth + 1, 100);
+
+    if (IsHotkey(HK_DECREASE_CONVERGENCE, true))
+      g_Config.iStereoConvergence = std::max(g_Config.iStereoConvergence - 5, 0);
+
+    if (IsHotkey(HK_INCREASE_CONVERGENCE, true))
+      g_Config.iStereoConvergence = std::min(g_Config.iStereoConvergence + 5, 500);
+
+    // Freelook
+    static float fl_speed = 1.0;
+
+    if (IsHotkey(HK_FREELOOK_DECREASE_SPEED, true))
+      fl_speed /= 1.1f;
+
+    if (IsHotkey(HK_FREELOOK_INCREASE_SPEED, true))
+      fl_speed *= 1.1f;
+
+    if (IsHotkey(HK_FREELOOK_RESET_SPEED, true))
+      fl_speed = 1.0;
+
+    if (IsHotkey(HK_FREELOOK_UP, true))
+      VertexShaderManager::TranslateView(0.0, 0.0, -fl_speed);
+
+    if (IsHotkey(HK_FREELOOK_DOWN, true))
+      VertexShaderManager::TranslateView(0.0, 0.0, fl_speed);
+
+    if (IsHotkey(HK_FREELOOK_LEFT, true))
+      VertexShaderManager::TranslateView(fl_speed, 0.0);
+
+    if (IsHotkey(HK_FREELOOK_RIGHT, true))
+      VertexShaderManager::TranslateView(-fl_speed, 0.0);
+
+    if (IsHotkey(HK_FREELOOK_ZOOM_IN, true))
+      VertexShaderManager::TranslateView(0.0, fl_speed);
+
+    if (IsHotkey(HK_FREELOOK_ZOOM_OUT, true))
+      VertexShaderManager::TranslateView(0.0, -fl_speed);
+
+    if (IsHotkey(HK_FREELOOK_RESET, true))
+      VertexShaderManager::ResetView();
+
+    // Savestates
+    for (u32 i = 0; i < State::NUM_STATES; i++)
+    {
+      if (IsHotkey(HK_LOAD_STATE_SLOT_1 + i))
+        State::Load(i + 1);
+
+      if (IsHotkey(HK_SAVE_STATE_SLOT_1 + i))
+        State::Save(i + 1);
+
+      if (IsHotkey(HK_LOAD_LAST_STATE_1 + i))
+        State::LoadLastSaved(i + 1);
+
+      if (IsHotkey(HK_SELECT_STATE_SLOT_1 + i))
+        emit SetStateSlotHotkey(i + 1);
+    }
+
+    if (IsHotkey(HK_SAVE_FIRST_STATE))
+      State::SaveFirstSaved();
+
+    if (IsHotkey(HK_UNDO_LOAD_STATE))
+      State::UndoLoadState();
+
+    if (IsHotkey(HK_UNDO_SAVE_STATE))
+      State::UndoSaveState();
+  }
+}
diff --git a/Source/Core/DolphinQt2/HotkeyScheduler.h b/Source/Core/DolphinQt2/HotkeyScheduler.h
new file mode 100644
index 0000000000..2deebbce16
--- /dev/null
+++ b/Source/Core/DolphinQt2/HotkeyScheduler.h
@@ -0,0 +1,37 @@
+// Copyright 2017 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <thread>
+
+#include <QObject>
+
+#include "Common/Flag.h"
+
+class HotkeyScheduler : public QObject
+{
+  Q_OBJECT
+public:
+  explicit HotkeyScheduler();
+  ~HotkeyScheduler();
+
+  void Start();
+  void Stop();
+signals:
+  void ExitHotkey();
+  void FullScreenHotkey();
+  void StopHotkey();
+  void PauseHotkey();
+  void ScreenShotHotkey();
+  void SetStateSlotHotkey(int slot);
+  void StateLoadSlotHotkey();
+  void StateSaveSlotHotkey();
+
+private:
+  void Run();
+
+  Common::Flag m_stop_requested;
+  std::thread m_thread;
+};
diff --git a/Source/Core/DolphinQt2/MainWindow.cpp b/Source/Core/DolphinQt2/MainWindow.cpp
index a8049826f2..a487e37b2a 100644
--- a/Source/Core/DolphinQt2/MainWindow.cpp
+++ b/Source/Core/DolphinQt2/MainWindow.cpp
@@ -24,9 +24,13 @@
 
 #include "DolphinQt2/AboutDialog.h"
 #include "DolphinQt2/Config/ControllersWindow.h"
+
+#include "DolphinQt2/Config/Mapping/MappingWindow.h"
 #include "DolphinQt2/Config/SettingsWindow.h"
 #include "DolphinQt2/Host.h"
+#include "DolphinQt2/HotkeyScheduler.h"
 #include "DolphinQt2/MainWindow.h"
+#include "DolphinQt2/QtUtils/FocusEventFilter.h"
 #include "DolphinQt2/Resources.h"
 #include "DolphinQt2/Settings.h"
 
@@ -64,16 +68,32 @@ void MainWindow::InitControllers()
   Pad::Initialize();
   Keyboard::Initialize();
   Wiimote::Initialize(Wiimote::InitializeMode::DO_NOT_WAIT_FOR_WIIMOTES);
-  HotkeyManagerEmu::Initialize();
+  m_hotkey_scheduler = new HotkeyScheduler();
+  m_hotkey_scheduler->Start();
+
+  ConnectHotkeys();
 }
 
 void MainWindow::ShutdownControllers()
 {
+  m_hotkey_scheduler->Stop();
+
   g_controller_interface.Shutdown();
   Pad::Shutdown();
   Keyboard::Shutdown();
   Wiimote::Shutdown();
   HotkeyManagerEmu::Shutdown();
+
+  m_hotkey_scheduler->deleteLater();
+}
+
+static void InstallHotkeyFilter(QDialog* dialog)
+{
+  auto* filter = new FocusEventFilter();
+  dialog->installEventFilter(filter);
+
+  filter->connect(filter, &FocusEventFilter::focusOutEvent, [] { HotkeyManagerEmu::Enable(true); });
+  filter->connect(filter, &FocusEventFilter::focusInEvent, [] { HotkeyManagerEmu::Enable(false); });
 }
 
 void MainWindow::CreateComponents()
@@ -85,6 +105,11 @@ void MainWindow::CreateComponents()
   m_stack = new QStackedWidget(this);
   m_controllers_window = new ControllersWindow(this);
   m_settings_window = new SettingsWindow(this);
+  m_hotkey_window = new MappingWindow(this, 0);
+
+  InstallHotkeyFilter(m_hotkey_window);
+  InstallHotkeyFilter(m_controllers_window);
+  InstallHotkeyFilter(m_settings_window);
 }
 
 void MainWindow::ConnectMenuBar()
@@ -113,6 +138,9 @@ void MainWindow::ConnectMenuBar()
   connect(m_menu_bar, &MenuBar::StateSaveOldest, this, &MainWindow::StateSaveOldest);
   connect(m_menu_bar, &MenuBar::SetStateSlot, this, &MainWindow::SetStateSlot);
 
+  // Options
+  connect(m_menu_bar, &MenuBar::ConfigureHotkeys, this, &MainWindow::ShowHotkeyDialog);
+
   // View
   connect(m_menu_bar, &MenuBar::ShowTable, m_game_list, &GameList::SetTableView);
   connect(m_menu_bar, &MenuBar::ShowList, m_game_list, &GameList::SetListView);
@@ -130,6 +158,22 @@ void MainWindow::ConnectMenuBar()
           [=]() { m_controllers_window->OnEmulationStateChanged(false); });
 }
 
+void MainWindow::ConnectHotkeys()
+{
+  connect(m_hotkey_scheduler, &HotkeyScheduler::ExitHotkey, this, &MainWindow::close);
+  connect(m_hotkey_scheduler, &HotkeyScheduler::PauseHotkey, this, &MainWindow::Pause);
+  connect(m_hotkey_scheduler, &HotkeyScheduler::StopHotkey, this, &MainWindow::Stop);
+  connect(m_hotkey_scheduler, &HotkeyScheduler::ScreenShotHotkey, this, &MainWindow::ScreenShot);
+  connect(m_hotkey_scheduler, &HotkeyScheduler::FullScreenHotkey, this, &MainWindow::FullScreen);
+
+  connect(m_hotkey_scheduler, &HotkeyScheduler::StateLoadSlotHotkey, this,
+          &MainWindow::StateLoadSlot);
+  connect(m_hotkey_scheduler, &HotkeyScheduler::StateSaveSlotHotkey, this,
+          &MainWindow::StateSaveSlot);
+  connect(m_hotkey_scheduler, &HotkeyScheduler::SetStateSlotHotkey, this,
+          &MainWindow::SetStateSlot);
+}
+
 void MainWindow::ConnectToolBar()
 {
   addToolBar(m_tool_bar);
@@ -364,6 +408,14 @@ void MainWindow::ShowAboutDialog()
   about->show();
 }
 
+void MainWindow::ShowHotkeyDialog()
+{
+  m_hotkey_window->ChangeMappingType(MappingWindow::Type::MAPPING_HOTKEYS);
+  m_hotkey_window->show();
+  m_hotkey_window->raise();
+  m_hotkey_window->activateWindow();
+}
+
 void MainWindow::StateLoad()
 {
   QString path = QFileDialog::getOpenFileName(this, tr("Select a File"), QDir::currentPath(),
diff --git a/Source/Core/DolphinQt2/MainWindow.h b/Source/Core/DolphinQt2/MainWindow.h
index 5e089c2572..2b9b2c4e90 100644
--- a/Source/Core/DolphinQt2/MainWindow.h
+++ b/Source/Core/DolphinQt2/MainWindow.h
@@ -14,6 +14,8 @@
 #include "DolphinQt2/RenderWidget.h"
 #include "DolphinQt2/ToolBar.h"
 
+class HotkeyScheduler;
+class MappingWindow;
 class SettingsWindow;
 class ControllersWindow;
 
@@ -58,6 +60,7 @@ private:
   void CreateComponents();
 
   void ConnectGameList();
+  void ConnectHotkeys();
   void ConnectMenuBar();
   void ConnectRenderWidget();
   void ConnectStack();
@@ -73,6 +76,7 @@ private:
   void ShowSettingsWindow();
   void ShowControllersWindow();
   void ShowAboutDialog();
+  void ShowHotkeyDialog();
 
   QStackedWidget* m_stack;
   ToolBar* m_tool_bar;
@@ -82,6 +86,8 @@ private:
   bool m_rendering_to_main;
   int m_state_slot = 1;
 
+  HotkeyScheduler* m_hotkey_scheduler;
   ControllersWindow* m_controllers_window;
   SettingsWindow* m_settings_window;
+  MappingWindow* m_hotkey_window;
 };
diff --git a/Source/Core/DolphinQt2/MenuBar.cpp b/Source/Core/DolphinQt2/MenuBar.cpp
index ef9a449621..011649e0e1 100644
--- a/Source/Core/DolphinQt2/MenuBar.cpp
+++ b/Source/Core/DolphinQt2/MenuBar.cpp
@@ -20,7 +20,7 @@ MenuBar::MenuBar(QWidget* parent) : QMenuBar(parent)
   AddFileMenu();
   AddEmulationMenu();
   addMenu(tr("Movie"));
-  addMenu(tr("Options"));
+  AddOptionsMenu();
   AddToolsMenu();
   AddViewMenu();
   AddHelpMenu();
@@ -171,6 +171,12 @@ void MenuBar::AddViewMenu()
   AddTableColumnsMenu(view_menu);
 }
 
+void MenuBar::AddOptionsMenu()
+{
+  QMenu* options_menu = addMenu(tr("Options"));
+  options_menu->addAction(tr("Hotkey Settings"), this, &MenuBar::ConfigureHotkeys);
+}
+
 void MenuBar::AddHelpMenu()
 {
   QMenu* help_menu = addMenu(tr("Help"));
diff --git a/Source/Core/DolphinQt2/MenuBar.h b/Source/Core/DolphinQt2/MenuBar.h
index fa8c67cc38..a0346f4277 100644
--- a/Source/Core/DolphinQt2/MenuBar.h
+++ b/Source/Core/DolphinQt2/MenuBar.h
@@ -38,6 +38,9 @@ signals:
   void StateSaveOldest();
   void SetStateSlot(int slot);
 
+  // Options
+  void ConfigureHotkeys();
+
   // View
   void ShowTable();
   void ShowList();
@@ -66,6 +69,7 @@ private:
   void AddGameListTypeSection(QMenu* view_menu);
   void AddTableColumnsMenu(QMenu* view_menu);
 
+  void AddOptionsMenu();
   void AddToolsMenu();
   void AddHelpMenu();
 
diff --git a/Source/Core/DolphinQt2/QtUtils/FocusEventFilter.cpp b/Source/Core/DolphinQt2/QtUtils/FocusEventFilter.cpp
new file mode 100644
index 0000000000..c6023f4ff7
--- /dev/null
+++ b/Source/Core/DolphinQt2/QtUtils/FocusEventFilter.cpp
@@ -0,0 +1,19 @@
+// Copyright 2017 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include <QEvent>
+#include <QObject>
+
+#include "DolphinQt2/QtUtils/FocusEventFilter.h"
+
+bool FocusEventFilter::eventFilter(QObject* object, QEvent* event)
+{
+  if (event->type() == QEvent::FocusOut)
+    emit focusOutEvent();
+
+  if (event->type() == QEvent::FocusIn)
+    emit focusInEvent();
+
+  return false;
+}
diff --git a/Source/Core/DolphinQt2/QtUtils/FocusEventFilter.h b/Source/Core/DolphinQt2/QtUtils/FocusEventFilter.h
new file mode 100644
index 0000000000..0783622b0f
--- /dev/null
+++ b/Source/Core/DolphinQt2/QtUtils/FocusEventFilter.h
@@ -0,0 +1,18 @@
+// Copyright 2017 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <QObject>
+
+class FocusEventFilter : public QObject
+{
+  Q_OBJECT
+signals:
+  void focusInEvent();
+  void focusOutEvent();
+
+private:
+  bool eventFilter(QObject* object, QEvent* event) override;
+};