Skip to content

Commit 555fed2

Browse files
committed
Merge branch 'artemisbot-features/keybindings'
2 parents 7a2b75c + 7a2f68e commit 555fed2

File tree

4 files changed

+257
-23
lines changed

4 files changed

+257
-23
lines changed

src/web/BindingsWaiter.js

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
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+
const modKey = this.app.options.useMetaKey ? e.metaKey : e.altKey;
27+
28+
if (e.ctrlKey && modKey) {
29+
let elem;
30+
switch (e.code) {
31+
case "KeyF": // Focus search
32+
e.preventDefault();
33+
document.getElementById("search").focus();
34+
break;
35+
case "KeyI": // Focus input
36+
e.preventDefault();
37+
document.getElementById("input-text").focus();
38+
break;
39+
case "KeyO": // Focus output
40+
e.preventDefault();
41+
document.getElementById("output-text").focus();
42+
break;
43+
case "Period": // Focus next operation
44+
e.preventDefault();
45+
try {
46+
elem = document.activeElement.closest(".operation") || document.querySelector("#rec-list .operation");
47+
if (elem.parentNode.lastChild === elem) {
48+
// If operation is last in recipe, loop around to the top operation's first argument
49+
elem.parentNode.firstChild.querySelectorAll(".arg")[0].focus();
50+
} else {
51+
// Focus first argument of next operation
52+
elem.nextSibling.querySelectorAll(".arg")[0].focus();
53+
}
54+
} catch (e) {
55+
// do nothing, just don't throw an error
56+
}
57+
break;
58+
case "KeyB": // Set breakpoint
59+
e.preventDefault();
60+
try {
61+
elem = document.activeElement.closest(".operation").querySelectorAll(".breakpoint")[0];
62+
if (elem.getAttribute("break") === "false") {
63+
elem.setAttribute("break", "true"); // add break point if not already enabled
64+
elem.classList.add("breakpoint-selected");
65+
} else {
66+
elem.setAttribute("break", "false"); // remove break point if already enabled
67+
elem.classList.remove("breakpoint-selected");
68+
}
69+
window.dispatchEvent(this.manager.statechange);
70+
} catch (e) {
71+
// do nothing, just don't throw an error
72+
}
73+
break;
74+
case "KeyD": // Disable operation
75+
e.preventDefault();
76+
try {
77+
elem = document.activeElement.closest(".operation").querySelectorAll(".disable-icon")[0];
78+
if (elem.getAttribute("disabled") === "false") {
79+
elem.setAttribute("disabled", "true"); // disable operation if enabled
80+
elem.classList.add("disable-elem-selected");
81+
elem.parentNode.parentNode.classList.add("disabled");
82+
} else {
83+
elem.setAttribute("disabled", "false"); // enable operation if disabled
84+
elem.classList.remove("disable-elem-selected");
85+
elem.parentNode.parentNode.classList.remove("disabled");
86+
}
87+
this.app.progress = 0;
88+
window.dispatchEvent(this.manager.statechange);
89+
} catch (e) {
90+
// do nothing, just don't throw an error
91+
}
92+
break;
93+
case "Space": // Bake
94+
e.preventDefault();
95+
this.app.bake();
96+
break;
97+
case "Quote": // Step through
98+
e.preventDefault();
99+
this.app.bake(true);
100+
break;
101+
case "KeyC": // Clear recipe
102+
e.preventDefault();
103+
this.manager.recipe.clearRecipe();
104+
break;
105+
case "KeyS": // Save output to file
106+
e.preventDefault();
107+
this.manager.output.saveClick();
108+
break;
109+
case "KeyL": // Load recipe
110+
e.preventDefault();
111+
this.manager.controls.loadClick();
112+
break;
113+
case "KeyM": // Switch input and output
114+
e.preventDefault();
115+
this.manager.output.switchClick();
116+
break;
117+
default:
118+
if (e.code.match(/Digit[0-9]/g)) { // Select nth operation
119+
e.preventDefault();
120+
try {
121+
// Select the first argument of the operation corresponding to the number pressed
122+
document.querySelector(`li:nth-child(${e.code.substr(-1)}) .arg`).focus();
123+
} catch (e) {
124+
// do nothing, just don't throw an error
125+
}
126+
}
127+
break;
128+
}
129+
}
130+
};
131+
132+
133+
/**
134+
* Updates keybinding list when metaKey option is toggled
135+
*
136+
*/
137+
BindingsWaiter.prototype.updateKeybList = function() {
138+
let modWinLin = "Alt";
139+
let modMac = "Opt";
140+
if (this.app.options.useMetaKey) {
141+
modWinLin = "Win";
142+
modMac = "Cmd";
143+
}
144+
document.getElementById("keybList").innerHTML = `
145+
<tr>
146+
<td><b>Command</b></td>
147+
<td><b>Shortcut (Win/Linux)</b></td>
148+
<td><b>Shortcut (Mac)</b></td>
149+
</tr>
150+
<tr>
151+
<td>Place cursor in search field</td>
152+
<td>Ctrl+${modWinLin}+f</td>
153+
<td>Ctrl+${modMac}+f</td>
154+
<tr>
155+
<td>Place cursor in input box</td>
156+
<td>Ctrl+${modWinLin}+i</td>
157+
<td>Ctrl+${modMac}+i</td>
158+
</tr>
159+
<tr>
160+
<td>Place cursor in output box</td>
161+
<td>Ctrl+${modWinLin}+o</td>
162+
<td>Ctrl+${modMac}+o</td>
163+
</tr>
164+
<tr>
165+
<td>Place cursor in first argument field of the next operation in the recipe</td>
166+
<td>Ctrl+${modWinLin}+.</td>
167+
<td>Ctrl+${modMac}+.</td>
168+
</tr>
169+
<tr>
170+
<td>Place cursor in first argument field of the nth operation in the recipe</td>
171+
<td>Ctrl+${modWinLin}+[1-9]</td>
172+
<td>Ctrl+${modMac}+[1-9]</td>
173+
</tr>
174+
<tr>
175+
<td>Disable current operation</td>
176+
<td>Ctrl+${modWinLin}+d</td>
177+
<td>Ctrl+${modMac}+d</td>
178+
</tr>
179+
<tr>
180+
<td>Set/clear breakpoint</td>
181+
<td>Ctrl+${modWinLin}+b</td>
182+
<td>Ctrl+${modMac}+b</td>
183+
</tr>
184+
<tr>
185+
<td>Bake</td>
186+
<td>Ctrl+${modWinLin}+Space</td>
187+
<td>Ctrl+${modMac}+Space</td>
188+
</tr>
189+
<tr>
190+
<td>Step</td>
191+
<td>Ctrl+${modWinLin}+'</td>
192+
<td>Ctrl+${modMac}+'</td>
193+
</tr>
194+
<tr>
195+
<td>Clear recipe</td>
196+
<td>Ctrl+${modWinLin}+c</td>
197+
<td>Ctrl+${modMac}+c</td>
198+
</tr>
199+
<tr>
200+
<td>Save to file</td>
201+
<td>Ctrl+${modWinLin}+s</td>
202+
<td>Ctrl+${modMac}+s</td>
203+
</tr>
204+
<tr>
205+
<td>Load recipe</td>
206+
<td>Ctrl+${modWinLin}+l</td>
207+
<td>Ctrl+${modMac}+l</td>
208+
</tr>
209+
<tr>
210+
<td>Move output to input</td>
211+
<td>Ctrl+${modWinLin}+m</td>
212+
<td>Ctrl+${modMac}+m</td>
213+
</tr>
214+
`;
215+
};
216+
217+
export default BindingsWaiter;

