Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Notifications] [Windows] Native interactive notifications #946

Merged
merged 21 commits into from
Mar 8, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion config/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@ const defaultConfig = {
enabled: false,
unpauseNotification: false,
urgency: "normal", //has effect only on Linux
interactive: false //has effect only on Windows
// the following has effect only on Windows
interactive: true,
toastStyle: 1, // see plugins/notifications/utils for more info
hideButtonText: false
},
"precise-volume": {
enabled: false,
Expand Down
37 changes: 22 additions & 15 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const { isTesting } = require("./utils/testing");
const { setUpTray } = require("./tray");
const { setupSongInfo } = require("./providers/song-info");
const { setupAppControls, restart } = require("./providers/app-controls");
const { APP_PROTOCOL, setupProtocolHandler, handleProtocol } = require("./providers/protocol-handler");

// Catch errors and log them
unhandled({
Expand All @@ -29,17 +30,9 @@ const app = electron.app;
let mainWindow;
autoUpdater.autoDownload = false;

if(config.get("options.singleInstanceLock")){
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) app.quit();

app.on('second-instance', () => {
if (!mainWindow) return;
if (mainWindow.isMinimized()) mainWindow.restore();
if (!mainWindow.isVisible()) mainWindow.show();
mainWindow.focus();
});
}
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) app.exit();

app.commandLine.appendSwitch(
"js-flags",
Expand Down Expand Up @@ -82,6 +75,7 @@ function onClosed() {
mainWindow = null;
}

/** @param {Electron.BrowserWindow} win */
function loadPlugins(win) {
injectCSS(win.webContents, path.join(__dirname, "youtube-music.css"));
// Load user CSS
Expand Down Expand Up @@ -354,7 +348,7 @@ app.on("ready", () => {
// Clear cache after 20s
const clearCacheTimeout = setTimeout(() => {
if (is.dev()) {
console.log("Clearing app cache.");
console.log("Clearing app cache.");
}
electron.session.defaultSession.clearCache();
clearTimeout(clearCacheTimeout);
Expand All @@ -363,9 +357,6 @@ app.on("ready", () => {

// Register appID on windows
if (is.windows()) {
// Depends on SnoreToast version https://github.com/KDE/snoretoast/blob/master/CMakeLists.txt#L5
const toastActivatorClsid = "eb1fdd5b-8f70-4b5a-b230-998a2dc19303";

const appID = "com.github.th-ch.youtube-music";
app.setAppUserModelId(appID);
const appLocation = process.execPath;
Expand All @@ -391,7 +382,6 @@ app.on("ready", () => {
cwd: path.dirname(appLocation),
description: "YouTube Music Desktop App - including custom plugins",
appUserModelId: appID,
toastActivatorClsid
}
);
}
Expand All @@ -402,6 +392,23 @@ app.on("ready", () => {
setApplicationMenu(mainWindow);
setUpTray(app, mainWindow);

setupProtocolHandler(mainWindow);

app.on('second-instance', (_event, commandLine, _workingDirectory) => {
const uri = `${APP_PROTOCOL}://`;
const protocolArgv = commandLine.find(arg => arg.startsWith(uri));
if (protocolArgv) {
const command = protocolArgv.slice(uri.length, -1);
if (is.dev()) console.debug(`Received command over protocol: "${command}"`);
handleProtocol(command);
return;
}
if (!mainWindow) return;
if (mainWindow.isMinimized()) mainWindow.restore();
if (!mainWindow.isVisible()) mainWindow.show();
mainWindow.focus();
});

// Autostart at login
app.setLoginItemSettings({
openAtLogin: config.get("options.startAtLogin"),
Expand Down
12 changes: 5 additions & 7 deletions menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,16 +131,14 @@ const mainMenuTemplate = (win) => {
],
},
{
label: "Single instance lock",
label: "Release single instance lock",
Araxeus marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: it seems the button will request the lock it it does not have it, name can be misleading

Suggested change
label: "Release single instance lock",
label: "Single instance lock",

type: "checkbox",
checked: config.get("options.singleInstanceLock"),
checked: false,
click: (item) => {
config.setMenuOption("options.singleInstanceLock", item.checked);
if (item.checked && !app.hasSingleInstanceLock()) {
app.requestSingleInstanceLock();
} else if (!item.checked && app.hasSingleInstanceLock()) {
if (item.checked && app.hasSingleInstanceLock())
app.releaseSingleInstanceLock();
}
else if (!item.checked && !app.hasSingleInstanceLock())
app.requestSingleInstanceLock();
},
},
{
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@
"md5": "^2.3.0",
"mpris-service": "^2.1.2",
"node-fetch": "^2.6.7",
"node-notifier": "^10.0.1",
"ytdl-core": "^4.11.1",
"ytpl": "^2.3.0"
},
Expand Down
17 changes: 10 additions & 7 deletions plugins/notifications/back.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@ const { Notification } = require("electron");
const is = require("electron-is");
const registerCallback = require("../../providers/song-info");
const { notificationImage } = require("./utils");
const config = require("./config");

const notify = (info, options) => {
const notify = (info) => {

// Fill the notification with content
const notification = {
title: info.title || "Playing",
body: info.artist,
icon: notificationImage(info),
silent: true,
urgency: options.urgency,
urgency: config.get('urgency'),
};

// Send the notification
Expand All @@ -21,24 +22,26 @@ const notify = (info, options) => {
return currentNotification;
};

const setup = (options) => {
const setup = () => {
let oldNotification;
let currentUrl;

registerCallback(songInfo => {
if (!songInfo.isPaused && (songInfo.url !== currentUrl || options.unpauseNotification)) {
if (!songInfo.isPaused && (songInfo.url !== currentUrl || config.get('unpauseNotification'))) {
// Close the old notification
oldNotification?.close();
currentUrl = songInfo.url;
// This fixes a weird bug that would cause the notification to be updated instead of showing
setTimeout(() => { oldNotification = notify(songInfo, options) }, 10);
setTimeout(() => { oldNotification = notify(songInfo) }, 10);
}
});
}

/** @param {Electron.BrowserWindow} win */
module.exports = (win, options) => {
config.init(options);
// Register the callback for new song information
is.windows() && options.interactive ?
require("./interactive")(win, options.unpauseNotification) :
setup(options);
require("./interactive")(win) :
setup();
};
22 changes: 22 additions & 0 deletions plugins/notifications/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const { setOptions, setMenuOptions } = require("../../config/plugins");

let config;

module.exports.init = (options) => {
config = options;
}

module.exports.setAndMaybeRestart = (option, value) => {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: should we make the name more explicit? Sthg like

Suggested change
module.exports.setAndMaybeRestart = (option, value) => {
module.exports.setInMenu = (option, value) => {

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think setAndMaybeRestart is more explicit since the function name indicates that the difference is the restart option
setInMenu doesn't tell me much tbh

config[option] = value;
setMenuOptions("notifications", config);
}

module.exports.set = (option, value) => {
config[option] = value;
setOptions("notifications", config);
}

module.exports.get = (option) => {
let res = config[option];
return res;
}
Loading