fix: user uploaded themes are now stored in the config directory. Missing directories will be created and added docs for theming
109
README.md
@ -1,16 +1,22 @@
|
|||||||
# Tidal-hifi<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 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.
|
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
|
## Table of Contents
|
||||||
|
|
||||||
<!-- toc -->
|
<!-- toc -->
|
||||||
|
|
||||||
- [Installation](#installation)
|
- [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)
|
- [Dependencies](#dependencies)
|
||||||
- [Using releases](#using-releases)
|
- [Using releases](#using-releases)
|
||||||
- [Snap](#snap)
|
- [Snap](#snap)
|
||||||
@ -18,25 +24,57 @@ The web version of [listen.tidal.com](https://listen.tidal.com) running in elect
|
|||||||
- [Flatpak](#flatpak)
|
- [Flatpak](#flatpak)
|
||||||
- [Nix](#nix)
|
- [Nix](#nix)
|
||||||
- [Using source](#using-source)
|
- [Using source](#using-source)
|
||||||
- [Features](#features)
|
- [Integrations](#integrations)
|
||||||
- [Integrations](#integrations)
|
|
||||||
- [Known bugs](#known-bugs)
|
- [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)
|
- [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)
|
- [Special thanks to](#special-thanks-to)
|
||||||
- [Why not extend existing projects?](#why-not-extend-existing-projects)
|
- [Donations](#donations)
|
||||||
- [Special thanks to](#special-thanks-to)
|
- [Images](#images)
|
||||||
- [Buy me a coffee? Please don't](#buy-me-a-coffee-please-dont)
|
|
||||||
- [Images](#images)
|
|
||||||
- [Settings window](#settings-window)
|
- [Settings window](#settings-window)
|
||||||
- [User setups](#user-setups)
|
- [User setups](#user-setups)
|
||||||
|
|
||||||
<!-- tocstop -->
|
<!-- tocstop -->
|
||||||
|
|
||||||
|
## 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
|
## Installation
|
||||||
|
|
||||||
### Dependencies
|
### 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
|
### Using releases
|
||||||
|
|
||||||
@ -48,15 +86,15 @@ To install with `snap` you need to download the pre-packaged snap-package from t
|
|||||||
|
|
||||||
1. Download
|
1. Download
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
wget <URI> #for instance: https://github.com/Mastermindzh/tidal-hifi/releases/download/1.0/tidal-hifi_1.0.0_amd64.snap
|
wget <URI> #for instance: https://github.com/Mastermindzh/tidal-hifi/releases/download/1.0/tidal-hifi_1.0.0_amd64.snap
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Install
|
2. Install
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
snap install --dangerous <path> #for instance: tidal-hifi_1.0.0_amd64.snap
|
snap install --dangerous <path> #for instance: tidal-hifi_1.0.0_amd64.snap
|
||||||
```
|
```
|
||||||
|
|
||||||
### Arch Linux
|
### Arch Linux
|
||||||
|
|
||||||
@ -91,23 +129,12 @@ To install and work with the code on this project follow these steps:
|
|||||||
- npm install
|
- npm install
|
||||||
- npm start
|
- 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
|
## Integrations
|
||||||
|
|
||||||
Tidal-hifi 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/integrations.png)
|
![integrations menu, showing a list of integrations](./docs/images/integrations.png)
|
||||||
|
|
||||||
It currently includes:
|
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).
|
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.
|
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
|
## Special thanks to
|
||||||
|
|
||||||
- [Castlabs](https://castlabs.com/)
|
- [Castlabs](https://castlabs.com/)
|
||||||
For maintaining Electron with Widevine CDM installation, Verified Media Path (VMP), and persistent licenses (StorageID)
|
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).
|
You can find my Github sponsorship page at: [https://github.com/sponsors/Mastermindzh](https://github.com/sponsors/Mastermindzh)
|
||||||
Inspired by [haydenjames' issue](https://github.com/Mastermindzh/tidal-hifi/issues/27#issuecomment-704198429)
|
|
||||||
|
|
||||||
## Images
|
## Images
|
||||||
|
|
||||||
### Settings window
|
### Settings window
|
||||||
|
|
||||||
![settings window](./docs/settings-preview.png)
|
![settings window](./docs/images/settings-preview.png)
|
||||||
|
|
||||||
### User setups
|
### User setups
|
||||||
|
|
||||||
|
BIN
docs/images/customcss-config.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
docs/images/customcss.png
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
docs/images/discord.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 800 KiB After Width: | Height: | Size: 800 KiB |
Before Width: | Height: | Size: 726 KiB After Width: | Height: | Size: 726 KiB |
Before Width: | Height: | Size: 317 KiB After Width: | Height: | Size: 317 KiB |
BIN
docs/images/theming.png
Normal file
After Width: | Height: | Size: 49 KiB |
38
docs/theming.md
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# Theming tidal-hifi
|
||||||
|
|
||||||
|
## Table of contents
|
||||||
|
|
||||||
|
<!-- toc -->
|
||||||
|
|
||||||
|
- [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)
|
||||||
|
|
||||||
|
<!-- tocstop -->
|
||||||
|
|
||||||
|
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).
|
@ -1,9 +1,10 @@
|
|||||||
import remote from "@electron/remote";
|
import remote, { app } from "@electron/remote";
|
||||||
import { ipcRenderer, shell } from "electron";
|
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 { settingsStore } from "./../../scripts/settings";
|
import { settingsStore } from "./../../scripts/settings";
|
||||||
|
import { getOptions, getOptionsHeader, getThemeListFromDirectory } from "./theming";
|
||||||
|
|
||||||
let adBlock: HTMLInputElement,
|
let adBlock: HTMLInputElement,
|
||||||
api: HTMLInputElement,
|
api: HTMLInputElement,
|
||||||
@ -25,23 +26,25 @@ let adBlock: HTMLInputElement,
|
|||||||
theme: HTMLSelectElement,
|
theme: HTMLSelectElement,
|
||||||
trayIcon: HTMLInputElement,
|
trayIcon: HTMLInputElement,
|
||||||
updateFrequency: HTMLInputElement;
|
updateFrequency: HTMLInputElement;
|
||||||
|
|
||||||
function getThemeFiles() {
|
function getThemeFiles() {
|
||||||
const selectElement = document.getElementById("themesList") as HTMLSelectElement;
|
const selectElement = document.getElementById("themesList") as HTMLSelectElement;
|
||||||
const fileNames = fs
|
const builtInThemes = getThemeListFromDirectory(process.resourcesPath);
|
||||||
.readdirSync(process.resourcesPath)
|
const userThemes = getThemeListFromDirectory(`${app.getPath("userData")}/themes`);
|
||||||
.filter((file) => file.endsWith(".css"))
|
|
||||||
.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
|
||||||
|
|
||||||
const options = fileNames.map((name) => {
|
let allThemes = [
|
||||||
return new Option(name.replace(".css", ""), name);
|
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
|
// empty old options
|
||||||
const oldOptions = document.querySelectorAll("#themesList option");
|
const oldOptions = document.querySelectorAll("#themesList option");
|
||||||
oldOptions.forEach((o) => o.remove());
|
oldOptions.forEach((o) => o.remove());
|
||||||
|
|
||||||
[new Option("Tidal - Default", "none")].concat(options).forEach((option) => {
|
allThemes.forEach((option) => {
|
||||||
selectElement.add(option, null);
|
selectElement.add(option, null);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -52,7 +55,7 @@ function handleFileUploads() {
|
|||||||
|
|
||||||
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 = `${process.resourcesPath}/${file.name}`;
|
const destination = `${app.getPath("userData")}/themes/${file.name}`;
|
||||||
fs.copyFileSync(file.path, destination, null);
|
fs.copyFileSync(file.path, destination, null);
|
||||||
});
|
});
|
||||||
fileMessage.innerText = `${e.target.files.length} files successfully uploaded`;
|
fileMessage.innerText = `${e.target.files.length} files successfully uploaded`;
|
||||||
|
@ -434,5 +434,12 @@ html {
|
|||||||
|
|
||||||
option {
|
option {
|
||||||
background-color: $tidal-grey-darkest;
|
background-color: $tidal-grey-darkest;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
font-size: 1.2em;
|
||||||
|
line-height: 1.5em;
|
||||||
|
text-align: center;
|
||||||
|
color: $white;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
55
src/pages/settings/theming.ts
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
};
|
@ -55,7 +55,7 @@ export const createSettingsWindow = function () {
|
|||||||
settingsWindow = new BrowserWindow({
|
settingsWindow = new BrowserWindow({
|
||||||
width: 700,
|
width: 700,
|
||||||
height: 600,
|
height: 600,
|
||||||
resizable: false,
|
resizable: true,
|
||||||
show: false,
|
show: false,
|
||||||
transparent: true,
|
transparent: true,
|
||||||
frame: false,
|
frame: false,
|
||||||
|