src/web/Manager.js

Lines changed: 5 additions & 0 deletions
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 = {};
@@ -75,6 +77,7 @@ Manager.prototype.setup = function() {
7577
this.worker.registerChefWorker();
7678
this.recipe.initialiseOperationDragNDrop();
7779
this.controls.autoBakeChange();
80+
this.bindings.updateKeybList();
7881
this.seasonal.load();
7982
};
8083

@@ -160,12 +163,14 @@ Manager.prototype.initialiseEventListeners = function() {
160163
document.getElementById("reset-options").addEventListener("click", this.options.resetOptionsClick.bind(this.options));
161164
$(document).on("switchChange.bootstrapSwitch", ".option-item input:checkbox", this.options.switchChange.bind(this.options));
162165
$(document).on("switchChange.bootstrapSwitch", ".option-item input:checkbox", this.options.setWordWrap.bind(this.options));
166+
$(document).on("switchChange.bootstrapSwitch", ".option-item input:checkbox#useMetaKey", this.bindings.updateKeybList.bind(this.bindings));
163167
this.addDynamicListener(".option-item input[type=number]", "keyup", this.options.numberChange, this.options);
164168
this.addDynamicListener(".option-item input[type=number]", "change", this.options.numberChange, this.options);
165169
this.addDynamicListener(".option-item select", "change", this.options.selectChange, this.options);
166170
document.getElementById("theme").addEventListener("change", this.options.themeChange.bind(this.options));
167171

168172
// Misc
173+
window.addEventListener("keydown", this.bindings.parseInput.bind(this.bindings));
169174
document.getElementById("alert-close").addEventListener("click", this.app.alertCloseClick.bind(this.app));
170175
};
171176

0 commit comments

Comments
 (0)