Skip to content

Commit 638e038

Browse files
committed
Initial keybinding functionality + documentation
Todo: - allow user to specify whether to use alt or meta key (relatively easy to implement) - keybinding icon for about pane
1 parent bd1790b commit 638e038

File tree

3 files changed

+222
-24
lines changed

3 files changed

+222
-24
lines changed

src/web/BindingsWaiter.js

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/**
2+
* Waiter to handle keybindings to CyberChef functions (i.e. Bake, Step, Save, Load etc.)
3+
*
4+
* @author Matt C [matt@artemisbot.uk]
5+
* @copyright Crown Copyright 2016
6+
* @license Apache-2.0
7+
*
8+
* @constructor
9+
* @param {App} app - The main view object for CyberChef.
10+
* @param {Manager} manager - The CyberChef event manager.
11+
*/
12+
const BindingsWaiter = function (app, manager) {
13+
this.app = app;
14+
this.manager = manager;
15+
};
16+
17+
18+
/**
19+
* Handler for all keydown events
20+
* Checks whether valid keyboard shortcut has been instated
21+
*
22+
* @fires Manager#statechange
23+
* @param {event} e
24+
*/
25+
BindingsWaiter.prototype.parseInput = function(e) {
26+
if (e.ctrlKey && e.altKey) {
27+
let elem;
28+
switch (e.code) {
29+
case "KeyF":
30+
e.preventDefault();
31+
document.getElementById("search").focus(); // Focus search
32+
break;
33+
case "KeyI":
34+
e.preventDefault();
35+
document.getElementById("input-text").focus(); // Focus input
36+
break;
37+
case "KeyO":
38+
e.preventDefault();
39+
document.getElementById("output-text").focus(); // Focus output
40+
break;
41+
case "Period":
42+
try {
43+
elem = document.activeElement.closest(".operation");
44+
if (elem.parentNode.lastChild === elem) {
45+
elem.parentNode.firstChild.querySelectorAll(".arg")[0].focus(); // if operation is last in recipe, loop around to the top operation's first argument
46+
} else {
47+
elem.nextSibling.querySelectorAll(".arg")[0].focus(); //focus first argument of next operation
48+
}
49+
} catch (e) {
50+
// do nothing, just don't throw an error
51+
}
52+
break;
53+
case "KeyB":
54+
try {
55+
elem = document.activeElement.closest(".operation").querySelectorAll(".breakpoint")[0];
56+
if (elem.getAttribute("break") === "false") {
57+
elem.setAttribute("break", "true"); // add break point if not already enabled
58+
elem.classList.add("breakpoint-selected");
59+
} else {
60+
elem.setAttribute("break", "false"); // remove break point if already enabled
61+
elem.classList.remove("breakpoint-selected");
62+
}
63+
window.dispatchEvent(this.manager.statechange);
64+
} catch (e) {
65+
// do nothing, just don't throw an error
66+
}
67+
break;
68+
case "KeyD":
69+
try {
70+
elem = document.activeElement.closest(".operation").querySelectorAll(".disable-icon")[0];
71+
if (elem.getAttribute("disabled") === "false") {
72+
elem.setAttribute("disabled", "true"); // disable operation if enabled
73+
elem.classList.add("disable-elem-selected");
74+
elem.parentNode.parentNode.classList.add("disabled");
75+
} else {
76+
elem.setAttribute("disabled", "false"); // enable operation if disabled
77+
elem.classList.remove("disable-elem-selected");
78+
elem.parentNode.parentNode.classList.remove("disabled");
79+
}
80+
this.app.progress = 0;
81+
window.dispatchEvent(this.manager.statechange);
82+
} catch (e) {
83+
// do nothing, just don't throw an error
84+
}
85+
break;
86+
case "Space":
87+
this.app.bake(); // bake the recipe
88+
break;
89+
case "Quote":
90+
this.app.bake(true); // step through the recipe
91+
break;
92+
case "KeyC":
93+
this.manager.recipe.clearRecipe(); // clear recipe
94+
break;
95+
case "KeyS":
96+
this.manager.output.saveClick(); // save output to file
97+
break;
98+
case "KeyL":
99+
this.manager.controls.loadClick(); // load a recipe
100+
break;
101+
case "KeyM":
102+
this.manager.controls.switchClick(); // switch input & output
103+
break;
104+
default:
105+
if (e.code.match(/Digit[0-9]/g)) {
106+
e.preventDefault();
107+
try {
108+
document.querySelector(`li:nth-child(${e.code.substr(-1)}) .arg`).focus(); // select the first argument of the operation corresponding to the number pressed
109+
} catch (e) {
110+
// do nothing, just don't throw an error
111+
}
112+
}
113+
break;
114+
}
115+
}
116+
};
117+
118+
export default BindingsWaiter;

src/web/Manager.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import OutputWaiter from "./OutputWaiter.js";
88
import OptionsWaiter from "./OptionsWaiter.js";
99
import HighlighterWaiter from "./HighlighterWaiter.js";
1010
import SeasonalWaiter from "./SeasonalWaiter.js";
11+
import BindingsWaiter from "./BindingsWaiter.js";
1112

1213

1314
/**
@@ -60,6 +61,7 @@ const Manager = function(app) {
6061
this.options = new OptionsWaiter(this.app);
6162
this.highlighter = new HighlighterWaiter(this.app, this);
6263
this.seasonal = new SeasonalWaiter(this.app, this);
64+
this.bindings = new BindingsWaiter(this.app, this);
6365

6466
// Object to store dynamic handlers to fire on elements that may not exist yet
6567
this.dynamicHandlers = {};
@@ -89,7 +91,7 @@ Manager.prototype.initialiseEventListeners = function() {
8991
window.addEventListener("focus", this.window.windowFocus.bind(this.window));
9092
window.addEventListener("statechange", this.app.stateChange.bind(this.app));
9193
window.addEventListener("popstate", this.app.popState.bind(this.app));
92-
94+
window.addEventListener("keydown", this.bindings.parseInput.bind(this.bindings));
9395
// Controls
9496
document.getElementById("bake").addEventListener("click", this.controls.bakeClick.bind(this.controls));
9597
document.getElementById("auto-bake").addEventListener("change", this.controls.autoBakeChange.bind(this.controls));

0 commit comments

Comments
 (0)