diff --git a/README.md b/README.md index f187f6a..00d4ec0 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,80 @@ # Tidal-hifi -![GitHub release](https://img.shields.io/github/release/Mastermindzh/tidal-hifi.svg) +![GitHub release](https://img.shields.io/github/release/Mastermindzh/tidal-hifi.svg) [![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 support thanks to widevine. -![tidal-hifi preview](./docs/preview.png) +![tidal-hifi preview](./docs/images/preview.png) ## Table of Contents -- [Installation](#installation) - - [Dependencies](#dependencies) - - [Using releases](#using-releases) - - [Snap](#snap) - - [Arch Linux](#arch-linux) - - [Flatpak](#flatpak) - - [Nix](#nix) - - [Using source](#using-source) -- [Features](#features) -- [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) -- [Why](#why) -- [Why not extend existing projects?](#why-not-extend-existing-projects) -- [Special thanks to](#special-thanks-to) -- [Buy me a coffee? Please don't](#buy-me-a-coffee-please-dont) -- [Images](#images) - - [Settings window](#settings-window) - - [User setups](#user-setups) +- [Tidal-hifi](#tidal-hifi) + - [Table of Contents](#table-of-contents) + - [Features](#features) + - [Contributions](#contributions) + - [Why did I create tidal-hifi?](#why-did-i-create-tidal-hifi) + - [Why not extend existing projects?](#why-not-extend-existing-projects) + - [Installation](#installation) + - [Dependencies](#dependencies) + - [Using releases](#using-releases) + - [Snap](#snap) + - [Arch Linux](#arch-linux) + - [Flatpak](#flatpak) + - [Nix](#nix) + - [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) + - [Special thanks to](#special-thanks-to) + - [Donations](#donations) + - [Images](#images) + - [Settings window](#settings-window) + - [User setups](#user-setups) +## Features + +- HiFi playback +- Notifications +- Custom [theming](./docs/theming.md) +- Custom hotkeys ([source](https://defkey.com/tidal-desktop-shortcuts)) +- 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)) +- Custom [integrations](#integrations) +- [Settings feature](./docs/images/settings.png) to disable certain functionality. (`ctrl+=` or `ctrl+0`) +- AlbumArt in integrations ([best-effort](https://github.com/Mastermindzh/tidal-hifi/pull/88#pullrequestreview-840814847)) + +## Contributions + +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? + +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. + +### Why not extend existing projects? + +Whilst there are a handful of projects attempting to run Tidal on Electron they are all unappealing to me because of various reasons: + +- Lack of maintainers/developers. (no hotfixes, no issues being handled etc) +- Most are simple web wrappers, not my cup of tea. +- Some are DE oriented. I want this to work on WM's too. +- None have widevine working at the moment + +Sometimes it's just easier to start over, cover my own needs and then making it available to the public :) + ## Installation ### Dependencies -Note that you **need** a notification library such as [libnotify](https://github.com/GNOME/libnotify) or [dunst](https://github.com/dunst-project/dunst) in order for the software to work properly. +Note that you **need** a notification library such as [libnotify](https://github.com/GNOME/libnotify) or [dunst](https://github.com/dunst-project/dunst) for the software to work properly. ### Using releases @@ -48,15 +86,15 @@ To install with `snap` you need to download the pre-packaged snap-package from t 1. Download -```sh -wget #for instance: https://github.com/Mastermindzh/tidal-hifi/releases/download/1.0/tidal-hifi_1.0.0_amd64.snap -``` + ```sh + wget #for instance: https://github.com/Mastermindzh/tidal-hifi/releases/download/1.0/tidal-hifi_1.0.0_amd64.snap + ``` 2. Install -```sh -snap install --dangerous #for instance: tidal-hifi_1.0.0_amd64.snap -``` + ```sh + snap install --dangerous #for instance: tidal-hifi_1.0.0_amd64.snap + ``` ### Arch Linux @@ -91,23 +129,12 @@ To install and work with the code on this project follow these steps: - npm install - npm start -## Features - -- HiFi playback -- Notifications -- Custom hotkeys ([source](https://defkey.com/tidal-desktop-shortcuts)) -- 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)) -- Custom [integrations](#integrations) -- [Settings feature](./docs/settings.png) to disable certain functionality. (`ctrl+=` or `ctrl+0`) -- AlbumArt in integrations ([best-effort](https://github.com/Mastermindzh/tidal-hifi/pull/88#pullrequestreview-840814847)) - ## Integrations 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. -![integrations menu, showing a list of integrations](./docs/integrations.png) +![integrations menu, showing a list of integrations](./docs/images/integrations.png) It currently includes: @@ -126,38 +153,20 @@ The last.fm login doesn't work, as is evident from the following issue: [Last.fm 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. -## Why - -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. - -## Why not extend existing projects? - -Whilst there are a handful of projects attempting to run Tidal on Electron they are all unappealing to me because of various reasons: - -- Lack of a maintainers/developers. (no hotfixes, no issues being handled etc) -- Most are simple web wrappers, not my cup of tea. -- Some are DE oriented. I want this to work on WM's too. -- None have widevine working at the moment - -Sometimes it's just easier to start over, cover my own needs and then making it available to the public :) - ## Special thanks to - [Castlabs](https://castlabs.com/) For maintaining Electron with Widevine CDM installation, Verified Media Path (VMP), and persistent licenses (StorageID) -## Buy me a coffee? Please don't +## Donations -Instead spend some money on a charity I care for: [kwf.nl](https://www.kwf.nl/donatie/donation). -Inspired by [haydenjames' issue](https://github.com/Mastermindzh/tidal-hifi/issues/27#issuecomment-704198429) +You can find my Github sponsorship page at: [https://github.com/sponsors/Mastermindzh](https://github.com/sponsors/Mastermindzh) ## Images ### Settings window -![settings window](./docs/settings-preview.png) +![settings window](./docs/images/settings-preview.png) ### User setups diff --git a/docs/images/customcss-config.png b/docs/images/customcss-config.png new file mode 100644 index 0000000..4104a2a Binary files /dev/null and b/docs/images/customcss-config.png differ diff --git a/docs/images/customcss.png b/docs/images/customcss.png new file mode 100644 index 0000000..8cff474 Binary files /dev/null and b/docs/images/customcss.png differ diff --git a/docs/images/discord.png b/docs/images/discord.png new file mode 100644 index 0000000..c4ee7fa Binary files /dev/null and b/docs/images/discord.png differ diff --git a/docs/integrations.png b/docs/images/integrations.png similarity index 100% rename from docs/integrations.png rename to docs/images/integrations.png diff --git a/docs/preview.png b/docs/images/preview.png similarity index 100% rename from docs/preview.png rename to docs/images/preview.png diff --git a/docs/settings-preview.png b/docs/images/settings-preview.png similarity index 100% rename from docs/settings-preview.png rename to docs/images/settings-preview.png diff --git a/docs/settings.png b/docs/images/settings.png similarity index 100% rename from docs/settings.png rename to docs/images/settings.png diff --git a/docs/images/theming.png b/docs/images/theming.png new file mode 100644 index 0000000..5792a55 Binary files /dev/null and b/docs/images/theming.png differ diff --git a/docs/theming.md b/docs/theming.md new file mode 100644 index 0000000..2599ca4 --- /dev/null +++ b/docs/theming.md @@ -0,0 +1,38 @@ +# Theming tidal-hifi + +## Table of contents + + + +- [Theming tidal-hifi](#theming-tidal-hifi) + - [Table of contents](#table-of-contents) + - [Custom CSS](#custom-css) + - [config](#config) + - [Warning! Themes might break](#warning-themes-might-break) + + + +By default tidal-hifi 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) + +## Custom CSS + +The custom CSS will be added to the HTML document last. +This means that it will overwrite any existing CSS, even that of themes, unless the original has an access modifier such as `$important`. + +![settings window on the theming tab with a custom CSS override](./images/customcss.png) + +## config + +The theme selector and customCSS are stored in the config file. +The custom CSS is stored as a list of lines. + +![settings window on the theming tab next to the config file](./images/customcss-config.png) + +## Warning! Themes might break + +Themes might break at any point. Tidal changes their webpage structure a ton (they probably generate classNames and don't provide roles/ids/attributes.) + +If one breaks you can create an Issue on GitHub or ask for assistance in the [Discord channel](https://discord.gg/yhNwf4v4He). diff --git a/src/pages/settings/preload.ts b/src/pages/settings/preload.ts index d6f51f6..965ccf8 100644 --- a/src/pages/settings/preload.ts +++ b/src/pages/settings/preload.ts @@ -1,9 +1,10 @@ -import remote from "@electron/remote"; +import remote, { app } from "@electron/remote"; import { ipcRenderer, shell } from "electron"; import fs from "fs"; import { globalEvents } from "../../constants/globalEvents"; import { settings } from "../../constants/settings"; import { settingsStore } from "./../../scripts/settings"; +import { getOptions, getOptionsHeader, getThemeListFromDirectory } from "./theming"; let adBlock: HTMLInputElement, api: HTMLInputElement, @@ -25,23 +26,25 @@ let adBlock: HTMLInputElement, theme: HTMLSelectElement, trayIcon: HTMLInputElement, updateFrequency: HTMLInputElement; - function getThemeFiles() { const selectElement = document.getElementById("themesList") as HTMLSelectElement; - const fileNames = fs - .readdirSync(process.resourcesPath) - .filter((file) => file.endsWith(".css")) - .sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())); + const builtInThemes = getThemeListFromDirectory(process.resourcesPath); + const userThemes = getThemeListFromDirectory(`${app.getPath("userData")}/themes`); - const options = fileNames.map((name) => { - return new Option(name.replace(".css", ""), name); - }); + let allThemes = [ + getOptionsHeader("Built-in Themes"), + new Option("Tidal - Default", "none"), + ].concat(getOptions(builtInThemes)); + + if (userThemes.length >= 1) { + allThemes = allThemes.concat([getOptionsHeader("User Themes")]).concat(getOptions(userThemes)); + } // empty old options const oldOptions = document.querySelectorAll("#themesList option"); oldOptions.forEach((o) => o.remove()); - [new Option("Tidal - Default", "none")].concat(options).forEach((option) => { + allThemes.forEach((option) => { selectElement.add(option, null); }); } @@ -52,7 +55,7 @@ function handleFileUploads() { document.getElementById("theme-files").addEventListener("change", function (e: any) { Array.from(e.target.files).forEach((file: File) => { - const destination = `${process.resourcesPath}/${file.name}`; + const destination = `${app.getPath("userData")}/themes/${file.name}`; fs.copyFileSync(file.path, destination, null); }); fileMessage.innerText = `${e.target.files.length} files successfully uploaded`; diff --git a/src/pages/settings/settings.scss b/src/pages/settings/settings.scss index 75c99b1..8e7318a 100644 --- a/src/pages/settings/settings.scss +++ b/src/pages/settings/settings.scss @@ -434,5 +434,12 @@ html { option { background-color: $tidal-grey-darkest; + + &:disabled { + font-size: 1.2em; + line-height: 1.5em; + text-align: center; + color: $white; + } } } diff --git a/src/pages/settings/theming.ts b/src/pages/settings/theming.ts new file mode 100644 index 0000000..1eb10e2 --- /dev/null +++ b/src/pages/settings/theming.ts @@ -0,0 +1,55 @@ +import fs from "fs"; + +const cssFilter = (file: string) => file.endsWith(".css"); +const sort = (a: string, b: string) => a.toLowerCase().localeCompare(b.toLowerCase()); + +/** + * Create an "options header" (disabled option) based on a bit of text + * @param text of the header + * @returns + */ +export const getOptionsHeader = (text: string): HTMLOptionElement => { + const opt = new Option(text, undefined, false, false); + opt.disabled = true; + return opt; +}; + +/** + * Maps a list of filenames to a list of HTMLOptionElements + * Will strip ".css" from the name but keeps it in the value + * @param array array of filenames + * @returns + */ +export const getOptions = (array: string[]) => { + return array.map((name) => { + return new Option(name.replace(".css", ""), name); + }); +}; + +/** + * Read .css files from a directory and return them in a sorted array. + * @param directory to read from. Will be created if it doesn't exist + * @returns + */ +export const getThemeListFromDirectory = (directory: string) => { + try { + makeUserThemesDirectory(directory); + return fs.readdirSync(directory).filter(cssFilter).sort(sort); + } catch (err) { + console.error(err); + } +}; + +/** + * Create the directory to store user themes in + * @param directory directory to create + */ +export const makeUserThemesDirectory = (directory: string) => { + try { + fs.mkdir(directory, { recursive: true }, (err) => { + if (err) throw err; + }); + } catch (err) { + console.error(err); + } +}; diff --git a/src/scripts/settings.ts b/src/scripts/settings.ts index 9fa27b4..2d4ed7b 100644 --- a/src/scripts/settings.ts +++ b/src/scripts/settings.ts @@ -55,7 +55,7 @@ export const createSettingsWindow = function () { settingsWindow = new BrowserWindow({ width: 700, height: 600, - resizable: false, + resizable: true, show: false, transparent: true, frame: false,