123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252 |
- /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
- "use strict";
- const Services = require("Services");
- const EventEmitter = require("devtools/shared/event-emitter");
- const isOSX = Services.appinfo.OS === "Darwin";
- const {KeyCodes} = require("devtools/client/shared/keycodes");
- // List of electron keys mapped to DOM API (DOM_VK_*) key code
- const ElectronKeysMapping = {
- "F1": "DOM_VK_F1",
- "F2": "DOM_VK_F2",
- "F3": "DOM_VK_F3",
- "F4": "DOM_VK_F4",
- "F5": "DOM_VK_F5",
- "F6": "DOM_VK_F6",
- "F7": "DOM_VK_F7",
- "F8": "DOM_VK_F8",
- "F9": "DOM_VK_F9",
- "F10": "DOM_VK_F10",
- "F11": "DOM_VK_F11",
- "F12": "DOM_VK_F12",
- "F13": "DOM_VK_F13",
- "F14": "DOM_VK_F14",
- "F15": "DOM_VK_F15",
- "F16": "DOM_VK_F16",
- "F17": "DOM_VK_F17",
- "F18": "DOM_VK_F18",
- "F19": "DOM_VK_F19",
- "F20": "DOM_VK_F20",
- "F21": "DOM_VK_F21",
- "F22": "DOM_VK_F22",
- "F23": "DOM_VK_F23",
- "F24": "DOM_VK_F24",
- "Space": "DOM_VK_SPACE",
- "Backspace": "DOM_VK_BACK_SPACE",
- "Delete": "DOM_VK_DELETE",
- "Insert": "DOM_VK_INSERT",
- "Return": "DOM_VK_RETURN",
- "Enter": "DOM_VK_RETURN",
- "Up": "DOM_VK_UP",
- "Down": "DOM_VK_DOWN",
- "Left": "DOM_VK_LEFT",
- "Right": "DOM_VK_RIGHT",
- "Home": "DOM_VK_HOME",
- "End": "DOM_VK_END",
- "PageUp": "DOM_VK_PAGE_UP",
- "PageDown": "DOM_VK_PAGE_DOWN",
- "Escape": "DOM_VK_ESCAPE",
- "Esc": "DOM_VK_ESCAPE",
- "Tab": "DOM_VK_TAB",
- "VolumeUp": "DOM_VK_VOLUME_UP",
- "VolumeDown": "DOM_VK_VOLUME_DOWN",
- "VolumeMute": "DOM_VK_VOLUME_MUTE",
- "PrintScreen": "DOM_VK_PRINTSCREEN",
- };
- /**
- * Helper to listen for keyboard events decribed in .properties file.
- *
- * let shortcuts = new KeyShortcuts({
- * window
- * });
- * shortcuts.on("Ctrl+F", event => {
- * // `event` is the KeyboardEvent which relates to the key shortcuts
- * });
- *
- * @param DOMWindow window
- * The window object of the document to listen events from.
- * @param DOMElement target
- * Optional DOM Element on which we should listen events from.
- * If omitted, we listen for all events fired on `window`.
- */
- function KeyShortcuts({ window, target }) {
- this.window = window;
- this.target = target || window;
- this.keys = new Map();
- this.eventEmitter = new EventEmitter();
- this.target.addEventListener("keydown", this);
- }
- /*
- * Parse an electron-like key string and return a normalized object which
- * allow efficient match on DOM key event. The normalized object matches DOM
- * API.
- *
- * @param DOMWindow window
- * Any DOM Window object, just to fetch its `KeyboardEvent` object
- * @param String str
- * The shortcut string to parse, following this document:
- * https://github.com/electron/electron/blob/master/docs/api/accelerator.md
- */
- KeyShortcuts.parseElectronKey = function (window, str) {
- let modifiers = str.split("+");
- let key = modifiers.pop();
- let shortcut = {
- ctrl: false,
- meta: false,
- alt: false,
- shift: false,
- // Set for character keys
- key: undefined,
- // Set for non-character keys
- keyCode: undefined,
- };
- for (let mod of modifiers) {
- if (mod === "Alt") {
- shortcut.alt = true;
- } else if (["Command", "Cmd"].includes(mod)) {
- shortcut.meta = true;
- } else if (["CommandOrControl", "CmdOrCtrl"].includes(mod)) {
- if (isOSX) {
- shortcut.meta = true;
- } else {
- shortcut.ctrl = true;
- }
- } else if (["Control", "Ctrl"].includes(mod)) {
- shortcut.ctrl = true;
- } else if (mod === "Shift") {
- shortcut.shift = true;
- } else {
- console.error("Unsupported modifier:", mod, "from key:", str);
- return null;
- }
- }
- // Plus is a special case. It's a character key and shouldn't be matched
- // against a keycode as it is only accessible via Shift/Capslock
- if (key === "Plus") {
- key = "+";
- }
- if (typeof key === "string" && key.length === 1) {
- // Match any single character
- shortcut.key = key.toLowerCase();
- } else if (key in ElectronKeysMapping) {
- // Maps the others manually to DOM API DOM_VK_*
- key = ElectronKeysMapping[key];
- shortcut.keyCode = KeyCodes[key];
- // Used only to stringify the shortcut
- shortcut.keyCodeString = key;
- shortcut.key = key;
- } else {
- console.error("Unsupported key:", key);
- return null;
- }
- return shortcut;
- };
- KeyShortcuts.stringify = function (shortcut) {
- let list = [];
- if (shortcut.alt) {
- list.push("Alt");
- }
- if (shortcut.ctrl) {
- list.push("Ctrl");
- }
- if (shortcut.meta) {
- list.push("Cmd");
- }
- if (shortcut.shift) {
- list.push("Shift");
- }
- let key;
- if (shortcut.key) {
- key = shortcut.key.toUpperCase();
- } else {
- key = shortcut.keyCodeString;
- }
- list.push(key);
- return list.join("+");
- };
- KeyShortcuts.prototype = {
- destroy() {
- this.target.removeEventListener("keydown", this);
- this.keys.clear();
- },
- doesEventMatchShortcut(event, shortcut) {
- if (shortcut.meta != event.metaKey) {
- return false;
- }
- if (shortcut.ctrl != event.ctrlKey) {
- return false;
- }
- if (shortcut.alt != event.altKey) {
- return false;
- }
- if (shortcut.shift != event.shiftKey) {
- // Shift is a special modifier, it may implicitely be required if the expected key
- // is a special character accessible via shift.
- let isAlphabetical = event.key && event.key.match(/[a-zA-Z]/);
- // OSX: distinguish cmd+[key] from cmd+shift+[key] shortcuts (Bug 1300458)
- let cmdShortcut = shortcut.meta && !shortcut.alt && !shortcut.ctrl;
- if (isAlphabetical || cmdShortcut) {
- return false;
- }
- }
- if (shortcut.keyCode) {
- return event.keyCode == shortcut.keyCode;
- } else if (event.key in ElectronKeysMapping) {
- return ElectronKeysMapping[event.key] === shortcut.key;
- }
- // get the key from the keyCode if key is not provided.
- let key = event.key || String.fromCharCode(event.keyCode);
- // For character keys, we match if the final character is the expected one.
- // But for digits we also accept indirect match to please azerty keyboard,
- // which requires Shift to be pressed to get digits.
- return key.toLowerCase() == shortcut.key ||
- (shortcut.key.match(/[0-9]/) &&
- event.keyCode == shortcut.key.charCodeAt(0));
- },
- handleEvent(event) {
- for (let [key, shortcut] of this.keys) {
- if (this.doesEventMatchShortcut(event, shortcut)) {
- this.eventEmitter.emit(key, event);
- }
- }
- },
- on(key, listener) {
- if (typeof listener !== "function") {
- throw new Error("KeyShortcuts.on() expects a function as " +
- "second argument");
- }
- if (!this.keys.has(key)) {
- let shortcut = KeyShortcuts.parseElectronKey(this.window, key);
- // The key string is wrong and we were unable to compute the key shortcut
- if (!shortcut) {
- return;
- }
- this.keys.set(key, shortcut);
- }
- this.eventEmitter.on(key, listener);
- },
- off(key, listener) {
- this.eventEmitter.off(key, listener);
- },
- };
- exports.KeyShortcuts = KeyShortcuts;
|