diff --git a/CHANGELOG.md b/CHANGELOG.md
index 89cde4526b..c02676c7ba 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Added daily Mac builds
 - Allow spawning individual pet species, not just generic body kinds.
 - Configurable fonts
+- Configurable keybindings from the Controls menu
 - Tanslation status tracking
 - Added gamma setting
 - Added new orc hairstyles
diff --git a/assets/voxygen/i18n/en.ron b/assets/voxygen/i18n/en.ron
index d1ca9fb2cc..368f02145b 100644
--- a/assets/voxygen/i18n/en.ron
+++ b/assets/voxygen/i18n/en.ron
@@ -250,90 +250,7 @@ Enjoy your stay in the World of Veloren."#,
         "hud.settings.sound_effect_volume": "Sound Effects Volume",
         "hud.settings.audio_device": "Audio Device",
 
-        // Control list
-        "hud.settings.control_names": r#"Free Cursor
-Toggle Help Window
-Toggle Interface
-Toggle FPS and Debug Info
-Take Screenshot
-Toggle Nametags
-Toggle Fullscreen
-
-
-Move Forward
-Move Left
-Move Right
-Move Backwards
-
-Jump
-
-Glider
-
-Dodge
-
-Roll
-
-Climb
-
-Climb down
-
-Auto Walk
-
-Sheathe/Draw Weapons
-
-Put on/Remove Helmet
-
-Sit
-
-Mount
-
-Interact
-
-
-Basic Attack
-Secondary Attack/Block/Aim
-
-
-Skillbar Slot 1
-Skillbar Slot 2
-Skillbar Slot 3
-Skillbar Slot 4
-Skillbar Slot 5
-Skillbar Slot 6
-Skillbar Slot 7
-Skillbar Slot 8
-Skillbar Slot 9
-Skillbar Slot 10
-
-
-Pause Menu
-Settings
-Social
-Map
-Spellbook
-Character
-Questlog
-Bag
-
-
-
-Send Chat Message
-Scroll Chat
-
-
-Free look
-
-
-Chat commands:  
-
-/alias [Name] - Change your Chat Name   
-/tp [Name] - Teleports you to another player    
-/jump <dx> <dy> <dz> - Offset your position
-/goto <x> <y> <z> - Teleport to a position  
-/kill - Kill yourself   
-/pig - Spawn pig NPC    
-/wolf - Spawn wolf NPC  
-/help - Display chat commands"#,
+        "hud.settings.awaitingkey": "Press a key...",
 
         "hud.social": "Social",
         "hud.social.online": "Online",
@@ -348,6 +265,48 @@ Chat commands:
         /// End HUD section
 
 
+        /// Start GameInput section
+
+        "gameinput.primary": "Basic Attack",
+        "gameinput.secondary": "Secondary Attack/Block/Aim",
+        "gameinput.ability3": "Hotbar Slot 1",
+        "gameinput.swaploadout": "Swap Loadout",
+        "gameinput.togglecursor": "Free Cursor",
+        "gameinput.help": "Toggle Help Window",
+        "gameinput.toggleinterface": "Toggle Interface",
+        "gameinput.toggledebug": "Toggle FPS and Debug Info",
+        "gameinput.screenshot": "Take Screenshot",
+        "gameinput.toggleingameui": "Toggle Nametags",
+        "gameinput.fullscreen": "Toggle Fullscreen",
+        "gameinput.moveforward": "Move Forward",
+        "gameinput.moveleft": "Move Left",
+        "gameinput.moveright": "Move Right",
+        "gameinput.moveback": "Move Backwards",
+        "gameinput.jump": "Jump",
+        "gameinput.glide": "Glider",
+        "gameinput.roll": "Roll",
+        "gameinput.climb": "Climb",
+        "gameinput.climbdown": "Climb Down",
+        "gameinput.wallleap": "Wall Leap",
+        "gameinput.mount": "Mount",
+        "gameinput.enter": "Enter",
+        "gameinput.command": "Command",
+        "gameinput.escape": "Escape",
+        "gameinput.map": "Map",
+        "gameinput.bag": "Bag",
+        "gameinput.social": "Social",
+        "gameinput.sit": "Sit",
+        "gameinput.spellbook": "Spell Book",
+        "gameinput.settings": "Settings",
+        "gameinput.respawn": "Respawn",
+        "gameinput.charge": "Charge",
+        "gameinput.togglewield": "Toggle Wield",
+        "gameinput.interact": "Interact",
+        "gameinput.freelook": "Free look behavior",
+
+        /// End GameInput section
+
+
         /// Start chracter selection section
         "char_selection.delete_permanently": "Permanently delete this Character?",
         "char_selection.change_server": "Change Server",
diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs
index fe58075ddb..ebe1d3f163 100644
--- a/voxygen/src/hud/mod.rs
+++ b/voxygen/src/hud/mod.rs
@@ -237,6 +237,7 @@ pub enum Event {
     Logout,
     Quit,
     ChangeLanguage(LanguageMetadata),
+    ChangeBinding(GameInput),
     ChangeFreeLookBehavior(PressBehavior),
 }
 
