diff --git a/src/backend/app-management/electron/events/when-ready.js b/src/backend/app-management/electron/events/when-ready.js index 84caa96ce..65108e0f0 100644 --- a/src/backend/app-management/electron/events/when-ready.js +++ b/src/backend/app-management/electron/events/when-ready.js @@ -193,8 +193,8 @@ exports.whenReady = async () => { setupCommonListeners(); windowManagement.updateSplashScreenStatus("Loading hotkeys..."); - const hotkeyManager = require("../../../hotkeys/hotkey-manager"); - hotkeyManager.refreshHotkeyCache(); + const { HotkeyManager } = require("../../../hotkeys/hotkey-manager"); + HotkeyManager.loadHotkeys(); windowManagement.updateSplashScreenStatus("Starting currency timer..."); const currencyManager = require("../../../currency/currency-manager"); diff --git a/src/backend/app-management/electron/events/windows-all-closed.js b/src/backend/app-management/electron/events/windows-all-closed.js index 505ebe1be..9ab384241 100644 --- a/src/backend/app-management/electron/events/windows-all-closed.js +++ b/src/backend/app-management/electron/events/windows-all-closed.js @@ -19,8 +19,8 @@ exports.windowsAllClosed = async () => { await customScriptRunner.stopAllScripts(); // Unregister all shortcuts. - const hotkeyManager = require("../../../hotkeys/hotkey-manager"); - hotkeyManager.unregisterAllHotkeys(); + const { HotkeyManager } = require("../../../hotkeys/hotkey-manager"); + HotkeyManager.unregisterAllHotkeys(); // Stop the chat moderation service const chatModerationManager = require("../../../chat/moderation/chat-moderation-manager"); diff --git a/src/backend/hotkeys/hotkey-manager.js b/src/backend/hotkeys/hotkey-manager.js deleted file mode 100644 index 585b4d74e..000000000 --- a/src/backend/hotkeys/hotkey-manager.js +++ /dev/null @@ -1,108 +0,0 @@ -"use strict"; - -const { ipcMain, globalShortcut } = require("electron"); -const profileManager = require("../../backend/common/profile-manager.js"); -const { TriggerType } = require("../common/EffectType"); -const effectRunner = require("../common/effect-runner.js"); - -const accountAccess = require("../common/account-access"); -const logger = require("../logwrapper"); - -let hotkeysCache = []; - -function runHotkey(code) { - const hotkey = hotkeysCache.find(k => k.code === code); - - const effects = hotkey.effects; - - if (effects == null) { - return; - } - - const processEffectsRequest = { - trigger: { - type: TriggerType.HOTKEY, - metadata: { - username: accountAccess.getAccounts().streamer.username, - hotkey: hotkey - } - }, - effects: effects - }; - effectRunner.processEffects(processEffectsRequest); -} - -// Unregister Shortcuts -// When closing, this is called to unregister the global shortcuts that were created. -function unregisterAllHotkeys() { - globalShortcut.unregisterAll(); -} - -function registerHotkey(accelerator) { - try { - const success = globalShortcut.register(accelerator, () => { - runHotkey(accelerator); - }); - if (!success) { - logger.warn(`Unable to register hotkey ${accelerator} with OS. This typically means it is already taken by another application.`); - } - } catch (error) { - logger.error(`Error while registering hotkey ${accelerator} with OS`, error); - } -} - -function registerAllHotkeys() { - if (hotkeysCache == null) { - return; - } - hotkeysCache.filter(hk => hk.active).forEach(k => { - registerHotkey(k.code); - }); -} - -function refreshHotkeyCache(retry = 1) { - // Setup events db. - const dbEvents = profileManager.getJsonDbInProfile("/hotkeys"); - - try { - if (retry <= 3) { - try { - // Update Cache - const hkraw = dbEvents.getData("/"); - if (hkraw != null && Array.isArray(hkraw)) { - hotkeysCache = hkraw; - } - logger.info("Updated Hotkeys cache."); - unregisterAllHotkeys(); - registerAllHotkeys(); - } catch (err) { - logger.error( - `Hotkeys cache update failed. Retrying. (Try ${retry++}/3)`, err - ); - refreshHotkeyCache(retry); - } - } else { - renderWindow.webContents.send( - "error", - "Could not sync up Hotkeys cache." - ); - } - } catch (err) { - logger.error(err.message); - } -} - -function getHotkeyCache() { - return hotkeysCache; -} - -// Refresh Event Cache -// This refreshes the event cache for the backend with frontend changes are saved. -ipcMain.on("refreshHotkeyCache", function() { - refreshHotkeyCache(); -}); - -// Export Functions -exports.getHotkeyCache = getHotkeyCache; -exports.refreshHotkeyCache = refreshHotkeyCache; -exports.unregisterAllHotkeys = unregisterAllHotkeys; diff --git a/src/backend/hotkeys/hotkey-manager.ts b/src/backend/hotkeys/hotkey-manager.ts new file mode 100644 index 000000000..c272df6f0 --- /dev/null +++ b/src/backend/hotkeys/hotkey-manager.ts @@ -0,0 +1,155 @@ +import { globalShortcut } from "electron"; +import { JsonDB } from "node-json-db"; +import { v4 as uuid } from "uuid"; + +import logger from "../logwrapper"; +import frontendCommunicator from "../common/frontend-communicator.js"; +import profileManager from "../../backend/common/profile-manager.js"; +import accountAccess from "../common/account-access"; +import { TriggerType } from "../common/EffectType"; +import effectRunner from "../common/effect-runner"; +import { EffectList } from "../../types/effects"; + +interface FirebotHotkey { + id: string; + code: Electron.Accelerator; + active: boolean; + effects: EffectList; +} + +class HotkeyManager { + hotkeys: FirebotHotkey[] = []; + + constructor() { + frontendCommunicator.on("hotkeys:get-hotkeys", () => { + return this.hotkeys; + }); + + frontendCommunicator.on("hotkeys:add-hotkey", (hotkey: FirebotHotkey) => { + this.addHotkey(hotkey); + }); + + frontendCommunicator.on("hotkeys:update-hotkey", (hotkey: FirebotHotkey) => { + this.updateHotkey(hotkey); + }); + + frontendCommunicator.on("hotkeys:delete-hotkey", (id: string) => { + this.deleteHotkey(id); + }); + } + + private getHotkeyDb(): JsonDB { + return profileManager.getJsonDbInProfile("/hotkeys"); + } + + loadHotkeys() { + try { + const hotkeyData = this.getHotkeyDb().getData("/"); + + if (hotkeyData?.length) { + this.hotkeys = hotkeyData || []; + } + + this.unregisterAllHotkeys(); + this.registerAllHotkeys(); + + frontendCommunicator.send("hotkeys:hotkeys-updated", this.hotkeys); + logger.info("Loaded hotkeys"); + } catch (err) { + logger.error(err); + } + } + + unregisterAllHotkeys() { + globalShortcut.unregisterAll(); + } + + addHotkey(hotkey: FirebotHotkey) { + hotkey.id = uuid(); + + this.hotkeys.push(hotkey); + this.registerHotkey(hotkey.code); + + this.saveHotkeys(); + } + + updateHotkey(hotkey: FirebotHotkey) { + const index = this.hotkeys.findIndex(h => h.id === hotkey.id); + + if (index > -1) { + this.hotkeys[index] = hotkey; + + this.saveHotkeys(); + } + } + + deleteHotkey(id: string) { + const hotkey = this.hotkeys.find(h => h.id === id); + + this.hotkeys.splice(this.hotkeys.indexOf(hotkey), 1); + + globalShortcut.unregister(hotkey.code); + + this.saveHotkeys(); + } + + private saveHotkeys() { + try { + this.getHotkeyDb().push("/", this.hotkeys); + } catch (error) { + logger.error("Error saving hotkeys", error); + } + + frontendCommunicator.send("hotkeys:hotkeys-updated", this.hotkeys); + } + + private registerAllHotkeys() { + if (this.hotkeys == null) { + return; + } + + this.hotkeys.filter(h => h.active).forEach((k) => { + this.registerHotkey(k.code); + }); + } + + private registerHotkey(accelerator: Electron.Accelerator) { + try { + const success = globalShortcut.register(accelerator, () => { + this.runHotkey(accelerator); + }); + + if (!success) { + logger.warn(`Unable to register hotkey ${accelerator} with OS. This typically means it is already taken by another application.`); + } + } catch (error) { + logger.error(`Error while registering hotkey ${accelerator} with OS`, error); + } + } + + private runHotkey(code: Electron.Accelerator) { + const hotkey = this.hotkeys.find(k => k.code === code); + + const effects = hotkey.effects; + + if (effects == null) { + return; + } + + const processEffectsRequest = { + trigger: { + type: TriggerType.HOTKEY, + metadata: { + username: accountAccess.getAccounts().streamer.username, + hotkey: hotkey + } + }, + effects: effects + }; + effectRunner.processEffects(processEffectsRequest); + } +} + +const hotkeyManager = new HotkeyManager(); + +export { hotkeyManager as HotkeyManager }; \ No newline at end of file diff --git a/src/gui/app/controllers/hotkeys.controller.js b/src/gui/app/controllers/hotkeys.controller.js index edac48461..df0856028 100644 --- a/src/gui/app/controllers/hotkeys.controller.js +++ b/src/gui/app/controllers/hotkeys.controller.js @@ -1,4 +1,5 @@ "use strict"; + (function() { //This handles the Hotkeys tab @@ -24,13 +25,13 @@ resolveObj: { hotkey: () => hotkey }, - closeCallback: resp => { + closeCallback: (resp) => { const action = resp.action, hotkey = resp.hotkey; switch (action) { case "add": - hotkeyService.saveHotkey(hotkey); + hotkeyService.addHotkey(hotkey); break; case "update": hotkeyService.updateHotkey(hotkey); @@ -43,7 +44,7 @@ confirmLabel: "Delete", confirmBtnType: "btn-danger" }) - .then(confirmed => { + .then((confirmed) => { if (confirmed) { hotkeyService.deleteHotkey(hotkey); } @@ -54,4 +55,4 @@ }); }; }); -}()); +}()); \ No newline at end of file diff --git a/src/gui/app/directives/modals/hotkeys/addOrEditHotkey/addOrEditHotkeyModal.js b/src/gui/app/directives/modals/hotkeys/addOrEditHotkey/addOrEditHotkeyModal.js index 104295007..2bcafffe5 100644 --- a/src/gui/app/directives/modals/hotkeys/addOrEditHotkey/addOrEditHotkeyModal.js +++ b/src/gui/app/directives/modals/hotkeys/addOrEditHotkey/addOrEditHotkeyModal.js @@ -11,9 +11,7 @@ modalInstance: "<" }, controller: function( - $scope, hotkeyService, - utilityService, ngToast ) { const $ctrl = this; @@ -95,4 +93,4 @@ }; } }); -}()); +}()); \ No newline at end of file diff --git a/src/gui/app/services/hot-key.service.js b/src/gui/app/services/hot-key.service.js index 0e7dcbaa7..607799908 100644 --- a/src/gui/app/services/hot-key.service.js +++ b/src/gui/app/services/hot-key.service.js @@ -1,6 +1,4 @@ "use strict"; -const profileManager = require("../../backend/common/profile-manager.js"); -const { v4: uuid } = require("uuid"); (function() { // This provides methods for handling hotkeys @@ -11,8 +9,8 @@ const { v4: uuid } = require("uuid"); const service = {}; /** - * Hotkey Capturing - */ + * Hotkey Capturing + */ service.isCapturingHotkey = false; // keys not accepted by Electron for global shortcuts @@ -147,7 +145,7 @@ const { v4: uuid } = require("uuid"); } //clear out any keys that have since been released - releasedKeyCodes.forEach(k => { + releasedKeyCodes.forEach((k) => { const normalizedK = k.toUpperCase(); if ( cachedKeys.some(key => key.rawKey.toUpperCase() === normalizedK) @@ -241,56 +239,22 @@ const { v4: uuid } = require("uuid"); return keys.join(" + "); }; - /** - * Hotkey Data Access - */ - let userHotkeys = []; service.loadHotkeys = function() { - const hotkeyDb = profileManager.getJsonDbInProfile("/hotkeys"); - try { - const hotkeyData = hotkeyDb.getData("/"); - if (hotkeyData != null && hotkeyData.length > 0) { - userHotkeys = hotkeyData || []; - } - } catch (err) { - logger.error(err); - } + userHotkeys = backendCommunicator.fireEventSync("hotkeys:get-hotkeys"); }; - function saveHotkeysToFile() { - const hotkeyDb = profileManager.getJsonDbInProfile("/hotkeys"); - try { - hotkeyDb.push("/", userHotkeys); - } catch (err) { - logger.error(err); - } - - // Refresh the backend hotkeycache - ipcRenderer.send("refreshHotkeyCache"); - } - - service.saveHotkey = function(hotkey) { - hotkey.id = uuid(); - - userHotkeys.push(hotkey); - - saveHotkeysToFile(); + service.addHotkey = function(hotkey) { + backendCommunicator.send("hotkeys:add-hotkey", hotkey); }; service.updateHotkey = function(hotkey) { - const index = userHotkeys.findIndex(k => k.id === hotkey.id); - - userHotkeys[index] = hotkey; - - saveHotkeysToFile(); + backendCommunicator.send("hotkeys:update-hotkey", hotkey); }; service.deleteHotkey = function(hotkey) { - userHotkeys = userHotkeys.filter(k => k.id !== hotkey.id); - - saveHotkeysToFile(); + backendCommunicator.send("hotkeys:delete-hotkey", hotkey.id); }; service.hotkeyCodeExists = function(hotkeyId, hotkeyCode) { @@ -309,12 +273,10 @@ const { v4: uuid } = require("uuid"); service.loadHotkeys(); }); - backendCommunicator.on("remove-hotkey", (hotkeyId) => { - service.service.deleteHotkey({ - id: hotkeyId - }); + backendCommunicator.on("hotkeys:hotkeys-updated", (hotkeys) => { + userHotkeys = hotkeys; }); return service; }); -}()); +}()); \ No newline at end of file