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"/>
|
||||
|
||||
![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
|
||||
|
||||
<!-- 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)
|
||||
- [Using releases](#using-releases)
|
||||
- [Snap](#snap)
|
||||
@ -18,25 +24,57 @@ The web version of [listen.tidal.com](https://listen.tidal.com) running in elect
|
||||
- [Flatpak](#flatpak)
|
||||
- [Nix](#nix)
|
||||
- [Using source](#using-source)
|
||||
- [Features](#features)
|
||||
- [Integrations](#integrations)
|
||||
- [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)
|
||||
- [Special thanks to](#special-thanks-to)
|
||||
- [Donations](#donations)
|
||||
- [Images](#images)
|
||||
- [Settings window](#settings-window)
|
||||
- [User setups](#user-setups)
|
||||
|
||||
<!-- 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
|
||||
|
||||
### 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 <URI> #for instance: https://github.com/Mastermindzh/tidal-hifi/releases/download/1.0/tidal-hifi_1.0.0_amd64.snap
|
||||
```
|
||||
```sh
|
||||
wget <URI> #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 <path> #for instance: tidal-hifi_1.0.0_amd64.snap
|
||||
```
|
||||
```sh
|
||||
snap install --dangerous <path> #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
|
||||
|
||||
|
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 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`;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
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({
|
||||
width: 700,
|
||||
height: 600,
|
||||
resizable: false,
|
||||
resizable: true,
|
||||
show: false,
|
||||
transparent: true,
|
||||
frame: false,
|
||||
|