diff --git a/.vscode/settings.json b/.vscode/settings.json index 70e1c0e..74f8aa3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,8 +6,10 @@ "Flatpak", "geqnfr", "hifi", + "libnotify", "listenbrainz", "playpause", + "prs", "rescrobbler", "scrobble", "scrobbling", @@ -16,5 +18,9 @@ "tracklist", "widevine", "xesam" - ] + ], + "sonarlint.connectedMode.project": { + "connectionId": "public-sonarcloud", + "projectKey": "Mastermindzh_tidal-hifi" + } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 655ce66..ad4ae70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,23 @@ 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/), 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-hifi client supports High & Max audio settings. fixes [#261](https://github.com/Mastermindzh/tidal-hifi/issues/261) +- 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 @@ -89,7 +102,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - New settings window by BlueManCZ - Fixed the desktop files in electron-builder - icon is set to new static path based on Arch/Debian - - Name has changed to Tidal-Hifi + - Name has changed to TIDAL Hi-Fi ## 4.1.2 @@ -137,7 +150,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated to Electron 15 - Fixed the develop "build-unpacked" command -- Added setting to disable multiple tidal-hifi windows (defaults to true) +- Added setting to disable multiple TIDAL Hi-Fi windows (defaults to true) - Added setting to disable HardwareMediaKeyHandling (defaults to false) ## 2.8.2 @@ -175,7 +188,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## 2.5.0 -- Notify-send now correctly shows "Tidal HiFi" as the program name +- Notify-send now correctly shows "Tidal Hi-Fi" as the program name - Updated dependencies (including electron itself) ### known issues diff --git a/README.md b/README.md index 9055891..767a941 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,20 @@ -# Tidal-hifi +# TIDAL Hi-Fi (Max quality) ![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 hifi (High & Max) support thanks to widevine. +The web version of [listen.tidal.com](https://listen.tidal.com) running in electron with Hi-Fi (High & Max) support thanks to widevine. -![tidal-hifi preview](./docs/images/preview.png) +![TIDAL Hi-Fi preview](./docs/images/preview.png) ## Table of Contents -- [Tidal-hifi](#tidal-hifi) +- [TIDAL Hi-Fi (Max quality)](#tidal-hi-fi-max-quality) - [Table of Contents](#table-of-contents) - [Features](#features) - [Contributions](#contributions) - - [Why did I create tidal-hifi?](#why-did-i-create-tidal-hifi) + - [Why did I create TIDAL Hi-Fi?](#why-did-i-create-tidal-hi-fi) - [Why not extend existing projects?](#why-not-extend-existing-projects) - [Installation](#installation) - [Dependencies](#dependencies) @@ -26,7 +26,6 @@ The web version of [listen.tidal.com](https://listen.tidal.com) running in elect - [Using source](#using-source) - [Integrations](#integrations) - [Known bugs](#known-bugs) - - [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) - [Donations](#donations) @@ -42,6 +41,7 @@ The web version of [listen.tidal.com](https://listen.tidal.com) running in elect - Notifications - Custom [theming](./docs/theming.md) - Custom hotkeys ([source](https://defkey.com/tidal-desktop-shortcuts)) +- Better icons thanks to [Papirus-icon-theme](https://github.com/PapirusDevelopmentTeam/papirus-icon-theme/) - [Settings feature](./docs/images/settings.png) to disable certain functionality. (`ctrl+=` or `ctrl+0`) - 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)) @@ -51,14 +51,15 @@ The web version of [listen.tidal.com](https://listen.tidal.com) running in elect - Songwhip.com integration (hotkey `ctrl + w`) - 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 -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) -## Why did I create tidal-hifi? +## Why did I create TIDAL Hi-Fi? 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. @@ -103,10 +104,10 @@ To install with `snap` you need to download the pre-packaged snap-package from t ### Arch Linux -Arch Linux users can use the AUR to install tidal-hifi: +Arch Linux users can use the AUR to install TIDAL Hi-Fi: ```sh -trizen tidal-hifi-bin +trizen tidal-hifi-git ``` ### Flatpak @@ -130,13 +131,13 @@ nix-env -iA nixpkgs.tidal-hifi 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) -- cd tidal-hifi +- cd TIDAL Hi-Fi - npm install - npm start ## Integrations -tidal-hifi comes with several integrations out of the box. +TIDAL Hi-Fi comes with several integrations out of the box. 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) @@ -148,15 +149,9 @@ Integrations with other projects that are not included natively: ## Known bugs -### last.fm doesn't work out of the box. Use rescrobbler as a workaround - -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. +Most Windows users run into DRM issues when trying to use TIDAL Hi-Fi. 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 diff --git a/assets/TIDAL.icns b/assets/TIDAL.icns deleted file mode 100755 index e587578..0000000 Binary files a/assets/TIDAL.icns and /dev/null differ diff --git a/assets/icon-inverted.png b/assets/icon-inverted.png index 17abbd5..c3d1e38 100644 Binary files a/assets/icon-inverted.png and b/assets/icon-inverted.png differ diff --git a/assets/icon.png b/assets/icon.png index 31838df..0ff0d5b 100644 Binary files a/assets/icon.png and b/assets/icon.png differ diff --git a/assets/icons/128x128.png b/assets/icons/128x128.png index b627e54..d8effde 100644 Binary files a/assets/icons/128x128.png and b/assets/icons/128x128.png differ diff --git a/assets/icons/16x16.png b/assets/icons/16x16.png index 245778c..0f1cb27 100644 Binary files a/assets/icons/16x16.png and b/assets/icons/16x16.png differ diff --git a/assets/icons/22x22.png b/assets/icons/22x22.png index 69bd795..ab4d4ec 100644 Binary files a/assets/icons/22x22.png and b/assets/icons/22x22.png differ diff --git a/assets/icons/24x24.png b/assets/icons/24x24.png index 949e47a..0dc67a3 100644 Binary files a/assets/icons/24x24.png and b/assets/icons/24x24.png differ diff --git a/assets/icons/256x256.png b/assets/icons/256x256.png index 16f86f2..66a099a 100644 Binary files a/assets/icons/256x256.png and b/assets/icons/256x256.png differ diff --git a/assets/icons/32x32.png b/assets/icons/32x32.png index 7e502ab..956117d 100644 Binary files a/assets/icons/32x32.png and b/assets/icons/32x32.png differ diff --git a/assets/icons/384x384.png b/assets/icons/384x384.png index 3215e4c..4bb67f8 100644 Binary files a/assets/icons/384x384.png and b/assets/icons/384x384.png differ diff --git a/assets/icons/48x48.png b/assets/icons/48x48.png index 6821fe2..c44b423 100644 Binary files a/assets/icons/48x48.png and b/assets/icons/48x48.png differ diff --git a/assets/icons/64x64.png b/assets/icons/64x64.png index 1e02797..5b4cc85 100644 Binary files a/assets/icons/64x64.png and b/assets/icons/64x64.png differ diff --git a/build/icon-inverted.png b/build/icon-inverted.png index 17abbd5..c3d1e38 100644 Binary files a/build/icon-inverted.png and b/build/icon-inverted.png differ diff --git a/build/icon.icns b/build/icon.icns old mode 100755 new mode 100644 index e587578..5e75873 Binary files a/build/icon.icns and b/build/icon.icns differ diff --git a/build/icon.png b/build/icon.png index 31838df..0ff0d5b 100644 Binary files a/build/icon.png and b/build/icon.png differ diff --git a/docs/images/customcss-menu.png b/docs/images/customcss-menu.png new file mode 100644 index 0000000..13e4996 Binary files /dev/null and b/docs/images/customcss-menu.png differ diff --git a/docs/images/integrations.png b/docs/images/integrations.png index b9c7d98..3f616aa 100644 Binary files a/docs/images/integrations.png and b/docs/images/integrations.png differ diff --git a/docs/images/new-about.png b/docs/images/new-about.png new file mode 100644 index 0000000..b34f4b4 Binary files /dev/null and b/docs/images/new-about.png differ diff --git a/docs/theming.md b/docs/theming.md index 2599ca4..bc2317a 100644 --- a/docs/theming.md +++ b/docs/theming.md @@ -1,10 +1,10 @@ -# Theming tidal-hifi +# Theming TIDAL Hi-Fi ## Table of contents -- [Theming tidal-hifi](#theming-tidal-hifi) +- [Theming TIDAL Hi-Fi](#theming-TIDAL Hi-Fi) - [Table of contents](#table-of-contents) - [Custom CSS](#custom-css) - [config](#config) @@ -12,7 +12,7 @@ -By default tidal-hifi comes with a few themes. +By default TIDAL Hi-Fi comes with a few themes. 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) diff --git a/package-lock.json b/package-lock.json index 481c596..30005af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "tidal-hifi", + "name": "TIDAL Hi-Fi", "version": "5.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "tidal-hifi", + "name": "TIDAL Hi-Fi", "version": "5.6.0", "license": "MIT", "dependencies": { @@ -14,7 +14,7 @@ "discord-rpc": "^4.0.1", "electron-store": "^8.1.0", "express": "^4.18.2", - "hotkeys-js": "^3.11.2", + "hotkeys-js": "^3.12.0", "mpris-service": "^2.1.2", "request": "^2.88.2", "sass": "^1.64.1" @@ -4855,9 +4855,9 @@ } }, "node_modules/hotkeys-js": { - "version": "3.11.2", - "resolved": "https://registry.npmjs.org/hotkeys-js/-/hotkeys-js-3.11.2.tgz", - "integrity": "sha512-hLuR+wtvrVrVFHku5cud44cPXQf5ke+96TonRaGzlNSt22rh2LH65avLYFNsc+QpzMuOEQG/IxJT5mEop99rvg==" + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/hotkeys-js/-/hotkeys-js-3.12.0.tgz", + "integrity": "sha512-Z+N573ycUKIGwFYS3ID1RzMJiGmtWMGKMiaNLyJS8B1ei+MllF4ZYmKS2T0kMWBktOz+WZLVNikftEgnukOrXg==" }, "node_modules/html-tags": { "version": "3.3.1", @@ -9340,4 +9340,4 @@ } } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index e587566..d75bb00 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tidal-hifi", - "version": "5.6.0", + "version": "5.7.0", "description": "Tidal on Electron with widevine(hifi) support", "main": "ts-dist/main.js", "scripts": { @@ -43,7 +43,7 @@ "discord-rpc": "^4.0.1", "electron-store": "^8.1.0", "express": "^4.18.2", - "hotkeys-js": "^3.11.2", + "hotkeys-js": "^3.12.0", "mpris-service": "^2.1.2", "request": "^2.88.2", "sass": "^1.64.1" @@ -72,4 +72,4 @@ "typescript": "^5.1.6" }, "prettier": "@mastermindzh/prettier-config" -} +} \ No newline at end of file diff --git a/scripts/resize-icons.sh b/scripts/resize-icons.sh new file mode 100644 index 0000000..a5e2d78 --- /dev/null +++ b/scripts/resize-icons.sh @@ -0,0 +1,16 @@ +#!/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 diff --git a/listen.tidal.com-parsing-scripts/verifyElements.js b/scripts/verifyElements.js similarity index 100% rename from listen.tidal.com-parsing-scripts/verifyElements.js rename to scripts/verifyElements.js diff --git a/src/constants/settings.ts b/src/constants/settings.ts index 482c6e2..70ea358 100644 --- a/src/constants/settings.ts +++ b/src/constants/settings.ts @@ -20,11 +20,16 @@ export const settings = { disableHardwareMediaKeys: "disableHardwareMediaKeys", enableCustomHotkeys: "enableCustomHotkeys", 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: { root: "flags", diff --git a/src/constants/values.ts b/src/constants/values.ts index a4e2764..57465a6 100644 --- a/src/constants/values.ts +++ b/src/constants/values.ts @@ -1,3 +1,3 @@ export default { - name: "tidal-hifi", + name: "TIDAL Hi-Fi", }; diff --git a/src/features/flags/flags.ts b/src/features/flags/flags.ts index 3ec730a..df5dbef 100644 --- a/src/features/flags/flags.ts +++ b/src/features/flags/flags.ts @@ -21,7 +21,6 @@ export function setManagedFlagsFromSettings(app: App) { for (const [key, value] of Object.entries(flagsFromSettings)) { if (value) { flags[key].forEach((flag) => { - Logger.log(`enabling command line option ${flag.flag} with value ${flag.value}`); setFlag(app, flag.flag, flag.value); }); } @@ -37,5 +36,6 @@ export function setManagedFlagsFromSettings(app: App) { */ // 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); } diff --git a/src/features/theming/theming.ts b/src/features/theming/theming.ts new file mode 100644 index 0000000..b2821a4 --- /dev/null +++ b/src/features/theming/theming.ts @@ -0,0 +1,31 @@ +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(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(settings.customCSS).join("\n"); + document.head.appendChild(style); + }); +} diff --git a/src/main.ts b/src/main.ts index 216bd07..f20b6ef 100644 --- a/src/main.ts +++ b/src/main.ts @@ -37,10 +37,14 @@ const tidalUrl = "https://listen.tidal.com"; let mainInhibitorId = -1; initialize(); - let mainWindow: BrowserWindow; const icon = path.join(__dirname, "../assets/icon.png"); const PROTOCOL_PREFIX = "tidal"; +const windowPreferences = { + sandbox: false, + plugins: true, + devTools: true, // I like tinkering, others might too +}; setDefaultFlags(app); setManagedFlagsFromSettings(app); @@ -84,10 +88,10 @@ function createWindow(options = { x: 0, y: 0, backgroundColor: "white" }) { backgroundColor: options.backgroundColor, autoHideMenuBar: true, webPreferences: { - sandbox: false, - preload: path.join(__dirname, "preload.js"), - plugins: true, - devTools: true, // I like tinkering, others might too + ...windowPreferences, + ...{ + preload: path.join(__dirname, "preload.js"), + }, }, }); enable(mainWindow.webContents); @@ -120,6 +124,18 @@ function createWindow(options = { x: 0, y: 0, backgroundColor: "white" }) { const { width, height } = mainWindow.getBounds(); 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() { diff --git a/src/pages/settings/icon.png b/src/pages/settings/icon.png index 31838df..0ff0d5b 100644 Binary files a/src/pages/settings/icon.png and b/src/pages/settings/icon.png differ diff --git a/src/pages/settings/preload.ts b/src/pages/settings/preload.ts index 0194a41..3a69df7 100644 --- a/src/pages/settings/preload.ts +++ b/src/pages/settings/preload.ts @@ -4,9 +4,24 @@ import fs from "fs"; import { globalEvents } from "../../constants/globalEvents"; import { settings } from "../../constants/settings"; import { Logger } from "../../features/logger"; +import { addCustomCss } from "../../features/theming/theming"; import { settingsStore } from "./../../scripts/settings"; 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, api: HTMLInputElement, customCSS: HTMLInputElement, @@ -30,7 +45,12 @@ let adBlock: HTMLInputElement, enableListenBrainz: HTMLInputElement, ListenBrainzAPI: HTMLInputElement, ListenBrainzToken: HTMLInputElement, - enableWaylandSupport: HTMLInputElement; + listenbrainz_delay: HTMLInputElement, + enableWaylandSupport: HTMLInputElement, + discord_details_prefix: HTMLInputElement, + discord_button_text: HTMLInputElement; + +addCustomCss(app, Logger.bind(this)); function getThemeFiles() { const selectElement = document.getElementById("themesList") as HTMLSelectElement; @@ -59,6 +79,7 @@ function handleFileUploads() { const fileMessage = document.getElementById("file-message"); 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) { Array.from(e.target.files).forEach((file: File) => { const destination = `${app.getPath("userData")}/themes/${file.name}`; @@ -69,6 +90,20 @@ 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 */ @@ -98,6 +133,14 @@ function refreshSettings() { 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); } @@ -117,14 +160,6 @@ function hide() { ipcRenderer.send(globalEvents.hideSettings); } -/** - * Restart tidal-hifi after changes - */ -function restart() { - app.relaunch(); - app.exit(); -} - /** * Bind UI components to functions after DOMContentLoaded */ @@ -137,25 +172,28 @@ window.addEventListener("DOMContentLoaded", () => { handleFileUploads(); document.getElementById("close").addEventListener("click", hide); - document.getElementById("restart").addEventListener("click", restart); document.querySelectorAll(".external-link").forEach((elem) => elem.addEventListener("click", function (event) { openExternal((event.target as HTMLElement).getAttribute("data-url")); }) ); - function addInputListener(source: HTMLInputElement, key: string) { + function addInputListener( + source: HTMLInputElement, + key: string, + toggleOptions?: { switch: string; classToHide: string } + ) { source.addEventListener("input", () => { if (source.value === "on") { settingsStore.set(key, source.checked); } else { settingsStore.set(key, source.value); } - // Live update the view for ListenBrainz input, hide if disabled/show if enabled - if (source.value === "on" && source.id === "enableListenBrainz") { - source.checked - ? document.getElementById("listenbrainz__options").removeAttribute("hidden") - : document.getElementById("listenbrainz__options").setAttribute("hidden", "true"); + + if (toggleOptions) { + if (source.value === "on" && source.id === toggleOptions.switch) { + setElementHidden(source.checked, toggleOptions); + } } ipcRenderer.send(globalEvents.storeChanged); }); @@ -207,19 +245,18 @@ window.addEventListener("DOMContentLoaded", () => { 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(); - enableListenBrainz.checked - ? document.getElementById("listenbrainz__options").removeAttribute("hidden") - : document.getElementById("listenbrainz__options").setAttribute("hidden", "true"); - addInputListener(adBlock, settings.adBlock); addInputListener(api, settings.api); addTextAreaListener(customCSS, settings.customCSS); addInputListener(disableBackgroundThrottle, settings.disableBackgroundThrottle); addInputListener(disableHardwareMediaKeys, settings.flags.disableHardwareMediaKeys); addInputListener(enableCustomHotkeys, settings.enableCustomHotkeys); - addInputListener(enableDiscord, settings.enableDiscord); + addInputListener(enableDiscord, settings.enableDiscord, switchesWithSettings.discord); addInputListener(enableWaylandSupport, settings.flags.enableWaylandSupport); addInputListener(gpuRasterization, settings.flags.gpuRasterization); addInputListener(menuBar, settings.menuBar); @@ -234,7 +271,14 @@ window.addEventListener("DOMContentLoaded", () => { addSelectListener(theme, settings.theme); addInputListener(trayIcon, settings.trayIcon); addInputListener(updateFrequency, settings.updateFrequency); - addInputListener(enableListenBrainz, settings.ListenBrainz.enabled); - addTextAreaListener(ListenBrainzAPI, settings.ListenBrainz.api); - addTextAreaListener(ListenBrainzToken, settings.ListenBrainz.token); + 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); }); diff --git a/src/pages/settings/settings.html b/src/pages/settings/settings.html index a06070c..8b76022 100644 --- a/src/pages/settings/settings.html +++ b/src/pages/settings/settings.html @@ -7,6 +7,7 @@ + @@ -201,6 +202,9 @@ + +
+

Discord

Discord RPC

@@ -211,6 +215,23 @@
+
+
+
+

Details prefix

+

Prefix for the "details" field of Discord's rich presence.

+ +
+
+ +
+
+

Button text

+

Text to display on the button below the song information.

+ +
+
+

ListenBrainz

@@ -224,21 +245,27 @@
- @@ -250,7 +277,7 @@

Update frequency

- The amount of time, in milliseconds, that tidal-hifi will refresh its playback info by scraping the + The amount of time, in milliseconds, that TIDAL Hi-Fi will refresh its playback info by scraping the website. 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. @@ -300,7 +327,7 @@

Wayland support

- Adds a couple of Electron flags to help Tidal-hifi run smoothly on the Wayland window system. + Adds a couple of Electron flags to help TIDAL Hi-Fi run smoothly on the Wayland window system.

diff --git a/src/pages/settings/settings.scss b/src/pages/settings/settings.scss index 8e7318a..9c5c437 100644 --- a/src/pages/settings/settings.scss +++ b/src/pages/settings/settings.scss @@ -8,6 +8,7 @@ $tidal-grey: #72777f; $tidal-grey-darker: #404248; $tidal-grey-darker-focus: #55585f; $tidal-grey-darkest: #242528; +$tidal-grey-darkest-focus: #2e2f33; // --- Fonts --- @@ -309,26 +310,75 @@ html { } .about-section { - padding-top: 120px; + padding-top: 40px; text-align: center; &__icon { display: inline-block; - width: 100px; + width: 200px; } &__text { display: block; - max-width: 350px; - margin: 20px auto 0; + max-width: 500px; + margin: -15px 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; + } } - // --- Footer --- + &__links { + 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 { position: sticky; - top: calc(100% - 120px); height: 100px; padding-top: 20px; text-align: center; @@ -443,3 +493,7 @@ html { } } } + +.hidden { + display: none !important; +} diff --git a/src/preload.ts b/src/preload.ts index 2c6e2dc..0537c08 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -1,6 +1,5 @@ import { app, dialog, Notification } from "@electron/remote"; import { clipboard, ipcRenderer } from "electron"; -import fs from "fs"; import Player from "mpris-service"; import { globalEvents } from "./constants/globalEvents"; import { settings } from "./constants/settings"; @@ -12,6 +11,7 @@ import { import { StoreData } from "./features/listenbrainz/models/storeData"; import { Logger } from "./features/logger"; import { Songwhip } from "./features/songwhip/songwhip"; +import { addCustomCss } from "./features/theming/theming"; import { MediaStatus } from "./models/mediaStatus"; import { Options } from "./models/options"; import { downloadFile } from "./scripts/download"; @@ -20,10 +20,12 @@ import { settingsStore } from "./scripts/settings"; import { setTitle } from "./scripts/window-functions"; const notificationPath = `${app.getPath("userData")}/notification.jpg`; -const appName = "Tidal Hifi"; +const appName = "TIDAL Hi-Fi"; let currentSong = ""; let player: Player; let currentPlayStatus = MediaStatus.paused; +let currentListenBrainzDelayId: ReturnType; +let scrobbleWaitingForDelay = false; const elements = { play: '*[data-test="play"]', @@ -156,32 +158,6 @@ const elements = { }, }; -function addCustomCss() { - window.addEventListener("DOMContentLoaded", () => { - const selectedTheme = settingsStore.get(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(settings.customCSS).join("\n"); - document.head.appendChild(style); - }); -} - /** * Get the update frequency from the store * make sure it returns a number, if not use the default @@ -395,6 +371,9 @@ function updateMpris(options: Options) { } } +/** + * 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; @@ -402,12 +381,22 @@ function updateListenBrainz(options: Options) { (!oldData && options.status === MediaStatus.playing) || (oldData && oldData.title !== options.title) ) { - ListenBrainz.scrobble( - options.title, - options.artists, - options.status, - convertDuration(options.duration) - ); + 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 + ); + } } } } @@ -531,8 +520,8 @@ setInterval(function () { if (process.platform === "linux" && settingsStore.get(settings.mpris)) { try { player = Player({ - name: "tidal-hifi", - identity: "tidal-hifi", + name: "TIDAL Hi-Fi", + identity: "TIDAL Hi-Fi", supportedUriSchemes: ["file"], supportedMimeTypes: [ "audio/mpeg", @@ -544,7 +533,6 @@ if (process.platform === "linux" && settingsStore.get(settings.mpris)) { supportedInterfaces: ["player"], desktopEntry: "tidal-hifi", }); - // Events const events = { next: "next", @@ -582,7 +570,8 @@ if (process.platform === "linux" && settingsStore.get(settings.mpris)) { console.log("player api not working"); } } -addCustomCss(); + +addCustomCss(app, Logger.bind(this)); addHotKeys(); addIPCEventListeners(); addFullScreenListeners(); diff --git a/src/scripts/discord.ts b/src/scripts/discord.ts index 909c5b8..6e87657 100644 --- a/src/scripts/discord.ts +++ b/src/scripts/discord.ts @@ -1,8 +1,11 @@ import { Client } from "discord-rpc"; import { app, ipcMain } from "electron"; import { globalEvents } from "../constants/globalEvents"; +import { settings } from "../constants/settings"; +import { Logger } from "../features/logger"; import { MediaStatus } from "../models/mediaStatus"; import { mediaInfo } from "./mediaInfo"; +import { settingsStore } from "./settings"; const clientId = "833617820704440341"; @@ -15,7 +18,7 @@ function timeToSeconds(timeArray: string[]) { export let rpc: Client; const observer = () => { - if (mediaInfo.status == MediaStatus.paused && rpc) { + if (mediaInfo.status === MediaStatus.paused && rpc) { rpc.setActivity(idleStatus); } else if (rpc) { const currentSeconds = timeToSeconds(mediaInfo.current.split(":")); @@ -23,17 +26,21 @@ const observer = () => { const date = new Date(); const now = (date.getTime() / 1000) | 0; const remaining = date.setSeconds(date.getSeconds() + (durationSeconds - currentSeconds)); + const detailsPrefix = + settingsStore.get(settings.discord.detailsPrefix) ?? "Listening to "; + const buttonText = + settingsStore.get(settings.discord.buttonText) ?? "Play on TIDAL"; if (mediaInfo.url) { rpc.setActivity({ ...idleStatus, ...{ - details: `Listening to ${mediaInfo.title}`, + details: `${detailsPrefix}${mediaInfo.title}`, state: mediaInfo.artists ? mediaInfo.artists : "unknown artist(s)", startTimestamp: now, endTimestamp: remaining, largeImageKey: mediaInfo.image, largeImageText: mediaInfo.album ? mediaInfo.album : `${idleStatus.largeImageText}`, - buttons: [{ label: "Play on Tidal", url: mediaInfo.url }], + buttons: [{ label: buttonText, url: mediaInfo.url }], }, }); } else { @@ -53,7 +60,7 @@ const observer = () => { const idleStatus = { details: `Browsing Tidal`, largeImageKey: "tidal-hifi-icon", - largeImageText: `Tidal HiFi ${app.getVersion()}`, + largeImageText: `TIDAL Hi-Fi ${app.getVersion()}`, instance: false, }; @@ -70,7 +77,7 @@ export const initRPC = () => { ipcMain.on(globalEvents.updateInfo, observer); }, () => { - console.error("Can't connect to Discord, is it running?"); + Logger.log("Can't connect to Discord, is it running?"); } ); }; diff --git a/src/scripts/express.ts b/src/scripts/express.ts index d731039..25f466d 100644 --- a/src/scripts/express.ts +++ b/src/scripts/express.ts @@ -8,7 +8,7 @@ import { mediaInfo } from "./mediaInfo"; import { settingsStore } from "./settings"; /** - * Function to enable tidal-hifi's express api + * Function to enable TIDAL Hi-Fi's express api */ // expressModule.run = function (mainWindow) diff --git a/src/scripts/menu.ts b/src/scripts/menu.ts index 786842b..7dd90aa 100644 --- a/src/scripts/menu.ts +++ b/src/scripts/menu.ts @@ -33,7 +33,6 @@ export const getMenu = function (mainWindow: BrowserWindow) { { label: name, submenu: [ - { role: "about" }, settingsMenuEntry, { type: "separator" }, { role: "services" }, @@ -101,12 +100,6 @@ export const getMenu = function (mainWindow: BrowserWindow) { ], }, settingsMenuEntry, - { - label: "About", - click() { - showSettingsWindow("about"); - }, - }, toggleWindow, quitMenuEntry, ]; diff --git a/src/scripts/settings.ts b/src/scripts/settings.ts index 8378b60..0a18c32 100644 --- a/src/scripts/settings.ts +++ b/src/scripts/settings.ts @@ -1,8 +1,8 @@ import Store from "electron-store"; -import { settings } from "../constants/settings"; -import path from "path"; import { BrowserWindow } from "electron"; +import path from "path"; +import { settings } from "../constants/settings"; let settingsWindow: BrowserWindow; @@ -18,10 +18,15 @@ export const settingsStore = new Store({ disableHardwareMediaKeys: false, enableCustomHotkeys: false, enableDiscord: false, + discord: { + detailsPrefix: "Listening to ", + buttonText: "Play on Tidal", + }, ListenBrainz: { enabled: false, api: "https://api.listenbrainz.org", token: "", + delay: 5000, }, flags: { disableHardwareMediaKeys: false, @@ -49,6 +54,13 @@ export const settingsStore = new Store({ 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 + ); + }, }, }); @@ -59,8 +71,8 @@ const settingsModule = { export const createSettingsWindow = function () { settingsWindow = new BrowserWindow({ - width: 700, - height: 600, + width: 650, + height: 700, resizable: true, show: false, transparent: true, diff --git a/src/themes/Tokyo Night.scss b/src/themes/Tokyo Night.scss index b54fc60..ef86cdc 100644 --- a/src/themes/Tokyo Night.scss +++ b/src/themes/Tokyo Night.scss @@ -74,7 +74,7 @@ button.feedBell--kvAbD { .container--PFTHk { background-color: var(--right-queue-background); } -.container--cl4MJ{ +.container--cl4MJ { background-color: var(--search-background); } .searchFieldHighlighted--Fitvs { @@ -83,3 +83,122 @@ button.feedBell--kvAbD { .searchField--EGBSq { 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); +}