Compare commits

..

1 Commits

Author SHA1 Message Date
53b1e55dbd Merge f5ccbda7d9 into ad05b767d8 2023-07-24 12:04:15 +02:00
50 changed files with 269 additions and 1016 deletions

14
.vscode/settings.json vendored
View File

@@ -1,26 +1,14 @@
{ {
"cSpell.words": [ "cSpell.words": [
"Brainz",
"Castlabs",
"flac", "flac",
"Flatpak",
"geqnfr", "geqnfr",
"hifi", "hifi",
"libnotify",
"listenbrainz",
"playpause", "playpause",
"prs",
"rescrobbler", "rescrobbler",
"scrobble",
"scrobbling",
"Songwhip", "Songwhip",
"trackid", "trackid",
"tracklist", "tracklist",
"widevine", "widevine",
"xesam" "xesam"
], ]
"sonarlint.connectedMode.project": {
"connectionId": "public-sonarcloud",
"projectKey": "Mastermindzh_tidal-hifi"
}
} }

View File

@@ -4,33 +4,6 @@ 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).
## [5.7.0]
- Renamed app to TIDAL Hi-Fi.
- Made sure all windows run with the same web preferences set (compared to main app).
- Fixes the last.fm bug.
- Added settings to customize the Discord rich presence information
- Discord settings are now also collapsible like the ListenBrainz ones are
- Restyled settings menu to include version number and useful links on the about page
![The new about page](./docs/images/new-about.png)
- The ListenBrainz integration has been extended with a configurable (5 seconds by default) delay in song reporting so that it doesn't spam the API when you are cycling through songs.
- Custom CSS now also applies to settings window
![Tokyo Night theme on settings window](./docs/images/customcss-menu.png)
## [5.6.0]
- Added support for Wayland (on by default) fixes [#262](https://github.com/Mastermindzh/tidal-hifi/issues/262) and [#157](https://github.com/Mastermindzh/tidal-hifi/issues/157)
- Made it clear in the readme that this TIDAL Hi-Fi client supports High & Max audio settings. fixes [#261](https://github.com/Mastermindzh/tidal-hifi/issues/261)
- Added app suspension inhibitors when music is playing. fixes [#257](https://github.com/Mastermindzh/tidal-hifi/issues/257)
- Fixed bug with theme files from user directory trying to load: "an error occurred reading the theme file"
- Fixed: config flags not being set correctly
- [DEV]:
- Logger is now static and will automatically call either ipcRenderer or ipcMain
## 5.5.0
- ListenBrainz integration added (thanks @Mar0xy)
## 5.4.0 ## 5.4.0
- Removed Windows builds (from publishes) as they don't work anymore. - Removed Windows builds (from publishes) as they don't work anymore.
@@ -45,7 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- SPKChaosPhoenix updated the beautiful Tokyo Night theme: - SPKChaosPhoenix updated the beautiful Tokyo Night theme:
![tidal with the tokyo night theme applied](./docs/images/tokyo-night.png) ![](./docs/images/tokyo-night.png)
## 5.2.0 ## 5.2.0
@@ -102,7 +75,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- New settings window by BlueManCZ - New settings window by BlueManCZ
- Fixed the desktop files in electron-builder - Fixed the desktop files in electron-builder
- icon is set to new static path based on Arch/Debian - icon is set to new static path based on Arch/Debian
- Name has changed to TIDAL Hi-Fi - Name has changed to Tidal-Hifi
## 4.1.2 ## 4.1.2
@@ -150,7 +123,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Updated to Electron 15 - Updated to Electron 15
- Fixed the develop "build-unpacked" command - Fixed the develop "build-unpacked" command
- Added setting to disable multiple TIDAL Hi-Fi windows (defaults to true) - Added setting to disable multiple tidal-hifi windows (defaults to true)
- Added setting to disable HardwareMediaKeyHandling (defaults to false) - Added setting to disable HardwareMediaKeyHandling (defaults to false)
## 2.8.2 ## 2.8.2
@@ -188,7 +161,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## 2.5.0 ## 2.5.0
- Notify-send now correctly shows "Tidal Hi-Fi" as the program name - Notify-send now correctly shows "Tidal HiFi" as the program name
- Updated dependencies (including electron itself) - Updated dependencies (including electron itself)
### known issues ### known issues

View File

@@ -1,20 +1,20 @@
# TIDAL Hi-Fi (Max quality)<img src = "./build/icon.png" height="40" align="right"/> # Tidal-hifi<img src = "./build/icon.png" height="40" align="right"/>
![GitHub release](https://img.shields.io/github/release/Mastermindzh/tidal-hifi.svg) [![github builds](https://github.com/mastermindzh/tidal-hifi/actions/workflows/build.yml/badge.svg)](https://github.com/Mastermindzh/tidal-hifi/actions) [![Build Status](https://ci.mastermindzh.tech/api/badges/Mastermindzh/tidal-hifi/status.svg)](https://ci.mastermindzh.tech/Mastermindzh/tidal-hifi) [![Discord logo](./docs/images/discord.png)](https://discord.gg/yhNwf4v4He) ![GitHub release](https://img.shields.io/github/release/Mastermindzh/tidal-hifi.svg) [![github builds](https://github.com/mastermindzh/tidal-hifi/actions/workflows/build.yml/badge.svg)](https://github.com/Mastermindzh/tidal-hifi/actions) [![Build Status](https://ci.mastermindzh.tech/api/badges/Mastermindzh/tidal-hifi/status.svg)](https://ci.mastermindzh.tech/Mastermindzh/tidal-hifi) [![Discord logo](./docs/images/discord.png)](https://discord.gg/yhNwf4v4He)
The web version of [listen.tidal.com](https://listen.tidal.com) running in electron with Hi-Fi (High & Max) support thanks to widevine. The web version of [listen.tidal.com](https://listen.tidal.com) running in electron with hifi support thanks to widevine.
![TIDAL Hi-Fi preview](./docs/images/preview.png) ![tidal-hifi preview](./docs/images/preview.png)
## Table of Contents ## Table of Contents
<!-- toc --> <!-- toc -->
- [TIDAL Hi-Fi (Max quality)](#tidal-hi-fi-max-quality) - [Tidal-hifi](#tidal-hifi)
- [Table of Contents](#table-of-contents) - [Table of Contents](#table-of-contents)
- [Features](#features) - [Features](#features)
- [Contributions](#contributions) - [Contributions](#contributions)
- [Why did I create TIDAL Hi-Fi?](#why-did-i-create-tidal-hi-fi) - [Why did I create tidal-hifi?](#why-did-i-create-tidal-hifi)
- [Why not extend existing projects?](#why-not-extend-existing-projects) - [Why not extend existing projects?](#why-not-extend-existing-projects)
- [Installation](#installation) - [Installation](#installation)
- [Dependencies](#dependencies) - [Dependencies](#dependencies)
@@ -25,8 +25,9 @@ The web version of [listen.tidal.com](https://listen.tidal.com) running in elect
- [Nix](#nix) - [Nix](#nix)
- [Using source](#using-source) - [Using source](#using-source)
- [Integrations](#integrations) - [Integrations](#integrations)
- [Known bugs](#known-bugs) - [Known bugs](#known-bugs)
- [DRM not working on Windows](#drm-not-working-on-windows) - [last.fm doesn't work out of the box. Use rescrobbler as a workaround](#lastfm-doesnt-work-out-of-the-box-use-rescrobbler-as-a-workaround)
- [DRM not working on Windows](#drm-not-working-on-windows)
- [Special thanks to](#special-thanks-to) - [Special thanks to](#special-thanks-to)
- [Donations](#donations) - [Donations](#donations)
- [Images](#images) - [Images](#images)
@@ -37,33 +38,28 @@ The web version of [listen.tidal.com](https://listen.tidal.com) running in elect
## Features ## Features
- HiFi playback (High & Max settings) - HiFi playback
- Notifications - Notifications
- Custom [theming](./docs/theming.md) - Custom [theming](./docs/theming.md)
- Custom hotkeys ([source](https://defkey.com/tidal-desktop-shortcuts)) - Custom hotkeys ([source](https://defkey.com/tidal-desktop-shortcuts))
- Better icons thanks to [Papirus-icon-theme](https://github.com/PapirusDevelopmentTeam/papirus-icon-theme/) - Songwhip.com integration (hotkey `ctrl + w`)
- [Settings feature](./docs/images/settings.png) to disable certain functionality. (`ctrl+=` or `ctrl+0`)
- API for status and playback - API for status and playback
- Disabled audio & visual ads, unlocked lyrics, suggested track, track info, and unlimited skips thanks to uBlockOrigin custom filters ([source](https://github.com/uBlockOrigin/uAssets/issues/17495)) - Disabled audio & visual ads, unlocked lyrics, suggested track, track info, and unlimited skips thanks to uBlockOrigin custom filters ([source](https://github.com/uBlockOrigin/uAssets/issues/17495))
- AlbumArt in integrations ([best-effort](https://github.com/Mastermindzh/tidal-hifi/pull/88#pullrequestreview-840814847))
- Custom [integrations](#integrations) - Custom [integrations](#integrations)
- [ListenBrainz](https://listenbrainz.org/?redirect=false) integration - [Settings feature](./docs/images/settings.png) to disable certain functionality. (`ctrl+=` or `ctrl+0`)
- Songwhip.com integration (hotkey `ctrl + w`) - AlbumArt in integrations ([best-effort](https://github.com/Mastermindzh/tidal-hifi/pull/88#pullrequestreview-840814847))
- Discord RPC integration (showing "now listening", "Browsing", etc)
- MPRIS integration
- UI + Json config (`~/.config/tidal-hifi/`, or `~/.var/app/com.mastermindzh.tidal-hifi/` for Flatpak)
## Contributions ## Contributions
To contribute you can use the standard GitHub features (issues, prs, etc.) or join the discord server to talk with like-minded individuals. To contribute you can use the standard GitHub features (issues, prs, etc) or join the discord server to talk with like-minded individuals.
- ![Discord logo](./docs/images/discord.png) [Join the Discord server](https://discord.gg/yhNwf4v4He) - ![Discord logo](./docs/images/discord.png) [Join the Discord server](https://discord.gg/yhNwf4v4He)
## Why did I create TIDAL Hi-Fi? ## Why did I create tidal-hifi?
I moved from Spotify over to Tidal and found Linux support to be lacking. I moved from Spotify over to Tidal and found Linux support to be lacking.
When I started this project there weren't any Linux apps that offered Tidal's "hifi" options nor any scripts to control it. When I started this project there weren't any Linux apps that offered Tidal's "hifi" options nor any scripts to control it.
I made this app to support the highest quality audio available on the Linux platform. It used to be "hifi" but now is ["High & Max"](https://tidal.com/sound-quality).
### Why not extend existing projects? ### Why not extend existing projects?
@@ -104,10 +100,10 @@ To install with `snap` you need to download the pre-packaged snap-package from t
### Arch Linux ### Arch Linux
Arch Linux users can use the AUR to install TIDAL Hi-Fi: Arch Linux users can use the AUR to install tidal-hifi:
```sh ```sh
trizen tidal-hifi-git trizen tidal-hifi-bin
``` ```
### Flatpak ### Flatpak
@@ -131,27 +127,37 @@ nix-env -iA nixpkgs.tidal-hifi
To install and work with the code on this project follow these steps: To install and work with the code on this project follow these steps:
- git clone [https://github.com/Mastermindzh/tidal-hifi.git](https://github.com/Mastermindzh/tidal-hifi.git) - git clone [https://github.com/Mastermindzh/tidal-hifi.git](https://github.com/Mastermindzh/tidal-hifi.git)
- cd TIDAL Hi-Fi - cd tidal-hifi
- npm install - npm install
- npm start - npm start
## Integrations ## Integrations
TIDAL Hi-Fi comes with several integrations out of the box. Tidal-hifi comes with several integrations out of the box.
You can find these in the settings menu (`ctrl + =` by default) under the "integrations" tab. You can find these in the settings menu (`ctrl + =` by default) under the "integrations" tab.
![integrations menu, showing a list of integrations](./docs/images/integrations.png) ![integrations menu, showing a list of integrations](./docs/images/integrations.png)
Integrations with other projects that are not included natively: It currently includes:
- MPRIS - MPRIS media player controls/status
- Discord - Shows what you're listening to on Discord.
Not included:
- [i3 blocks config](https://github.com/Mastermindzh/dotfiles/commit/9714b2fa1d670108ce811d5511fd3b7a43180647) - My dotfiles where I use this app to fetch currently playing music (direct commit) - [i3 blocks config](https://github.com/Mastermindzh/dotfiles/commit/9714b2fa1d670108ce811d5511fd3b7a43180647) - My dotfiles where I use this app to fetch currently playing music (direct commit)
- [neptune](https://github.com/uwu/neptune) third party plugins & theming
## Known bugs ### Known bugs
### DRM not working on Windows #### last.fm doesn't work out of the box. Use rescrobbler as a workaround
Most Windows users run into DRM issues when trying to use TIDAL Hi-Fi. The last.fm login doesn't work, as is evident from the following issue: [Last.fm login doesn't work](https://github.com/Mastermindzh/tidal-hifi/issues/4).
However, in that same issue you can read about a workaround using [rescrobbler](https://github.com/InputUsername/rescrobbled).
For now that will be the default workaround.
#### DRM not working on Windows
Most Windows users run into DRM issues when trying to use Tidal-hifi.
Nothing I can do about that I'm afraid... Tidal is working on removing/changing DRM so when they finish with that we can give it another shot. Nothing I can do about that I'm afraid... Tidal is working on removing/changing DRM so when they finish with that we can give it another shot.
## Special thanks to ## Special thanks to

View File

@@ -1,11 +0,0 @@
# Security Policy
## Supported Versions
Only the very latest 😄.
## Reporting a Vulnerability
If you find a vulnerability just add it as an issue.
If there's an especially bad vulnerability that you don't want to make public just send me a private message (email, discord, wherever).

BIN
assets/TIDAL.icns Executable file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 102 KiB

BIN
build/icon.icns Normal file → Executable file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

View File

@@ -1,10 +1,10 @@
# Theming TIDAL Hi-Fi # Theming tidal-hifi
## Table of contents ## Table of contents
<!-- toc --> <!-- toc -->
- [Theming TIDAL Hi-Fi](#theming-TIDAL Hi-Fi) - [Theming tidal-hifi](#theming-tidal-hifi)
- [Table of contents](#table-of-contents) - [Table of contents](#table-of-contents)
- [Custom CSS](#custom-css) - [Custom CSS](#custom-css)
- [config](#config) - [config](#config)
@@ -12,7 +12,7 @@
<!-- tocstop --> <!-- tocstop -->
By default TIDAL Hi-Fi comes with a few themes. By default tidal-hifi comes with a few themes.
You can select these in the settings window under the theming tab as shown below. You can select these in the settings window under the theming tab as shown below.
![Settings window with the theming tab opened](./images/theming.png) ![Settings window with the theming tab opened](./images/theming.png)

16
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "TIDAL Hi-Fi", "name": "tidal-hifi",
"version": "5.6.0", "version": "5.4.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "TIDAL Hi-Fi", "name": "tidal-hifi",
"version": "5.6.0", "version": "5.4.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@electron/remote": "^2.0.10", "@electron/remote": "^2.0.10",
@@ -14,7 +14,7 @@
"discord-rpc": "^4.0.1", "discord-rpc": "^4.0.1",
"electron-store": "^8.1.0", "electron-store": "^8.1.0",
"express": "^4.18.2", "express": "^4.18.2",
"hotkeys-js": "^3.12.0", "hotkeys-js": "^3.11.2",
"mpris-service": "^2.1.2", "mpris-service": "^2.1.2",
"request": "^2.88.2", "request": "^2.88.2",
"sass": "^1.64.1" "sass": "^1.64.1"
@@ -4855,9 +4855,9 @@
} }
}, },
"node_modules/hotkeys-js": { "node_modules/hotkeys-js": {
"version": "3.12.0", "version": "3.11.2",
"resolved": "https://registry.npmjs.org/hotkeys-js/-/hotkeys-js-3.12.0.tgz", "resolved": "https://registry.npmjs.org/hotkeys-js/-/hotkeys-js-3.11.2.tgz",
"integrity": "sha512-Z+N573ycUKIGwFYS3ID1RzMJiGmtWMGKMiaNLyJS8B1ei+MllF4ZYmKS2T0kMWBktOz+WZLVNikftEgnukOrXg==" "integrity": "sha512-hLuR+wtvrVrVFHku5cud44cPXQf5ke+96TonRaGzlNSt22rh2LH65avLYFNsc+QpzMuOEQG/IxJT5mEop99rvg=="
}, },
"node_modules/html-tags": { "node_modules/html-tags": {
"version": "3.3.1", "version": "3.3.1",

View File

@@ -1,6 +1,6 @@
{ {
"name": "tidal-hifi", "name": "tidal-hifi",
"version": "5.7.0", "version": "5.4.0",
"description": "Tidal on Electron with widevine(hifi) support", "description": "Tidal on Electron with widevine(hifi) support",
"main": "ts-dist/main.js", "main": "ts-dist/main.js",
"scripts": { "scripts": {
@@ -43,7 +43,7 @@
"discord-rpc": "^4.0.1", "discord-rpc": "^4.0.1",
"electron-store": "^8.1.0", "electron-store": "^8.1.0",
"express": "^4.18.2", "express": "^4.18.2",
"hotkeys-js": "^3.12.0", "hotkeys-js": "^3.11.2",
"mpris-service": "^2.1.2", "mpris-service": "^2.1.2",
"request": "^2.88.2", "request": "^2.88.2",
"sass": "^1.64.1" "sass": "^1.64.1"

View File

@@ -1,16 +0,0 @@
#!/bin/bash
if [ "$1" != "" ]; then # check if arg 1 is present
FILE=$1
else
echo "Please provide a file as an argument."
exit 1
fi
SIZES=("16x16" "22x22" "24x24" "32x32" "48x48" "64x64" "128x128" "256x256" "384x384")
echo "Resizing $FILE..."
for i in "${SIZES[@]}"; do
convert "$FILE" -resize "$i" "$i.png"
done

View File

@@ -1,9 +1,4 @@
export const flags: { [key: string]: { flag: string; value?: string }[] } = { export const flags: { [key: string]: { flag: string; value?: string }[] } = {
gpuRasterization: [{ flag: "enable-gpu-rasterization", value: undefined }], gpuRasterization: [{ flag: "enable-gpu-rasterization", value: undefined }],
disableHardwareMediaKeys: [{ flag: "disable-features", value: "HardwareMediaKeyHandling" }], disableHardwareMediaKeys: [{ flag: "disable-features", value: "HardwareMediaKeyHandling" }],
enableWaylandSupport: [
{ flag: "enable-features", value: "UseOzonePlatform" },
{ flag: "ozone-platform-hint", value: "auto" },
{ flag: "enable-features", value: "WaylandWindowDecorations" },
],
}; };

View File

@@ -20,22 +20,10 @@ export const settings = {
disableHardwareMediaKeys: "disableHardwareMediaKeys", disableHardwareMediaKeys: "disableHardwareMediaKeys",
enableCustomHotkeys: "enableCustomHotkeys", enableCustomHotkeys: "enableCustomHotkeys",
enableDiscord: "enableDiscord", enableDiscord: "enableDiscord",
discord: {
detailsPrefix: "discord.detailsPrefix",
buttonText: "discord.buttonText",
},
ListenBrainz: {
root: "ListenBrainz",
enabled: "ListenBrainz.enabled",
api: "ListenBrainz.api",
token: "ListenBrainz.token",
delay: "ListenBrainz.delay",
},
flags: { flags: {
root: "flags", root: "flags",
disableHardwareMediaKeys: "flags.disableHardwareMediaKeys", disableHardwareMediaKeys: "flags.disableHardwareMediaKeys",
gpuRasterization: "flags.gpuRasterization", gpuRasterization: "flags.gpuRasterization",
enableWaylandSupport: "flags.enableWaylandSupport",
}, },
menuBar: "menuBar", menuBar: "menuBar",
minimizeOnClose: "minimizeOnClose", minimizeOnClose: "minimizeOnClose",

View File

@@ -0,0 +1,4 @@
export const statuses = {
playing: "playing",
paused: "paused",
};

View File

@@ -1,3 +1,3 @@
export default { export default {
name: "TIDAL Hi-Fi", name: "tidal-hifi",
}; };

View File

@@ -1,41 +0,0 @@
import { App } from "electron";
import { flags } from "../../constants/flags";
import { settings } from "../../constants/settings";
import { settingsStore } from "../../scripts/settings";
import { Logger } from "../logger";
/**
* Set default Electron flags
*/
export function setDefaultFlags(app: App) {
setFlag(app, "disable-seccomp-filter-sandbox");
}
/**
* Set Tidal's managed flags from the user settings
* @param app
*/
export function setManagedFlagsFromSettings(app: App) {
const flagsFromSettings = settingsStore.get(settings.flags.root);
if (flagsFromSettings) {
for (const [key, value] of Object.entries(flagsFromSettings)) {
if (value) {
flags[key].forEach((flag) => {
setFlag(app, flag.flag, flag.value);
});
}
}
}
}
/**
* Set a single flag for Electron
* @param app app to set it on
* @param flag flag name
* @param value value to be set for the flag
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function setFlag(app: App, flag: string, value?: any) {
Logger.log(`enabling command line option ${flag} with value ${value}`);
app.commandLine.appendSwitch(flag, value);
}

View File

@@ -1,65 +0,0 @@
import { PowerSaveBlocker, powerSaveBlocker } from "electron";
import { Logger } from "../logger";
/**
* Start blocking idle/screen timeouts
* @param blocker optional instance of the powerSaveBlocker to use
* @returns id of current block
*/
export const acquireInhibitor = (blocker?: PowerSaveBlocker): number => {
const currentBlocker = blocker ?? powerSaveBlocker;
const blockId = currentBlocker.start("prevent-app-suspension");
Logger.log(`Started preventing app suspension with id: ${blockId}`);
return blockId;
};
/**
* Check whether there is a blocker active for the current id, if not start it.
* @param id id of inhibitor you want to check activity against
* @param blocker optional instance of the powerSaveBlocker to use
*/
export const acquireInhibitorIfInactive = (id: number, blocker?: PowerSaveBlocker): number => {
const currentBlocker = blocker ?? powerSaveBlocker;
if (!isInhibitorActive(id, currentBlocker)) {
return acquireInhibitor();
}
return id;
};
/**
* stop blocking idle/screen timeouts
* @param id id of inhibitor you want to check activity against
* @param blocker optional instance of the powerSaveBlocker to use
*/
export const releaseInhibitor = (id: number, blocker?: PowerSaveBlocker) => {
try {
const currentBlocker = blocker ?? powerSaveBlocker;
currentBlocker.stop(id);
Logger.log(`Released inhibitor with id: ${id}`);
} catch (error) {
Logger.log("Releasing inhibitor failed");
}
};
/**
* stop blocking idle/screen timeouts if a inhibitor is active
* @param id id of inhibitor you want to check activity against
* @param blocker optional instance of the powerSaveBlocker to use
*/
export const releaseInhibitorIfActive = (id: number, blocker?: PowerSaveBlocker) => {
const currentBlocker = blocker ?? powerSaveBlocker;
if (isInhibitorActive(id, currentBlocker)) {
releaseInhibitor(id, currentBlocker);
}
};
/**
* check whether the inhibitor is active
* @param id id of inhibitor you want to check activity against
* @param blocker optional instance of the powerSaveBlocker to use
*/
export const isInhibitorActive = (id: number, blocker?: PowerSaveBlocker) => {
const currentBlocker = blocker ?? powerSaveBlocker;
return currentBlocker.isStarted(id);
};

View File

@@ -1,132 +0,0 @@
import axios from "axios";
import Store from "electron-store";
import { settings } from "../../constants/settings";
import { MediaStatus } from "../../models/mediaStatus";
import { settingsStore } from "../../scripts/settings";
import { Logger } from "../logger";
import { StoreData } from "./models/storeData";
const ListenBrainzStore = new Store({ name: "listenbrainz" });
export const ListenBrainzConstants = {
oldData: "oldData",
};
export class ListenBrainz {
/**
* Create the object to store old information in the Store :)
* @param title
* @param artists
* @param duration
* @returns data passed along in an object + a "listenedAt" key with the current time
*/
private static constructStoreData(title: string, artists: string, duration: number): StoreData {
return {
listenedAt: Math.floor(new Date().getTime() / 1000),
title,
artists,
duration,
};
}
/**
* Call the ListenBrainz API and create playing now payload and scrobble old song
* @param title
* @param artists
* @param status
* @param duration
*/
public static async scrobble(
title: string,
artists: string,
status: string,
duration: number
): Promise<void> {
try {
if (status === MediaStatus.paused) {
return;
} else {
// Fetches the oldData required for scrobbling and proceeds to construct a playing_now data payload for the Playing Now area
const oldData = ListenBrainzStore.get(ListenBrainzConstants.oldData) as StoreData;
const playing_data = {
listen_type: "playing_now",
payload: [
{
track_metadata: {
additional_info: {
media_player: "Tidal Hi-Fi",
submission_client: "Tidal Hi-Fi",
music_service: "tidal.com",
duration: duration,
},
artist_name: artists,
track_name: title,
},
},
],
};
await axios.post(
`${settingsStore.get<string, string>(settings.ListenBrainz.api)}/1/submit-listens`,
playing_data,
{
headers: {
"Content-Type": "application/json",
Authorization: `Token ${settingsStore.get<string, string>(
settings.ListenBrainz.token
)}`,
},
}
);
if (!oldData) {
ListenBrainzStore.set(
ListenBrainzConstants.oldData,
this.constructStoreData(title, artists, duration)
);
} else {
if (oldData.title !== title) {
// This constructs the data required to scrobble the data after the song finishes
const scrobble_data = {
listen_type: "single",
payload: [
{
listened_at: oldData.listenedAt,
track_metadata: {
additional_info: {
media_player: "Tidal Hi-Fi",
submission_client: "Tidal Hi-Fi",
music_service: "listen.tidal.com",
duration: oldData.duration,
},
artist_name: oldData.artists,
track_name: oldData.title,
},
},
],
};
await axios.post(
`${settingsStore.get<string, string>(settings.ListenBrainz.api)}/1/submit-listens`,
scrobble_data,
{
headers: {
"Content-Type": "application/json",
Authorization: `Token ${settingsStore.get<string, string>(
settings.ListenBrainz.token
)}`,
},
}
);
ListenBrainzStore.set(
ListenBrainzConstants.oldData,
this.constructStoreData(title, artists, duration)
);
}
}
}
} catch (error) {
Logger.log(JSON.stringify(error));
}
}
}
export { ListenBrainzStore };

View File

@@ -1,9 +0,0 @@
/**
* Data saved for ListenBrainz
*/
export interface StoreData {
listenedAt: number;
title: string;
artists: string;
duration: number;
}

View File

@@ -1,52 +1,33 @@
import { IpcMain, ipcMain, IpcMainEvent, ipcRenderer } from "electron"; import { IpcMain, IpcRenderer } from "electron";
import { globalEvents } from "../constants/globalEvents"; import { globalEvents } from "../constants/globalEvents";
export class Logger { export class Logger {
/**
*
* @param ipcRenderer renderer IPC client so we can send messages to the main thread
*/
constructor(private ipcRenderer: IpcRenderer) {}
/** /**
* Subscribe to watch for logs from the IPC client * Subscribe to watch for logs from the IPC client
* @param ipcMain main thread IPC client so we can subscribe to events * @param ipcMain main thread IPC client so we can subscribe to events
*/ */
public static watch(ipcMain: IpcMain) { public static watch(ipcMain: IpcMain) {
ipcMain.on( ipcMain.on(globalEvents.log, (event, content, object) => {
globalEvents.log, console.log(content, JSON.stringify(object, null, 2));
(event: IpcMainEvent | { content: string; message: string }, message) => { });
const { content, object } = message ?? event;
this.logToSTDOut(content, object);
}
);
} }
/** /**
* Log content to STDOut * Log content to STDOut
* @param content * @param content
* @param object js(on) object that will be prettyPrinted * @param object js(on) object that will be prettyPrinted
*/ */
public static log(content: string, object: object = {}) { public log(content: string, object: object = {}) {
if (ipcRenderer) { if (this.ipcRenderer) {
ipcRenderer.send(globalEvents.log, { content, object }); this.ipcRenderer.send(globalEvents.log, { content, object });
} else { } else {
ipcMain.emit(globalEvents.log, { content, object }); console.log(`${content} \n ${JSON.stringify(object, null, 2)}`);
} }
} }
/**
* Log content to STDOut and use the provided alert function to alert
* @param content
* @param object js(on) object that will be prettyPrinted
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public static alert(content: string, object: any = {}, alert?: (msg: string) => void) {
Logger.log(content, object);
if (alert) {
alert(`${content} \n\nwith details: \n${JSON.stringify(object, null, 2)}`);
}
}
/**
* Log to STDOut
* @param content
* @param object
*/
private static logToSTDOut(content: string, object = {}) {
console.log(content, Object.keys(object).length > 0 ? JSON.stringify(object, null, 2) : "");
}
} }

View File

@@ -1,31 +0,0 @@
import fs from "fs";
import { settings } from "../../constants/settings";
import { settingsStore } from "../../scripts/settings";
import { Logger } from "../logger";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function addCustomCss(app: any, logger: typeof Logger) {
window.addEventListener("DOMContentLoaded", () => {
const selectedTheme = settingsStore.get<string, string>(settings.theme);
if (selectedTheme !== "none") {
const userThemePath = `${app.getPath("userData")}/themes/${selectedTheme}`;
const resourcesThemePath = `${process.resourcesPath}/${selectedTheme}`;
const themeFile = fs.existsSync(userThemePath) ? userThemePath : resourcesThemePath;
fs.readFile(themeFile, "utf-8", (err, data) => {
if (err) {
logger.alert("An error ocurred reading the theme file.", err, alert);
return;
}
const themeStyle = document.createElement("style");
themeStyle.innerHTML = data;
document.head.appendChild(themeStyle);
});
}
// read customCSS (it will override the theme)
const style = document.createElement("style");
style.innerHTML = settingsStore.get<string, string[]>(settings.customCSS).join("\n");
document.head.appendChild(style);
});
}

View File

@@ -1,7 +1,7 @@
import { enable, initialize } from "@electron/remote/main"; import { enable, initialize } from "@electron/remote/main";
import { import {
app,
BrowserWindow, BrowserWindow,
app,
components, components,
globalShortcut, globalShortcut,
ipcMain, ipcMain,
@@ -9,18 +9,9 @@ import {
session, session,
} from "electron"; } from "electron";
import path from "path"; import path from "path";
import { flags } from "./constants/flags";
import { globalEvents } from "./constants/globalEvents"; import { globalEvents } from "./constants/globalEvents";
import { mediaKeys } from "./constants/mediaKeys"; import { mediaKeys } from "./constants/mediaKeys";
import { settings } from "./constants/settings";
import { setDefaultFlags, setManagedFlagsFromSettings } from "./features/flags/flags";
import {
acquireInhibitorIfInactive,
releaseInhibitorIfActive,
} from "./features/idleInhibitor/idleInhibitor";
import { Logger } from "./features/logger";
import { Songwhip } from "./features/songwhip/songwhip";
import { MediaInfo } from "./models/mediaInfo";
import { MediaStatus } from "./models/mediaStatus";
import { initRPC, rpc, unRPC } from "./scripts/discord"; import { initRPC, rpc, unRPC } from "./scripts/discord";
import { startExpress } from "./scripts/express"; import { startExpress } from "./scripts/express";
import { updateMediaInfo } from "./scripts/mediaInfo"; import { updateMediaInfo } from "./scripts/mediaInfo";
@@ -29,28 +20,45 @@ import {
closeSettingsWindow, closeSettingsWindow,
createSettingsWindow, createSettingsWindow,
hideSettingsWindow, hideSettingsWindow,
settingsStore,
showSettingsWindow, showSettingsWindow,
settingsStore,
} from "./scripts/settings"; } from "./scripts/settings";
import { settings } from "./constants/settings";
import { addTray, refreshTray } from "./scripts/tray"; import { addTray, refreshTray } from "./scripts/tray";
import { MediaInfo } from "./models/mediaInfo";
import { Songwhip } from "./features/songwhip/songwhip";
import { Logger } from "./features/logger";
const tidalUrl = "https://listen.tidal.com"; const tidalUrl = "https://listen.tidal.com";
let mainInhibitorId = -1;
initialize(); initialize();
let mainWindow: BrowserWindow; let mainWindow: BrowserWindow;
const icon = path.join(__dirname, "../assets/icon.png"); const icon = path.join(__dirname, "../assets/icon.png");
const PROTOCOL_PREFIX = "tidal"; const PROTOCOL_PREFIX = "tidal";
const windowPreferences = {
sandbox: false,
plugins: true,
devTools: true, // I like tinkering, others might too
};
setDefaultFlags(app); setFlags();
setManagedFlagsFromSettings(app);
function setFlags() {
const flagsFromSettings = settingsStore.get(settings.flags.root);
if (flagsFromSettings) {
for (const [key, value] of Object.entries(flags)) {
if (value) {
flags[key].forEach((flag) => {
console.log(`enabling command line switch ${flag.flag} with value ${flag.value}`);
app.commandLine.appendSwitch(flag.flag, flag.value);
});
}
}
}
/**
* Fix Display Compositor issue.
*/
app.commandLine.appendSwitch("disable-seccomp-filter-sandbox");
}
/** /**
* Update the menuBarVisibility according to the store value * Update the menuBarVisbility according to the store value
* *
*/ */
function syncMenuBarWithStore() { function syncMenuBarWithStore() {
@@ -82,16 +90,16 @@ function createWindow(options = { x: 0, y: 0, backgroundColor: "white" }) {
mainWindow = new BrowserWindow({ mainWindow = new BrowserWindow({
x: options.x, x: options.x,
y: options.y, y: options.y,
width: settingsStore?.get(settings.windowBounds.width), width: settingsStore && settingsStore.get(settings.windowBounds.width),
height: settingsStore?.get(settings.windowBounds.height), height: settingsStore && settingsStore.get(settings.windowBounds.height),
icon, icon,
backgroundColor: options.backgroundColor, backgroundColor: options.backgroundColor,
autoHideMenuBar: true, autoHideMenuBar: true,
webPreferences: { webPreferences: {
...windowPreferences, sandbox: false,
...{ preload: path.join(__dirname, "preload.js"),
preload: path.join(__dirname, "preload.js"), plugins: true,
}, devTools: true, // I like tinkering, others might too
}, },
}); });
enable(mainWindow.webContents); enable(mainWindow.webContents);
@@ -116,7 +124,6 @@ function createWindow(options = { x: 0, y: 0, backgroundColor: "white" }) {
}); });
// Emitted when the window is closed. // Emitted when the window is closed.
mainWindow.on("closed", function () { mainWindow.on("closed", function () {
releaseInhibitorIfActive(mainInhibitorId);
closeSettingsWindow(); closeSettingsWindow();
app.quit(); app.quit();
}); });
@@ -124,18 +131,6 @@ function createWindow(options = { x: 0, y: 0, backgroundColor: "white" }) {
const { width, height } = mainWindow.getBounds(); const { width, height } = mainWindow.getBounds();
settingsStore.set(settings.windowBounds.root, { width, height }); settingsStore.set(settings.windowBounds.root, { width, height });
}); });
mainWindow.webContents.setWindowOpenHandler(() => {
return {
action: "allow",
overrideBrowserWindowOptions: {
webPreferences: {
sandbox: false,
plugins: true,
devTools: true, // I like tinkering, others might too
},
},
};
});
} }
function registerHttpProtocols() { function registerHttpProtocols() {
@@ -201,12 +196,6 @@ app.on("browser-window-created", (_, window) => {
// IPC // IPC
ipcMain.on(globalEvents.updateInfo, (_event, arg: MediaInfo) => { ipcMain.on(globalEvents.updateInfo, (_event, arg: MediaInfo) => {
updateMediaInfo(arg); updateMediaInfo(arg);
if (arg.status === MediaStatus.playing) {
mainInhibitorId = acquireInhibitorIfInactive(mainInhibitorId);
} else {
releaseInhibitorIfActive(mainInhibitorId);
mainInhibitorId = -1;
}
}); });
ipcMain.on(globalEvents.hideSettings, () => { ipcMain.on(globalEvents.hideSettings, () => {
@@ -235,7 +224,7 @@ ipcMain.on(globalEvents.error, (event) => {
}); });
ipcMain.handle(globalEvents.whip, async (event, url) => { ipcMain.handle(globalEvents.whip, async (event, url) => {
return Songwhip.whip(url); return await Songwhip.whip(url);
}); });
Logger.watch(ipcMain); Logger.watch(ipcMain);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 102 KiB

View File

@@ -3,25 +3,9 @@ import { ipcRenderer, shell } from "electron";
import fs from "fs"; import fs from "fs";
import { globalEvents } from "../../constants/globalEvents"; import { globalEvents } from "../../constants/globalEvents";
import { settings } from "../../constants/settings"; import { settings } from "../../constants/settings";
import { Logger } from "../../features/logger";
import { addCustomCss } from "../../features/theming/theming";
import { settingsStore } from "./../../scripts/settings"; import { settingsStore } from "./../../scripts/settings";
import { getOptions, getOptionsHeader, getThemeListFromDirectory } from "./theming"; import { getOptions, getOptionsHeader, getThemeListFromDirectory } from "./theming";
// All switches on the settings screen that show additional options based on their state
const switchesWithSettings = {
listenBrainz: {
switch: "enableListenBrainz",
classToHide: "listenbrainz__options",
settingsKey: settings.ListenBrainz.enabled,
},
discord: {
switch: "enableDiscord",
classToHide: "discord_options",
settingsKey: settings.enableDiscord,
},
};
let adBlock: HTMLInputElement, let adBlock: HTMLInputElement,
api: HTMLInputElement, api: HTMLInputElement,
customCSS: HTMLInputElement, customCSS: HTMLInputElement,
@@ -41,17 +25,7 @@ let adBlock: HTMLInputElement,
skippedArtists: HTMLInputElement, skippedArtists: HTMLInputElement,
theme: HTMLSelectElement, theme: HTMLSelectElement,
trayIcon: HTMLInputElement, trayIcon: HTMLInputElement,
updateFrequency: HTMLInputElement, updateFrequency: HTMLInputElement;
enableListenBrainz: HTMLInputElement,
ListenBrainzAPI: HTMLInputElement,
ListenBrainzToken: HTMLInputElement,
listenbrainz_delay: HTMLInputElement,
enableWaylandSupport: HTMLInputElement,
discord_details_prefix: HTMLInputElement,
discord_button_text: HTMLInputElement;
addCustomCss(app, Logger.bind(this));
function getThemeFiles() { function getThemeFiles() {
const selectElement = document.getElementById("themesList") as HTMLSelectElement; const selectElement = document.getElementById("themesList") as HTMLSelectElement;
const builtInThemes = getThemeListFromDirectory(process.resourcesPath); const builtInThemes = getThemeListFromDirectory(process.resourcesPath);
@@ -79,7 +53,6 @@ function handleFileUploads() {
const fileMessage = document.getElementById("file-message"); const fileMessage = document.getElementById("file-message");
fileMessage.innerText = "or drag and drop files here"; fileMessage.innerText = "or drag and drop files here";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
document.getElementById("theme-files").addEventListener("change", function (e: any) { document.getElementById("theme-files").addEventListener("change", function (e: any) {
Array.from(e.target.files).forEach((file: File) => { Array.from(e.target.files).forEach((file: File) => {
const destination = `${app.getPath("userData")}/themes/${file.name}`; const destination = `${app.getPath("userData")}/themes/${file.name}`;
@@ -90,60 +63,30 @@ function handleFileUploads() {
}); });
} }
/**
* hide or unhide an element
* @param checked
* @param toggleOptions
*/
function setElementHidden(
checked: boolean,
toggleOptions: { switch: string; classToHide: string }
) {
const element = document.getElementById(toggleOptions.classToHide);
checked ? element.classList.remove("hidden") : element.classList.add("hidden");
}
/** /**
* Sync the UI forms with the current settings * Sync the UI forms with the current settings
*/ */
function refreshSettings() { function refreshSettings() {
try { adBlock.checked = settingsStore.get(settings.adBlock);
adBlock.checked = settingsStore.get(settings.adBlock); api.checked = settingsStore.get(settings.api);
api.checked = settingsStore.get(settings.api); customCSS.value = settingsStore.get<string, string[]>(settings.customCSS).join("\n");
customCSS.value = settingsStore.get<string, string[]>(settings.customCSS).join("\n"); disableBackgroundThrottle.checked = settingsStore.get(settings.disableBackgroundThrottle);
disableBackgroundThrottle.checked = settingsStore.get(settings.disableBackgroundThrottle); disableHardwareMediaKeys.checked = settingsStore.get(settings.flags.disableHardwareMediaKeys);
disableHardwareMediaKeys.checked = settingsStore.get(settings.flags.disableHardwareMediaKeys); enableCustomHotkeys.checked = settingsStore.get(settings.enableCustomHotkeys);
enableCustomHotkeys.checked = settingsStore.get(settings.enableCustomHotkeys); enableDiscord.checked = settingsStore.get(settings.enableDiscord);
enableDiscord.checked = settingsStore.get(settings.enableDiscord); gpuRasterization.checked = settingsStore.get(settings.flags.gpuRasterization);
enableWaylandSupport.checked = settingsStore.get(settings.flags.enableWaylandSupport); menuBar.checked = settingsStore.get(settings.menuBar);
gpuRasterization.checked = settingsStore.get(settings.flags.gpuRasterization); minimizeOnClose.checked = settingsStore.get(settings.minimizeOnClose);
menuBar.checked = settingsStore.get(settings.menuBar); mpris.checked = settingsStore.get(settings.mpris);
minimizeOnClose.checked = settingsStore.get(settings.minimizeOnClose); notifications.checked = settingsStore.get(settings.notifications);
mpris.checked = settingsStore.get(settings.mpris); playBackControl.checked = settingsStore.get(settings.playBackControl);
notifications.checked = settingsStore.get(settings.notifications); port.value = settingsStore.get(settings.apiSettings.port);
playBackControl.checked = settingsStore.get(settings.playBackControl); singleInstance.checked = settingsStore.get(settings.singleInstance);
port.value = settingsStore.get(settings.apiSettings.port); skipArtists.checked = settingsStore.get(settings.skipArtists);
singleInstance.checked = settingsStore.get(settings.singleInstance); theme.value = settingsStore.get(settings.theme);
skipArtists.checked = settingsStore.get(settings.skipArtists); skippedArtists.value = settingsStore.get<string, string[]>(settings.skippedArtists).join("\n");
theme.value = settingsStore.get(settings.theme); trayIcon.checked = settingsStore.get(settings.trayIcon);
skippedArtists.value = settingsStore.get<string, string[]>(settings.skippedArtists).join("\n"); updateFrequency.value = settingsStore.get(settings.updateFrequency);
trayIcon.checked = settingsStore.get(settings.trayIcon);
updateFrequency.value = settingsStore.get(settings.updateFrequency);
enableListenBrainz.checked = settingsStore.get(settings.ListenBrainz.enabled);
ListenBrainzAPI.value = settingsStore.get(settings.ListenBrainz.api);
ListenBrainzToken.value = settingsStore.get(settings.ListenBrainz.token);
listenbrainz_delay.value = settingsStore.get(settings.ListenBrainz.delay);
discord_details_prefix.value = settingsStore.get(settings.discord.detailsPrefix);
discord_button_text.value = settingsStore.get(settings.discord.buttonText);
// set state of all switches with additional settings
Object.values(switchesWithSettings).forEach((settingSwitch) => {
setElementHidden(settingsStore.get(settingSwitch.settingsKey), settingSwitch);
});
} catch (error) {
Logger.log("Refreshing settings failed.", error);
}
} }
/** /**
@@ -160,6 +103,14 @@ function hide() {
ipcRenderer.send(globalEvents.hideSettings); ipcRenderer.send(globalEvents.hideSettings);
} }
/**
* Restart tidal-hifi after changes
*/
function restart() {
app.relaunch();
app.exit();
}
/** /**
* Bind UI components to functions after DOMContentLoaded * Bind UI components to functions after DOMContentLoaded
*/ */
@@ -172,29 +123,20 @@ window.addEventListener("DOMContentLoaded", () => {
handleFileUploads(); handleFileUploads();
document.getElementById("close").addEventListener("click", hide); document.getElementById("close").addEventListener("click", hide);
document.getElementById("restart").addEventListener("click", restart);
document.querySelectorAll(".external-link").forEach((elem) => document.querySelectorAll(".external-link").forEach((elem) =>
elem.addEventListener("click", function (event) { elem.addEventListener("click", function (event) {
openExternal((event.target as HTMLElement).getAttribute("data-url")); openExternal((event.target as HTMLElement).getAttribute("data-url"));
}) })
); );
function addInputListener( function addInputListener(source: HTMLInputElement, key: string) {
source: HTMLInputElement,
key: string,
toggleOptions?: { switch: string; classToHide: string }
) {
source.addEventListener("input", () => { source.addEventListener("input", () => {
if (source.value === "on") { if (source.value === "on") {
settingsStore.set(key, source.checked); settingsStore.set(key, source.checked);
} else { } else {
settingsStore.set(key, source.value); settingsStore.set(key, source.value);
} }
if (toggleOptions) {
if (source.value === "on" && source.id === toggleOptions.switch) {
setElementHidden(source.checked, toggleOptions);
}
}
ipcRenderer.send(globalEvents.storeChanged); ipcRenderer.send(globalEvents.storeChanged);
}); });
} }
@@ -228,7 +170,6 @@ window.addEventListener("DOMContentLoaded", () => {
disableHardwareMediaKeys = get("disableHardwareMediaKeys"); disableHardwareMediaKeys = get("disableHardwareMediaKeys");
enableCustomHotkeys = get("enableCustomHotkeys"); enableCustomHotkeys = get("enableCustomHotkeys");
enableDiscord = get("enableDiscord"); enableDiscord = get("enableDiscord");
enableWaylandSupport = get("enableWaylandSupport");
gpuRasterization = get("gpuRasterization"); gpuRasterization = get("gpuRasterization");
menuBar = get("menuBar"); menuBar = get("menuBar");
minimizeOnClose = get("minimizeOnClose"); minimizeOnClose = get("minimizeOnClose");
@@ -242,22 +183,16 @@ window.addEventListener("DOMContentLoaded", () => {
skippedArtists = get("skippedArtists"); skippedArtists = get("skippedArtists");
singleInstance = get("singleInstance"); singleInstance = get("singleInstance");
updateFrequency = get("updateFrequency"); updateFrequency = get("updateFrequency");
enableListenBrainz = get("enableListenBrainz");
ListenBrainzAPI = get("ListenBrainzAPI");
ListenBrainzToken = get("ListenBrainzToken");
discord_details_prefix = get("discord_details_prefix");
listenbrainz_delay = get("listenbrainz_delay");
discord_button_text = get("discord_button_text");
refreshSettings(); refreshSettings();
addInputListener(adBlock, settings.adBlock); addInputListener(adBlock, settings.adBlock);
addInputListener(api, settings.api); addInputListener(api, settings.api);
addTextAreaListener(customCSS, settings.customCSS); addTextAreaListener(customCSS, settings.customCSS);
addInputListener(disableBackgroundThrottle, settings.disableBackgroundThrottle); addInputListener(disableBackgroundThrottle, settings.disableBackgroundThrottle);
addInputListener(disableHardwareMediaKeys, settings.flags.disableHardwareMediaKeys); addInputListener(disableHardwareMediaKeys, settings.flags.disableHardwareMediaKeys);
addInputListener(enableCustomHotkeys, settings.enableCustomHotkeys); addInputListener(enableCustomHotkeys, settings.enableCustomHotkeys);
addInputListener(enableDiscord, settings.enableDiscord, switchesWithSettings.discord); addInputListener(enableDiscord, settings.enableDiscord);
addInputListener(enableWaylandSupport, settings.flags.enableWaylandSupport);
addInputListener(gpuRasterization, settings.flags.gpuRasterization); addInputListener(gpuRasterization, settings.flags.gpuRasterization);
addInputListener(menuBar, settings.menuBar); addInputListener(menuBar, settings.menuBar);
addInputListener(minimizeOnClose, settings.minimizeOnClose); addInputListener(minimizeOnClose, settings.minimizeOnClose);
@@ -271,14 +206,4 @@ window.addEventListener("DOMContentLoaded", () => {
addSelectListener(theme, settings.theme); addSelectListener(theme, settings.theme);
addInputListener(trayIcon, settings.trayIcon); addInputListener(trayIcon, settings.trayIcon);
addInputListener(updateFrequency, settings.updateFrequency); addInputListener(updateFrequency, settings.updateFrequency);
addInputListener(
enableListenBrainz,
settings.ListenBrainz.enabled,
switchesWithSettings.listenBrainz
);
addInputListener(ListenBrainzAPI, settings.ListenBrainz.api);
addInputListener(ListenBrainzToken, settings.ListenBrainz.token);
addInputListener(listenbrainz_delay, settings.ListenBrainz.delay);
addInputListener(discord_details_prefix, settings.discord.detailsPrefix);
addInputListener(discord_button_text, settings.discord.buttonText);
}); });

View File

@@ -7,7 +7,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" />
<link rel="stylesheet" href="./settings.css" /> <link rel="stylesheet" href="./settings.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
</head> </head>
<body class="settings-window"> <body class="settings-window">
@@ -202,9 +201,6 @@
<span class="switch__slider"></span> <span class="switch__slider"></span>
</label> </label>
</div> </div>
</div>
<div class="group">
<p class="group__title">Discord</p>
<div class="group__option"> <div class="group__option">
<div class="group__description"> <div class="group__description">
<h4>Discord RPC</h4> <h4>Discord RPC</h4>
@@ -215,58 +211,6 @@
<span class="switch__slider"></span> <span class="switch__slider"></span>
</label> </label>
</div> </div>
<div id="discord_options">
<div class="group__option" class="hidden">
<div class="group__description">
<h4>Details prefix</h4>
<p>Prefix for the "details" field of Discord's rich presence.</p>
<input id="discord_details_prefix" type="text" class="text-input" name="discord_details_prefix" />
</div>
</div>
<div class="group__option">
<div class="group__description">
<h4>Button text</h4>
<p>Text to display on the button below the song information.</p>
<input id="discord_button_text" type="text" class="text-input" name="discord_button_text" />
</div>
</div>
</div>
</div>
<div class="group">
<p class="group__title">ListenBrainz</p>
<div class="group__option">
<div class="group__description">
<h4>Enable ListenBrainz</h4>
<p>Scrobble your listens directly to ListenBrainz.</p>
</div>
<label class="switch">
<input id="enableListenBrainz" type="checkbox" />
<span class="switch__slider"></span>
</label>
</div>
<div id="listenbrainz__options" class="hidden">
<div class="group__option">
<div class="group__description">
<h4>ListenBrainz API Url</h4>
<p>There are multiple instances for ListenBrainz you can set the corresponding API url below.</p>
<input id="ListenBrainzAPI" type="text" class="text-input" name="ListenBrainzAPI" />
</div>
</div>
<div class="group__option">
<div class="group__description">
<h4>ListenBrainz User Token</h4>
<p>Provide the user token you can get from the settings page.</p>
<input id="ListenBrainzToken" type="text" class="text-input" name="ListenBrainzToken" />
</div>
</div>
</div>
<div class="group__description">
<h4>ScrobbleDelay</h4>
<p>The delay (in ms) to send a song to ListenBrainz. Prevents spamming the API when you fast forward
immediately</p>
<input id="listenbrainz_delay" type="number" class="text-input" name="listenbrainz_delay" />
</div>
</div> </div>
</section> </section>
@@ -277,7 +221,7 @@
<div class="group__description"> <div class="group__description">
<h4>Update frequency</h4> <h4>Update frequency</h4>
<p> <p>
The amount of time, in milliseconds, that TIDAL Hi-Fi will refresh its playback info by scraping the The amount of time, in milliseconds, that tidal-hifi will refresh its playback info by scraping the
website. website.
The default of 500 seems to work in more cases but if you are fine with a bit more resource usage you The default of 500 seems to work in more cases but if you are fine with a bit more resource usage you
can decrease it as well. can decrease it as well.
@@ -285,57 +229,44 @@
<input id="updateFrequency" type="number" class="text-input" name="updateFrequency" /> <input id="updateFrequency" type="number" class="text-input" name="updateFrequency" />
</div> </div>
</div> </div>
</div> <div class="group">
<div class="group"> <p class="group__title">Flags</p>
<p class="group__title">Flags</p> <div class="group__option">
<div class="group__option"> <div class="group__description">
<div class="group__description"> <h4>Disable hardware built-in media keys</h4>
<h4>Disable hardware built-in media keys</h4> <p>
<p> Also prevents certain desktop environments from recognizing the chrome MPRIS
Also prevents certain desktop environments from recognizing the chrome MPRIS client separately from the custom MPRIS client.
client separately from the custom MPRIS client. </p>
</p> </div>
<label class="switch">
<input id="disableHardwareMediaKeys" type="checkbox" />
<span class="switch__slider"></span>
</label>
</div> </div>
<label class="switch"> <div class="group__option">
<input id="disableHardwareMediaKeys" type="checkbox" /> <div class="group__description">
<span class="switch__slider"></span> <h4>Enable GPU rasterization</h4>
</label> <p>Move a part of the rendering to the GPU for increased performance.</p>
</div> </div>
<div class="group__option"> <label class="switch">
<div class="group__description"> <input id="gpuRasterization" type="checkbox" />
<h4>Enable GPU rasterization</h4> <span class="switch__slider"></span>
<p>Move a part of the rendering to the GPU for increased performance.</p> </label>
</div> </div>
<label class="switch"> <div class="group__option">
<input id="gpuRasterization" type="checkbox" /> <div class="group__description">
<span class="switch__slider"></span> <h4>Disable Background Throttling</h4>
</label> <p>
</div> Makes app more responsive while in the background, at the cost of performance.
<div class="group__option"> </p>
<div class="group__description"> </div>
<h4>Disable Background Throttling</h4> <label class="switch">
<p> <input id="disableBackgroundThrottle" type="checkbox" />
Makes app more responsive while in the background, at the cost of performance. <span class="switch__slider"></span>
</p> </label>
</div> </div>
<label class="switch">
<input id="disableBackgroundThrottle" type="checkbox" />
<span class="switch__slider"></span>
</label>
</div> </div>
<div class="group__option">
<div class="group__description">
<h4>Wayland support</h4>
<p>
Adds a couple of Electron flags to help TIDAL Hi-Fi run smoothly on the Wayland window system.
</p>
</div>
<label class="switch">
<input id="enableWaylandSupport" type="checkbox" />
<span class="switch__slider"></span>
</label>
</div>
</div>
</section> </section>
<section id="theming-section" class="tabs__section"> <section id="theming-section" class="tabs__section">
@@ -387,24 +318,21 @@
<section id="about-section" class="tabs__section about-section"> <section id="about-section" class="tabs__section about-section">
<img alt="tidal icon" class="about-section__icon" src="./icon.png" /> <img alt="tidal icon" class="about-section__icon" src="./icon.png" />
<h4>TIDAL Hi-Fi</h4> <p class="about-section__text">
<div class="about-section__version"> <a class="external-link" data-url="https://github.com/Mastermindzh/tidal-hifi">TIDAL Hi-Fi</a>
<a href="">5.7.0</a> is made by
</div> <a class="external-link" data-url="https://www.rickvanlieshout.com">
<div class="about-section__links"> Rick van Lieshout</a>. <br />It uses
<a href="https://github.com/mastermindzh/tidal-hifi/" class="about-section__button">Github <i <a class="external-link" data-url="https://castlabs.com/">Castlabs'</a>
class="fa fa-external-link"></i></a> version of Electron for widevine support.
<a href="https://github.com/Mastermindzh/tidal-hifi/issues" class="about-section__button">Report an issue <i </p>
class="fa fa-external-link"></i></a>
<a href="https://github.com/Mastermindzh/tidal-hifi/graphs/contributors"
class="about-section__button">Contributors <i class="fa fa-external-link"></i></a>
</div>
</section> </section>
<footer class="footer"> <footer class="footer">
<p class="footer__note"> <p class="footer__note">
<strong>Note</strong>: some settings may require a restart of TIDAL Hi-Fi. Some settings may require a restart of TIDAL Hi-Fi. To do so, click the button below:
</p> </p>
<button class="footer__button" id="restart">Restart TIDAL Hi-Fi</button>
</footer> </footer>
</div> </div>
</main> </main>

View File

@@ -8,7 +8,6 @@ $tidal-grey: #72777f;
$tidal-grey-darker: #404248; $tidal-grey-darker: #404248;
$tidal-grey-darker-focus: #55585f; $tidal-grey-darker-focus: #55585f;
$tidal-grey-darkest: #242528; $tidal-grey-darkest: #242528;
$tidal-grey-darkest-focus: #2e2f33;
// --- Fonts --- // --- Fonts ---
@@ -310,75 +309,26 @@ html {
} }
.about-section { .about-section {
padding-top: 40px; padding-top: 120px;
text-align: center; text-align: center;
&__icon { &__icon {
display: inline-block; display: inline-block;
width: 200px; width: 100px;
} }
&__text { &__text {
display: block; display: block;
max-width: 500px; max-width: 350px;
margin: -15px auto 0; margin: 20px auto 0;
}
&__table {
width: 120px;
margin: 0 auto 0;
td {
text-align: left;
}
}
&__version {
margin: -10px 0px 30px 0px;
a {
background-color: $tidal-grey-darker;
border: none;
color: $tidal-blue;
padding: 8px 20px;
font-weight: bold;
text-align: center;
text-decoration: none;
display: inline-block;
border-radius: 100px;
}
} }
&__links { // --- Footer ---
width: 300px;
margin: 0 auto;
a {
border-radius: 10px;
border: none;
color: $white;
padding: 10px 10px 10px 20px;
margin: 8px;
text-align: left;
font-size: 16px;
line-height: 30px;
display: flex;
text-decoration: none;
justify-content: space-between;
background-color: $tidal-grey-darkest;
i {
color: $white;
line-height: 30px;
font-size: 18px;
}
&:hover {
background-color: $tidal-grey-darkest-focus;
}
}
}
} }
// --- Footer ---
.footer { .footer {
position: sticky; position: sticky;
top: calc(100% - 120px);
height: 100px; height: 100px;
padding-top: 20px; padding-top: 20px;
text-align: center; text-align: center;
@@ -493,7 +443,3 @@ html {
} }
} }
} }
.hidden {
display: none !important;
}

View File

@@ -1,5 +1,4 @@
import fs from "fs"; import fs from "fs";
import { Logger } from "../../features/logger";
const cssFilter = (file: string) => file.endsWith(".css"); const cssFilter = (file: string) => file.endsWith(".css");
const sort = (a: string, b: string) => a.toLowerCase().localeCompare(b.toLowerCase()); const sort = (a: string, b: string) => a.toLowerCase().localeCompare(b.toLowerCase());
@@ -37,7 +36,7 @@ export const getThemeListFromDirectory = (directory: string): string[] => {
makeUserThemesDirectory(directory); makeUserThemesDirectory(directory);
return fs.readdirSync(directory).filter(cssFilter).sort(sort); return fs.readdirSync(directory).filter(cssFilter).sort(sort);
} catch (err) { } catch (err) {
Logger.log(`Failed to get files from ${directory}`, err); console.error(err);
return []; return [];
} }
}; };
@@ -50,6 +49,6 @@ export const makeUserThemesDirectory = (directory: string) => {
try { try {
fs.mkdirSync(directory, { recursive: true }); fs.mkdirSync(directory, { recursive: true });
} catch (err) { } catch (err) {
Logger.log(`Failed to make user theme directory: ${directory}`, err); console.error(err);
} }
}; };

View File

@@ -1,18 +1,11 @@
import { app, dialog, Notification } from "@electron/remote"; import { app, dialog, Notification } from "@electron/remote";
import { clipboard, ipcRenderer } from "electron"; import { clipboard, ipcRenderer } from "electron";
import fs from "fs";
import Player from "mpris-service"; import Player from "mpris-service";
import { globalEvents } from "./constants/globalEvents"; import { globalEvents } from "./constants/globalEvents";
import { settings } from "./constants/settings"; import { settings } from "./constants/settings";
import { import { statuses } from "./constants/statuses";
ListenBrainz,
ListenBrainzConstants,
ListenBrainzStore,
} from "./features/listenbrainz/listenbrainz";
import { StoreData } from "./features/listenbrainz/models/storeData";
import { Logger } from "./features/logger";
import { Songwhip } from "./features/songwhip/songwhip"; import { Songwhip } from "./features/songwhip/songwhip";
import { addCustomCss } from "./features/theming/theming";
import { MediaStatus } from "./models/mediaStatus";
import { Options } from "./models/options"; import { Options } from "./models/options";
import { downloadFile } from "./scripts/download"; import { downloadFile } from "./scripts/download";
import { addHotkey } from "./scripts/hotkeys"; import { addHotkey } from "./scripts/hotkeys";
@@ -20,12 +13,10 @@ import { settingsStore } from "./scripts/settings";
import { setTitle } from "./scripts/window-functions"; import { setTitle } from "./scripts/window-functions";
const notificationPath = `${app.getPath("userData")}/notification.jpg`; const notificationPath = `${app.getPath("userData")}/notification.jpg`;
const appName = "TIDAL Hi-Fi"; const appName = "Tidal Hifi";
let currentSong = ""; let currentSong = "";
let player: Player; let player: Player;
let currentPlayStatus = MediaStatus.paused; let currentPlayStatus = statuses.paused;
let currentListenBrainzDelayId: ReturnType<typeof setTimeout>;
let scrobbleWaitingForDelay = false;
const elements = { const elements = {
play: '*[data-test="play"]', play: '*[data-test="play"]',
@@ -114,7 +105,7 @@ const elements = {
window.location.href.includes("/playlist/") || window.location.href.includes("/playlist/") ||
window.location.href.includes("/mix/") window.location.href.includes("/mix/")
) { ) {
if (currentPlayStatus === MediaStatus.playing) { if (currentPlayStatus === statuses.playing) {
// find the currently playing element from the list (which might be in an album icon), traverse back up to the mediaItem (row) and select the album cell. // find the currently playing element from the list (which might be in an album icon), traverse back up to the mediaItem (row) and select the album cell.
// document.querySelector("[class^='isPlayingIcon'], [data-test-is-playing='true']").closest('[data-type="mediaItem"]').querySelector('[class^="album"]').textContent // document.querySelector("[class^='isPlayingIcon'], [data-test-is-playing='true']").closest('[data-type="mediaItem"]').querySelector('[class^="album"]').textContent
const row = window.document.querySelector(this.currentlyPlaying).closest(this.mediaItem); const row = window.document.querySelector(this.currentlyPlaying).closest(this.mediaItem);
@@ -158,12 +149,36 @@ const elements = {
}, },
}; };
function addCustomCss() {
window.addEventListener("DOMContentLoaded", () => {
const selectedTheme = settingsStore.get(settings.theme);
if (selectedTheme !== "none") {
const themeFile = `${process.resourcesPath}/${selectedTheme}`;
fs.readFile(themeFile, "utf-8", (err, data) => {
if (err) {
alert("An error ocurred reading the theme file.");
return;
}
const themeStyle = document.createElement("style");
themeStyle.innerHTML = data;
document.head.appendChild(themeStyle);
});
}
// read customCSS (it will override the theme)
const style = document.createElement("style");
style.innerHTML = settingsStore.get<string, string[]>(settings.customCSS).join("\n");
document.head.appendChild(style);
});
}
/** /**
* Get the update frequency from the store * Get the update frequency from the store
* make sure it returns a number, if not use the default * make sure it returns a number, if not use the default
*/ */
function getUpdateFrequency() { function getUpdateFrequency() {
const storeValue = settingsStore.get<string, number>(settings.updateFrequency); const storeValue = settingsStore.get(settings.updateFrequency) as number;
const defaultValue = 500; const defaultValue = 500;
if (!isNaN(storeValue)) { if (!isNaN(storeValue)) {
@@ -186,11 +201,6 @@ function playPause() {
} }
} }
/**
* Clears the old listenbrainz data on launch
*/
ListenBrainzStore.clear();
/** /**
* Add hotkeys for when tidal is focused * Add hotkeys for when tidal is focused
* Reflects the desktop hotkeys found on: * Reflects the desktop hotkeys found on:
@@ -264,7 +274,7 @@ function handleLogout() {
defaultId: 2, defaultId: 2,
}) })
.then((result: { response: number }) => { .then((result: { response: number }) => {
if (logoutOptions.indexOf("Yes, please") === result.response) { if (logoutOptions.indexOf("Yes, please") == result.response) {
for (let i = 0; i < window.localStorage.length; i++) { for (let i = 0; i < window.localStorage.length; i++) {
const key = window.localStorage.key(i); const key = window.localStorage.key(i);
if (key.startsWith("_TIDAL_activeSession")) { if (key.startsWith("_TIDAL_activeSession")) {
@@ -306,8 +316,6 @@ function addIPCEventListeners() {
case globalEvents.pause: case globalEvents.pause:
elements.click("pause"); elements.click("pause");
break; break;
default:
break;
} }
}); });
}); });
@@ -322,9 +330,9 @@ function getCurrentlyPlayingStatus() {
// if pause button is visible tidal is playing // if pause button is visible tidal is playing
if (pause) { if (pause) {
status = MediaStatus.playing; status = statuses.playing;
} else { } else {
status = MediaStatus.paused; status = statuses.paused;
} }
return status; return status;
} }
@@ -349,54 +357,19 @@ function updateMediaInfo(options: Options, notify: boolean) {
if (settingsStore.get(settings.notifications) && notify) { if (settingsStore.get(settings.notifications) && notify) {
new Notification({ title: options.title, body: options.artists, icon: options.icon }).show(); new Notification({ title: options.title, body: options.artists, icon: options.icon }).show();
} }
updateMpris(options); if (player) {
updateListenBrainz(options); player.metadata = {
} ...player.metadata,
} ...{
"xesam:title": options.title,
function updateMpris(options: Options) { "xesam:artist": [options.artists],
if (player) { "xesam:album": options.album,
player.metadata = { "mpris:artUrl": options.image,
...player.metadata, "mpris:length": convertDuration(options.duration) * 1000 * 1000,
...{ "mpris:trackid": "/org/mpris/MediaPlayer2/track/" + getTrackID(),
"xesam:title": options.title, },
"xesam:artist": [options.artists], };
"xesam:album": options.album, player.playbackStatus = options.status == statuses.paused ? "Paused" : "Playing";
"mpris:artUrl": options.image,
"mpris:length": convertDuration(options.duration) * 1000 * 1000,
"mpris:trackid": "/org/mpris/MediaPlayer2/track/" + getTrackID(),
},
};
player.playbackStatus = options.status === MediaStatus.paused ? "Paused" : "Playing";
}
}
/**
* Update the listenbrainz service with new data based on a few conditions
*/
function updateListenBrainz(options: Options) {
if (settingsStore.get(settings.ListenBrainz.enabled)) {
const oldData = ListenBrainzStore.get(ListenBrainzConstants.oldData) as StoreData;
if (
(!oldData && options.status === MediaStatus.playing) ||
(oldData && oldData.title !== options.title)
) {
if (!scrobbleWaitingForDelay) {
scrobbleWaitingForDelay = true;
clearTimeout(currentListenBrainzDelayId);
currentListenBrainzDelayId = setTimeout(
() => {
ListenBrainz.scrobble(
options.title,
options.artists,
options.status,
convertDuration(options.duration)
);
scrobbleWaitingForDelay = false;
},
settingsStore.get(settings.ListenBrainz.delay) ?? 0
);
}
} }
} }
} }
@@ -520,8 +493,8 @@ setInterval(function () {
if (process.platform === "linux" && settingsStore.get(settings.mpris)) { if (process.platform === "linux" && settingsStore.get(settings.mpris)) {
try { try {
player = Player({ player = Player({
name: "TIDAL Hi-Fi", name: "tidal-hifi",
identity: "TIDAL Hi-Fi", identity: "tidal-hifi",
supportedUriSchemes: ["file"], supportedUriSchemes: ["file"],
supportedMimeTypes: [ supportedMimeTypes: [
"audio/mpeg", "audio/mpeg",
@@ -533,6 +506,7 @@ if (process.platform === "linux" && settingsStore.get(settings.mpris)) {
supportedInterfaces: ["player"], supportedInterfaces: ["player"],
desktopEntry: "tidal-hifi", desktopEntry: "tidal-hifi",
}); });
// Events // Events
const events = { const events = {
next: "next", next: "next",
@@ -570,8 +544,7 @@ if (process.platform === "linux" && settingsStore.get(settings.mpris)) {
console.log("player api not working"); console.log("player api not working");
} }
} }
addCustomCss();
addCustomCss(app, Logger.bind(this));
addHotKeys(); addHotKeys();
addIPCEventListeners(); addIPCEventListeners();
addFullScreenListeners(); addFullScreenListeners();

View File

@@ -1,11 +1,8 @@
import { Client } from "discord-rpc"; import { Client } from "discord-rpc";
import { app, ipcMain } from "electron"; import { app, ipcMain } from "electron";
import { globalEvents } from "../constants/globalEvents"; import { globalEvents } from "../constants/globalEvents";
import { settings } from "../constants/settings";
import { Logger } from "../features/logger";
import { MediaStatus } from "../models/mediaStatus"; import { MediaStatus } from "../models/mediaStatus";
import { mediaInfo } from "./mediaInfo"; import { mediaInfo } from "./mediaInfo";
import { settingsStore } from "./settings";
const clientId = "833617820704440341"; const clientId = "833617820704440341";
@@ -18,7 +15,7 @@ function timeToSeconds(timeArray: string[]) {
export let rpc: Client; export let rpc: Client;
const observer = () => { const observer = () => {
if (mediaInfo.status === MediaStatus.paused && rpc) { if (mediaInfo.status == MediaStatus.paused && rpc) {
rpc.setActivity(idleStatus); rpc.setActivity(idleStatus);
} else if (rpc) { } else if (rpc) {
const currentSeconds = timeToSeconds(mediaInfo.current.split(":")); const currentSeconds = timeToSeconds(mediaInfo.current.split(":"));
@@ -26,21 +23,17 @@ const observer = () => {
const date = new Date(); const date = new Date();
const now = (date.getTime() / 1000) | 0; const now = (date.getTime() / 1000) | 0;
const remaining = date.setSeconds(date.getSeconds() + (durationSeconds - currentSeconds)); const remaining = date.setSeconds(date.getSeconds() + (durationSeconds - currentSeconds));
const detailsPrefix =
settingsStore.get<string, string>(settings.discord.detailsPrefix) ?? "Listening to ";
const buttonText =
settingsStore.get<string, string>(settings.discord.buttonText) ?? "Play on TIDAL";
if (mediaInfo.url) { if (mediaInfo.url) {
rpc.setActivity({ rpc.setActivity({
...idleStatus, ...idleStatus,
...{ ...{
details: `${detailsPrefix}${mediaInfo.title}`, details: `Listening to ${mediaInfo.title}`,
state: mediaInfo.artists ? mediaInfo.artists : "unknown artist(s)", state: mediaInfo.artists ? mediaInfo.artists : "unknown artist(s)",
startTimestamp: now, startTimestamp: now,
endTimestamp: remaining, endTimestamp: remaining,
largeImageKey: mediaInfo.image, largeImageKey: mediaInfo.image,
largeImageText: mediaInfo.album ? mediaInfo.album : `${idleStatus.largeImageText}`, largeImageText: mediaInfo.album ? mediaInfo.album : `${idleStatus.largeImageText}`,
buttons: [{ label: buttonText, url: mediaInfo.url }], buttons: [{ label: "Play on Tidal", url: mediaInfo.url }],
}, },
}); });
} else { } else {
@@ -60,7 +53,7 @@ const observer = () => {
const idleStatus = { const idleStatus = {
details: `Browsing Tidal`, details: `Browsing Tidal`,
largeImageKey: "tidal-hifi-icon", largeImageKey: "tidal-hifi-icon",
largeImageText: `TIDAL Hi-Fi ${app.getVersion()}`, largeImageText: `Tidal HiFi ${app.getVersion()}`,
instance: false, instance: false,
}; };
@@ -77,7 +70,7 @@ export const initRPC = () => {
ipcMain.on(globalEvents.updateInfo, observer); ipcMain.on(globalEvents.updateInfo, observer);
}, },
() => { () => {
Logger.log("Can't connect to Discord, is it running?"); console.error("Can't connect to Discord, is it running?");
} }
); );
}; };

View File

@@ -1,14 +1,14 @@
import { BrowserWindow, dialog } from "electron"; import { BrowserWindow, dialog } from "electron";
import express, { Response } from "express"; import express, { Response } from "express";
import fs from "fs"; import fs from "fs";
import { settings } from "../constants/settings";
import { MediaStatus } from "../models/mediaStatus";
import { globalEvents } from "./../constants/globalEvents"; import { globalEvents } from "./../constants/globalEvents";
import { statuses } from "./../constants/statuses";
import { mediaInfo } from "./mediaInfo"; import { mediaInfo } from "./mediaInfo";
import { settingsStore } from "./settings"; import { settingsStore } from "./settings";
import { settings } from "../constants/settings";
/** /**
* Function to enable TIDAL Hi-Fi's express api * Function to enable tidal-hifi's express api
*/ */
// expressModule.run = function (mainWindow) // expressModule.run = function (mainWindow)
@@ -44,7 +44,7 @@ export const startExpress = (mainWindow: BrowserWindow) => {
expressApp.get("/next", (req, res) => handleGlobalEvent(res, globalEvents.next)); expressApp.get("/next", (req, res) => handleGlobalEvent(res, globalEvents.next));
expressApp.get("/previous", (req, res) => handleGlobalEvent(res, globalEvents.previous)); expressApp.get("/previous", (req, res) => handleGlobalEvent(res, globalEvents.previous));
expressApp.get("/playpause", (req, res) => { expressApp.get("/playpause", (req, res) => {
if (mediaInfo.status === MediaStatus.playing) { if (mediaInfo.status == statuses.playing) {
handleGlobalEvent(res, globalEvents.pause); handleGlobalEvent(res, globalEvents.pause);
} else { } else {
handleGlobalEvent(res, globalEvents.play); handleGlobalEvent(res, globalEvents.play);

View File

@@ -1,12 +1,12 @@
import { MediaInfo } from "../models/mediaInfo"; import { MediaInfo } from "../models/mediaInfo";
import { MediaStatus } from "../models/mediaStatus"; import { statuses } from "./../constants/statuses";
export const mediaInfo = { export const mediaInfo = {
title: "", title: "",
artists: "", artists: "",
album: "", album: "",
icon: "", icon: "",
status: MediaStatus.paused as string, status: statuses.paused,
url: "", url: "",
current: "", current: "",
duration: "", duration: "",

View File

@@ -33,6 +33,7 @@ export const getMenu = function (mainWindow: BrowserWindow) {
{ {
label: name, label: name,
submenu: [ submenu: [
{ role: "about" },
settingsMenuEntry, settingsMenuEntry,
{ type: "separator" }, { type: "separator" },
{ role: "services" }, { role: "services" },
@@ -100,6 +101,12 @@ export const getMenu = function (mainWindow: BrowserWindow) {
], ],
}, },
settingsMenuEntry, settingsMenuEntry,
{
label: "About",
click() {
showSettingsWindow("about");
},
},
toggleWindow, toggleWindow,
quitMenuEntry, quitMenuEntry,
]; ];

View File

@@ -1,8 +1,8 @@
import Store from "electron-store"; import Store from "electron-store";
import { BrowserWindow } from "electron";
import path from "path";
import { settings } from "../constants/settings"; import { settings } from "../constants/settings";
import path from "path";
import { BrowserWindow } from "electron";
let settingsWindow: BrowserWindow; let settingsWindow: BrowserWindow;
@@ -18,20 +18,9 @@ export const settingsStore = new Store({
disableHardwareMediaKeys: false, disableHardwareMediaKeys: false,
enableCustomHotkeys: false, enableCustomHotkeys: false,
enableDiscord: false, enableDiscord: false,
discord: {
detailsPrefix: "Listening to ",
buttonText: "Play on Tidal",
},
ListenBrainz: {
enabled: false,
api: "https://api.listenbrainz.org",
token: "",
delay: 5000,
},
flags: { flags: {
disableHardwareMediaKeys: false,
enableWaylandSupport: true,
gpuRasterization: true, gpuRasterization: true,
disableHardwareMediaKeys: false,
}, },
menuBar: true, menuBar: true,
minimizeOnClose: false, minimizeOnClose: false,
@@ -54,13 +43,6 @@ export const settingsStore = new Store({
migrationStore.get("disableHardwareMediaKeys") ?? false migrationStore.get("disableHardwareMediaKeys") ?? false
); );
}, },
"5.7.0": (migrationStore) => {
console.log("running migrations for 5.7.0");
migrationStore.set(
settings.ListenBrainz.delay,
migrationStore.get(settings.ListenBrainz.delay) ?? 5000
);
},
}, },
}); });
@@ -71,8 +53,8 @@ const settingsModule = {
export const createSettingsWindow = function () { export const createSettingsWindow = function () {
settingsWindow = new BrowserWindow({ settingsWindow = new BrowserWindow({
width: 650, width: 700,
height: 700, height: 600,
resizable: true, resizable: true,
show: false, show: false,
transparent: true, transparent: true,

View File

@@ -74,7 +74,7 @@ button.feedBell--kvAbD {
.container--PFTHk { .container--PFTHk {
background-color: var(--right-queue-background); background-color: var(--right-queue-background);
} }
.container--cl4MJ { .container--cl4MJ{
background-color: var(--search-background); background-color: var(--search-background);
} }
.searchFieldHighlighted--Fitvs { .searchFieldHighlighted--Fitvs {
@@ -83,122 +83,3 @@ button.feedBell--kvAbD {
.searchField--EGBSq { .searchField--EGBSq {
background-color: var(--search-background); background-color: var(--search-background);
} }
// Settings window styling
.settings-window {
color: var(--sidebar-menu-playlist-text);
}
.settings-window__wrapper {
background: var(--main-background);
box-shadow: inset 0 0 2px 0 var(--main-feed-button-background);
}
.settings-window__close-button:hover {
background: var(--main-feed-button-background);
}
.settings input:checked + label {
border-bottom: 2px solid var(--player-control-active-button);
color: var(--player-control-active-button);
}
.tabs::-webkit-scrollbar-thumb {
background-color: #404248;
box-shadow: inset 0 0 10px 2px var(--search-background);
}
.group {
border-bottom: 1px solid #333;
}
.group__description p {
color: var(--sidebar-menu-top-text);
}
.group__description .text-input {
border-bottom: solid 1px #333;
color: var(--sidebar-menu-top-text);
}
.group__description .text-input:focus {
border-color: var(--player-control-active-button);
color: var(--sidebar-menu-playlist-text);
}
.switch input:checked + .switch__slider {
background-color: var(--player-control-active-button);
}
.switch input:checked + .switch__slider::before {
background-color: var(--sidebar-menu-playlist-text);
}
.switch input:focus + .switch__slider {
box-shadow: inset 0 0 0 1px var(--player-control-active-button);
}
.switch__slider {
background-color: var(--search-background);
}
.switch__slider::before {
background-color: var(--sidebar-menu-playlist-text);
}
.textarea {
background: var(--search-background);
color: var(--sidebar-menu-top-text);
}
.textarea:focus {
border-color: var(--player-control-active-button);
color: var(--sidebar-menu-playlist-text);
}
.about-section__version a {
background-color: #404248;
color: var(--player-control-active-button);
}
.about-section__links a {
color: var(--sidebar-menu-playlist-text);
background-color: var(--search-background);
}
.about-section__links a i {
color: var(--sidebar-menu-playlist-text);
}
.about-section__links a:hover {
background-color: var(--player-control-favorite);
}
.footer__note {
color: var(--sidebar-menu-top-text);
}
.footer__button {
background: #404248;
color: var(--sidebar-menu-playlist-text);
}
.footer__button:hover {
background: #55585f;
}
.file-drop-area {
border: 1px dashed var(--sidebar-menu-top-text);
}
.file-drop-area.is-active {
background-color: #17171a;
}
.file-btn {
background-color: #17171a;
border: 1px solid var(--sidebar-menu-top-text);
}
.select-input {
border-bottom: solid 1px #333;
color: var(--sidebar-menu-top-text);
}
.select-input:focus {
border-color: var(--player-control-active-button);
color: var(--sidebar-menu-playlist-text);
}
.select-input option {
background-color: var(--search-background);
}
.select-input option:disabled {
color: var(--sidebar-menu-playlist-text);
}