Index: binaries/data/config/default.cfg
===================================================================
--- binaries/data/config/default.cfg
+++ binaries/data/config/default.cfg
@@ -141,7 +141,7 @@
; See keys.txt for the list of key names.
; > SYSTEM SETTINGS
-exit = "Ctrl+Break", "Super+Q" ; Exit to desktop
+exit = "Ctrl+Break", "Super+Q", "Alt+F4" ; Exit to desktop
cancel = Escape ; Close or cancel the current dialog box/popup
confirm = Return ; Confirm the current command
pause = Pause ; Pause/unpause game
@@ -186,8 +186,8 @@
reset = "R" ; Reset camera rotation to default.
follow = "F" ; Follow the first unit in the selection
rallypointfocus = unused ; Focus the camera on the rally point of the selected building
-zoom.in = Plus, Equals, NumPlus ; Zoom camera in (continuous control)
-zoom.out = Minus, Underscore, NumMinus ; Zoom camera out (continuous control)
+zoom.in = Plus, NumPlus ; Zoom camera in (continuous control)
+zoom.out = Minus, NumMinus ; Zoom camera out (continuous control)
zoom.wheel.in = WheelUp ; Zoom camera in (stepped control)
zoom.wheel.out = WheelDown ; Zoom camera out (stepped control)
rotate.up = "Ctrl+UpArrow", "Ctrl+W" ; Rotate camera to look upwards
@@ -238,13 +238,13 @@
toggle = "Ctrl+F11" ; Enable/disable HTTP/GPU modes for new profiler
[hotkey.selection]
+cancel = Esc ; Un-select all units and cancel building placement
add = Shift ; Add units to selection
militaryonly = Alt ; Add only military units to the selection
nonmilitaryonly = "Alt+Y" ; Add only non-military units to the selection
idleonly = "I" ; Select only idle units
woundedonly = "O" ; Select only wounded units
remove = Ctrl ; Remove units from selection
-cancel = Esc ; Un-select all units and cancel building placement
idleworker = Period, NumPoint ; Select next idle worker
idlewarrior = ForwardSlash, NumDivide ; Select next idle warrior
idleunit = BackSlash ; Select next idle unit
@@ -312,8 +312,8 @@
; Overlays
showstatusbars = Tab ; Toggle display of status bars
devcommands.toggle = "Alt+D" ; Toggle developer commands panel
-highlightguarding = PgDn ; Toggle highlight of guarding units
-highlightguarded = PgUp ; Toggle highlight of guarded units
+highlightguarding = PageDown ; Toggle highlight of guarding units
+highlightguarded = PageUp ; Toggle highlight of guarded units
diplomacycolors = "Alt+X" ; Toggle diplomacy colors
toggleattackrange = "Alt+C" ; Toggle display of attack range overlays of selected defensive structures
toggleaurasrange = "Alt+V" ; Toggle display of aura range overlays of selected units and structures
Index: binaries/data/config/keys.txt
===================================================================
--- binaries/data/config/keys.txt
+++ binaries/data/config/keys.txt
@@ -1,107 +1,38 @@
## This file documents keynames that can be used in .cfg files for specifying hotkeys
+## Note: all are case-insensitive.
## Note: the keynames are not actually configured or implemented here
-## Also note: if your keyboard supports keys that do not appear here (such as the French é letter),
-## you should be able to use it directly.
+## Note: those names map to 'scancodes', i.e. positions on a classic QWERTY keyboard.
+## The in-game hotkey editor will show you what that corresponds to.
-Backspace, BkSp
-Tab
-Clear
-Return, Ret
-Pause
+
+MouseLeft
+MouseRight
+MouseMiddle
+MouseX1
+MouseX2
+WheelUp
+WheelDown
+WheelLeft
+WheelRight
+
+A-Z
+0-9
+Return, Break
Escape, Esc
-Space, Spc
-!, Exclaim
-", DoubleQuote
-#, Hash
-$, Dollar
-&, Ampersand
-', SingleQuote
-(, LeftParen
-), RightParen
-*, Asterisk
-+, Plus
-,, Comma
+Backspace, Del
+Tab
+Space
-, Minus
-., Period
-/, ForwardSlash
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
-:, Colon
-;, Semicolon
-<, LessThan
-=, Equals
->, GreaterThan
-?, Question
-@, At
+=, Plus, Equals
[, LeftBracket
-\, BackSlash
], RightBracket
-^, Caret
-_, Underscore
+\\, BackSlash ## You do need the espacing in the first case.
+;, Semicolon
+', Quote ## Maps to the quote/doubleQuote key on QWERTY.
`, BackQuote
-A
-B
-C
-D
-E
-F
-G
-H
-I
-J
-K
-L
-M
-N
-O
-P
-Q
-R
-S
-T
-U
-V
-W
-X
-Y
-Z
-Delete, Del
-Numpad 0, Num0
-Numpad 1, Num1
-Numpad 2, Num2
-Numpad 3, Num3
-Numpad 4, Num4
-Numpad 5, Num5
-Numpad 6, Num6
-Numpad 7, Num7
-Numpad 8, Num8
-Numpad 9, Num9
-Numpad ., NumPoint
-Numpad /, NumDivide
-Numpad *, NumMultiply
-Numpad -, NumMinus
-Numpad +, NumPlus
-Numpad Enter, NumEnter
-Numpad =, NumEquals
-
-Arrow Up, UpArrow
-Arrow Down, DownArrow
-Arrow Right, RightArrow
-Arrow Left, LeftArrow
-Insert, Ins
-Home
-End
-Page Up, PgUp
-Page Down, PgDn
-
+,, Comma
+., Period
+/, ForwardSlash
F1
F2
F3
@@ -114,41 +45,155 @@
F10
F11
F12
+PrintScreen
+ScrollLock
+Pause
+Insert
+Home
+PageUp
+Delete
+End
+PageDown
+Right, RightArrow
+Left, LeftArrow
+Down, DownArrow
+Up, UpArrow
+Numlock
+Keypad /
+Keypad *
+Keypad -, NumMinus
+Keypad +, NumPlus
+Keypad Enter
+Keypad 1
+Keypad 2
+Keypad 3
+Keypad 4
+Keypad 5
+Keypad 6
+Keypad 7
+Keypad 8
+Keypad 9
+Keypad 0
+Keypad .
+Application
+Power
+Keypad =
F13
F14
F15
-
-Num Lock, NumLock
-Caps Lock, CapsLock
-Scroll Lock, ScrlLock
-Right Shift, RightShift
-Left Shift, LeftShift
-Shift, AnyShift
-Right Ctrl, RightCtrl
-Left Ctrl, LeftCtrl
-Ctrl, AnyCtrl
-Right Alt, RightAlt
-Left Alt, LeftAlt
-Alt, AnyAlt
-Left Super, LeftWin
-Right Super, RightWin
-Super, AnyWindows ## Windows key, also Command/meta key on Macs
-Alt Gr, AltGr
-Compose
-
+F16
+F17
+F18
+F19
+F20
+F21
+F22
+F23
+F24
+Execute
Help
-Print Screen, PrtSc
-SysRq
-Break
Menu
-Power
-Euro
+Select
+Stop
+Again
Undo
-Left Mouse Button, MouseLeft
-Right Mouse Button, MouseRight
-Middle Mouse Button, MouseMiddle
-Mouse Wheel Up, WheelUp
-Mouse Wheel Down, WheelDown
-MouseButtonX, MouseNX # where X is a number 1-255, for extra mouse buttons
-## (note that some mice start numbering their buttons at higher numbers
-## so the first extra button on your mouse might be #8 here)
+Cut
+Copy
+Paste
+Find
+Mute
+VolumeUp
+VolumeDown
+Keypad ,
+AltErase
+SysReq
+Cancel
+Clear
+Prior
+Return
+Separator
+Out
+Oper
+Clear / Again
+CrSel
+ExSel
+Keypad 00
+Keypad 000
+ThousandsSeparator
+DecimalSeparator
+CurrencyUnit
+CurrencySubUnit
+Keypad (
+Keypad )
+Keypad {
+Keypad }
+Keypad Tab
+Keypad Backspace
+Keypad A
+Keypad B
+Keypad C
+Keypad D
+Keypad E
+Keypad F
+Keypad XOR
+Keypad ^
+Keypad %
+Keypad <
+Keypad >
+Keypad &
+Keypad &&
+Keypad |
+Keypad ||
+Keypad :
+Keypad #
+Keypad Space
+Keypad @
+Keypad !
+Keypad MemStore
+Keypad MemRecall
+Keypad MemClear
+Keypad MemAdd
+Keypad MemSubtract
+Keypad MemMultiply
+Keypad MemDivide
+Keypad +/-
+Keypad Clear
+Keypad ClearEntry
+Keypad Binary
+Keypad Octal
+Keypad Decimal
+Keypad Hexadecimal
+Left Ctrl, Ctrl
+Left Shift, Shift
+Left Alt, Alt
+Left GUI, Super
+Right Ctrl, Ctrl
+Right Shift, Shift
+Right Alt, Alt
+Right GUI, Super
+ModeSwitch
+AudioNext
+AudioPrev
+AudioStop
+AudioPlay
+AudioMute
+MediaSelect
+WWW
+Mail
+Calculator
+Computer
+AC Search
+AC Home
+AC Back
+AC Forward
+AC Stop
+AC Refresh
+AC Bookmarks
+BrightnessDown
+BrightnessUp
+DisplaySwitch
+KBDIllumToggle
+KBDIllumDown
+KBDIllumUp
+Eject
+Sleep
Index: binaries/data/mods/public/gui/common/hotkeys.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/gui/common/hotkeys.js
@@ -0,0 +1,31 @@
+/**
+ * Holds a map of scancode name -> user keyboard name
+ */
+var g_ScancodesMap;
+
+function hotkeySort(a, b)
+{
+ const specialKeys = ["Shift", "Alt", "Ctrl", "Super"];
+ // Quick hack to put those first.
+ if (specialKeys.indexOf(a) !== -1)
+ a = ' ' + a;
+ if (specialKeys.indexOf(b) !== -1)
+ b = ' ' + b;
+ return a.localeCompare(b, Engine.GetCurrentLocale().substr(0, 2), { "numeric": true });
+}
+
+function formatHotkeyCombination(comb)
+{
+ if (!g_ScancodesMap)
+ g_ScancodesMap = Engine.GetScancodeKeyNames();
+
+ let str = comb.sort(hotkeySort).map(hk => g_ScancodesMap[hk]).join("+");
+ return str;//.replace('\\', '\\\\').replace('[', '\\[');
+}
+
+function formatHotkeyCombinations(combinations)
+{
+ let combs = combinations.map(formatHotkeyCombination);
+ combs.sort((a, b) => a.length > b.length || a > b);
+ return combs.join(", ");
+}
Index: binaries/data/mods/public/gui/hotkeys/HotkeyPicker.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/gui/hotkeys/HotkeyPicker.js
@@ -0,0 +1,113 @@
+/**
+ * Handle the interface to pick a hotkey combination.
+ * The player must keep a key combination for 2s in the input field for it to be registered.
+ */
+class HotkeyPicker
+{
+ constructor(onClose, name, combinations)
+ {
+ this.name = name;
+ this.combinations = combinations;
+ this.window = Engine.GetGUIObjectByName("hotkeyPicker");
+ this.window.hidden = false;
+
+ this.enteringInput = -1;
+
+ Engine.GetGUIObjectByName("hotkeyPickerTitle").caption = translate(this.name);
+
+ this.setupCombinations();
+ this.render();
+
+ Engine.GetGUIObjectByName("hotkeyPickerReset").onPress = () => {
+ // This is a bit "using a bazooka to kill a fly"
+ Engine.ConfigDB_RemoveValue("user", "hotkey." + this.name);
+ Engine.ConfigDB_WriteFile("user", "config/user.cfg");
+ Engine.ReloadHotkeys();
+ let data = Engine.GetHotkeyMap();
+ this.combinations = data[this.name];
+ this.setupCombinations();
+ this.render();
+ };
+ Engine.GetGUIObjectByName("hotkeyPickerCancel").onPress = () => {
+ onClose(this, false);
+ };
+ Engine.GetGUIObjectByName("hotkeyPickerSave").onPress = () => {
+ onClose(this, true);
+ };
+ }
+
+ setupCombinations()
+ {
+ for (let i = 0; i < 4; ++i)
+ {
+ let s = Engine.GetGUIObjectByName("combination[" + i + "]").size;
+ s.top = +i * 60 + 90;
+ s.bottom = +i * 60 + 120;
+ Engine.GetGUIObjectByName("combination[" + i + "]").size = s;
+ Engine.GetGUIObjectByName("combNb[" + i + "]").caption = sprintf(translate("#%i"), i);
+
+ if (i == this.combinations.length)
+ this.combinations.push([]);
+
+ let input = Engine.GetGUIObjectByName("combMapping[" + i + "]");
+
+ let picker = Engine.GetGUIObjectByName("picker[" + i + "]");
+ Engine.GetGUIObjectByName("combMappingBtn[" + i + "]").onPress = () => {
+ this.enteringInput = i;
+ picker.focus();
+ this.render();
+ };
+
+ picker.onKeyChange = keys => {
+ input.caption = (keys.length ?
+ formatHotkeyCombination(keys) + translate(" (hold to register)") :
+ translate("Enter new Hotkey, hold to register."));
+ };
+
+ Engine.GetGUIObjectByName("deleteComb[" + i + "]").onPress = (j => () => {
+ this.combinations[j] = [];
+ this.render();
+ })(i);
+
+ picker.onCombination = (j => keys => {
+ this.combinations[j] = keys;
+ this.enteringInput = -1;
+ picker.blur();
+
+ this.render();
+ })(i);
+ }
+ }
+
+ close()
+ {
+ this.window.hidden = true;
+ for (let i = 0; i < 4; ++i)
+ Engine.GetGUIObjectByName("picker[" + i + "]").blur();
+ }
+
+ render()
+ {
+ for (let i = 0; i < 4; ++i)
+ {
+ let input = Engine.GetGUIObjectByName("combMapping[" + i + "]");
+ if (i == this.enteringInput)
+ input.caption = translate("Enter new Hotkey, hold to register.");
+ else
+ input.caption = formatHotkeyCombination(this.combinations[i]) || "(unused)";
+ Engine.GetGUIObjectByName("conflicts[" + i + "]").caption = "";
+
+ Engine.GetGUIObjectByName("deleteComb[" + i + "]").hidden = !this.combinations[i].length;
+
+ let conflicts = (Engine.GetConflicts(this.combinations[i]) || [])
+ .filter(name => name != this.name).map(translate);
+ if (conflicts.length)
+ Engine.GetGUIObjectByName("conflicts[" + i + "]").caption =
+ coloredText(translate("May conflict with: "), "255 153 0") + conflicts.join(", ");
+ }
+ // Gray out buttons when entering an input so it's obvious clicking on them will do nothing.
+ Engine.GetGUIObjectByName("hotkeyPickerReset").enabled = this.enteringInput == -1;
+ Engine.GetGUIObjectByName("hotkeyPickerCancel").enabled = this.enteringInput == -1;
+ Engine.GetGUIObjectByName("hotkeyPickerSave").enabled = this.enteringInput == -1;
+ }
+}
Index: binaries/data/mods/public/gui/hotkeys/HotkeysPage.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/gui/hotkeys/HotkeysPage.js
@@ -0,0 +1,184 @@
+class HotkeysPage
+{
+ constructor()
+ {
+ Engine.GetGUIObjectByName("hotkeyList").onMouseLeftDoubleClickItem = () => {
+ let idx = Engine.GetGUIObjectByName("hotkeyList").selected;
+ let picker = new HotkeyPicker(
+ this.onHotkeyPicked.bind(this),
+ Engine.GetGUIObjectByName("hotkeyList").list_data[idx],
+ this.hotkeys[Engine.GetGUIObjectByName("hotkeyList").list_data[idx]]
+ );
+ };
+ Engine.GetGUIObjectByName("hotkeyFilter").onSelectionChange = () => this.setupHotkeyList();
+
+ Engine.GetGUIObjectByName("hotkeyTextFilter").onTextEdit = () => this.setupHotkeyList();
+
+ Engine.GetGUIObjectByName("hotkeyClose").onPress = () => Engine.PopGuiPage();
+ Engine.GetGUIObjectByName("hotkeyReset").onPress = () => this.resetUserHotkeys();
+ Engine.GetGUIObjectByName("hotkeySave").onPress = () => {
+ this.saveUserHotkeys();
+ };
+
+ this.setupHotkeyData();
+ this.setupFilters();
+ this.setupHotkeyList();
+ }
+
+ setupFilters()
+ {
+ let dropdown = Engine.GetGUIObjectByName("hotkeyFilter");
+ let names = [];
+ for (let cat in this.categories)
+ names.push(this.categories[cat].label);
+ dropdown.list = [translate("All Hotkeys")].concat(names);
+ dropdown.list_data = [-1].concat(Object.keys(this.categories));
+ dropdown.selected = 0;
+ }
+
+ setupHotkeyList()
+ {
+ let hotkeyList = Engine.GetGUIObjectByName("hotkeyList");
+ hotkeyList.selected = -1;
+ let textFilter = Engine.GetGUIObjectByName("hotkeyTextFilter").caption;
+ let dropdown = Engine.GetGUIObjectByName("hotkeyFilter");
+ if (dropdown.selected && dropdown.selected !== 0)
+ {
+ let category = this.categories[dropdown.list_data[dropdown.selected]];
+ // This is inefficient but it seems fast enough.
+ let hotkeys = category.hotkeys.filter(x => translate(x[0]).indexOf(textFilter) !== -1);
+ hotkeyList.list_name = hotkeys.map(x => translate(x[0]));
+ hotkeyList.list_mapping = hotkeys.map(x => formatHotkeyCombinations(x[1]));
+ hotkeyList.list = hotkeys.map(() => 0);
+ hotkeyList.list_data = hotkeys.map(x => x[0]);
+ }
+ else
+ {
+ // TODO SM62+ : refactor using flat()
+ let flattened = [];
+ for (let cat in this.categories)
+ flattened = flattened.concat(this.categories[cat].hotkeys);
+ flattened = flattened.filter(x => translate(x[0]).indexOf(textFilter) !== -1);
+ hotkeyList.list_name = flattened.map(x => translate(x[0]));
+ hotkeyList.list_mapping = flattened.map(x => formatHotkeyCombinations(x[1]));
+ hotkeyList.list = flattened.map(() => 0);
+ hotkeyList.list_data = flattened.map(x => x[0]);
+ }
+ }
+
+ formatHotkeyCombinations(combinations)
+ {
+ return combinations.map(formatHotkeyCombination).join(", ");
+ }
+
+
+ onHotkeyPicked(picker, success)
+ {
+ picker.close();
+ if (!success)
+ return;
+
+ // Remove empty combinations which the picker added.
+ picker.combinations = picker.combinations.filter(x => x.length);
+
+ this.hotkeys[picker.name] = picker.combinations;
+ // Have to find the correct line.
+ let panel = Engine.GetGUIObjectByName("hotkeyList");
+ for (let cat in this.categories)
+ {
+ let idx = this.categories[cat].hotkeys.findIndex(([name, _]) => name == picker.name);
+ if (idx === -1)
+ continue;
+ this.categories[cat].hotkeys[idx][1] = picker.combinations;
+ }
+
+ this.setupHotkeyList();
+ }
+
+ setupHotkeyData()
+ {
+ let hotkeydata = Engine.GetHotkeyMap();
+ this.hotkeys = hotkeydata;
+
+ let categories = {
+ "other": {
+ "label": translate("Other hotkeys"),
+ "hotkeys": []
+ }
+ };
+ let n_categories = 1;
+ for (let hotkeyName in this.hotkeys)
+ {
+ let category = "other";
+ let firstdot = hotkeyName.indexOf('.');
+ if (firstdot !== -1)
+ category = hotkeyName.substr(0, firstdot);
+ if (!(category in categories))
+ {
+ if (n_categories > 18)
+ category = "other";
+ categories[category] = {
+ "label": category,
+ "hotkeys": []
+ };
+ }
+ categories[category].hotkeys.push([hotkeyName, this.hotkeys[hotkeyName]]);
+ }
+ // Remove categories that are too small to deserve a tab.
+ for (let cat of Object.keys(categories))
+ if (categories[cat].hotkeys.length < 3)
+ {
+ categories.other.hotkeys = categories.other.hotkeys.concat(categories[cat].hotkeys);
+ delete categories[cat];
+ }
+ for (let cat in categories)
+ categories[cat].hotkeys = categories[cat].hotkeys.sort();
+
+ this.categories = categories;
+ }
+
+ resetUserHotkeys()
+ {
+ messageBox(
+ 400, 200,
+ translate("Reset all hotkeys to default values?"),
+ translate("Confirmation"),
+ [translate("No"), translate("Yes")],
+ [
+ () => {},
+ () => {
+ for (let cat in this.categories)
+ this.categories[cat].hotkeys.forEach(([name, _]) => {
+ Engine.ConfigDB_RemoveValue("user", "hotkey." + name);
+ });
+ Engine.ConfigDB_WriteFile("user", "config/user.cfg");
+ Engine.ReloadHotkeys();
+ this.setupHotkeyData();
+ this.setupHotkeyList();
+ }
+ ]);
+ }
+
+ saveUserHotkeys()
+ {
+ for (let cat in this.categories)
+ {
+ this.categories[cat].hotkeys.forEach(([name, mapping]) => {
+ let keymap = mapping.map(comb => comb.sort(hotkeySort).join("+"));
+ Engine.ConfigDB_RemoveValue("user", "hotkey." + name);
+ let val = Engine.ConfigDB_GetValue("user", "hotkey." + name);
+ // val is a string here so I join above.
+ if (keymap.join(", ") !== val)
+ Engine.ConfigDB_CreateValues("user", "hotkey." + name, keymap);
+ });
+ }
+ Engine.ConfigDB_WriteFile("user", "config/user.cfg");
+ Engine.ReloadHotkeys();
+ }
+}
+
+
+function init(data)
+{
+ let hotkeyPage = new HotkeysPage(data);
+}
Index: binaries/data/mods/public/gui/hotkeys/hotkeys.xml
===================================================================
--- /dev/null
+++ binaries/data/mods/public/gui/hotkeys/hotkeys.xml
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Hotkey
+
+
+
+ Click on any mapping to modify it.\n You may have up to 4 different hotkeys.
+
+
+
+
+
+
+
+
+
+ Click to set the hotkey
+
+
+ Click to delete the hotkey
+
+
+
+
+
+
+ Reset
+ Resets user settings to their game default
+
+
+
+ Accept
+ Change the hotkeys and close
+
+
+
+ Cancel
+ The hotkeys will not be modified
+
+
+
+
Index: binaries/data/mods/public/gui/hotkeys/page_hotkeys.xml
===================================================================
--- /dev/null
+++ binaries/data/mods/public/gui/hotkeys/page_hotkeys.xml
@@ -0,0 +1,15 @@
+
+
+ common/modern/setup.xml
+ common/modern/styles.xml
+ common/modern/sprites.xml
+
+ common/setup.xml
+ common/sprites.xml
+ common/styles.xml
+
+ common/global.xml
+
+ hotkeys/sprites.xml
+ hotkeys/hotkeys.xml
+
Index: binaries/data/mods/public/gui/hotkeys/sprites.xml
===================================================================
--- /dev/null
+++ binaries/data/mods/public/gui/hotkeys/sprites.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
Index: binaries/data/mods/public/gui/manual/intro.txt
===================================================================
--- binaries/data/mods/public/gui/manual/intro.txt
+++ binaries/data/mods/public/gui/manual/intro.txt
@@ -7,13 +7,13 @@
[font="sans-bold-16"]Playing the game[font="sans-14"]
The controls and gameplay should be familiar to players of traditional real-time strategy (RTS) games. There are currently a lot of missing features and poorly-balanced stats – you will probably have to wait until a beta release for it to work well.
-Basic controls:
+Basic controls (by default):
• Left-click to select units
• Left-click-and-drag to select groups of units
• Right-click to order units to the target
• Arrow keys or WASD keys to move the camera
• Ctrl + arrow keys, or shift + mouse wheel, to rotate the camera
-• Mouse wheel, or “=”/“+” and “-”/“−” keys, to zoom
+• Mouse wheel, or “hotkey.camera.zoom.in” and ““hotkey.camera.zoom.out” keys, to zoom
[font="sans-bold-16"]Modes[font="sans-14"]
The main menu gives access to two game modes:
@@ -37,122 +37,118 @@
When you are ready to start, click the “Start game” button.
[font="sans-bold-16"]Hotkeys[font="sans-14"]
+You may change hotkeys in [font="sans-bold-14"]Options > Hotkeys[font="sans-14"] to suit your liking.
+
[font="sans-bold-14"]Program-wide[font="sans-14"]
- Alt + F4 – Immediately close the game, without asking for confirmation
- Alt + Enter (Return) – Toggle between fullscreen and windowed
- ~ or F9 – Toggle console
- Alt + F – Toggle frame counter (FPS)
- F11 – Toggle real-time profiler (cycles through the displays of information)
- Shift + F11 – Save current profiler data to “logs/profile.txt”
- F2 – Take screenshot (in .png format, location is displayed in the top left of the GUI after the file has been saved, and can also be seen in the console/logs if you miss it there)
- Shift + F2 – Take huge screenshot (6400×4800 pixels, in .bmp format, location is displayed in the top left of the GUI after the file has been saved, and can also be seen in the console/logs if you miss it there)
- Tab, Alt + S – Switch to the next tab
- Shift + Tab, Alt + W – Switch to the previous tab
+ hotkey.exit – Immediately close the game, without asking for confirmation
+ hotkey.togglefullscreen – Toggle between fullscreen and windowed
+ hotkey.console.toggle – Toggle console
+ hotkey.fps.toggle – Toggle frame counter (FPS)
+ hotkey.profile.toggle – Toggle real-time profiler (cycles through the displays of information)
+ hotkey.profile.save – Save current profiler data to “logs/profile.txt”
+ hotkey.screenshot – Take screenshot (in .png format, location is displayed in the top left of the GUI after the file has been saved, and can also be seen in the console/logs if you miss it there)
+ hotkey.bigscreenshot – Take huge screenshot (6400×4800 pixels, in .bmp format, location is displayed in the top left of the GUI after the file has been saved, and can also be seen in the console/logs if you miss it there)
+ hotkey.tab.next – Switch to the next tab
+ hotkey.tab.prev – Switch to the previous tab
[font="sans-bold-14"]In Game[font="sans-14"]
Double Left Click \[on unit] – Select all of your units of the same kind on the screen (even if they're different ranks)
Triple Left Click \[on unit] – Select all of your units of the same kind and the same rank on the screen
Alt + Double Left Click \[on unit] – Select all your units of the same kind on the entire map (even if they have different ranks)
Alt + Triple Left Click \[on unit] – Select all your units of the same kind and rank on the entire map
- Shift + F5 – Quicksave
- Shift + F8 – Quickload
- F10 – Toggle menu
- F12 – Toggle time elapsed counter
- Esc – Close all dialogs (chat, menu) or clear selected units
- Enter (Return) – Open chat or send message
- T – Send team chat
- L – Chat with the previously selected private chat partner
- Pause – Pause or resume the game
- Delete – Delete currently selected unit(s)/structure(s), ask for confirmation
- Shift + Delete – Immediately delete currently selected unit(s)/structure(s), without asking for confirmation
- / (Slash) – Select idle fighter
- Shift + / (Slash) – Add idle fighter to selection
- Alt + / (Slash) – Select all idle fighters
- . (Period) – Select idle worker (including citizen-soldiers)
- Shift + . (Period) – Add idle worker to selection (including citizen-soldiers)
- Alt + . (Period) – Select all idle workers (including citizen-soldiers)
- \\ (Backslash) – Select idle unit
- Shift + \\ (Backslash) – Add idle unit to selection
- Alt + \\ (Backslash) – Select all idle units
- H – Stop (halt) the currently selected units
- Y – The unit will go back to work
- U – Unload the garrisoned units of the selected structure(s)
- Ctrl + 1 (and so on up to Ctrl + 0) – Create control group 1 (to 0) from the selected unit(s)/structure(s)
- 1 (and so on up to 0) – Select the unit(s)/structure(s) in control group 1 (to 0)
- Shift + 1 (to 0) – Add control group 1 (to 0) to the selected unit(s)/structure(s)
- Ctrl + F5 (and so on up to F8) – Mark the current camera position, for jumping back to later
- F5 (and so on up to F8) – Move the camera to a marked position. Jump back to the last location if the camera is already over the marked position
- Z, X, C, V, B, N, M – With training structure selected. Add the 1st, 2nd, … unit shown to the training queue for all the selected structures
- PageUp with unit(s) selected – Highlight the unit(s)/structure(s) guarded by the selection
- PageDown with unit(s)/structure(s) selected – Highlight the unit(s) guarding the selection
- Tab – See all status bars (which would also show the building progress)
- Ctrl + Tab – Toggle summary window
- Alt + L – Show the multiplayer lobby in a dialog window
- Ctrl + H – Toggle in-game diplomacy panel
- Ctrl + B – Toggle in-game barter and trade panel
- Ctrl + O – Toggle in-game objectives panel
- Ctrl + P – Toggle in-game tutorial panel
- Alt + Shift + T – Toggle structure tree panel
- Alt + Shift + H – Toggle civilization info panel
+ hotkey.quicksave – Quicksave
+ hotkey.quickload – Quickload
+ hotkey.session.gui.menu.toggle – Toggle menu
+ hotkey.timeelapsedcounter.toggle – Toggle time elapsed counter
+ hotkey.cancel – Close all dialogs (chat, menu)
+ hotkey.confirm – Open chat or send message
+ hotkey.teamchat – Send team chat
+ hotkey.privatechat – Chat with the previously selected private chat partner
+ hotkey.pause – Pause or resume the game
+ hotkey.session.kill – Delete currently selected unit(s)/structure(s), ask for confirmation
+ • With hotkey.session.noconfirmation – Immediately delete currently selected unit(s)/structure(s), without asking for confirmation
+ hotkey.selection.add – Modifier - add to selection (works with clicking and hotkeys, e.g. the idle hotkeys)
+ hotkey.selection.remove – Modifier - remove from selection (works with clicking and hotkeys, e.g. the idle hotkeys)
+ hotkey.selection.offscreen - Modifier - add all units, including offscreen units, to selection.
+ hotkey.selection.cancel – Unselect all units, cancel building placement.
+ hotkey.selection.idlewarrior – Select idle fighter
+ hotkey.selection.idleworker – Select idle worker (including citizen-soldiers)
+ hotkey.selection.idleunit – Select idle unit
+ hotkey.session.stop – Stop (halt) the currently selected units
+ hotkey.session.backtowork – The unit will go back to work
+ hotkey.session.unload – Unload the garrisoned units of the selected structure(s)
+ hotkey.selection.group.save.1 – Create control group 1 (by default, respectively 2,3, ...) from the selected unit(s)/structure(s)
+ hotkey.selection.group.select.1 - Select the unit(s)/structure(s) in control group 1
+ hotkey.selection.group.add.1 – Add control group 1 (to 0) to the selected unit(s)/structure(s)
+ hotkey.camera.jump.set.1 – Mark the current camera position, for jumping back to later (by default, there are 4 possible positions)
+ hotkey.camera.jump.1 – Move the camera to a marked position. Jump back to the last location if the camera is already over the marked position
+ hotkey.session.queueunit.1, hotkey.session.queueunit.2, hotkey.session.queueunit.3, hotkey.session.queueunit.4, hotkey.session.queueunit.5, hotkey.session.queueunit.6, hotkey.session.queueunit.7 – With training structure selected. Add the 1st, 2nd, … unit shown to the training queue for all the selected structures
+ hotkey.session.highlightguarded with unit(s) selected – Highlight the unit(s)/structure(s) guarded by the selection
+ hotkey.session.highlightguarding with unit(s)/structure(s) selected – Highlight the unit(s) guarding the selection
+ highlightguarding.showstatusbars – See all status bars (which would also show the building progress)
+ hotkey.summary – Toggle summary window
+ hotkey.lobby – Show the multiplayer lobby in a dialog window
+ hotkey.session.gui.diplomacy.toggle – Toggle in-game diplomacy panel
+ hotkey.session.gui.barter.toggle – Toggle in-game barter and trade panel
+ hotkey.session.gui.objectives.toggle – Toggle in-game objectives panel
+ hotkey.session.gui.tutorial.toggle – Toggle in-game tutorial panel
+ hotkey.structree – Toggle structure tree panel
+ hotkey.civinfo – Toggle civilization info panel
[font="sans-bold-14"]Modify mouse action[font="sans-14"]
- Ctrl + Right Click on structure – Garrison
- J + Right Click on structure – Repair
- P + Right Click – Patrol
- Shift + Right Click – Queue the move/build/gather/etc. order
- Alt + Right Click – Order one unit from the current selection to move/build/gather/etc. and unselect it. Used to quickly dispatch units with specific tasks
- Shift + Left Click when training units – Add units in batches (the batch size is 5 by default and can be changed in the options)
- Shift + Left Click or Left Drag over unit on map – Add unit to selection
- Ctrl + Left Click or Left Drag over unit on map – Remove unit from selection
- Alt + Left Drag over units on map – Only select military units
- Alt + Y + Left Drag over units on map – Only select non-military units
- I + Left Drag over units on map – Only select idle units
- O + Left Drag over units on map – Only select wounded units
- Ctrl + Left Click on unit/group icon with multiple units selected – Deselect
+ hotkey.session.garrison + Right Click on structure – Garrison
+ hotkey.session.repair + Right Click on structure – Repair
+ hotkey.session.patrol + Right Click – Patrol
+ hotkey.session.queue + Right Click – Queue the move/build/gather/etc. order
+ hotkey.session.orderone + Right Click – Order one unit from the current selection to move/build/gather/etc. and unselect it. Used to quickly dispatch units with specific tasks
+ hotkey.session.batchtrain + Left Click when training units – Add units in batches (the batch size is 5 by default and can be changed in the options)
+ hotkey.selection.add + Left Click or Left Drag over unit on map – Add unit to selection
+ hotkey.selection.remove + Left Click or Left Drag over unit on map – Remove unit from selection
+ hotkey.selection.militaryonly + Left Drag over units on map – Only select military units
+ hotkey.selection.nonmilitaryonly + Left Drag over units on map – Only select non-military units
+ hotkey.selection.idleonly + Left Drag over units on map – Only select idle units
+ hotkey.selection.woundedonly + Left Drag over units on map – Only select wounded units
Right Click with a structure(s) selected – Set a rally point for units created/ungarrisoned from that structure
- Ctrl + Right Click with unit(s) selected:
- • If the cursor is over an own or allied structure – Garrison
- • If the cursor is over an enemy unit/structure – Attack (instead of capture or gather)
- • Otherwise – Attack move (by default all enemy units and structures along the way are targeted)
- Ctrl + Q + Right Click with unit(s) selected – Attack move, only units along the way are targeted
- Ctrl + Mouse Move near structures – Align the new structure with an existing nearby structure
+ hotkey.session.garrison + Right Click with unit(s) selected - Garrison (If the cursor is over an own or allied structure)
+ hotkey.session.attack + Right Click with unit(s) selected - Attack (instead of capture or gather)
+ hotkey.session.attackmove + Right Click with unit(s) selected - Attack move (by default all enemy units and structures along the way are targeted)
+ hotkey.session.attackmoveUnit + Right Click with unit(s) selected - Attack move, only units along the way are targeted
+ hotkey.session.snaptoedges + Mouse Move near structures – Align the new structure with an existing nearby structure
[font="sans-bold-14"]Overlays[font="sans-14"]
- Alt + G – Toggle the GUI
- Alt + D – Toggle developer overlay (with developer options)
- Alt + Shift + W – Toggle wireframe mode (press once to get wireframes overlaid over the textured models, twice to get just the wireframes colored by the textures, thrice to get back to normal textured mode)
- Alt + Shift + S – Toggle unit silhouettes (might give a small performance boost)
- Alt + Z – Toggle sky
- Alt + X – Toggle diplomacy colors
- Alt + C – Toggle attack range visualizations of selected units and structures
- Alt + V – Toggle aura range visualizations of selected units and structures
- Alt + B – Toggle heal range visualizations of selected units
+ hotkey.session.gui.toggle – Toggle the GUI
+ hotkey.session.devcommands.toggle – Toggle developer overlay (with developer options)
+ hotkey.wireframe – Toggle wireframe mode (press once to get wireframes overlaid over the textured models, twice to get just the wireframes colored by the textures, thrice to get back to normal textured mode)
+ hotkey.silhouettes – Toggle unit silhouettes (might give a small performance boost)
+ hotkey.showsky – Toggle sky
+ hotkey.session.diplomacycolors – Toggle diplomacy colors
+ hotkey.session.toggleattackrange – Toggle attack range visualizations of selected units and structures
+ hotkey.session.toggleaurasrange – Toggle aura range visualizations of selected units and structures
+ hotkey.session.togglehealrange – Toggle heal range visualizations of selected units
[font="sans-bold-14"]Camera manipulation[font="sans-14"]
- W or ↑ (Up) – Pan screen up
- S or ↓ (Down) – Pan screen down
- A or ← (Left) – Pan screen left
- D or → (Right) – Pan screen right
- Ctrl + W or Ctrl + ↑ (Up) – Rotate camera to look upward
- Ctrl + S or Ctrl + ↓ (Down) – Rotate camera to look downward
- Ctrl + A or Ctrl + ← (Left) – Rotate camera clockwise around terrain
- Ctrl + D or Ctrl + → (Right) – Rotate camera counter-clockwise around terrain
- Q – Rotate camera clockwise around terrain
- E – Rotate camera counter-clockwise around terrain
- Shift + Mouse Wheel Rotate Up – Rotate camera clockwise around terrain
- Shift + Mouse Wheel Rotate Down – Rotate camera counter-clockwise around terrain
- F – Follow the selected unit (move the camera to stop following the unit)
- R – Reset camera zoom and orientation
- = (Equals) or + (Plus) – Zoom in (keep pressed for continuous zoom)
- - (Hyphen) or − (Minus) – Zoom out (keep pressed for continuous zoom)
- Middle Mouse Button – Keep pressed and move the mouse to pan
+ hotkey.camera.up – Pan screen up
+ hotkey.camera.down – Pan screen down
+ hotkey.camera.left – Pan screen left
+ hotkey.camera.right – Pan screen right
+ hotkey.camera.rotate.up – Rotate camera to look upward
+ hotkey.camera.rotate.down – Rotate camera to look downward
+ hotkey.camera.rotate.cw – Rotate camera clockwise around terrain
+ hotkey.camera.rotate.ccw – Rotate camera counter-clockwise around terrain
+ hotkey.camera.rotate.wheel.cw – Rotate camera clockwise around terrain
+ hotkey.camera.rotate.wheel.ccw – Rotate camera counter-clockwise around terrain
+ hotkey.camera.follow – Follow the selected unit (move the camera to stop following the unit)
+ hotkey.camera.reset – Reset camera zoom and orientation
+ hotkey.camera.zoom.in, hotkey.camera.zoom.wheel.in – Zoom in (keep pressed for continuous zoom)
+ hotkey.camera.zoom.out, hotkey.camera.zoom.wheel.out – Zoom out (keep pressed for continuous zoom)
+ hotkey.camera.pan – Keep pressed and move the mouse to pan
[font="sans-bold-14"]During Structure Placement[font="sans-14"]
- \[ (Left Bracket) – Rotate structure 15 degrees counter-clockwise
- ] (Right Bracket) – Rotate structure 15 degrees clockwise
+ hotkey.session.rotate.ccw – Rotate structure 15 degrees counter-clockwise
+ hotkey.session.rotate.cw – Rotate structure 15 degrees clockwise
Left Drag – Rotate structure using mouse (foundation will be placed on mouse release)
[font="sans-bold-14"]When loading a saved game[font="sans-14"]
- Esc – Cancel
- Delete – Delete the selected saved game, ask for confirmation
- Shift + Delete – Immediately delete the selected saved game, without asking for confirmation
+ hotkey.cancel – Cancel
+ hotkey.session.savedgames.delete – Delete the selected saved game, ask for confirmation
+ • With hotkey.session.savedgames.noconfirmation – Don't ask for confirmation
Index: binaries/data/mods/public/gui/manual/manual.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/gui/manual/manual.js
@@ -0,0 +1,10 @@
+function init()
+{
+ let mainText = Engine.GetGUIObjectByName("mainText");
+ let text = Engine.TranslateLines(Engine.ReadFile("gui/manual/intro.txt"));
+
+ let hotkeys = Engine.GetHotkeyMap();
+
+ // Replace anything starting with 'hotkey.' with its hotkey.
+ mainText.caption = text.replace(/hotkey\.([a-z0-9_\.]+)/g, (_, k) => formatHotkeyCombinations(hotkeys[k]));
+}
Index: binaries/data/mods/public/gui/manual/manual.xml
===================================================================
--- binaries/data/mods/public/gui/manual/manual.xml
+++ binaries/data/mods/public/gui/manual/manual.xml
@@ -14,9 +14,7 @@
-
- this.caption = Engine.TranslateLines(Engine.ReadFile("gui/manual/intro.txt"));
-
+
Index: binaries/data/mods/public/gui/options/options.xml
===================================================================
--- binaries/data/mods/public/gui/options/options.xml
+++ binaries/data/mods/public/gui/options/options.xml
@@ -58,7 +58,7 @@
Close
- Unsaved changes affect this session only
+ Unsaved changes will be lostclosePage();
Index: binaries/data/mods/public/gui/pregame/MainMenuItems.js
===================================================================
--- binaries/data/mods/public/gui/pregame/MainMenuItems.js
+++ binaries/data/mods/public/gui/pregame/MainMenuItems.js
@@ -148,6 +148,13 @@
fireConfigChangeHandlers);
}
},
+ {
+ "caption": translate("Hotkeys"),
+ "tooltip": translate("Adjust hotkeys."),
+ "onPress": () => {
+ Engine.PushGuiPage("hotkeys/page_hotkeys.xml");
+ }
+ },
{
"caption": translate("Language"),
"tooltip": translate("Choose the language of the game."),
Index: binaries/data/mods/public/gui/session/MenuButtons.js
===================================================================
--- binaries/data/mods/public/gui/session/MenuButtons.js
+++ binaries/data/mods/public/gui/session/MenuButtons.js
@@ -174,6 +174,27 @@
}
};
+MenuButtons.prototype.Hotkeys = class
+{
+ constructor(button, pauseControl)
+ {
+ this.button = button;
+ this.button.caption = translate("Hotkeys");
+ this.pauseControl = pauseControl;
+ }
+
+ onPress()
+ {
+ closeOpenDialogs();
+ this.pauseControl.implicitPause();
+
+ Engine.PushGuiPage(
+ "hotkeys/page_hotkeys.xml",
+ {},
+ () => { resumeGame(); });
+ }
+};
+
MenuButtons.prototype.Pause = class
{
constructor(button, pauseControl, playerViewControl)
Index: source/gui/CGUI.cpp
===================================================================
--- source/gui/CGUI.cpp
+++ source/gui/CGUI.cpp
@@ -150,24 +150,22 @@
m_MousePos = CPos((float)ev->ev.button.x / g_GuiScale, (float)ev->ev.button.y / g_GuiScale);
}
+ // Allow the focused object to pre-empt regular GUI events.
+ if (GetFocusedObject())
+ ret = GetFocusedObject()->PreemptEvent(ev);
+
// Only one object can be hovered
- IGUIObject* pNearest = nullptr;
+ // pNearest will after this point at the hovered object, possibly nullptr
+ IGUIObject* pNearest = FindObjectUnderMouse();
- // TODO Gee: (2004-09-08) Big TODO, don't do the below if the SDL_Event is something like a keypress!
+ if (ret == IN_PASS)
{
- PROFILE("mouse events");
- // TODO Gee: Optimizations needed!
- // these two recursive function are quite overhead heavy.
-
- // pNearest will after this point at the hovered object, possibly nullptr
- pNearest = FindObjectUnderMouse();
-
- // Now we'll call UpdateMouseOver on *all* objects,
- // we'll input the one hovered, and they will each
- // update their own data and send messages accordingly
- m_BaseObject.RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::UpdateMouseOver, static_cast(pNearest));
-
- if (ev->ev.type == SDL_MOUSEBUTTONDOWN)
+ if (ev->ev.type == SDL_MOUSEMOTION)
+ // Now we'll call UpdateMouseOver on *all* objects,
+ // we'll input the one hovered, and they will each
+ // update their own data and send messages accordingly
+ m_BaseObject.RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::UpdateMouseOver, static_cast(pNearest));
+ else if (ev->ev.type == SDL_MOUSEBUTTONDOWN)
{
switch (ev->ev.button.button)
{
@@ -252,19 +250,13 @@
if (ev->ev.type == SDL_MOUSEBUTTONDOWN || ev->ev.type == SDL_MOUSEBUTTONUP)
m_MousePos = oldMousePos;
- // Handle keys for input boxes
- if (GetFocusedObject())
+ // Let GUI items handle keys after everything else, e.g. for input boxes.
+ if (ret == IN_PASS && GetFocusedObject())
{
- if ((ev->ev.type == SDL_KEYDOWN &&
- ev->ev.key.keysym.sym != SDLK_ESCAPE &&
- !g_keys[SDLK_LCTRL] && !g_keys[SDLK_RCTRL] &&
- !g_keys[SDLK_LALT] && !g_keys[SDLK_RALT]) ||
- ev->ev.type == SDL_HOTKEYDOWN ||
- ev->ev.type == SDL_TEXTINPUT ||
- ev->ev.type == SDL_TEXTEDITING)
- {
- ret = GetFocusedObject()->ManuallyHandleEvent(ev);
- }
+ if (ev->ev.type == SDL_KEYUP || ev->ev.type == SDL_KEYDOWN ||
+ ev->ev.type == SDL_HOTKEYUP || ev->ev.type == SDL_HOTKEYDOWN ||
+ ev->ev.type == SDL_TEXTINPUT || ev->ev.type == SDL_TEXTEDITING)
+ ret = GetFocusedObject()->ManuallyHandleKeys(ev);
// else will return IN_PASS because we never used the button.
}
@@ -273,6 +265,7 @@
void CGUI::TickObjects()
{
+ m_BaseObject.RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::Tick);
SendEventToAll(EventNameTick);
m_Tooltip.Update(FindObjectUnderMouse(), m_MousePos, *this);
}
Index: source/gui/GUIObjectTypes.h
===================================================================
--- source/gui/GUIObjectTypes.h
+++ source/gui/GUIObjectTypes.h
@@ -21,6 +21,7 @@
#include "gui/ObjectTypes/CChart.h"
#include "gui/ObjectTypes/CCheckBox.h"
#include "gui/ObjectTypes/CDropDown.h"
+#include "gui/ObjectTypes/CHotkeyPicker.h"
#include "gui/ObjectTypes/CImage.h"
#include "gui/ObjectTypes/CInput.h"
#include "gui/ObjectTypes/CList.h"
@@ -39,6 +40,7 @@
AddObjectType("checkbox", &CCheckBox::ConstructObject);
AddObjectType("dropdown", &CDropDown::ConstructObject);
AddObjectType("empty", &CGUIDummyObject::ConstructObject);
+ AddObjectType("hotkeypicker", &CHotkeyPicker::ConstructObject);
AddObjectType("image", &CImage::ConstructObject);
AddObjectType("input", &CInput::ConstructObject);
AddObjectType("list", &CList::ConstructObject);
Index: source/gui/ObjectBases/IGUIObject.h
===================================================================
--- source/gui/ObjectBases/IGUIObject.h
+++ source/gui/ObjectBases/IGUIObject.h
@@ -255,6 +255,12 @@
//@{
public:
+
+ /**
+ * Called on every GUI tick unless the object or one of its parent is hidden/ghost.
+ */
+ virtual void Tick() {};
+
/**
* This function is called with different messages
* for instance when the mouse enters the object.
@@ -290,7 +296,17 @@
virtual void Draw() = 0;
/**
- * Some objects need to handle the SDL_Event_ manually.
+ * Some objects need to be able to pre-emptively process SDL_Event_.
+ *
+ * Only the object with focus will have this function called.
+ *
+ * Returns either IN_PASS or IN_HANDLED. If IN_HANDLED, then
+ * the event won't be passed on and processed by other handlers.
+ */
+ virtual InReaction PreemptEvent(const SDL_Event_* UNUSED(ev)) { return IN_PASS; }
+
+ /**
+ * Some objects need to handle the text-related SDL_Event_ manually.
* For instance the input box.
*
* Only the object with focus will have this function called.
@@ -299,7 +315,7 @@
* the key won't be passed on and processed by other handlers.
* This is used for keys that the GUI uses.
*/
- virtual InReaction ManuallyHandleEvent(const SDL_Event_* UNUSED(ev)) { return IN_PASS; }
+ virtual InReaction ManuallyHandleKeys(const SDL_Event_* UNUSED(ev)) { return IN_PASS; }
/**
* Loads a style.
Index: source/gui/ObjectTypes/CDropDown.h
===================================================================
--- source/gui/ObjectTypes/CDropDown.h
+++ source/gui/ObjectTypes/CDropDown.h
@@ -58,7 +58,7 @@
/**
* Handle events manually to catch keyboard inputting.
*/
- virtual InReaction ManuallyHandleEvent(const SDL_Event_* ev);
+ virtual InReaction ManuallyHandleKeys(const SDL_Event_* ev);
/**
* Draws the Button
Index: source/gui/ObjectTypes/CDropDown.cpp
===================================================================
--- source/gui/ObjectTypes/CDropDown.cpp
+++ source/gui/ObjectTypes/CDropDown.cpp
@@ -273,7 +273,7 @@
SetupText();
}
-InReaction CDropDown::ManuallyHandleEvent(const SDL_Event_* ev)
+InReaction CDropDown::ManuallyHandleKeys(const SDL_Event_* ev)
{
InReaction result = IN_PASS;
bool update_highlight = false;
@@ -298,7 +298,7 @@
if (!m_Open)
return IN_PASS;
// Set current selected item to highlighted, before
- // then really processing these in CList::ManuallyHandleEvent()
+ // then really processing these in CList::ManuallyHandleKeys()
SetSetting("selected", m_ElementHighlight, true);
update_highlight = true;
break;
@@ -356,7 +356,7 @@
}
}
- if (CList::ManuallyHandleEvent(ev) == IN_HANDLED)
+ if (CList::ManuallyHandleKeys(ev) == IN_HANDLED)
result = IN_HANDLED;
if (update_highlight)
Index: source/gui/ObjectTypes/CHotkeyPicker.h
===================================================================
--- /dev/null
+++ source/gui/ObjectTypes/CHotkeyPicker.h
@@ -0,0 +1,78 @@
+/* Copyright (C) 2020 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#ifndef INCLUDED_CHOTKEYPICKER
+#define INCLUDED_CHOTKEYPICKER
+
+#include "lib/external_libraries/libsdl.h"
+#include "ps/CStr.h"
+
+class ScriptInterface;
+
+/**
+ * When in focus, returns all currently pressed keys.
+ * After a set time without changes, it will trigger a "combination" event.
+ *
+ * Used to create new hotkey combinations in-game. Mostly custom.
+ * This object does not draw anything.
+ *
+ * NB: because of how input is handled, mouse clicks
+ */
+class CHotkeyPicker : public IGUIObject
+{
+ GUI_OBJECT(CHotkeyPicker)
+
+ friend class ScriptInterface;
+public:
+ CHotkeyPicker(CGUI& pGUI);
+ virtual ~CHotkeyPicker();
+
+ // Do nothing.
+ virtual void Draw() {};
+
+ // Checks if the timer has passed and we need to fire a "combination" event.
+ virtual void Tick();
+
+ // React to blur/focus.
+ virtual void HandleMessage(SGUIMessage& Message);
+
+ // Pre-empt events: this is our sole purpose.
+ virtual InReaction PreemptEvent(const SDL_Event_* ev);
+protected:
+ // Fire an event with m_KeysPressed as argument.
+ void FireEvent(const CStr& event);
+
+ // Time without changes until a "combination" event is sent.
+ float m_TimeToCombination;
+ // Time of the last registered key change.
+ double m_LastKeyChange;
+
+ // Keep track of which keys we are pressing, and precompute their name for JS code.
+ struct Key
+ {
+ // The scancode is used for fast comparisons.
+ SDL_Scancode code;
+ // This is the name ultimately stored in the config file.
+ CStr scancodeName;
+ };
+ std::vector m_KeysPressed;
+
+ static const CStr EventNameCombination;
+ static const CStr EventNameKeyChange;
+};
+
+#endif // INCLUDED_CHOTKEYPICKER
Index: source/gui/ObjectTypes/CHotkeyPicker.cpp
===================================================================
--- /dev/null
+++ source/gui/ObjectTypes/CHotkeyPicker.cpp
@@ -0,0 +1,193 @@
+/* Copyright (C) 2020 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#include "precompiled.h"
+
+#include "CHotkeyPicker.h"
+
+#include "lib/timer.h"
+#include "ps/Hotkey.h"
+#include "ps/KeyName.h"
+#include "scriptinterface/ScriptConversions.h"
+
+const CStr CHotkeyPicker::EventNameCombination = "Combination";
+const CStr CHotkeyPicker::EventNameKeyChange = "KeyChange";
+
+// Don't send the scancode, JS doesn't care.
+template<> void ScriptInterface::ToJSVal(JSContext *cx, JS::MutableHandleValue ret, const CHotkeyPicker::Key& val)
+{
+ ScriptInterface::ToJSVal(cx, ret, val.scancodeName);
+}
+
+// Unused, but JSVAL_VECTOR requires it.
+template<> bool ScriptInterface::FromJSVal(JSContext*, const JS::HandleValue, CHotkeyPicker::Key&)
+{
+ LOGWARNING("FromJSVal: Not implemented");
+ return false;
+}
+
+JSVAL_VECTOR(CHotkeyPicker::Key);
+
+CHotkeyPicker::CHotkeyPicker(CGUI& pGUI) : IGUIObject(pGUI), m_TimeToCombination(1.f)
+{
+ RegisterSetting("time_to_combination", m_TimeToCombination);
+ // 8 keys at the same time is probably more than we'll ever need.
+ m_KeysPressed.reserve(8);
+}
+
+CHotkeyPicker::~CHotkeyPicker()
+{
+}
+
+void CHotkeyPicker::FireEvent(const CStr& event)
+{
+ JSContext* cx = m_pGUI.GetScriptInterface()->GetContext();
+ JSAutoRequest rq(cx);
+
+ JS::AutoValueArray<1> args(cx);
+ JS::RootedValue keys(cx);
+ m_pGUI.GetScriptInterface()->ToJSVal(cx, &keys, m_KeysPressed);
+ args[0].set(keys);
+ ScriptEvent(event, args);
+}
+
+void CHotkeyPicker::Tick()
+{
+ if (m_KeysPressed.size() == 0)
+ return;
+
+ double time = timer_Time();
+ if (time - m_LastKeyChange < m_TimeToCombination)
+ return;
+
+ FireEvent(EventNameCombination);
+
+ return;
+}
+
+void CHotkeyPicker::HandleMessage(SGUIMessage& Message)
+{
+ IGUIObject::HandleMessage(Message);
+ switch (Message.type)
+ {
+ case GUIM_GOT_FOCUS:
+ case GUIM_LOST_FOCUS:
+ {
+ m_KeysPressed.clear();
+ m_LastKeyChange = timer_Time();
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+InReaction CHotkeyPicker::PreemptEvent(const SDL_Event_* ev)
+{
+ switch (ev->ev.type)
+ {
+ // Handle the same mouse events that hotkeys handle
+ case SDL_MOUSEBUTTONDOWN:
+ case SDL_MOUSEBUTTONUP:
+ case SDL_MOUSEWHEEL:
+ {
+ SDL_Scancode scancode;
+
+ if (ev->ev.type != SDL_MOUSEWHEEL)
+ {
+ // Wait a little bit -> this gets triggered when clicking on a button,
+ // but after the button click is processed, thus immediately triggering...
+ if (timer_Time()-m_LastKeyChange < 0.2)
+ return IN_HANDLED;
+ // This is from hotkeyHandler - not sure what it does in all honesty.
+ if(ev->ev.button.button >= SDL_BUTTON_X1)
+ scancode = static_cast(MOUSE_BASE + (int)ev->ev.button.button + 2);
+ else
+ scancode = static_cast(MOUSE_BASE + (int)ev->ev.button.button);
+ }
+ else
+ {
+ if (ev->ev.wheel.y > 0)
+ scancode = static_cast(MOUSE_WHEELUP);
+ else if (ev->ev.wheel.y < 0)
+ scancode = static_cast(MOUSE_WHEELDOWN);
+ else if (ev->ev.wheel.x > 0)
+ scancode = static_cast(MOUSE_X2);
+ else if (ev->ev.wheel.x < 0)
+ scancode = static_cast(MOUSE_X1);
+ else
+ return IN_HANDLED;
+ }
+ // Don't handle keys and mouse together except for modifiers.
+ std::remove_if(m_KeysPressed.begin(), m_KeysPressed.end(), [](const Key& k) { return k.code < UNIFIED_SHIFT || k.code >= UNIFIED_LAST; } );
+ m_KeysPressed.emplace_back(Key{scancode, FindScancodeName(scancode)});
+ // For mouse events, assume we immediately want to return.
+ FireEvent(EventNameCombination);
+
+ return IN_HANDLED;
+ }
+ case SDL_KEYDOWN:
+ case SDL_KEYUP:
+ {
+ SDL_Scancode scancode = ev->ev.key.keysym.scancode;
+
+ // Don't handle caps-lock, it doesn't really work in-game and it's a weird hotkey.
+ if (scancode == SDL_SCANCODE_CAPSLOCK)
+ return IN_PASS;
+
+ if (scancode == SDL_SCANCODE_LSHIFT || scancode == SDL_SCANCODE_RSHIFT)
+ scancode = static_cast(UNIFIED_SHIFT);
+ else if (scancode == SDL_SCANCODE_LCTRL || scancode == SDL_SCANCODE_RCTRL)
+ scancode = static_cast(UNIFIED_CTRL);
+ else if (scancode == SDL_SCANCODE_LALT || scancode == SDL_SCANCODE_RALT)
+ scancode = static_cast(UNIFIED_ALT);
+ else if (scancode == SDL_SCANCODE_LGUI || scancode == SDL_SCANCODE_RGUI)
+ scancode = static_cast(UNIFIED_SUPER);
+
+ printf("press %i\n", scancode);
+
+ if (ev->ev.type == SDL_KEYDOWN)
+ {
+ std::vector::const_iterator it = \
+ std::find_if(m_KeysPressed.begin(), m_KeysPressed.end(), [&scancode](Key& k) { return k.code == scancode; });
+ // Can happen if multiple keys are mapped the same.
+ if (it != m_KeysPressed.end())
+ return IN_HANDLED;
+ m_KeysPressed.emplace_back(Key{scancode, FindScancodeName(scancode)});
+ }
+ else
+ {
+ std::vector::const_iterator it = \
+ std::find_if(m_KeysPressed.begin(), m_KeysPressed.end(), [&scancode](Key& k) { return k.code == scancode; });
+ // Might happen if a key was down before this object is created.
+ if (it == m_KeysPressed.end())
+ return IN_HANDLED;
+ m_KeysPressed.erase(it);
+ }
+
+ FireEvent(EventNameKeyChange);
+
+ // Register after-JS in case this takes a while (probably not but it doesn't hurt).
+ m_LastKeyChange = timer_Time();
+ return IN_HANDLED;
+ }
+ default:
+ {
+ return IN_PASS;
+ }
+ }
+}
Index: source/gui/ObjectTypes/CInput.h
===================================================================
--- source/gui/ObjectTypes/CInput.h
+++ source/gui/ObjectTypes/CInput.h
@@ -64,7 +64,7 @@
/**
* Handle events manually to catch keyboard inputting.
*/
- virtual InReaction ManuallyHandleEvent(const SDL_Event_* ev);
+ virtual InReaction ManuallyHandleKeys(const SDL_Event_* ev);
/**
* Handle events manually to catch keys which change the text.
@@ -77,7 +77,7 @@
virtual void ManuallyImmutableHandleKeyDownEvent(const SDL_Keycode keyCode);
/**
- * Handle hotkey events (called by ManuallyHandleEvent)
+ * Handle hotkey events (called by ManuallyHandleKeys)
*/
virtual InReaction ManuallyHandleHotkeyEvent(const SDL_Event_* ev);
Index: source/gui/ObjectTypes/CInput.cpp
===================================================================
--- source/gui/ObjectTypes/CInput.cpp
+++ source/gui/ObjectTypes/CInput.cpp
@@ -114,7 +114,7 @@
m_iComposedPos = 0;
}
-InReaction CInput::ManuallyHandleEvent(const SDL_Event_* ev)
+InReaction CInput::ManuallyHandleKeys(const SDL_Event_* ev)
{
ENSURE(m_iBufferPos != -1);
@@ -1117,7 +1117,7 @@
evt.ev.edit.length = 0;
evt.ev.edit.start = 0;
evt.ev.edit.text[0] = 0;
- ManuallyHandleEvent(&evt);
+ ManuallyHandleKeys(&evt);
}
SDL_StopTextInput();
Index: source/gui/ObjectTypes/CList.h
===================================================================
--- source/gui/ObjectTypes/CList.h
+++ source/gui/ObjectTypes/CList.h
@@ -73,7 +73,7 @@
/**
* Handle events manually to catch keyboard inputting.
*/
- virtual InReaction ManuallyHandleEvent(const SDL_Event_* ev);
+ virtual InReaction ManuallyHandleKeys(const SDL_Event_* ev);
/**
* Draws the List box
Index: source/gui/ObjectTypes/CList.cpp
===================================================================
--- source/gui/ObjectTypes/CList.cpp
+++ source/gui/ObjectTypes/CList.cpp
@@ -251,7 +251,7 @@
IGUITextOwner::HandleMessage(Message);
}
-InReaction CList::ManuallyHandleEvent(const SDL_Event_* ev)
+InReaction CList::ManuallyHandleKeys(const SDL_Event_* ev)
{
InReaction result = IN_PASS;
Index: source/gui/Scripting/ScriptFunctions.cpp
===================================================================
--- source/gui/Scripting/ScriptFunctions.cpp
+++ source/gui/Scripting/ScriptFunctions.cpp
@@ -30,6 +30,7 @@
#include "ps/scripting/JSInterface_Console.h"
#include "ps/scripting/JSInterface_Debug.h"
#include "ps/scripting/JSInterface_Game.h"
+#include "ps/scripting/JSInterface_Hotkey.h"
#include "ps/scripting/JSInterface_Main.h"
#include "ps/scripting/JSInterface_Mod.h"
#include "ps/scripting/JSInterface_ModIo.h"
@@ -59,6 +60,7 @@
JSI_GUIManager::RegisterScriptFunctions(scriptInterface);
JSI_Game::RegisterScriptFunctions(scriptInterface);
JSI_GameView::RegisterScriptFunctions(scriptInterface);
+ JSI_Hotkey::RegisterScriptFunctions(scriptInterface);
JSI_L10n::RegisterScriptFunctions(scriptInterface);
JSI_Lobby::RegisterScriptFunctions(scriptInterface);
JSI_Main::RegisterScriptFunctions(scriptInterface);
Index: source/gui/tests/test_GuiManager.h
===================================================================
--- source/gui/tests/test_GuiManager.h
+++ source/gui/tests/test_GuiManager.h
@@ -73,7 +73,7 @@
// Press 'a'.
SDL_Event_ hotkeyNotification;
hotkeyNotification.ev.type = SDL_KEYDOWN;
- hotkeyNotification.ev.key.keysym.sym = SDLK_a;
+ hotkeyNotification.ev.key.keysym.scancode = SDL_SCANCODE_A;
hotkeyNotification.ev.key.repeat = 0;
// Init input and poll the event.
Index: source/ps/ConfigDB.h
===================================================================
--- source/ps/ConfigDB.h
+++ source/ps/ConfigDB.h
@@ -110,6 +110,8 @@
void SetValueBool(EConfigNamespace ns, const CStr& name, const bool value);
+ void SetValueList(EConfigNamespace ns, const CStr& name, std::vector values);
+
/**
* Remove a config value in the specified namespace.
*/
Index: source/ps/ConfigDB.cpp
===================================================================
--- source/ps/ConfigDB.cpp
+++ source/ps/ConfigDB.cpp
@@ -210,6 +210,18 @@
SetValueString(ns, name, valueString);
}
+void CConfigDB::SetValueList(EConfigNamespace ns, const CStr& name, std::vector values)
+{
+ CHECK_NS(;);
+
+ std::lock_guard s(cfgdb_mutex);
+ TConfigMap::iterator it = m_Map[ns].find(name);
+ if (it == m_Map[ns].end())
+ it = m_Map[ns].insert(m_Map[ns].begin(), make_pair(name, CConfigValueSet(1)));
+
+ it->second = values;
+}
+
void CConfigDB::RemoveValue(EConfigNamespace ns, const CStr& name)
{
CHECK_NS(;);
Index: source/ps/Hotkey.h
===================================================================
--- source/ps/Hotkey.h
+++ source/ps/Hotkey.h
@@ -33,15 +33,45 @@
#include "CStr.h"
#include "lib/input.h"
-#include "lib/external_libraries/libsdl.h" // see note below
-// note: we need the real SDL header - it defines SDL_USEREVENT, which is
-// required for our HOTKEY event type definition. this is OK since
-// hotkey.h is not included from any headers.
+#include
-const uint SDL_HOTKEYPRESS = SDL_USEREVENT;
-const uint SDL_HOTKEYDOWN = SDL_USEREVENT + 1;
-const uint SDL_HOTKEYUP = SDL_USEREVENT + 2;
+// SDL_Scancode is an enum, we'll use an explicit int to avoid including SDL in this header.
+using SDL_Scancode_ = int;
+
+// 0x8000 is SDL_USEREVENT, this is static_asserted in Hotkey.cpp
+// We do this to avoid including SDL in this header.
+const uint SDL_USEREVENT_ = 0x8000;
+const uint SDL_HOTKEYPRESS = SDL_USEREVENT_;
+const uint SDL_HOTKEYDOWN = SDL_USEREVENT_ + 1;
+const uint SDL_HOTKEYUP = SDL_USEREVENT_ + 2;
+
+struct SKey
+{
+ SDL_Scancode_ code; // scancode or MOUSE_ or UNIFIED_ value
+ bool negated; // whether the key must be pressed (false) or unpressed (true)
+
+ bool operator<(const SKey& o) const { return code < o.code && negated < o.negated; }
+ bool operator==(const SKey& o) const { return code == o.code && negated == o.negated; }
+};
+
+// Hotkey data associated with an externally-specified 'primary' keycode
+struct SHotkeyMapping
+{
+ CStr name; // name of the hotkey
+ bool negated; // whether the primary key must be pressed (false) or unpressed (true)
+ std::vector requires; // list of non-primary keys that must also be active
+};
+
+typedef std::vector KeyMapping;
+
+// A mapping of scancodes onto the hotkeys that are associated with that key.
+// (A hotkey triggered by a combination of multiple keys will be in this map
+// multiple times.)
+extern std::unordered_map g_HotkeyMap;
+
+// The current pressed status of hotkeys
+extern std::unordered_map g_HotkeyStatus;
extern void LoadHotkeys();
extern void UnloadHotkeys();
Index: source/ps/Hotkey.cpp
===================================================================
--- source/ps/Hotkey.cpp
+++ source/ps/Hotkey.cpp
@@ -20,6 +20,7 @@
#include
+#include "lib/external_libraries/libsdl.h"
#include "ps/CConsole.h"
#include "ps/CLogger.h"
#include "ps/CStr.h"
@@ -29,29 +30,11 @@
static bool unified[UNIFIED_LAST - UNIFIED_SHIFT];
-struct SKey
-{
- SDL_Keycode code; // keycode or MOUSE_ or UNIFIED_ value
- bool negated; // whether the key must be pressed (false) or unpressed (true)
-};
+std::unordered_map g_HotkeyMap;
+std::unordered_map g_HotkeyStatus;
-// Hotkey data associated with an externally-specified 'primary' keycode
-struct SHotkeyMapping
-{
- CStr name; // name of the hotkey
- bool negated; // whether the primary key must be pressed (false) or unpressed (true)
- std::vector requires; // list of non-primary keys that must also be active
-};
-
-typedef std::vector KeyMapping;
-
-// A mapping of keycodes onto the hotkeys that are associated with that key.
-// (A hotkey triggered by a combination of multiple keys will be in this map
-// multiple times.)
-static std::map g_HotkeyMap;
-
-// The current pressed status of hotkeys
-std::map g_HotkeyStatus;
+static_assert(std::is_integral::type>::value, "SDL_Scancode is not an integral enum.");
+static_assert(SDL_USEREVENT_ == SDL_USEREVENT, "SDL_USEREVENT_ is not the same type as the real SDL_USEREVENT");
// Look up each key binding in the config file and set the mappings for
// all key combinations that trigger it.
@@ -74,16 +57,14 @@
for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it)
{
// Attempt decode as key name
- int mapping = FindKeyCode(*it);
- if (!mapping)
- mapping = SDL_GetKeyFromName(it->c_str());
- if (!mapping)
+ SDL_Scancode scancode = FindScancode(it->c_str());
+ if (!scancode)
{
LOGWARNING("Hotkey mapping used invalid key '%s'", hotkey.c_str());
continue;
}
- SKey key = { (SDL_Keycode)mapping, false };
+ SKey key = { scancode, false };
keyCombination.push_back(key);
}
@@ -107,8 +88,6 @@
void LoadHotkeys()
{
- InitKeyNameMap();
-
LoadConfigBindings();
// Set up the state of the hotkeys given no key is down.
@@ -146,7 +125,7 @@
else if ((int)key.code < MOUSE_LAST && (int)key.code > MOUSE_BASE && g_mouse_buttons[key.code - MOUSE_BASE] == key.negated)
return false;
// Modifier keycodes are between the normal keys and the mouse 'keys'
- else if ((int)key.code < UNIFIED_LAST && (int)key.code > SDL_SCANCODE_TO_KEYCODE(SDL_NUM_SCANCODES) && unified[key.code - UNIFIED_SHIFT] == key.negated)
+ else if ((int)key.code < UNIFIED_LAST && (int)key.code > SDL_NUM_SCANCODES && unified[key.code - UNIFIED_SHIFT] == key.negated)
return false;
else
return true;
@@ -163,13 +142,13 @@
InReaction HotkeyInputHandler(const SDL_Event_* ev)
{
- int keycode = 0;
+ int scancode = SDL_SCANCODE_UNKNOWN;
switch(ev->ev.type)
{
case SDL_KEYDOWN:
case SDL_KEYUP:
- keycode = (int)ev->ev.key.keysym.sym;
+ scancode = ev->ev.key.keysym.scancode;
break;
case SDL_MOUSEBUTTONDOWN:
@@ -177,30 +156,30 @@
// Mousewheel events are no longer buttons, but we want to maintain the order
// expected by g_mouse_buttons for compatibility
if (ev->ev.button.button >= SDL_BUTTON_X1)
- keycode = MOUSE_BASE + (int)ev->ev.button.button + 2;
+ scancode = MOUSE_BASE + (int)ev->ev.button.button + 2;
else
- keycode = MOUSE_BASE + (int)ev->ev.button.button;
+ scancode = MOUSE_BASE + (int)ev->ev.button.button;
break;
case SDL_MOUSEWHEEL:
if (ev->ev.wheel.y > 0)
{
- keycode = MOUSE_WHEELUP;
+ scancode = MOUSE_WHEELUP;
break;
}
else if (ev->ev.wheel.y < 0)
{
- keycode = MOUSE_WHEELDOWN;
+ scancode = MOUSE_WHEELDOWN;
break;
}
else if (ev->ev.wheel.x > 0)
{
- keycode = MOUSE_X2;
+ scancode = MOUSE_X2;
break;
}
else if (ev->ev.wheel.x < 0)
{
- keycode = MOUSE_X1;
+ scancode = MOUSE_X1;
break;
}
return IN_PASS;
@@ -219,33 +198,33 @@
if (phantom.ev.type == SDL_KEYDOWN)
phantom.ev.key.repeat = ev->ev.type == SDL_KEYDOWN ? ev->ev.key.repeat : 0;
- if ((keycode == SDLK_LSHIFT) || (keycode == SDLK_RSHIFT))
+ if (scancode == SDL_SCANCODE_LSHIFT || scancode == SDL_SCANCODE_RSHIFT)
{
- phantom.ev.key.keysym.sym = (SDL_Keycode)UNIFIED_SHIFT;
+ phantom.ev.key.keysym.scancode = static_cast(UNIFIED_SHIFT);
unified[0] = (phantom.ev.type == SDL_KEYDOWN);
HotkeyInputHandler(&phantom);
}
- else if ((keycode == SDLK_LCTRL) || (keycode == SDLK_RCTRL))
+ else if (scancode == SDL_SCANCODE_LCTRL || scancode == SDL_SCANCODE_RCTRL)
{
- phantom.ev.key.keysym.sym = (SDL_Keycode)UNIFIED_CTRL;
+ phantom.ev.key.keysym.scancode = static_cast(UNIFIED_CTRL);
unified[1] = (phantom.ev.type == SDL_KEYDOWN);
HotkeyInputHandler(&phantom);
}
- else if ((keycode == SDLK_LALT) || (keycode == SDLK_RALT))
+ else if (scancode == SDL_SCANCODE_LALT || scancode == SDL_SCANCODE_RALT)
{
- phantom.ev.key.keysym.sym = (SDL_Keycode)UNIFIED_ALT;
+ phantom.ev.key.keysym.scancode = static_cast(UNIFIED_ALT);
unified[2] = (phantom.ev.type == SDL_KEYDOWN);
HotkeyInputHandler(&phantom);
}
- else if ((keycode == SDLK_LGUI) || (keycode == SDLK_RGUI))
+ else if (scancode == SDL_SCANCODE_LGUI || scancode == SDL_SCANCODE_RGUI)
{
- phantom.ev.key.keysym.sym = (SDL_Keycode)UNIFIED_SUPER;
+ phantom.ev.key.keysym.scancode = static_cast(UNIFIED_SUPER);
unified[3] = (phantom.ev.type == SDL_KEYDOWN);
HotkeyInputHandler(&phantom);
}
// Check whether we have any hotkeys registered for this particular keycode
- if (g_HotkeyMap.find(keycode) == g_HotkeyMap.end())
+ if (g_HotkeyMap.find(scancode) == g_HotkeyMap.end())
return (IN_PASS);
// Inhibit the dispatch of hotkey events caused by real keys (not fake mouse button
@@ -253,7 +232,7 @@
bool consoleCapture = false;
- if (g_Console && g_Console->IsActive() && keycode < SDL_SCANCODE_TO_KEYCODE(SDL_NUM_SCANCODES))
+ if (g_Console && g_Console->IsActive() && scancode < SDL_NUM_SCANCODES)
consoleCapture = true;
// Here's an interesting bit:
@@ -273,7 +252,7 @@
std::vector closestMapNames;
size_t closestMapMatch = 0;
- for (const SHotkeyMapping& hotkey : g_HotkeyMap[keycode])
+ for (const SHotkeyMapping& hotkey : g_HotkeyMap[scancode])
{
// If a key has been pressed, and this event triggers on its release, skip it.
// Similarly, if the key's been released and the event triggers on a keypress, skip it.
@@ -329,7 +308,7 @@
// -- KEYUP SECTION --
- for (const SHotkeyMapping& hotkey : g_HotkeyMap[keycode])
+ for (const SHotkeyMapping& hotkey : g_HotkeyMap[scancode])
{
// If it's a keydown event, won't cause HotKeyUps in anything that doesn't
// use this key negated => skip them
Index: source/ps/KeyName.h
===================================================================
--- source/ps/KeyName.h
+++ source/ps/KeyName.h
@@ -23,14 +23,16 @@
class CStr8;
-extern void InitKeyNameMap();
-extern CStr8 FindKeyName(int keycode);
-extern int FindKeyCode(const CStr8& keyname);
+extern SDL_Scancode FindScancode(const CStr& keyname);
+// Map a scancode to a locale-independent scancode name.
+extern CStr8 FindScancodeName(SDL_Scancode scancode);
+// Map a scancode to a locale-dependent key name (to show the user).
+extern CStr8 FindKeyName(SDL_Scancode scancode);
enum {
// Start sequential IDs in the right place
- // Pick a code which is greater than any keycodes used by SDL itself
- EXTRA_KEYS_BASE = SDL_SCANCODE_TO_KEYCODE(SDL_NUM_SCANCODES),
+ // Pick a code which is greater than any scancodes used by SDL itself
+ EXTRA_KEYS_BASE = SDL_NUM_SCANCODES,
// 'Keycodes' for the unified modifier keys
UNIFIED_SHIFT,
UNIFIED_CTRL,
Index: source/ps/KeyName.cpp
===================================================================
--- source/ps/KeyName.cpp
+++ source/ps/KeyName.cpp
@@ -24,215 +24,178 @@
#include "lib/external_libraries/libsdl.h"
#include "ps/CStr.h"
-#include