Feature/4.1.0 (#156)

* added protocol handler

* Switched icon strategies to fix bugs with icons

* fixed tray icon issues

* fixed about :)

* Fixed playback, mpris and API issues
This commit is contained in:
Rick van Lieshout 2022-08-07 16:05:48 +02:00 committed by GitHub
parent 3a3e0e1a2d
commit 1439a11969
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1030 additions and 1000 deletions

View File

@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 4.1.0
- Added `tidal://` protocol support
- Switched icon strategies to fix bugs with icons
- Fixed tray icon bugs
- Menu now shows in KDE as well
- Toggle window is supported from tray icon
- regular click is still ignored, see [this issue](https://github.com/electron/electron/issues/6773)
- Fixed about tab not showing
- Fixed playback, mpris and API issues
## 4.0.1 ## 4.0.1
- Updated build config to make use of a base file that doesn't build anything. - Updated build config to make use of a base file that doesn't build anything.

View File

@ -17,17 +17,23 @@ linux:
Name: tidal-hifi Name: tidal-hifi
GenericName: tidal-hifi GenericName: tidal-hifi
Comment: The web version of listen.tidal.com running in electron with hifi support thanks to widevine. Comment: The web version of listen.tidal.com running in electron with hifi support thanks to widevine.
Icon: assets/icon.png Icon: icon.png
StartupNotify: true StartupNotify: true
Terminal: false Terminal: false
Type: Application Type: Application
Categories: Network;Application;AudioVideo;Audio;Video Categories: Network;Application;AudioVideo;Audio;Video
StartupWMClass: tidal-hifi StartupWMClass: tidal-hifi
X-PulseAudio-Properties: media.role=music X-PulseAudio-Properties: media.role=music
MimeType: x-scheme-handler/tidal;
mac: mac:
category: public.app-category.entertainment category: public.app-category.entertainment
win: win:
icon: build/icon.png icon: icon.png
artifactName: "tidalhifi" artifactName: "tidalhifi"
appId: com.rickvanlieshout.tidalhifi appId: com.rickvanlieshout.tidalhifi
executableName: tidalhifi executableName: tidalhifi
protocols:
name: "tidal"
role: "Viewer"
schemes: ["tidal"]

View File

@ -1,6 +1,6 @@
extends: ./build/electron-builder.base.yml extends: ./build/electron-builder.base.yml
linux: linux:
category: Audio category: Audio
icon: ./assets/icon.png icon: icon.png
target: target:
- deb - deb

View File

@ -1,6 +1,6 @@
extends: ./build/electron-builder.base.yml extends: ./build/electron-builder.base.yml
linux: linux:
category: Audio category: Audio
icon: ./assets/icon.png icon: icon.png
target: target:
- pacman - pacman

View File

@ -1,6 +1,6 @@
extends: ./build/electron-builder.base.yml extends: ./build/electron-builder.base.yml
linux: linux:
category: Audio category: Audio
icon: ./assets/TIDAL.icns icon: icon.png
target: target:
- rpm - rpm

View File

@ -1,6 +1,6 @@
extends: ./build/electron-builder.base.yml extends: ./build/electron-builder.base.yml
linux: linux:
category: Audio category: Audio
icon: ./assets/icon.png icon: icon.png
target: target:
- snap - snap

View File

@ -14,7 +14,7 @@ linux:
- freebsd - freebsd
win: win:
target: msi target: msi
icon: build/icon.png icon: icon.png
artifactName: "tidalhifi" artifactName: "tidalhifi"
appId: com.rickvanlieshout.tidalhifi appId: com.rickvanlieshout.tidalhifi
executableName: tidalhifi executableName: tidalhifi

BIN
build/icon-inverted.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

BIN
build/icon.icns Executable file

Binary file not shown.

669
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "tidal-hifi", "name": "tidal-hifi",
"version": "4.0.1", "version": "4.1.0",
"description": "Tidal on Electron with widevine(hifi) support", "description": "Tidal on Electron with widevine(hifi) support",
"main": "src/main.js", "main": "src/main.js",
"scripts": { "scripts": {
@ -27,17 +27,17 @@
"dependencies": { "dependencies": {
"@electron/remote": "^2.0.8", "@electron/remote": "^2.0.8",
"discord-rpc": "^4.0.1", "discord-rpc": "^4.0.1",
"electron-store": "^8.0.1", "electron-store": "^8.1.0",
"express": "^4.17.1", "express": "^4.18.1",
"hotkeys-js": "^3.8.7", "hotkeys-js": "^3.9.4",
"mpris-service": "^2.1.2", "mpris-service": "^2.1.2",
"request": "^2.88.2" "request": "^2.88.2"
}, },
"devDependencies": { "devDependencies": {
"@mastermindzh/prettier-config": "^1.0.0", "@mastermindzh/prettier-config": "^1.0.0",
"electron": "git+https://github.com/castlabs/electron-releases.git#v19.0.5+wvcus", "electron": "git+https://github.com/castlabs/electron-releases.git#v19.0.5+wvcus",
"electron-builder": "^23.2.0", "electron-builder": "^23.3.3",
"prettier": "^2.5.0" "prettier": "^2.7.1"
}, },
"prettier": "@mastermindzh/prettier-config" "prettier": "@mastermindzh/prettier-config"
} }

3
src/constants/values.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = {
name: "tidal-hifi",
};

View File

@ -1,5 +1,5 @@
require("@electron/remote/main").initialize(); require("@electron/remote/main").initialize();
const { app, BrowserWindow, components, globalShortcut, ipcMain } = require("electron"); const { app, BrowserWindow, components, globalShortcut, ipcMain, protocol } = require("electron");
const { const {
settings, settings,
store, store,
@ -21,6 +21,7 @@ const flagValues = require("./constants/flags");
let mainWindow; let mainWindow;
let icon = path.join(__dirname, "../assets/icon.png"); let icon = path.join(__dirname, "../assets/icon.png");
const PROTOCOL_PREFIX = "tidal";
setFlags(); setFlags();
@ -84,7 +85,7 @@ function createWindow(options = {}) {
}, },
}); });
require("@electron/remote/main").enable(mainWindow.webContents); require("@electron/remote/main").enable(mainWindow.webContents);
registerHttpProtocols();
syncMenuBarWithStore(); syncMenuBarWithStore();
// load the Tidal website // load the Tidal website
@ -113,6 +114,13 @@ function createWindow(options = {}) {
}); });
} }
function registerHttpProtocols() {
protocol.registerHttpProtocol(PROTOCOL_PREFIX, (request, _callback) => {
mainWindow.loadURL(`${tidalUrl}/${request.url.substring(PROTOCOL_PREFIX.length + 3)}`);
});
app.setAsDefaultProtocolClient(PROTOCOL_PREFIX);
}
function addGlobalShortcuts() { function addGlobalShortcuts() {
Object.keys(mediaKeys).forEach((key) => { Object.keys(mediaKeys).forEach((key) => {
globalShortcut.register(`${key}`, () => { globalShortcut.register(`${key}`, () => {
@ -131,7 +139,7 @@ app.on("ready", async () => {
addMenu(); addMenu();
createSettingsWindow(); createSettingsWindow();
addGlobalShortcuts(); addGlobalShortcuts();
store.get(settings.trayIcon) && addTray({ icon }) && refreshTray(); store.get(settings.trayIcon) && addTray(mainWindow, { icon }) && refreshTray();
store.get(settings.api) && expressModule.run(mainWindow); store.get(settings.api) && expressModule.run(mainWindow);
store.get(settings.enableDiscord) && discordModule.initRPC(); store.get(settings.enableDiscord) && discordModule.initRPC();
// mainWindow.webContents.openDevTools(); // mainWindow.webContents.openDevTools();

View File

@ -26,10 +26,10 @@
</div> </div>
<div class="body"> <div class="body">
<div class="tabset"> <div class="tabset">
<!-- Tab 1 -->
<input type="radio" name="tabset" id="general" checked /> <input type="radio" name="tabset" id="general" checked />
<label for="general">General</label> <label for="general">General</label>
<!-- Tab 2 -->
<input type="radio" name="tabset" id="api" /> <input type="radio" name="tabset" id="api" />
<label for="api">Api</label> <label for="api">Api</label>
@ -108,7 +108,9 @@
<div class="option"> <div class="option">
<h4>Hotkeys</h4> <h4>Hotkeys</h4>
<p> <p>
Enables extra hotkeys to achieve feature parity with the <a id="openExternal" style="text-decoration: underline; cursor: pointer;" data-url= "https://defkey.com/tidal-desktop-shortcuts">desktop apps</a><br /> Enables extra hotkeys to achieve feature parity with the <a id="openExternal"
style="text-decoration: underline; cursor: pointer;"
data-url="https://defkey.com/tidal-desktop-shortcuts">desktop apps</a><br />
</p> </p>
<label class="switch"> <label class="switch">
<input id="enableCustomHotkeys" type="checkbox"> <input id="enableCustomHotkeys" type="checkbox">
@ -131,7 +133,8 @@
<div class="section"> <div class="section">
<h3>Api</h3> <h3>Api</h3>
<p style="margin-bottom: 15px;"> <p style="margin-bottom: 15px;">
Tidal-hifi has a web api built in to allow users to get current song information. You can optionally enable playback control as well. Tidal-hifi has a web api built in to allow users to get current song information. You can optionally
enable playback control as well.
</p> </p>
<div class="option"> <div class="option">
@ -188,7 +191,6 @@
</div> </div>
</div> </div>
</section> </section>
<section id="advanced" class="tab-panel"> <section id="advanced" class="tab-panel">
<div class="section"> <div class="section">
<h3>Flags</h3> <h3>Flags</h3>
@ -196,7 +198,8 @@
<h4>Disable hardware media keys</h4> <h4>Disable hardware media keys</h4>
<p> <p>
Disable built-in media keys. <br /> Disable built-in media keys. <br />
Also prevents certain desktop environments from recognizing the chrome MPRIS client separetely from the custom MPRIS client. Also prevents certain desktop environments from recognizing the chrome MPRIS client separetely from the
custom MPRIS client.
</p> </p>
<label class="switch"> <label class="switch">
<input id="disableHardwareMediaKeys" type="checkbox"> <input id="disableHardwareMediaKeys" type="checkbox">
@ -215,14 +218,18 @@
</div> </div>
</div> </div>
</section> </section>
<!-- about -->
<section id="about" class="tab-panel"> <section id="about" class="tab-panel">
<div class="section"> <div class="section">
<img alt="tidal icon" style="width: 100px; height: auto; display: block; margin: 0 auto; margin-bottom: 20px; margin-top: 20px;" src = "./icon.png"> <img alt="tidal icon"
style="width: 100px; height: auto; display: block; margin: 0 auto; margin-bottom: 20px; margin-top: 20px;"
src="./icon.png">
<p style="max-width: 350px; display:block; margin: 0 auto; text-align: center;"> <p style="max-width: 350px; display:block; margin: 0 auto; text-align: center;">
<a id="openExternal" style="text-decoration: underline; cursor: pointer;" data-url="https://github.com/Mastermindzh/tidal-hifi">Tidal-hifi</a> is made by <a id="openExternal" data-url="https://www.rickvanlieshout.com" style="text-decoration: underline; cursor: pointer;">Rick van Lieshout</a>.<br /> <a id="openExternal" style="text-decoration: underline; cursor: pointer;"
It uses <a style="text-decoration: underline; cursor: pointer;" id="openExternal" data-url="https://castlabs.com/">Castlabs'</a> versions of Electron for widevine support. data-url="https://github.com/Mastermindzh/tidal-hifi">Tidal-hifi</a> is made by <a id="openExternal"
data-url="https://www.rickvanlieshout.com" style="text-decoration: underline; cursor: pointer;">Rick van
Lieshout</a>.<br />
It uses <a style="text-decoration: underline; cursor: pointer;" id="openExternal"
data-url="https://castlabs.com/">Castlabs'</a> versions of Electron for widevine support.
</p> </p>
</div> </div>
</section> </section>
@ -239,6 +246,7 @@
-webkit-user-select: none; -webkit-user-select: none;
-webkit-app-region: drag; -webkit-app-region: drag;
} }
.header a { .header a {
-webkit-app-region: no-drag; -webkit-app-region: no-drag;
} }
@ -374,6 +382,7 @@
box-shadow: inset 0 0 6px rgba(0, 0, 0, .3); box-shadow: inset 0 0 6px rgba(0, 0, 0, .3);
background-color: #5a5a5a; background-color: #5a5a5a;
} }
/* Tabs */ /* Tabs */
.tabset>input[type="radio"] { .tabset>input[type="radio"] {
@ -388,8 +397,8 @@
.tabset>input:first-child:checked~.tab-panels>.tab-panel:first-child, .tabset>input:first-child:checked~.tab-panels>.tab-panel:first-child,
.tabset>input:nth-child(3):checked~.tab-panels>.tab-panel:nth-child(2), .tabset>input:nth-child(3):checked~.tab-panels>.tab-panel:nth-child(2),
.tabset>input:nth-child(5):checked~.tab-panels>.tab-panel:nth-child(3), .tabset>input:nth-child(5):checked~.tab-panels>.tab-panel:nth-child(3),
.tabset > input:nth-child(7):checked ~ .tab-panels > .tab-panel:nth-child(4) .tabset>input:nth-child(7):checked~.tab-panels>.tab-panel:nth-child(4),
{ .tabset>input:nth-child(9):checked~.tab-panels>.tab-panel:nth-child(5) {
display: block; display: block;
} }
@ -414,6 +423,7 @@
.tab-panel { .tab-panel {
padding: 10px 0; padding: 10px 0;
} }
/* switches */ /* switches */
/* The switch - the box around the slider */ /* The switch - the box around the slider */
@ -423,6 +433,7 @@
width: 50px; width: 50px;
height: 28px; height: 28px;
} }
/* Hide default HTML checkbox */ /* Hide default HTML checkbox */
.switch input { .switch input {
@ -430,6 +441,7 @@
width: 0; width: 0;
height: 0; height: 0;
} }
/* The slider */ /* The slider */
.slider { .slider {
@ -469,6 +481,7 @@
-ms-transform: translateX(22px); -ms-transform: translateX(22px);
transform: translateX(22px); transform: translateX(22px);
} }
/* Rounded sliders */ /* Rounded sliders */
.slider.round { .slider.round {
@ -478,6 +491,7 @@
.slider.round:before { .slider.round:before {
border-radius: 50%; border-radius: 50%;
} }
/* input field */ /* input field */
input { input {
@ -519,9 +533,11 @@
margin-top: 10px; margin-top: 10px;
cursor: pointer; cursor: pointer;
} }
button:hover { button:hover {
background-color: rgba(229, 238, 255, .3); background-color: rgba(229, 238, 255, .3);
} }
textarea { textarea {
color: #72777f; color: #72777f;
background: #242528; background: #242528;
@ -531,10 +547,12 @@
padding: 0 0 12px; padding: 0 0 12px;
display: block; display: block;
} }
textarea:focus { textarea:focus {
outline: none; outline: none;
border: 0; border: 0;
border-bottom: 1px solid #0ff; border-bottom: 1px solid #0ff;
} }
</style> </style>
</html> </html>

View File

@ -1,8 +1,8 @@
const { setTitle } = require("./scripts/window-functions"); const { setTitle } = require("./scripts/window-functions");
const { dialog, process, Notification } = require('@electron/remote'); const { dialog, process, Notification } = require("@electron/remote");
const { store, settings } = require("./scripts/settings"); const { store, settings } = require("./scripts/settings");
const { ipcRenderer } = require("electron"); const { ipcRenderer } = require("electron");
const { app } = require('@electron/remote'); const { app } = require("@electron/remote");
const { downloadFile } = require("./scripts/download"); const { downloadFile } = require("./scripts/download");
const statuses = require("./constants/statuses"); const statuses = require("./constants/statuses");
const hotkeys = require("./scripts/hotkeys"); const hotkeys = require("./scripts/hotkeys");
@ -12,10 +12,6 @@ const appName = "Tidal Hifi";
let currentSong = ""; let currentSong = "";
let player; let player;
let currentPlayStatus = statuses.paused; let currentPlayStatus = statuses.paused;
let progressBarTime;
let currentTimeChanged = false;
let currentTime;
let currentURL = undefined;
let isMutedArtist = false; let isMutedArtist = false;
const elements = { const elements = {
@ -313,19 +309,16 @@ function updateMediaInfo(options, notify) {
/** /**
* Checks if Tidal is playing a video or song by grabbing the "a" element from the title. * Checks if Tidal is playing a video or song by grabbing the "a" element from the title.
* If it's a song it sets the track URL as currentURL, If it's a video it will set currentURL to undefined. * If it's a song it returns the track URL, if not it will return undefined
*/ */
function updateURL() { function getTrackURL() {
const URLelement = elements.get("title").querySelector("a"); const URLelement = elements.get("title").querySelector("a");
switch (URLelement) { if (URLelement !== null) {
case null:
currentURL = undefined;
break;
default:
const id = URLelement.href.replace(/[^0-9]/g, ""); const id = URLelement.href.replace(/[^0-9]/g, "");
currentURL = `https://tidal.com/browse/track/${id}`; return `https://tidal.com/browse/track/${id}`;
break;
} }
return window.location;
} }
/** /**
@ -337,7 +330,6 @@ setInterval(function () {
const album = elements.getAlbumName(); const album = elements.getAlbumName();
const current = elements.getText("current"); const current = elements.getText("current");
const duration = elements.getText("duration"); const duration = elements.getText("duration");
const progressBarcurrentTime = elements.get("bar").getAttribute("aria-valuenow");
const songDashArtistTitle = `${title} - ${artists}`; const songDashArtistTitle = `${title} - ${artists}`;
const currentStatus = getCurrentlyPlayingStatus(); const currentStatus = getCurrentlyPlayingStatus();
const options = { const options = {
@ -345,46 +337,22 @@ setInterval(function () {
message: artists, message: artists,
album: album, album: album,
status: currentStatus, status: currentStatus,
url: currentURL, url: getTrackURL(),
current: current, current,
duration: duration, duration,
"app-name": appName, "app-name": appName,
}; };
const playStatusChanged = currentStatus !== currentPlayStatus;
const progressBarTimeChanged = progressBarcurrentTime !== progressBarTime;
const titleOrArtistChanged = currentSong !== songDashArtistTitle; const titleOrArtistChanged = currentSong !== songDashArtistTitle;
muteArtistIfFoundInMutedArtistsList(); muteArtistIfFoundInMutedArtistsList();
if (titleOrArtistChanged || playStatusChanged || progressBarTimeChanged || currentTimeChanged) {
// update title, url and play info with new info // update title, url and play info with new info
setTitle(songDashArtistTitle); setTitle(songDashArtistTitle);
updateURL(); getTrackURL();
currentSong = songDashArtistTitle; currentSong = songDashArtistTitle;
currentPlayStatus = currentStatus; currentPlayStatus = currentStatus;
// check progress bar value and make sure current stays up to date after switch
if (progressBarTime != progressBarcurrentTime && !titleOrArtistChanged) {
progressBarTime = progressBarcurrentTime;
currentTime = options.current;
options.duration = duration;
currentTimeChanged = true;
}
if (currentTimeChanged) {
if (options.current == currentTime && currentStatus != "paused") return;
currentTime = options.current;
currentTimeChanged = false;
}
// make sure current is set to 0 if title changes
if (titleOrArtistChanged) {
options.current = "0:00";
currentTime = options.current;
progressBarTime = progressBarcurrentTime;
}
const image = elements.getSongIcon(); const image = elements.getSongIcon();
new Promise((resolve) => { new Promise((resolve) => {
@ -410,7 +378,6 @@ setInterval(function () {
}, },
() => {} () => {}
); );
}
/** /**
* Checks whether the current artist is included in the "muted artists" list and if so it will automatically mute the player * Checks whether the current artist is included in the "muted artists" list and if so it will automatically mute the player
@ -429,7 +396,7 @@ setInterval(function () {
} }
} }
} }
}, 500); }, 1000);
if (process.platform === "linux" && store.get(settings.mpris)) { if (process.platform === "linux" && store.get(settings.mpris)) {
try { try {

View File

@ -1,6 +1,7 @@
const { Menu, app } = require("electron"); const { Menu, app } = require("electron");
const { showSettingsWindow } = require("./settings"); const { showSettingsWindow } = require("./settings");
const isMac = process.platform === "darwin"; const isMac = process.platform === "darwin";
const { name } = require("./../constants/values");
const settingsMenuEntry = { const settingsMenuEntry = {
label: "Settings", label: "Settings",
@ -15,14 +16,24 @@ const quitMenuEntry = {
click() { click() {
app.exit(0); app.exit(0);
}, },
accelerator: "Control+Q" accelerator: "Control+Q",
};
const menuModule = {};
menuModule.getMenu = function (mainWindow) {
const toggleWindow = {
label: "Toggle Window",
click: function () {
mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show();
},
}; };
const mainMenu = [ const mainMenu = [
...(isMac ...(isMac
? [ ? [
{ {
label: app.name, label: name,
submenu: [ submenu: [
{ role: "about" }, { role: "about" },
settingsMenuEntry, settingsMenuEntry,
@ -78,12 +89,14 @@ const mainMenu = [
{ role: "zoomout" }, { role: "zoomout" },
{ type: "separator" }, { type: "separator" },
{ role: "togglefullscreen" }, { role: "togglefullscreen" },
{ role: "toggledevtools" },
], ],
}, },
{ {
label: "Window", label: "Window",
submenu: [ submenu: [
{ role: "minimize" }, { role: "minimize" },
toggleWindow,
...(isMac ...(isMac
? [{ type: "separator" }, { role: "front" }, { type: "separator" }, { role: "window" }] ? [{ type: "separator" }, { role: "front" }, { type: "separator" }, { role: "window" }]
: [{ role: "close" }]), : [{ role: "close" }]),
@ -96,11 +109,9 @@ const mainMenu = [
showSettingsWindow("about"); showSettingsWindow("about");
}, },
}, },
toggleWindow,
]; ];
const menuModule = { mainMenu };
menuModule.getMenu = function () {
return Menu.buildFromTemplate(mainMenu); return Menu.buildFromTemplate(mainMenu);
}; };

View File

@ -1,48 +1,21 @@
const { Tray, app } = require("electron"); const { Tray } = require("electron");
const { Menu } = require("electron"); const { getMenu } = require("./menu");
const { getMenu, mainMenu } = require("./menu");
const { store, settings } = require("./settings");
const trayModule = {}; const trayModule = {};
let tray; let tray;
trayModule.addTray = function (options = { icon: "" }) { trayModule.addTray = function (mainWindow, options = { icon: "" }) {
tray = new Tray(options.icon); tray = new Tray(options.icon);
tray.setIgnoreDoubleClickEvents(true);
tray.setToolTip("Tidal-hifi");
const menu = getMenu(mainWindow);
tray.setContextMenu(menu);
}; };
trayModule.refreshTray = function (mainWindow) { trayModule.refreshTray = function (mainWindow) {
if (!tray) { if (!tray) {
trayModule.addTray(); trayModule.addTray(mainWindow);
}
tray.on("click", function (e) {
if (mainWindow) {
mainWindow.show();
}
});
tray.setToolTip("Tidal-hifi");
if (mainWindow && store.get(settings.minimizeOnClose)) {
tray.setContextMenu(
Menu.buildFromTemplate([
{
label: "Toggle Window",
click: function () {
mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show();
},
},
{
label: "Quit",
click: function () {
mainWindow.destroy();
app.quit();
},
},
...mainMenu, //we add menu items from the other context
])
);
} else {
tray.setContextMenu(getMenu());
} }
}; };