@@ -1494,66 +1495,70 @@ impl Hud {
             .set(self.ids.num_figures, ui_widgets);
 
             // Help Window
-            Text::new(
-                &self
-                    .voxygen_i18n
-                    .get("hud.press_key_to_toggle_keybindings_fmt")
-                    .replace(
-                        "{key}",
-                        &format!("{:?}", global_state.settings.controls.help),
-                    ),
-            )
-            .color(TEXT_COLOR)
-            .down_from(self.ids.num_figures, 5.0)
-            .font_id(self.fonts.cyri.conrod_id)
-            .font_size(self.fonts.cyri.scale(14))
-            .set(self.ids.help_info, ui_widgets);
+            if let Some(help_key) = global_state.settings.controls.get_binding(GameInput::Help) {
+                Text::new(
+                    &self
+                        .voxygen_i18n
+                        .get("hud.press_key_to_toggle_keybindings_fmt")
+                        .replace("{key}", help_key.to_string().as_str()),
+                )
+                .color(TEXT_COLOR)
+                .down_from(self.ids.num_figures, 5.0)
+                .font_id(self.fonts.cyri.conrod_id)
+                .font_size(self.fonts.cyri.scale(14))
+                .set(self.ids.help_info, ui_widgets);
+            }
             // Info about Debug Shortcut
-            Text::new(
-                &self
-                    .voxygen_i18n
-                    .get("hud.press_key_to_toggle_debug_info_fmt")
-                    .replace(
-                        "{key}",
-                        &format!("{:?}", global_state.settings.controls.toggle_debug),
-                    ),
-            )
-            .color(TEXT_COLOR)
-            .down_from(self.ids.help_info, 5.0)
-            .font_id(self.fonts.cyri.conrod_id)
-            .font_size(self.fonts.cyri.scale(14))
-            .set(self.ids.debug_info, ui_widgets);
+            if let Some(toggle_debug_key) = global_state
+                .settings
+                .controls
+                .get_binding(GameInput::ToggleDebug)
+            {
+                Text::new(
+                    &self
+                        .voxygen_i18n
+                        .get("hud.press_key_to_toggle_debug_info_fmt")
+                        .replace("{key}", toggle_debug_key.to_string().as_str()),
+                )
+                .color(TEXT_COLOR)
+                .down_from(self.ids.help_info, 5.0)
+                .font_id(self.fonts.cyri.conrod_id)
+                .font_size(self.fonts.cyri.scale(14))
+                .set(self.ids.debug_info, ui_widgets);
+            }
         } else {
             // Help Window
-            Text::new(
-                &self
-                    .voxygen_i18n
-                    .get("hud.press_key_to_show_keybindings_fmt")
-                    .replace(
-                        "{key}",
-                        &format!("{:?}", global_state.settings.controls.help),
-                    ),
-            )
-            .color(TEXT_COLOR)
-            .top_left_with_margins_on(ui_widgets.window, 5.0, 5.0)
-            .font_id(self.fonts.cyri.conrod_id)
-            .font_size(self.fonts.cyri.scale(16))
-            .set(self.ids.help_info, ui_widgets);
+            if let Some(help_key) = global_state.settings.controls.get_binding(GameInput::Help) {
+                Text::new(
+                    &self
+                        .voxygen_i18n
+                        .get("hud.press_key_to_show_keybindings_fmt")
+                        .replace("{key}", help_key.to_string().as_str()),
+                )
+                .color(TEXT_COLOR)
+                .top_left_with_margins_on(ui_widgets.window, 5.0, 5.0)
+                .font_id(self.fonts.cyri.conrod_id)
+                .font_size(self.fonts.cyri.scale(16))
+                .set(self.ids.help_info, ui_widgets);
+            }
             // Info about Debug Shortcut
-            Text::new(
-                &self
-                    .voxygen_i18n
-                    .get("hud.press_key_to_show_debug_info_fmt")
-                    .replace(
-                        "{key}",
-                        &format!("{:?}", global_state.settings.controls.toggle_debug),
-                    ),
-            )
-            .color(TEXT_COLOR)
-            .down_from(self.ids.help_info, 5.0)
-            .font_id(self.fonts.cyri.conrod_id)
-            .font_size(self.fonts.cyri.scale(12))
-            .set(self.ids.debug_info, ui_widgets);
+            if let Some(toggle_debug_key) = global_state
+                .settings
+                .controls
+                .get_binding(GameInput::ToggleDebug)
+            {
+                Text::new(
+                    &self
+                        .voxygen_i18n
+                        .get("hud.press_key_to_show_debug_info_fmt")
+                        .replace("{key}", toggle_debug_key.to_string().as_str()),
+                )
+                .color(TEXT_COLOR)
+                .down_from(self.ids.help_info, 5.0)
+                .font_id(self.fonts.cyri.conrod_id)
+                .font_size(self.fonts.cyri.scale(12))
+                .set(self.ids.debug_info, ui_widgets);
+            }
         }
 
         // Help Text
