diff --git a/src/web/BindingsWaiter.js b/src/web/BindingsWaiter.js new file mode 100644 index 00000000..2454ed59 --- /dev/null +++ b/src/web/BindingsWaiter.js @@ -0,0 +1,204 @@ +/** + * Waiter to handle keybindings to CyberChef functions (i.e. Bake, Step, Save, Load etc.) + * + * @author Matt C [matt@artemisbot.uk] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * @constructor + * @param {App} app - The main view object for CyberChef. + * @param {Manager} manager - The CyberChef event manager. + */ +const BindingsWaiter = function (app, manager) { + this.app = app; + this.manager = manager; +}; + + +/** + * Handler for all keydown events + * Checks whether valid keyboard shortcut has been instated + * + * @fires Manager#statechange + * @param {event} e + */ +BindingsWaiter.prototype.parseInput = function(e) { + let modKey = e.altKey; + if (this.app.options.useMetaKey) modKey = e.metaKey; + if (e.ctrlKey && modKey) { + let elem; + switch (e.code) { + case "KeyF": + e.preventDefault(); + document.getElementById("search").focus(); // Focus search + break; + case "KeyI": + e.preventDefault(); + document.getElementById("input-text").focus(); // Focus input + break; + case "KeyO": + e.preventDefault(); + document.getElementById("output-text").focus(); // Focus output + break; + case "Period": + try { + elem = document.activeElement.closest(".operation"); + if (elem.parentNode.lastChild === elem) { + elem.parentNode.firstChild.querySelectorAll(".arg")[0].focus(); // if operation is last in recipe, loop around to the top operation's first argument + } else { + elem.nextSibling.querySelectorAll(".arg")[0].focus(); //focus first argument of next operation + } + } catch (e) { + // do nothing, just don't throw an error + } + break; + case "KeyB": + try { + elem = document.activeElement.closest(".operation").querySelectorAll(".breakpoint")[0]; + if (elem.getAttribute("break") === "false") { + elem.setAttribute("break", "true"); // add break point if not already enabled + elem.classList.add("breakpoint-selected"); + } else { + elem.setAttribute("break", "false"); // remove break point if already enabled + elem.classList.remove("breakpoint-selected"); + } + window.dispatchEvent(this.manager.statechange); + } catch (e) { + // do nothing, just don't throw an error + } + break; + case "KeyD": + try { + elem = document.activeElement.closest(".operation").querySelectorAll(".disable-icon")[0]; + if (elem.getAttribute("disabled") === "false") { + elem.setAttribute("disabled", "true"); // disable operation if enabled + elem.classList.add("disable-elem-selected"); + elem.parentNode.parentNode.classList.add("disabled"); + } else { + elem.setAttribute("disabled", "false"); // enable operation if disabled + elem.classList.remove("disable-elem-selected"); + elem.parentNode.parentNode.classList.remove("disabled"); + } + this.app.progress = 0; + window.dispatchEvent(this.manager.statechange); + } catch (e) { + // do nothing, just don't throw an error + } + break; + case "Space": + this.app.bake(); // bake the recipe + break; + case "Quote": + this.app.bake(true); // step through the recipe + break; + case "KeyC": + this.manager.recipe.clearRecipe(); // clear recipe + break; + case "KeyS": + this.manager.output.saveClick(); // save output to file + break; + case "KeyL": + this.manager.controls.loadClick(); // load a recipe + break; + case "KeyM": + this.manager.controls.switchClick(); // switch input & output + break; + default: + if (e.code.match(/Digit[0-9]/g)) { + e.preventDefault(); + try { + document.querySelector(`li:nth-child(${e.code.substr(-1)}) .arg`).focus(); // select the first argument of the operation corresponding to the number pressed + } catch (e) { + // do nothing, just don't throw an error + } + } + break; + } + } +}; + +/** + * Updates keybinding list when metaKey option is toggled + * + */ +BindingsWaiter.prototype.updateKeybList = function() { + let modWinLin = "Alt"; + let modMac = "Opt"; + if (this.app.options.useMetaKey) { + modWinLin = "Win"; + modMac = "Cmd"; + } + document.getElementById("keybList").innerHTML = ` + + Command + Shortcut (Win/Linux) + Shortcut (Mac) + + + Place cursor in search field + Ctrl+${modWinLin}+f + Ctrl+${modMac}+f + + Place cursor in input box + Ctrl+${modWinLin}+i + Ctrl+${modMac}+i + + + Place cursor in output box + Ctrl+${modWinLin}+o + Ctrl+${modMac}+o + + + Place cursor in first argument field
of the next operation in the recipe + Ctrl+${modWinLin}+. + Ctrl+${modMac}+. + + + Place cursor in first argument field
of the nth operation in the recipe + Ctrl+${modWinLin}+[1-9] + Ctrl+${modMac}+[1-9] + + + Disable current operation + Ctrl+${modWinLin}+d + Ctrl+${modMac}+d + + + Set/clear breakpoint + Ctrl+${modWinLin}+b + Ctrl+${modMac}+b + + + Bake + Ctrl+${modWinLin}+Space + Ctrl+${modMac}+Space + + + Step + Ctrl+${modWinLin}+' + Ctrl+${modMac}+' + + + Clear recipe + Ctrl+${modWinLin}+c + Ctrl+${modMac}+c + + + Save to file + Ctrl+${modWinLin}+s + Ctrl+${modMac}+s + + + Load recipe + Ctrl+${modWinLin}+l + Ctrl+${modMac}+l + + + Move output to input + Ctrl+${modWinLin}+m + Ctrl+${modMac}+m + + `; +}; + +export default BindingsWaiter; diff --git a/src/web/Manager.js b/src/web/Manager.js index 5b8c98f0..c9ae1eb5 100755 --- a/src/web/Manager.js +++ b/src/web/Manager.js @@ -8,6 +8,7 @@ import OutputWaiter from "./OutputWaiter.js"; import OptionsWaiter from "./OptionsWaiter.js"; import HighlighterWaiter from "./HighlighterWaiter.js"; import SeasonalWaiter from "./SeasonalWaiter.js"; +import BindingsWaiter from "./BindingsWaiter.js"; /** @@ -60,6 +61,7 @@ const Manager = function(app) { this.options = new OptionsWaiter(this.app); this.highlighter = new HighlighterWaiter(this.app, this); this.seasonal = new SeasonalWaiter(this.app, this); + this.bindings = new BindingsWaiter(this.app, this); // Object to store dynamic handlers to fire on elements that may not exist yet this.dynamicHandlers = {}; @@ -76,6 +78,7 @@ Manager.prototype.setup = function() { this.recipe.initialiseOperationDragNDrop(); this.controls.autoBakeChange(); this.seasonal.load(); + this.bindings.updateKeybList(); }; @@ -89,7 +92,6 @@ Manager.prototype.initialiseEventListeners = function() { window.addEventListener("focus", this.window.windowFocus.bind(this.window)); window.addEventListener("statechange", this.app.stateChange.bind(this.app)); window.addEventListener("popstate", this.app.popState.bind(this.app)); - // Controls document.getElementById("bake").addEventListener("click", this.controls.bakeClick.bind(this.controls)); document.getElementById("auto-bake").addEventListener("change", this.controls.autoBakeChange.bind(this.controls)); @@ -165,6 +167,10 @@ Manager.prototype.initialiseEventListeners = function() { this.addDynamicListener(".option-item select", "change", this.options.selectChange, this.options); document.getElementById("theme").addEventListener("change", this.options.themeChange.bind(this.options)); + //Keybindings + window.addEventListener("keydown", this.bindings.parseInput.bind(this.bindings)); + $(document).on("switchChange.bootstrapSwitch", ".option-item input:checkbox#useMetaKey", this.bindings.updateKeybList.bind(this.bindings)); + // Misc document.getElementById("alert-close").addEventListener("click", this.app.alertCloseClick.bind(this.app)); }; diff --git a/src/web/html/index.html b/src/web/html/index.html index 6f7c878d..b1adfad7 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -1,17 +1,17 @@