ultrawidify/src/ext/lib/KbmHandler.ts

311 lines
9.5 KiB
TypeScript
Raw Normal View History

import Debug from '../conf/Debug';
import PlayerData from './video-data/PlayerData';
import ExtensionMode from '../../common/enums/ExtensionMode.enum';
2021-02-20 00:09:17 +01:00
import Logger from './Logger';
import PageInfo from './video-data/PageInfo';
import Settings from './Settings';
import VideoData from './video-data/VideoData';
import EventBus, { EventBusCommand } from './EventBus';
2020-04-13 15:20:29 +02:00
if(process.env.CHANNEL !== 'stable'){
2020-12-03 01:05:39 +01:00
console.info("Loading ActionHandler");
2020-04-13 15:20:29 +02:00
}
/**
* Handles keypresses and mouse movement
*/
class KbmHandler {
2021-02-20 00:09:17 +01:00
logger: Logger;
settings: Settings;
2022-05-06 00:23:15 +02:00
eventBus: EventBus;
2021-02-20 00:09:17 +01:00
allowShortcuts: boolean = true;
2021-02-20 00:09:17 +01:00
inputs: string[] = ['input', 'select', 'button', 'textarea'];
2021-03-06 02:53:58 +01:00
keyboardLocalDisabled: boolean = false;
2021-02-20 00:09:17 +01:00
keyboardEnabled: boolean = false;
mouseEnabled: boolean = false;
mouseMoveActions: any[] = [];
keypressActions: any[] = [];
eventBusCommands: { [x: string]: EventBusCommand } = {
'kbm-enable': {
function: () => this.enable()
},
'kbm-disable': {
function: () => this.disable()
},
'kbm-set-config': {
function: (data: {config: any, temporary?: boolean}) => this.setConfig(data.config, data.temporary),
},
}
2022-05-06 00:23:15 +02:00
//#region lifecycle
constructor(eventBus: EventBus, settings: Settings, logger: Logger) {
2022-05-06 00:23:15 +02:00
this.logger = logger;
this.settings = settings;
this.eventBus = eventBus;
this.init();
}
init() {
this.logger.log('info', 'debug', "[ActionHandler::init] starting init");
2022-05-06 00:23:15 +02:00
// build the action list — but only from actions that have shortcuts assigned
for (const key in this.settings.active.commands) {
for (const command of this.settings.active.commands[key]) {
if (command.shortcut) {
this.keypressActions.push(command);
}
}
}
this.load();
}
load() {
this.settings.active.kbmHandler.enabled ? this.addListener() : this.removeListener();
this.keyboardEnabled = this.settings.active.kbmHandler.keyboardEnabled;
this.mouseEnabled = this.settings.active.kbmHandler.mouseEnabled;
}
2022-05-06 00:23:15 +02:00
destroy() {
this.removeListener();
}
// convenience methods
addListener() {
2021-11-25 20:00:32 +01:00
// events should be handled in handleEvent function. We need to do things this
2021-02-09 00:54:04 +01:00
// way, otherwise we can't remove event listener
2020-10-21 23:52:16 +02:00
// https://stackoverflow.com/a/19507086
2022-05-06 00:23:15 +02:00
document.addEventListener('keyup', this );
}
removeListener() {
document.removeEventListener('keyup', this);
}
//#endregion
enable() {
this.addListener();
}
disable() {
this.removeListener();
}
2020-10-21 23:52:16 +02:00
handleEvent(event) {
switch(event.type) {
2021-11-25 20:00:32 +01:00
case 'keyup':
2020-10-21 23:52:16 +02:00
this.handleKeyup(event);
break;
case 'mousemove':
this.handleMouseMove(event);
break;
}
}
/**
* Sets configuration parameter for KbmHandler
* @param config
*/
setConfig(config, temporary = false) {
if (temporary) {
for (const confKey in config) {
switch (confKey) {
case 'enabled':
config[confKey] ? this.enable() : this.disable();
break;
case 'keyboardEnabled':
this.keyboardEnabled = config[confKey];
break;
case 'mouseEnabled':
this.mouseEnabled = config[confKey];
break;
}
}
return;
}
for (const confKey in config) {
this.settings.active.kbmHandler[confKey] = config[confKey];
}
this.settings.save();
this.load();
2020-10-21 23:52:16 +02:00
}
registerHandleMouse(videoData) {
this.logger.log('info', ['actionHandler', 'mousemove'], "[ActionHandler::registerHandleMouse] registering handle mouse for videodata:", videoData.id)
var ths = this;
if (videoData.player && videoData.player.element) {
videoData.player.element.addEventListener('mousemove', (event) => ths.handleMouseMove(event, videoData));
}
}
2019-08-25 01:52:04 +02:00
unregisterHandleMouse(videoData) {
var ths = this;
if (videoData.player && videoData.player.element) {
videoData.player.element.removeEventListener('mousemove', (event) => ths.handleMouseMove(event, videoData));
}
}
2019-06-02 23:54:32 +02:00
setKeyboardLocal(state) {
if (state === ExtensionMode.Enabled) {
this.keyboardLocalDisabled = false;
} else if (state === ExtensionMode.Disabled) {
this.keyboardLocalDisabled = true;
}
// don't do shit on invalid value of state
}
preventAction(event) {
var activeElement = document.activeElement;
if (this.logger.canLog('keyboard')) {
this.logger.pause(); // temp disable to avoid recursing;
const preventAction = this.preventAction(event);
this.logger.resume(); // undisable
this.logger.log('info', 'keyboard', "[ActionHandler::preventAction] Testing whether we're in a textbox or something. Detailed rundown of conditions:\n" +
"\nis tag one of defined inputs? (yes->prevent):", this.inputs.indexOf(activeElement.tagName.toLocaleLowerCase()) !== -1,
"\nis role = textbox? (yes -> prevent):", activeElement.getAttribute("role") === "textbox",
"\nis type === 'text'? (yes -> prevent):", activeElement.getAttribute("type") === "text",
"\nevent.target.isContentEditable? (yes -> prevent):", event.target.isContentEditable,
2019-06-02 23:54:32 +02:00
"\nis keyboard local disabled? (yes -> prevent):", this.keyboardLocalDisabled,
"\nis keyboard enabled in settings? (no -> prevent)", this.settings.keyboardShortcutsEnabled(window.location.hostname),
"\nwill the action be prevented? (yes -> prevent)", preventAction,
"\n-----------------{ extra debug info }-------------------",
"\ntag name? (lowercase):", activeElement.tagName, activeElement.tagName.toLocaleLowerCase(),
"\nrole:", activeElement.getAttribute('role'),
"\ntype:", activeElement.getAttribute('type'),
"\ninsta-fail inputs:", this.inputs,
"\nevent:", event,
"\nevent.target:", event.target
);
}
2019-06-02 23:54:32 +02:00
if (this.keyboardLocalDisabled) {
return true;
}
if (!this.settings.keyboardShortcutsEnabled(window.location.hostname)) {
return true;
}
if (this.inputs.indexOf(activeElement.tagName.toLocaleLowerCase()) !== -1) {
return true;
2021-11-25 20:00:32 +01:00
}
if (activeElement.getAttribute("role") === "textbox") {
return true;
}
if (event.target.isContentEditable) {
return true;
}
if (activeElement.getAttribute("type") === "text") {
return true;
}
return false;
}
2022-05-06 00:23:15 +02:00
ghettoLocalizeKeyboardEvent(event: KeyboardEvent) {
const realKey = event.key.toLocaleUpperCase();
const isLatin = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'.indexOf(realKey) !== -1;
if (!isLatin) {
return event;
}
const nativeKey = event.code.substring(3);
if (nativeKey !== realKey) {
return {
...event,
code: `Key${realKey}`
};
}
return event;
}
isLatin(key) {
2022-05-06 00:23:15 +02:00
return 'abcdefghijklmnopqrstuvwxyz1234567890'.indexOf(key.toLocaleLowerCase()) !== -1;
}
isActionMatchStandard(shortcut, event) {
return shortcut.key === event.key &&
shortcut.ctrlKey === event.ctrlKey &&
shortcut.metaKey === event.metaKey &&
shortcut.altKey === event.altKey &&
shortcut.shiftKey === event.shiftKey
}
isActionMatchKeyCode(shortcut, event) {
return shortcut.code === event.code &&
shortcut.ctrlKey === event.ctrlKey &&
shortcut.metaKey === event.metaKey &&
shortcut.altKey === event.altKey &&
shortcut.shiftKey === event.shiftKey
}
isActionMatch(shortcut, event, isLatin = true) {
// ASCII and symbols fall back to key code matching, because we don't know for sure that
// regular matching by key is going to work
2021-11-25 20:00:32 +01:00
return isLatin ?
this.isActionMatchStandard(shortcut, event) :
this.isActionMatchStandard(shortcut, event) || this.isActionMatchKeyCode(shortcut, event);
}
handleKeyup(event) {
if (!this.keyboardEnabled) {
this.logger.log('info', 'keyboard', "%c[ActionHandler::handleKeyup] kbmHandler.keyboardEnabled is set to false. Doing nothing.");
return;
}
this.logger.log('info', 'keyboard', "%c[ActionHandler::handleKeyup] we pressed a key: ", "color: #ff0", event.key , " | keyup: ", event.keyup, "event:", event);
2021-08-24 02:13:05 +02:00
try {
if (this.preventAction(event)) {
this.logger.log('info', 'keyboard', "[ActionHandler::handleKeyup] we are in a text box or something. Doing nothing.");
return;
}
this.logger.log('info', 'keyboard', "%c[ActionHandler::handleKeyup] Trying to find and execute action for event. Actions/event: ", "color: #ff0", this.keypressActions, event);
2022-05-06 00:23:15 +02:00
const isLatin = this.isLatin(event.key);
for (const command of this.keypressActions) {
2022-05-06 00:23:15 +02:00
if (this.isActionMatch(command.shortcut, event, isLatin)) {
2022-07-31 01:12:54 +02:00
this.eventBus.send(command.action, command.arguments);
2022-05-06 00:23:15 +02:00
}
2021-08-24 02:13:05 +02:00
}
} catch (e) {
2022-05-06 00:23:15 +02:00
this.logger.log('info', 'debug', '[ActionHandler::handleKeyup] Failed to handle keyup!', e);
2021-08-24 02:13:05 +02:00
}
}
2022-05-06 00:23:15 +02:00
2021-02-20 00:09:17 +01:00
handleMouseMove(event, videoData?: VideoData) {
if (!this.mouseEnabled) {
this.logger.log('info', 'keyboard', "%c[ActionHandler::handleKeyup] kbmHandler.keyboardEnabled is set to false. Doing nothing.");
return;
}
this.logger.log('info', 'keyboard', "[ActionHandler::handleMouseMove] mouse move is being handled.\nevent:", event, "\nvideo data:", videoData);
2022-05-06 00:23:15 +02:00
console.info('mousemove must be migrated!');
// videoData?.panHandler(event);
// this.execAction(this.mouseMoveActions, event, videoData)
}
}
2020-04-13 15:20:29 +02:00
if(process.env.CHANNEL !== 'stable'){
2020-12-03 01:05:39 +01:00
console.info("ActionHandler loaded");
2020-04-13 15:20:29 +02:00
}
export default KbmHandler;