Spaces:
Running
Running
File size: 6,282 Bytes
583c1c7 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 |
/**
* A service responsible for captruing keys within LiteGraph's canvas, and outside of it, allowing
* nodes and other services to confidently determine what's going on.
*/
class KeyEventService extends EventTarget {
readonly downKeys: { [key: string]: boolean } = {};
ctrlKey = false;
altKey = false;
metaKey = false;
shiftKey = false;
private readonly isMac: boolean = !!(
navigator.platform?.toLocaleUpperCase().startsWith("MAC") ||
(navigator as any).userAgentData?.platform?.toLocaleUpperCase().startsWith("MAC")
);
constructor() {
super();
this.initialize();
}
initialize() {
const that = this;
// [🤮] Sometimes ComfyUI and/or LiteGraph stop propagation of key events which makes it hard
// to determine if keys are currently pressed. To attempt to get around this, we'll hijack
// LiteGraph's processKey to try to get better consistency.
const processKey = LGraphCanvas.prototype.processKey;
LGraphCanvas.prototype.processKey = function (e: KeyboardEvent) {
if (e.type === "keydown" || e.type === "keyup") {
that.handleKeyDownOrUp(e);
}
return processKey.apply(this, [...arguments] as any) as any;
};
// Now that ComfyUI has more non-canvas UI (like the top bar), we listen on window as well, and
// de-dupe when we get multiple events from both window and/or LiteGraph.
window.addEventListener("keydown", (e) => {
that.handleKeyDownOrUp(e);
});
window.addEventListener("keyup", (e) => {
that.handleKeyDownOrUp(e);
});
// If we get a visibilitychange, then clear the keys since we can't listen for keys up/down when
// not visible.
document.addEventListener("visibilitychange", (e) => {
this.clearKeydowns();
});
// If we get a blur, then also clear the keys since we can't listen for keys up/down when
// blurred. This can happen w/o a visibilitychange, like a browser alert.
window.addEventListener("blur", (e) => {
this.clearKeydowns();
});
}
/**
* Adds a new queue item, unless the last is the same.
*/
handleKeyDownOrUp(e: KeyboardEvent) {
const key = e.key.toLocaleUpperCase();
// If we're already down, or already up, then ignore and don't fire.
if ((e.type === 'keydown' && this.downKeys[key] === true)
|| (e.type === 'keyup' && this.downKeys[key] === undefined)) {
return;
}
this.ctrlKey = !!e.ctrlKey;
this.altKey = !!e.altKey;
this.metaKey = !!e.metaKey;
this.shiftKey = !!e.shiftKey;
if (e.type === "keydown") {
this.downKeys[key] = true;
this.dispatchCustomEvent("keydown", { originalEvent: e });
} else if (e.type === "keyup") {
// See https://github.com/rgthree/rgthree-comfy/issues/238
// A little bit of a hack, but Mac reportedly does something odd with copy/paste. ComfyUI
// gobbles the copy event propagation, but it happens for paste too and reportedly 'Enter' which
// I can't find a reason for in LiteGraph/comfy. So, for Mac only, whenever we lift a Command
// (META) key, we'll also clear any other keys.
if (key === "META" && this.isMac) {
this.clearKeydowns();
} else {
delete this.downKeys[key];
// this.debugRenderKeys();
}
this.dispatchCustomEvent("keyup", { originalEvent: e });
}
}
private clearKeydowns() {
this.ctrlKey = false;
this.altKey = false;
this.metaKey = false;
this.shiftKey = false;
for (const key in this.downKeys) delete this.downKeys[key];
}
/**
* Wraps `dispatchEvent` for easier CustomEvent dispatching.
*/
private dispatchCustomEvent(event: string, detail?: any) {
if (detail != null) {
return this.dispatchEvent(new CustomEvent(event, { detail }));
}
return this.dispatchEvent(new CustomEvent(event));
}
/**
* Parses a shortcut string.
*
* - 's' => ['S']
* - 'shift + c' => ['SHIFT', 'C']
* - 'shift + meta + @' => ['SHIFT', 'META', '@']
* - 'shift + + + @' => ['SHIFT', '__PLUS__', '=']
* - '+ + p' => ['__PLUS__', 'P']
*/
private getKeysFromShortcut(shortcut: string | string[]) {
let keys;
if (typeof shortcut === "string") {
// Rip all spaces out. Note, Comfy swallows space, so we don't have to handle it. Otherwise,
// we would require space to be fed as "Space" or "Spacebar" instead of " ".
shortcut = shortcut.replace(/\s/g, "");
// Change a real "+" to something we can encode.
shortcut = shortcut.replace(/^\+/, "__PLUS__").replace(/\+\+/, "+__PLUS__");
keys = shortcut.split("+").map((i) => i.replace("__PLUS__", "+"));
} else {
keys = [...shortcut];
}
return keys.map((k) => k.toLocaleUpperCase());
}
/**
* Checks if all keys passed in are down.
*/
areAllKeysDown(keys: string | string[]) {
keys = this.getKeysFromShortcut(keys);
return keys.every((k) => {
return this.downKeys[k];
});
}
/**
* Checks if only the keys passed in are down; optionally and additionally allowing "shift" key.
*/
areOnlyKeysDown(keys: string | string[], alsoAllowShift = false) {
keys = this.getKeysFromShortcut(keys);
const allKeysDown = this.areAllKeysDown(keys);
const downKeysLength = Object.values(this.downKeys).length;
// All keys are down and they're the only ones.
if (allKeysDown && keys.length === downKeysLength) {
return true;
}
// Special case allowing the shift key in addition to the shortcut keys. This helps when a user
// may had originally defined "$" as a shortcut, but needs to press "shift + $" since it's an
// upper key character, etc.
if (alsoAllowShift && !keys.includes("SHIFT") && keys.length === downKeysLength - 1) {
// If we're holding down shift, have one extra key held down, and the original keys don't
// include shift, then we're good to go.
return allKeysDown && this.areAllKeysDown(["SHIFT"]);
}
return false;
}
}
/** The KeyEventService singleton. */
export const SERVICE = new KeyEventService();
|