@@ -1822,6 +1827,9 @@ impl Hud {
                     settings_window::Event::AdjustWindowSize(new_size) => {
                         events.push(Event::AdjustWindowSize(new_size));
                     },
+                    settings_window::Event::ChangeBinding(game_input) => {
+                        events.push(Event::ChangeBinding(game_input));
+                    },
                     settings_window::Event::ChangeFreeLookBehavior(behavior) => {
                         events.push(Event::ChangeFreeLookBehavior(behavior));
                     },
diff --git a/voxygen/src/hud/settings_window.rs b/voxygen/src/hud/settings_window.rs
index d8a3db3109..8ead5fb68d 100644
--- a/voxygen/src/hud/settings_window.rs
+++ b/voxygen/src/hud/settings_window.rs
@@ -6,12 +6,15 @@ use crate::{
     i18n::{list_localizations, LanguageMetadata, VoxygenLocalization},
     render::{AaMode, CloudMode, FluidMode},
     ui::{fonts::ConrodVoxygenFonts, ImageSlider, ScaleMode, ToggleButton},
+    window::GameInput,
     GlobalState,
 };
 use conrod_core::{
     color,
+    position::Relative,
     widget::{self, Button, DropDownList, Image, Rectangle, Scrollbar, Text},
-    widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon,
+    widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, Widget,
+    WidgetCommon,
 };
 
 const FPS_CHOICES: [u32; 11] = [15, 30, 40, 50, 60, 90, 120, 144, 240, 300, 500];
@@ -27,8 +30,9 @@ widget_ids! {
         settings_r,
         settings_l,
         settings_scrollbar,
-        controls_text,
-        controls_controls,
+        controls_texts[],
+        controls_buttons[],
+        controls_alignment_rectangle,
         button_help,
         button_help2,
         show_help_label,
@@ -221,6 +225,7 @@ pub enum Event {
     SctPlayerBatch(bool),
     SctDamageBatch(bool),
     ChangeLanguage(LanguageMetadata),
+    ChangeBinding(GameInput),
     ChangeFreeLookBehavior(PressBehavior),
 }
 
@@ -1328,142 +1333,86 @@ impl<'a> Widget for SettingsWindow<'a> {
         // Contents
         if let SettingsTab::Controls = self.show.settings_tab {
             let controls = &self.global_state.settings.controls;
-            Text::new(&self.localized_strings.get("hud.settings.control_names"))
-                .color(TEXT_COLOR)
-                .top_left_with_margins_on(state.ids.settings_content, 5.0, 5.0)
-                .font_id(self.fonts.cyri.conrod_id)
-                .font_size(self.fonts.cyri.scale(18))
-                .set(state.ids.controls_text, ui);
-            // TODO: Replace with buttons that show actual keybinds and allow the user to
-            // change them.
-            #[rustfmt::skip]
-            Text::new(&format!(
-                "{}\n\
-                 {}\n\
-                 {}\n\
-                 {}\n\
-                 {}\n\
-                 {}\n\
-                 {}\n\
-                 \n\
-                 \n\
-                 {}\n\
-                 {}\n\
-                 {}\n\
-                 {}\n\
-                 \n\
-                 {}\n\
-                 \n\
-                 {}\n\
-                 \n\
-                 {}\n\
-                 \n\
-                 {}\n\
-                 \n\
-                 {}\n\
-                 \n\
-                 {}\n\
-                 \n\
-                 {}\n\
-                 \n\
-                 {}\n\
-                 \n\
-                 {}\n\
-                 \n\
-                 {}\n\
-                 \n\
-                 {}\n\
-                 \n\
-                 {}\n\
-                 \n\
-                 \n\
-                 {}\n\
-                 {}\n\
-                 \n\
-                 \n\
-                 {}\n\
-                 {}\n\
-                 {}\n\
-                 {}\n\
-                 {}\n\
-                 {}\n\
-                 {}\n\
-                 {}\n\
-                 {}\n\
-                 {}\n\
-                 \n\
-                 \n\
-                 {}\n\
-                 {}\n\
-                 {}\n\
-                 {}\n\
-                 {}\n\
-                 {}\n\
-                 \n\
-                 \n\
-                 \n\
-                 {}\n\
-                 {}\n\
-                 \n\
-                 \n\
-                 {}\n\
-                 \n\
-                 \n\
-                 \n\
-                 \n\
-                 ",
-                controls.toggle_cursor,
-                controls.help,
-                controls.toggle_interface,
-                controls.toggle_debug,
-                controls.screenshot,
-                controls.toggle_ingame_ui,
-                controls.fullscreen,
-                controls.move_forward,
-                controls.move_left,
-                controls.move_back,
-                controls.move_right,
-                controls.jump,
-                controls.glide,
-                "??", // Dodge
-                controls.roll,
-                controls.climb,
-                controls.climb_down,
-                "??", // Auto Walk
-                controls.toggle_wield,
-                "??", // Put on/Remove Helmet
-                controls.sit,
-                controls.mount,
-                controls.interact,
-                controls.primary,
-                controls.secondary,
-                "1", // Skillbar Slot 1
-                "2", // Skillbar Slot 2
-                "3", // Skillbar Slot 3
-                "4", // Skillbar Slot 4
-                "5", // Skillbar Slot 5
-                "6", // Skillbar Slot 6
-                "7", // Skillbar Slot 7
-                "8", // Skillbar Slot 8
-                "9", // Skillbar Slot 9
-                "0", // Skillbar Slot 10
-                controls.escape,
-                controls.settings,
-                controls.social,
-                controls.map,
-                controls.spellbook,
-                //controls.character_window,
-                //controls.quest_log,
-                controls.bag,
-                controls.enter,
-                "Mouse Wheel", // Scroll chat
-                controls.free_look
-            ))
-            .color(TEXT_COLOR)
-            .right_from(state.ids.controls_text, 0.0)
-            .font_id(self.fonts.cyri.conrod_id)
-            .font_size(self.fonts.cyri.scale(18))
-            .set(state.ids.controls_controls, ui);
+            if controls.keybindings.len() > state.ids.controls_texts.len()
+                || controls.keybindings.len() > state.ids.controls_buttons.len()
+            {
+                state.update(|s| {
+                    s.ids
+                        .controls_texts
+                        .resize(controls.keybindings.len(), &mut ui.widget_id_generator());
+                    s.ids
+                        .controls_buttons
+                        .resize(controls.keybindings.len(), &mut ui.widget_id_generator());
+                });
+            }
+            // Used for sequential placement in a flow-down pattern
+            let mut previous_text_id = None;
+            let mut keybindings_vec: Vec<&GameInput> = controls.keybindings.keys().collect();
+            keybindings_vec.sort();
+            // Loop all existing keybindings and the ids for text and button widgets
+            for (game_input, (&text_id, &button_id)) in keybindings_vec.into_iter().zip(
+                state
+                    .ids
+                    .controls_texts
+                    .iter()
+                    .zip(state.ids.controls_buttons.iter()),
+            ) {
+                if let Some(key) = controls.get_binding(*game_input) {
+                    let loc_key = self
+                        .localized_strings
+                        .get(game_input.get_localization_key());
+                    let key_string = match self.global_state.window.remapping_keybindings {
+                        Some(game_input_binding) => {
+                            if *game_input == game_input_binding {
+                                String::from(self.localized_strings.get("hud.settings.awaitingkey"))
+                            } else {
+                                key.to_string()
+                            }
+                        },
+                        None => key.to_string(),
+                    };
+
+                    let text_widget = Text::new(loc_key)
+                        .color(TEXT_COLOR)
+                        .font_id(self.fonts.cyri.conrod_id)
+                        .font_size(self.fonts.cyri.scale(18));
+                    let button_widget = Button::new()
+                        .label(&key_string)
+                        .label_color(TEXT_COLOR)
+                        .label_font_id(self.fonts.cyri.conrod_id)
+                        .label_font_size(self.fonts.cyri.scale(15))
+                        .w(150.0)
+                        .rgba(0.0, 0.0, 0.0, 0.0)
+                        .border_rgba(0.0, 0.0, 0.0, 255.0)
+                        .label_y(Relative::Scalar(3.0));
+                    // Place top-left if it's the first text, else under the previous one
+                    let text_widget = match previous_text_id {
+                        None => text_widget.top_left_with_margins_on(
+                            state.ids.settings_content,
+                            10.0,
+                            5.0,
+                        ),
+                        Some(prev_id) => text_widget.down_from(prev_id, 10.0),
+                    };
+                    let text_width = text_widget.get_w(ui).unwrap_or(0.0);
+                    text_widget.set(text_id, ui);
+                    if button_widget
+                        .right_from(text_id, 350.0 - text_width)
+                        .set(button_id, ui)
+                        .was_clicked()
+                    {
+                        events.push(Event::ChangeBinding(*game_input));
+                    }
+                    // Set the previous id to the current one for the next cycle
+                    previous_text_id = Some(text_id);
+                }
+            }
+            // Add an empty text widget to simulate some bottom margin, because conrod sucks
+            if let Some(prev_id) = previous_text_id {
+                Rectangle::fill_with([1.0, 1.0], color::TRANSPARENT)
+                    .down_from(prev_id, 10.0)
+                    .set(state.ids.controls_alignment_rectangle, ui);
+            }
         }
 
         // 4) Video Tab -----------------------------------
diff --git a/voxygen/src/hud/skillbar.rs b/voxygen/src/hud/skillbar.rs
index 2c1f3136dd..036cbc8302 100644
--- a/voxygen/src/hud/skillbar.rs
+++ b/voxygen/src/hud/skillbar.rs
@@ -5,6 +5,7 @@ use super::{
 use crate::{
     i18n::{i18n_asset_key, VoxygenLocalization},
     ui::fonts::ConrodVoxygenFonts,
+    window::GameInput,
     GlobalState,
 };
 use common::{
@@ -300,36 +301,45 @@ impl<'a> Widget for Skillbar<'a> {
             .set(state.ids.level_down, ui);
         // Death message
         if self.stats.is_dead {
-            Text::new(&localized_strings.get("hud.you_died"))
-                .middle_of(ui.window)
-                .font_size(self.fonts.cyri.scale(50))
+            if let Some(key) = self
+                .global_state
+                .settings
+                .controls
+                .get_binding(GameInput::Respawn)
+            {
+                Text::new(&localized_strings.get("hud.you_died"))
+                    .middle_of(ui.window)
+                    .font_size(self.fonts.cyri.scale(50))
+                    .font_id(self.fonts.cyri.conrod_id)
+                    .color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
+                    .set(state.ids.death_message_1_bg, ui);
+                Text::new(
+                    &localized_strings
+                        .get("hud.press_key_to_respawn")
+                        .replace("{key}", key.to_string().as_str()),
+                )
+                .mid_bottom_with_margin_on(state.ids.death_message_1_bg, -120.0)
+                .font_size(self.fonts.cyri.scale(30))
                 .font_id(self.fonts.cyri.conrod_id)
                 .color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
-                .set(state.ids.death_message_1_bg, ui);
-            Text::new(&localized_strings.get("hud.press_key_to_respawn").replace(
-                "{key}",
-                &format!("{:?}", self.global_state.settings.controls.respawn),
-            ))
-            .mid_bottom_with_margin_on(state.ids.death_message_1_bg, -120.0)
-            .font_size(self.fonts.cyri.scale(30))
-            .font_id(self.fonts.cyri.conrod_id)
-            .color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
-            .set(state.ids.death_message_2_bg, ui);
-            Text::new(&localized_strings.get("hud.you_died"))
-                .bottom_left_with_margins_on(state.ids.death_message_1_bg, 2.0, 2.0)
-                .font_size(self.fonts.cyri.scale(50))
+                .set(state.ids.death_message_2_bg, ui);
+                Text::new(&localized_strings.get("hud.you_died"))
+                    .bottom_left_with_margins_on(state.ids.death_message_1_bg, 2.0, 2.0)
+                    .font_size(self.fonts.cyri.scale(50))
+                    .font_id(self.fonts.cyri.conrod_id)
+                    .color(CRITICAL_HP_COLOR)
+                    .set(state.ids.death_message_1, ui);
+                Text::new(
+                    &localized_strings
+                        .get("hud.press_key_to_respawn")
+                        .replace("{key}", key.to_string().as_str()),
+                )
+                .bottom_left_with_margins_on(state.ids.death_message_2_bg, 2.0, 2.0)
+                .font_size(self.fonts.cyri.scale(30))
                 .font_id(self.fonts.cyri.conrod_id)
                 .color(CRITICAL_HP_COLOR)
-                .set(state.ids.death_message_1, ui);
-            Text::new(&localized_strings.get("hud.press_key_to_respawn").replace(
-                "{key}",
-                &format!("{:?}", self.global_state.settings.controls.respawn),
-            ))
-            .bottom_left_with_margins_on(state.ids.death_message_2_bg, 2.0, 2.0)
-            .font_size(self.fonts.cyri.scale(30))
-            .font_id(self.fonts.cyri.conrod_id)
-            .color(CRITICAL_HP_COLOR)
-            .set(state.ids.death_message_2, ui);
+                .set(state.ids.death_message_2, ui);
+            }
         }
         // Experience-Bar
         match self.global_state.settings.gameplay.xp_bar {
diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs
index 4fa2b2b833..61bfcd3d92 100644
--- a/voxygen/src/session.rs
+++ b/voxygen/src/session.rs
@@ -713,6 +713,9 @@ impl PlayState for SessionState {
                         global_state.settings.graphics.window_size = new_size;
                         global_state.settings.save_to_file_warn();
                     },
+                    HudEvent::ChangeBinding(game_input) => {
+                        global_state.window.set_keybinding_mode(game_input);
+                    },
                     HudEvent::ChangeFreeLookBehavior(behavior) => {
                         global_state.settings.gameplay.free_look_behavior = behavior;
                     },
diff --git a/voxygen/src/settings.rs b/voxygen/src/settings.rs
index fddf225fb1..c062ae420f 100644
--- a/voxygen/src/settings.rs
+++ b/voxygen/src/settings.rs
@@ -3,7 +3,7 @@ use crate::{
     i18n,
     render::{AaMode, CloudMode, FluidMode},
     ui::ScaleMode,
-    window::KeyMouse,
+    window::{GameInput, KeyMouse},
 };
 use directories::{ProjectDirs, UserDirs};
 use glutin::{MouseButton, VirtualKeyCode};
@@ -12,46 +12,47 @@ use log::warn;
 use serde_derive::{Deserialize, Serialize};
 use std::{fs, io::prelude::*, path::PathBuf};
 
+// ControlSetting-like struct used by Serde, to handle not serializing/building
+// post-deserializing the inverse_keybindings hashmap
+#[derive(Serialize, Deserialize)]
+struct ControlSettingsSerde {
+    keybindings: HashMap<GameInput, KeyMouse>,
+}
+
+impl From<ControlSettings> for ControlSettingsSerde {
+    fn from(control_settings: ControlSettings) -> Self {
+        let mut user_bindings: HashMap<GameInput, KeyMouse> = HashMap::new();
+        // Do a delta between default() ControlSettings and the argument, and let
+        // keybindings be only the custom keybindings chosen by the user.
+        for (k, v) in control_settings.keybindings {
+            if ControlSettings::default_binding(k) != v {
+                // Keybinding chosen by the user
+                user_bindings.insert(k, v);
+            }
+        }
+        ControlSettingsSerde {
+            keybindings: user_bindings,
+        }
+    }
+}
+
 /// `ControlSettings` contains keybindings.
 #[derive(Clone, Debug, Serialize, Deserialize)]
-#[serde(default)]
+#[serde(from = "ControlSettingsSerde", into = "ControlSettingsSerde")]
 pub struct ControlSettings {
-    pub primary: KeyMouse,
-    pub secondary: KeyMouse,
-    pub ability3: KeyMouse,
-    pub toggle_cursor: KeyMouse,
-    pub escape: KeyMouse,
-    pub enter: KeyMouse,
-    pub command: KeyMouse,
-    pub move_forward: KeyMouse,
-    pub move_left: KeyMouse,
-    pub move_back: KeyMouse,
-    pub move_right: KeyMouse,
-    pub jump: KeyMouse,
-    pub sit: KeyMouse,
-    pub glide: KeyMouse,
-    pub climb: KeyMouse,
-    pub climb_down: KeyMouse,
-    pub wall_leap: KeyMouse,
-    pub mount: KeyMouse,
-    pub map: KeyMouse,
-    pub bag: KeyMouse,
-    pub social: KeyMouse,
-    pub spellbook: KeyMouse,
-    pub settings: KeyMouse,
-    pub help: KeyMouse,
-    pub toggle_interface: KeyMouse,
-    pub toggle_debug: KeyMouse,
-    pub fullscreen: KeyMouse,
-    pub screenshot: KeyMouse,
-    pub toggle_ingame_ui: KeyMouse,
-    pub roll: KeyMouse,
-    pub respawn: KeyMouse,
-    pub interact: KeyMouse,
-    pub toggle_wield: KeyMouse,
-    pub swap_loadout: KeyMouse,
-    pub charge: KeyMouse,
-    pub free_look: KeyMouse,
+    pub keybindings: HashMap<GameInput, KeyMouse>,
+    pub inverse_keybindings: HashMap<KeyMouse, HashSet<GameInput>>, // used in event loop
+}
+
+impl From<ControlSettingsSerde> for ControlSettings {
+    fn from(control_serde: ControlSettingsSerde) -> Self {
+        let user_keybindings = control_serde.keybindings;
+        let mut control_settings = ControlSettings::default();
+        for (k, v) in user_keybindings {
+            control_settings.modify_binding(k, v);
+        }
+        control_settings
+    }
 }
 
 /// Since Macbook trackpads lack middle click, on OS X we default to LShift
@@ -64,46 +65,135 @@ const MIDDLE_CLICK_KEY: KeyMouse = KeyMouse::Key(VirtualKeyCode::LShift);
 #[cfg(not(target_os = "macos"))]
 const MIDDLE_CLICK_KEY: KeyMouse = KeyMouse::Mouse(MouseButton::Middle);
 
+impl ControlSettings {
+    pub fn get_binding(&self, game_input: GameInput) -> Option<KeyMouse> {
+        self.keybindings.get(&game_input).copied()
+    }
+
+    pub fn get_associated_game_inputs(&self, key_mouse: &KeyMouse) -> Option<&HashSet<GameInput>> {
+        self.inverse_keybindings.get(key_mouse)
+    }
+
+    pub fn insert_binding(&mut self, game_input: GameInput, key_mouse: KeyMouse) {
+        self.keybindings.insert(game_input, key_mouse);
+        self.inverse_keybindings
+            .entry(key_mouse)
+            .or_default()
+            .insert(game_input);
+    }
+
+    pub fn modify_binding(&mut self, game_input: GameInput, key_mouse: KeyMouse) {
+        // For the KeyMouse->GameInput hashmap, we first need to remove the GameInput
+        // from the old binding
+        if let Some(old_binding) = self.get_binding(game_input) {
+            self.inverse_keybindings
+                .entry(old_binding)
+                .or_default()
+                .remove(&game_input);
+        }
+        // then we add the GameInput to the proper key
+        self.inverse_keybindings
+            .entry(key_mouse)
+            .or_default()
+            .insert(game_input);
+        // For the GameInput->KeyMouse hashmap, just overwrite the value
+        self.keybindings.insert(game_input, key_mouse);
+    }
+
+    pub fn default_binding(game_input: GameInput) -> KeyMouse {
+        // If a new GameInput is added, be sure to update ControlSettings::default()
+        // too!
+        match game_input {
+            GameInput::Primary => KeyMouse::Mouse(MouseButton::Left),
+            GameInput::Secondary => KeyMouse::Mouse(MouseButton::Right),
+            GameInput::ToggleCursor => KeyMouse::Key(VirtualKeyCode::Tab),
+            GameInput::Escape => KeyMouse::Key(VirtualKeyCode::Escape),
+            GameInput::Enter => KeyMouse::Key(VirtualKeyCode::Return),
+            GameInput::Command => KeyMouse::Key(VirtualKeyCode::Slash),
+            GameInput::MoveForward => KeyMouse::Key(VirtualKeyCode::W),
+            GameInput::MoveLeft => KeyMouse::Key(VirtualKeyCode::A),
+            GameInput::MoveBack => KeyMouse::Key(VirtualKeyCode::S),
+            GameInput::MoveRight => KeyMouse::Key(VirtualKeyCode::D),
+            GameInput::Jump => KeyMouse::Key(VirtualKeyCode::Space),
+            GameInput::Sit => KeyMouse::Key(VirtualKeyCode::K),
+            GameInput::Glide => KeyMouse::Key(VirtualKeyCode::LShift),
+            GameInput::Climb => KeyMouse::Key(VirtualKeyCode::Space),
+            GameInput::ClimbDown => KeyMouse::Key(VirtualKeyCode::LControl),
+            GameInput::WallLeap => MIDDLE_CLICK_KEY,
+            GameInput::Mount => KeyMouse::Key(VirtualKeyCode::F),
+            GameInput::Map => KeyMouse::Key(VirtualKeyCode::M),
+            GameInput::Bag => KeyMouse::Key(VirtualKeyCode::B),
+            GameInput::Social => KeyMouse::Key(VirtualKeyCode::O),
+            GameInput::Spellbook => KeyMouse::Key(VirtualKeyCode::P),
+            GameInput::Settings => KeyMouse::Key(VirtualKeyCode::N),
+            GameInput::Help => KeyMouse::Key(VirtualKeyCode::F1),
+            GameInput::ToggleInterface => KeyMouse::Key(VirtualKeyCode::F2),
+            GameInput::ToggleDebug => KeyMouse::Key(VirtualKeyCode::F3),
+            GameInput::Fullscreen => KeyMouse::Key(VirtualKeyCode::F11),
+            GameInput::Screenshot => KeyMouse::Key(VirtualKeyCode::F4),
+            GameInput::ToggleIngameUi => KeyMouse::Key(VirtualKeyCode::F6),
+            GameInput::Roll => MIDDLE_CLICK_KEY,
+            GameInput::Respawn => KeyMouse::Key(VirtualKeyCode::Space),
+            GameInput::Interact => KeyMouse::Mouse(MouseButton::Right),
+            GameInput::ToggleWield => KeyMouse::Key(VirtualKeyCode::T),
+            GameInput::Charge => KeyMouse::Key(VirtualKeyCode::Key1),
+            GameInput::FreeLook => KeyMouse::Key(VirtualKeyCode::L),
+            GameInput::Ability3 => KeyMouse::Key(VirtualKeyCode::Key1),
+            GameInput::SwapLoadout => KeyMouse::Key(VirtualKeyCode::LAlt),
+        }
+    }
+}
 impl Default for ControlSettings {
     fn default() -> Self {
-        Self {
-            primary: KeyMouse::Mouse(MouseButton::Left),
-            secondary: KeyMouse::Mouse(MouseButton::Right),
-            ability3: KeyMouse::Key(VirtualKeyCode::Key1),
-            toggle_cursor: KeyMouse::Key(VirtualKeyCode::Tab),
-            escape: KeyMouse::Key(VirtualKeyCode::Escape),
-            enter: KeyMouse::Key(VirtualKeyCode::Return),
-            command: KeyMouse::Key(VirtualKeyCode::Slash),
-            move_forward: KeyMouse::Key(VirtualKeyCode::W),
-            move_left: KeyMouse::Key(VirtualKeyCode::A),
-            move_back: KeyMouse::Key(VirtualKeyCode::S),
-            move_right: KeyMouse::Key(VirtualKeyCode::D),
-            jump: KeyMouse::Key(VirtualKeyCode::Space),
-            sit: KeyMouse::Key(VirtualKeyCode::K),
-            glide: KeyMouse::Key(VirtualKeyCode::LShift),
-            climb: KeyMouse::Key(VirtualKeyCode::Space),
-            climb_down: KeyMouse::Key(VirtualKeyCode::LControl),
-            wall_leap: MIDDLE_CLICK_KEY,
-            mount: KeyMouse::Key(VirtualKeyCode::F),
-            map: KeyMouse::Key(VirtualKeyCode::M),
-            bag: KeyMouse::Key(VirtualKeyCode::B),
-            social: KeyMouse::Key(VirtualKeyCode::O),
-            spellbook: KeyMouse::Key(VirtualKeyCode::P),
-            settings: KeyMouse::Key(VirtualKeyCode::N),
-            help: KeyMouse::Key(VirtualKeyCode::F1),
-            toggle_interface: KeyMouse::Key(VirtualKeyCode::F2),
-            toggle_debug: KeyMouse::Key(VirtualKeyCode::F3),
-            fullscreen: KeyMouse::Key(VirtualKeyCode::F11),
-            screenshot: KeyMouse::Key(VirtualKeyCode::F4),
-            toggle_ingame_ui: KeyMouse::Key(VirtualKeyCode::F6),
-            roll: MIDDLE_CLICK_KEY,
-            respawn: KeyMouse::Key(VirtualKeyCode::Space),
-            interact: KeyMouse::Mouse(MouseButton::Right),
-            toggle_wield: KeyMouse::Key(VirtualKeyCode::T),
-            swap_loadout: KeyMouse::Key(VirtualKeyCode::Q),
-            charge: KeyMouse::Key(VirtualKeyCode::Key1),
-            free_look: KeyMouse::Key(VirtualKeyCode::L),
+        let mut new_settings = Self {
+            keybindings: HashMap::new(),
+            inverse_keybindings: HashMap::new(),
+        };
+        // Sets the initial keybindings for those GameInputs. If a new one is created in
+        // future, you'll have to update default_binding, and you should update this vec
+        // too.
+        let game_inputs = vec![
+            GameInput::Primary,
+            GameInput::Secondary,
+            GameInput::ToggleCursor,
+            GameInput::MoveForward,
+            GameInput::MoveBack,
+            GameInput::MoveLeft,
+            GameInput::MoveRight,
+            GameInput::Jump,
+            GameInput::Sit,
+            GameInput::Glide,
+            GameInput::Climb,
+            GameInput::ClimbDown,
+            GameInput::WallLeap,
+            GameInput::Mount,
+            GameInput::Enter,
+            GameInput::Command,
+            GameInput::Escape,
+            GameInput::Map,
+            GameInput::Bag,
+            GameInput::Social,
+            GameInput::Spellbook,
+            GameInput::Settings,
+            GameInput::ToggleInterface,
+            GameInput::Help,
+            GameInput::ToggleDebug,
+            GameInput::Fullscreen,
+            GameInput::Screenshot,
+            GameInput::ToggleIngameUi,
+            GameInput::Roll,
+            GameInput::Respawn,
+            GameInput::Interact,
+            GameInput::ToggleWield,
+            GameInput::Charge,
+            GameInput::FreeLook,
+            GameInput::Ability3,
+            GameInput::SwapLoadout,
+        ];
+        for game_input in game_inputs {
+            new_settings.insert_binding(game_input, ControlSettings::default_binding(game_input));
         }
+        new_settings
     }
 }
 
diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs
index f54e0eabf1..9b80288f37 100644
--- a/voxygen/src/window.rs
+++ b/voxygen/src/window.rs
@@ -1,7 +1,7 @@
 use crate::{
     controller::*,
     render::{Renderer, WinColorFmt, WinDepthFmt},
-    settings::Settings,
+    settings::{ControlSettings, Settings},
     ui, Error,
 };
 use gilrs::{EventType, Gilrs};
@@ -12,7 +12,7 @@ use std::fmt;
 use vek::*;
 
 /// Represents a key that the game recognises after input mapping.
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
 pub enum GameInput {
     Primary,
     Secondary,
@@ -34,8 +34,6 @@ pub enum GameInput {
     Escape,
     Map,
     Bag,
-    QuestLog,
-    CharacterWindow,
     Social,
     Spellbook,
     Settings,
@@ -54,6 +52,49 @@ pub enum GameInput {
     FreeLook,
 }
 
+impl GameInput {
+    pub fn get_localization_key(&self) -> &str {
+        match *self {
+            GameInput::Primary => "gameinput.primary",
+            GameInput::Secondary => "gameinput.secondary",
+            GameInput::ToggleCursor => "gameinput.togglecursor",
+            GameInput::MoveForward => "gameinput.moveforward",
+            GameInput::MoveLeft => "gameinput.moveleft",
+            GameInput::MoveRight => "gameinput.moveright",
+            GameInput::MoveBack => "gameinput.moveback",
+            GameInput::Jump => "gameinput.jump",
+            GameInput::Sit => "gameinput.sit",
+            GameInput::Glide => "gameinput.glide",
+            GameInput::Climb => "gameinput.climb",
+            GameInput::ClimbDown => "gameinput.climbdown",
+            GameInput::WallLeap => "gameinput.wallleap",
+            GameInput::Mount => "gameinput.mount",
+            GameInput::Enter => "gameinput.enter",
+            GameInput::Command => "gameinput.command",
+            GameInput::Escape => "gameinput.escape",
+            GameInput::Map => "gameinput.map",
+            GameInput::Bag => "gameinput.bag",
+            GameInput::Social => "gameinput.social",
+            GameInput::Spellbook => "gameinput.spellbook",
+            GameInput::Settings => "gameinput.settings",
+            GameInput::ToggleInterface => "gameinput.toggleinterface",
+            GameInput::Help => "gameinput.help",
+            GameInput::ToggleDebug => "gameinput.toggledebug",
+            GameInput::Fullscreen => "gameinput.fullscreen",
+            GameInput::Screenshot => "gameinput.screenshot",
+            GameInput::ToggleIngameUi => "gameinput.toggleingameui",
+            GameInput::Roll => "gameinput.roll",
+            GameInput::Respawn => "gameinput.respawn",
+            GameInput::Interact => "gameinput.interact",
+            GameInput::ToggleWield => "gameinput.togglewield",
+            GameInput::Charge => "gameinput.charge",
+            GameInput::FreeLook => "gameinput.freelook",
+            GameInput::Ability3 => "gameinput.ability3",
+            GameInput::SwapLoadout => "gameinput.swaploadout",
+        }
+    }
+}
+
 /// Represents a key that the game menus recognise after input mapping
 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
 pub enum MenuInput {
@@ -319,8 +360,8 @@ pub struct Window {
     pub mouse_y_inversion: bool,
     fullscreen: bool,
     needs_refresh_resize: bool,
-    key_map: HashMap<KeyMouse, Vec<GameInput>>,
     keypress_map: HashMap<GameInput, glutin::ElementState>,
+    pub remapping_keybindings: Option<GameInput>,
     supplement_events: Vec<Event>,
     focused: bool,
     gilrs: Option<Gilrs>,
@@ -355,116 +396,6 @@ impl Window {
             )
             .map_err(|err| Error::BackendError(Box::new(err)))?;
 
-        let mut map: HashMap<_, Vec<_>> = HashMap::new();
-        map.entry(settings.controls.primary)
-            .or_default()
-            .push(GameInput::Primary);
-        map.entry(settings.controls.secondary)
-            .or_default()
-            .push(GameInput::Secondary);
-        map.entry(settings.controls.ability3)
-            .or_default()
-            .push(GameInput::Ability3);
-        map.entry(settings.controls.toggle_cursor)
-            .or_default()
-            .push(GameInput::ToggleCursor);
-        map.entry(settings.controls.escape)
-            .or_default()
-            .push(GameInput::Escape);
-        map.entry(settings.controls.enter)
-            .or_default()
-            .push(GameInput::Enter);
-        map.entry(settings.controls.command)
-            .or_default()
-            .push(GameInput::Command);
-        map.entry(settings.controls.move_forward)
-            .or_default()
-            .push(GameInput::MoveForward);
-        map.entry(settings.controls.move_left)
-            .or_default()
-            .push(GameInput::MoveLeft);
-        map.entry(settings.controls.move_back)
-            .or_default()
-            .push(GameInput::MoveBack);
-        map.entry(settings.controls.move_right)
-            .or_default()
-            .push(GameInput::MoveRight);
-        map.entry(settings.controls.jump)
-            .or_default()
-            .push(GameInput::Jump);
-        map.entry(settings.controls.sit)
-            .or_default()
-            .push(GameInput::Sit);
-        map.entry(settings.controls.glide)
-            .or_default()
-            .push(GameInput::Glide);
-        map.entry(settings.controls.climb)
-            .or_default()
-            .push(GameInput::Climb);
-        map.entry(settings.controls.climb_down)
-            .or_default()
-            .push(GameInput::ClimbDown);
-        map.entry(settings.controls.wall_leap)
-            .or_default()
-            .push(GameInput::WallLeap);
-        map.entry(settings.controls.mount)
-            .or_default()
-            .push(GameInput::Mount);
-        map.entry(settings.controls.map)
-            .or_default()
-            .push(GameInput::Map);
-        map.entry(settings.controls.bag)
-            .or_default()
-            .push(GameInput::Bag);
-        map.entry(settings.controls.social)
-            .or_default()
-            .push(GameInput::Social);
-        map.entry(settings.controls.spellbook)
-            .or_default()
-            .push(GameInput::Spellbook);
-        map.entry(settings.controls.settings)
-            .or_default()
-            .push(GameInput::Settings);
-        map.entry(settings.controls.help)
-            .or_default()
-            .push(GameInput::Help);
-        map.entry(settings.controls.toggle_interface)
-            .or_default()
-            .push(GameInput::ToggleInterface);
-        map.entry(settings.controls.toggle_debug)
-            .or_default()
-            .push(GameInput::ToggleDebug);
-        map.entry(settings.controls.fullscreen)
-            .or_default()
-            .push(GameInput::Fullscreen);
-        map.entry(settings.controls.screenshot)
-            .or_default()
-            .push(GameInput::Screenshot);
-        map.entry(settings.controls.toggle_ingame_ui)
-            .or_default()
-            .push(GameInput::ToggleIngameUi);
-        map.entry(settings.controls.roll)
-            .or_default()
-            .push(GameInput::Roll);
-        map.entry(settings.controls.respawn)
-            .or_default()
-            .push(GameInput::Respawn);
-        map.entry(settings.controls.interact)
-            .or_default()
-            .push(GameInput::Interact);
-        map.entry(settings.controls.toggle_wield)
-            .or_default()
-            .push(GameInput::ToggleWield);
-        map.entry(settings.controls.swap_loadout)
-            .or_default()
-            .push(GameInput::SwapLoadout);
-        map.entry(settings.controls.charge)
-            .or_default()
-            .push(GameInput::Charge);
-        map.entry(settings.controls.free_look)
-            .or_default()
-            .push(GameInput::FreeLook);
-
         let keypress_map = HashMap::new();
 
         let gilrs = match Gilrs::new() {
@@ -510,8 +441,8 @@ impl Window {
             mouse_y_inversion: settings.gameplay.mouse_y_inversion,
             fullscreen: false,
             needs_refresh_resize: false,
-            key_map: map,
             keypress_map,
+            remapping_keybindings: None,
             supplement_events: vec![],
             focused: true,
             gilrs,
@@ -543,8 +474,9 @@ impl Window {
         let cursor_grabbed = self.cursor_grabbed;
         let renderer = &mut self.renderer;
         let window = &mut self.window;
+        let remapping_keybindings = &mut self.remapping_keybindings;
         let focused = &mut self.focused;
-        let key_map = &self.key_map;
+        let controls = &mut settings.controls;
         let keypress_map = &mut self.keypress_map;
         let pan_sensitivity = self.pan_sensitivity;
         let zoom_sensitivity = self.zoom_sensitivity;
@@ -577,23 +509,27 @@ impl Window {
                     },
                     glutin::WindowEvent::ReceivedCharacter(c) => events.push(Event::Char(c)),
                     glutin::WindowEvent::MouseInput { button, state, .. } => {
-                        if let (true, Some(game_inputs)) =
-                            (cursor_grabbed, key_map.get(&KeyMouse::Mouse(button)))
-                        {
+                        if let Some(game_inputs) = Window::map_input(
+                            KeyMouse::Mouse(button),
+                            controls,
+                            remapping_keybindings,
+                        ) {
                             for game_input in game_inputs {
                                 events.push(Event::InputUpdate(
                                     *game_input,
                                     state == glutin::ElementState::Pressed,
                                 ));
                             }
+                            events.push(Event::MouseButton(button, state));
                         }
-                        events.push(Event::MouseButton(button, state));
                     },
-                    glutin::WindowEvent::KeyboardInput { input, .. } => match input.virtual_keycode
-                    {
-                        Some(key) => {
-                            let game_inputs = key_map.get(&KeyMouse::Key(key));
-                            if let Some(game_inputs) = game_inputs {
+                    glutin::WindowEvent::KeyboardInput { input, .. } => {
+                        if let Some(key) = input.virtual_keycode {
+                            if let Some(game_inputs) = Window::map_input(
+                                KeyMouse::Key(key),
+                                controls,
+                                remapping_keybindings,
+                            ) {
                                 for game_input in game_inputs {
                                     match game_input {
                                         GameInput::Fullscreen => {
@@ -631,9 +567,9 @@ impl Window {
                                     }
                                 }
                             }
-                        },
-                        _ => {},
+                        }
                     },
+
                     glutin::WindowEvent::Focused(state) => {
                         *focused = state;
                         events.push(Event::Focused(state));
@@ -1000,4 +936,33 @@ impl Window {
     ) {
         map.insert(input, state);
     }
+
+    // Function used to handle Mouse and Key events. It first checks if we're in
+    // remapping mode for a specific GameInput. If we are, we modify the binding
+    // of that GameInput with the KeyMouse passed. Else, we return an iterator of
+    // the GameInputs for that KeyMouse.
+    fn map_input<'a>(
+        key_mouse: KeyMouse,
+        controls: &'a mut ControlSettings,
+        remapping: &mut Option<GameInput>,
+    ) -> Option<impl Iterator<Item = &'a GameInput>> {
+        match *remapping {
+            Some(game_input) => {
+                controls.modify_binding(game_input, key_mouse);
+                *remapping = None;
+                None
+            },
+            None => {
+                if let Some(game_inputs) = controls.get_associated_game_inputs(&key_mouse) {
+                    Some(game_inputs.iter())
+                } else {
+                    None
+                }
+            },
+        }
+    }
+
+    pub fn set_keybinding_mode(&mut self, game_input: GameInput) {
+        self.remapping_keybindings = Some(game_input);
+    }
 }