diff --git a/config/defaults.js b/config/defaults.js index 552a4995cf..5bb74e09ee 100644 --- a/config/defaults.js +++ b/config/defaults.js @@ -30,6 +30,7 @@ const defaultConfig = { // Disabled plugins shortcuts: { enabled: false, + overrideMediaKeys: false, }, downloader: { enabled: false, @@ -59,9 +60,8 @@ const defaultConfig = { steps: 1, //percentage of volume to change arrowsShortcut: true, //enable ArrowUp + ArrowDown local shortcuts globalShortcuts: { - enabled: false, // enable global shortcuts - volumeUp: "Shift+PageUp", // Keybind default can be changed - volumeDown: "Shift+PageDown" + volumeUp: "", + volumeDown: "" }, savedVolume: undefined //plugin save volume between session here }, diff --git a/config/store.js b/config/store.js index f66c1f9aed..d6451cee9f 100644 --- a/config/store.js +++ b/config/store.js @@ -8,6 +8,27 @@ const migrations = { store.set("plugins.discord.listenAlong", true); } }, + ">=1.12.0": (store) => { + const options = store.get("plugins.shortcuts") + let updated = false; + for (const optionType of ["global", "local"]) { + if (Array.isArray(options[optionType])) { + const updatedOptions = {}; + for (const optionObject of options[optionType]) { + if (optionObject.action && optionObject.shortcut) { + updatedOptions[optionObject.action] = optionObject.shortcut; + } + } + + options[optionType] = updatedOptions; + updated = true; + } + } + + if (updated) { + store.set("plugins.shortcuts", options); + } + }, ">=1.11.0": (store) => { if (store.get("options.resumeOnStart") === undefined) { store.set("options.resumeOnStart", true); diff --git a/index.js b/index.js index c54d255511..90096ecf60 100644 --- a/index.js +++ b/index.js @@ -39,7 +39,9 @@ if (config.get("options.proxy")) { } // Adds debug features like hotkeys for triggering dev tools and reload -require("electron-debug")(); +require("electron-debug")({ + showDevTools: false //disable automatic devTools on new window +}); // Prevent window being garbage collected let mainWindow; @@ -60,7 +62,7 @@ function onClosed() { function loadPlugins(win) { injectCSS(win.webContents, path.join(__dirname, "youtube-music.css")); - win.webContents.on("did-finish-load", () => { + win.webContents.once("did-finish-load", () => { if (is.dev()) { console.log("did finish load"); win.webContents.openDevTools(); diff --git a/menu.js b/menu.js index 72b2cc663b..6ad4ccddf1 100644 --- a/menu.js +++ b/menu.js @@ -7,6 +7,9 @@ const is = require("electron-is"); const { getAllPlugins } = require("./plugins/utils"); const config = require("./config"); +const prompt = require("custom-electron-prompt"); +const promptOptions = require("./providers/prompt-options"); + // true only if in-app-menu was loaded on launch const inAppMenuActive = config.plugins.isEnabled("in-app-menu"); @@ -149,6 +152,14 @@ const mainMenuTemplate = (win) => { { label: "Advanced options", submenu: [ + { + label: "Proxy", + type: "checkbox", + checked: !!config.get("options.proxy"), + click: (item) => { + setProxy(item, win); + }, + }, { label: "Disable hardware acceleration", type: "checkbox", @@ -275,3 +286,25 @@ module.exports.setApplicationMenu = (win) => { const menu = Menu.buildFromTemplate(menuTemplate); Menu.setApplicationMenu(menu); }; + +async function setProxy(item, win) { + const output = await prompt({ + title: 'Set Proxy', + label: 'Enter Proxy Address: (leave empty to disable)', + value: config.get("options.proxy"), + type: 'input', + inputAttrs: { + type: 'url', + placeholder: "Example: 'socks5://127.0.0.1:9999" + }, + width: 450, + ...promptOptions() + }, win); + + if (typeof output === "string") { + config.set("options.proxy", output); + item.checked = output !== ""; + } else { //user pressed cancel + item.checked = !item.checked; //reset checkbox + } +} diff --git a/package.json b/package.json index 71063a7077..fd9cff7e48 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "YoutubeNonStop": "git://github.com/lawfx/YoutubeNonStop.git#v0.9.0", "async-mutex": "^0.3.2", "browser-id3-writer": "^4.4.0", + "custom-electron-prompt": "^1.1.0", "chokidar": "^3.5.2", "custom-electron-titlebar": "^3.2.7", "discord-rpc": "^3.2.0", diff --git a/plugins/precise-volume/front.js b/plugins/precise-volume/front.js index 7195b5bf33..f44aa643b9 100644 --- a/plugins/precise-volume/front.js +++ b/plugins/precise-volume/front.js @@ -12,9 +12,7 @@ module.exports = (options) => { setupLocalArrowShortcuts(options); - if (options.globalShortcuts?.enabled) { - setupGlobalShortcuts(options); - } + setupGlobalShortcuts(options); firstRun(options); diff --git a/plugins/precise-volume/menu.js b/plugins/precise-volume/menu.js index d4ed37a7a0..06ee84b9f0 100644 --- a/plugins/precise-volume/menu.js +++ b/plugins/precise-volume/menu.js @@ -1,13 +1,16 @@ const { enabled } = require("./back"); const { setOptions } = require("../../config/plugins"); +const prompt = require("custom-electron-prompt"); +const promptOptions = require("../../providers/prompt-options"); + module.exports = (win, options) => [ { - label: "Arrowkeys controls", + label: "Local Arrowkeys Controls", type: "checkbox", checked: !!options.arrowsShortcut, - click: (item) => { - // Dynamically change setting if plugin enabled + click: item => { + // Dynamically change setting if plugin is enabled if (enabled()) { win.webContents.send("setArrowsShortcut", item.checked); } else { // Fallback to usual method if disabled @@ -15,5 +18,61 @@ module.exports = (win, options) => [ setOptions("precise-volume", options); } } + }, + { + label: "Global Hotkeys", + type: "checkbox", + checked: !!options.globalShortcuts.volumeUp || !!options.globalShortcuts.volumeDown, + click: item => promptGlobalShortcuts(win, options, item) + }, + { + label: "Set Custom Volume Steps", + click: () => promptVolumeSteps(win, options) } ]; + +// Helper function for globalShortcuts prompt +const kb = (label_, value_, default_) => { return { value: value_, label: label_, default: default_ || undefined }; }; + +async function promptVolumeSteps(win, options) { + const output = await prompt({ + title: "Volume Steps", + label: "Choose Volume Increase/Decrease Steps", + value: options.steps || 1, + type: "counter", + counterOptions: { minimum: 0, maximum: 100, multiFire: true }, + width: 380, + ...promptOptions() + }, win) + + if (output || output === 0) { // 0 is somewhat valid + options.steps = output; + setOptions("precise-volume", options); + } +} + +async function promptGlobalShortcuts(win, options, item) { + const output = await prompt({ + title: "Global Volume Keybinds", + label: "Choose Global Volume Keybinds:", + type: "keybind", + keybindOptions: [ + kb("Increase Volume", "volumeUp", options.globalShortcuts?.volumeUp), + kb("Decrease Volume", "volumeDown", options.globalShortcuts?.volumeDown) + ], + ...promptOptions() + }, win) + + if (output) { + for (const { value, accelerator } of output) { + options.globalShortcuts[value] = accelerator; + } + + setOptions("precise-volume", options); + + item.checked = !!options.globalShortcuts.volumeUp || !!options.globalShortcuts.volumeDown; + } else { + // Reset checkbox if prompt was canceled + item.checked = !item.checked; + } +} diff --git a/plugins/shortcuts/back.js b/plugins/shortcuts/back.js index ba12ba42c0..f0d07e3982 100644 --- a/plugins/shortcuts/back.js +++ b/plugins/shortcuts/back.js @@ -21,9 +21,12 @@ function registerShortcuts(win, options) { const songControls = getSongControls(win); const { playPause, next, previous, search } = songControls; - _registerGlobalShortcut(win.webContents, "MediaPlayPause", playPause); - _registerGlobalShortcut(win.webContents, "MediaNextTrack", next); - _registerGlobalShortcut(win.webContents, "MediaPreviousTrack", previous); + if (options.overrideMediaKeys) { + _registerGlobalShortcut(win.webContents, "MediaPlayPause", playPause); + _registerGlobalShortcut(win.webContents, "MediaNextTrack", next); + _registerGlobalShortcut(win.webContents, "MediaPreviousTrack", previous); + } + _registerLocalShortcut(win, "CommandOrControl+F", search); _registerLocalShortcut(win, "CommandOrControl+L", search); @@ -44,24 +47,31 @@ function registerShortcuts(win, options) { } const { global, local } = options; - (global || []).forEach(({ shortcut, action }) => { - console.debug("Registering global shortcut", shortcut, ":", action); - if (!action || !songControls[action]) { - console.warn("Invalid action", action); - return; - } + const shortcutOptions = { global, local }; - _registerGlobalShortcut(win.webContents, shortcut, songControls[action]); - }); - (local || []).forEach(({ shortcut, action }) => { - console.debug("Registering local shortcut", shortcut, ":", action); - if (!action || !songControls[action]) { - console.warn("Invalid action", action); - return; - } + for (const optionType in shortcutOptions) { + registerAllShortcuts(shortcutOptions[optionType], optionType); + } - _registerLocalShortcut(win, shortcut, songControls[action]); - }); + function registerAllShortcuts(container, type) { + for (const action in container) { + if (!container[action]) { + continue; // Action accelerator is empty + } + + console.debug(`Registering ${type} shortcut`, container[action], ":", action); + if (!songControls[action]) { + console.warn("Invalid action", action); + continue; + } + + if (type === "global") { + _registerGlobalShortcut(win.webContents, container[action], songControls[action]); + } else { // type === "local" + _registerLocalShortcut(win, local[action], songControls[action]); + } + } + } } module.exports = registerShortcuts; diff --git a/plugins/shortcuts/menu.js b/plugins/shortcuts/menu.js new file mode 100644 index 0000000000..20f21233a9 --- /dev/null +++ b/plugins/shortcuts/menu.js @@ -0,0 +1,53 @@ +const { setOptions } = require("../../config/plugins"); +const prompt = require("custom-electron-prompt"); +const promptOptions = require("../../providers/prompt-options"); + +module.exports = (win, options) => [ + { + label: "Set Global Song Controls", + click: () => promptKeybind(options, win) + }, + { + label: "Override MediaKeys", + type: "checkbox", + checked: options.overrideMediaKeys, + click: item => setOption(options, "overrideMediaKeys", item.checked) + } +]; + +function setOption(options, key = null, newValue = null) { + if (key && newValue !== null) { + options[key] = newValue; + } + + setOptions("shortcuts", options); +} + +// Helper function for keybind prompt +const kb = (label_, value_, default_) => { return { value: value_, label: label_, default: default_ }; }; + +async function promptKeybind(options, win) { + const output = await prompt({ + title: "Global Keybinds", + label: "Choose Global Keybinds for Songs Control:", + type: "keybind", + keybindOptions: [ // If default=undefined then no default is used + kb("Previous", "previous", options.global?.previous), + kb("Play / Pause", "playPause", options.global?.playPause), + kb("Next", "next", options.global?.next) + ], + height: 270, + ...promptOptions() + }, win); + + if (output) { + if (!options.global) { + options.global = {}; + } + for (const { value, accelerator } of output) { + options.global[value] = accelerator; + } + setOption(options); + } + // else -> pressed cancel +} diff --git a/providers/prompt-custom-titlebar.js b/providers/prompt-custom-titlebar.js new file mode 100644 index 0000000000..affa92062c --- /dev/null +++ b/providers/prompt-custom-titlebar.js @@ -0,0 +1,14 @@ +const customTitlebar = require("custom-electron-titlebar"); + +module.exports = () => { + new customTitlebar.Titlebar({ + backgroundColor: customTitlebar.Color.fromHex("#050505"), + minimizable: false, + maximizable: false, + menu: null + }); + const mainStyle = document.querySelector("#container").style; + mainStyle.width = "100%"; + mainStyle.position = "fixed"; + mainStyle.border = "unset"; +}; diff --git a/providers/prompt-options.js b/providers/prompt-options.js new file mode 100644 index 0000000000..16f02a99c7 --- /dev/null +++ b/providers/prompt-options.js @@ -0,0 +1,18 @@ +const path = require("path"); +const is = require("electron-is"); + +const iconPath = path.join(__dirname, "..", "assets", "youtube-music-tray.png"); +const customTitlebarPath = path.join(__dirname, "prompt-custom-titlebar.js"); + +const promptOptions = is.macOS() ? { + customStylesheet: "dark", + icon: iconPath +} : { + customStylesheet: "dark", + // The following are used for custom titlebar + frame: false, + customScript: customTitlebarPath, + enableRemoteModule: true +}; + +module.exports = () => promptOptions; diff --git a/yarn.lock b/yarn.lock index 74e407dc97..da62af9305 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2960,6 +2960,11 @@ cssstyle@^2.2.0: dependencies: cssom "~0.3.6" +custom-electron-prompt@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/custom-electron-prompt/-/custom-electron-prompt-1.1.0.tgz#611b790047c91f6b532c7861355a0e1f9a81aef2" + integrity sha512-YZYmwZnMOdoWROUlJ+rEMHYsp4XJNNqLj6sZnx5aKBJ8cprEjKP4L5wfo6U+yyX/L9fxVOtvYD0Mp8ki5I9Kow== + custom-electron-titlebar@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/custom-electron-titlebar/-/custom-electron-titlebar-3.2.7.tgz#fb249d6180cbda074b1d392bea755fa0743012a8"