diff --git a/.drone.yml b/.drone.yml
new file mode 100644
index 0000000..e74f2f7
--- /dev/null
+++ b/.drone.yml
@@ -0,0 +1,16 @@
+kind: pipeline
+type: docker
+name: default
+
+steps:
+ - name: install
+ image: node:19.4.0
+ commands:
+ - npm install
+
+ - name: build_with_linux
+ image: node:19.4.0
+ commands:
+ - apt-get update && apt-get upgrade -y
+ - apt-get install -y libarchive-tools rpm
+ - npm run build
diff --git a/.gitignore b/.gitignore
index 2e56407..6121081 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,4 +14,6 @@ build/linux/arch/*
.idea
ts-dist/**
ts-dist
+themes
+!src/themes
.sass-cache
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 24397c2..6f2b6e3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,8 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## 5.2.0
- moved from Javascript to Typescript for all files
+
- use `npm run watch` to watch for changes & recompile typescript and sass files
+- Added support for theming the application
+- Added drone build file use `drone exec` or drone.ci to build it
+
## 5.1.0
### New features
diff --git a/README.md b/README.md
index f187f6a..6fc5c6a 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) [![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 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/build/electron-builder.base.yml b/build/electron-builder.base.yml
index b35a36c..27f2319 100644
--- a/build/electron-builder.base.yml
+++ b/build/electron-builder.base.yml
@@ -7,6 +7,8 @@ snap:
plugs:
- default
- screen-inhibit-control
+extraResources:
+ - "themes/**"
linux:
category: AudioVideo
icon: assets/icons
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/package-lock.json b/package-lock.json
index fc82f3b..e38be89 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8846,4 +8846,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/package.json b/package.json
index 12518c3..11d102b 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,8 @@
"compile": "tsc && npm run sass-and-copy",
"watch": "tsc-watch --onSuccess \"npm run sass-and-copy\"",
"copy-files": "copyfiles -u 1 --exclude './src/**/*.ts' --exclude './src/**/*.scss' \"./src/**/*\" ts-dist",
- "sass-and-copy": "npm run sass && npm run copy-files",
+ "copy-themes-dev": "copyfiles -u 1 \"./themes/*\" node_modules/electron/dist/resources",
+ "sass-and-copy": "npm run sass && npm run copy-files && npm run copy-themes-dev",
"build": "npm run builder -- -c ./build/electron-builder.yml",
"build-deb": "npm run builder -- -c ./build/electron-builder.deb.yml",
"build-unpacked": "npm run builder -- -c ./build/electron-builder.unpacked.yml",
@@ -20,7 +21,7 @@
"build-base": "npm run builder -- -c ./build/electron-builder.base.yml",
"prebuilder": "npm run compile",
"builder": "electron-builder --publish=never",
- "sass": "sass ./src/pages/settings/settings.scss ./src/pages/settings/settings.css",
+ "sass": "sass ./src/pages/settings/settings.scss ./src/pages/settings/settings.css && sass --no-source-map src/themes:themes",
"style-lint": "npx stylelint **/*.scss",
"style-lint-fix": "npx stylelint --fix **/*.scss"
},
diff --git a/src/constants/settings.ts b/src/constants/settings.ts
index 04444e6..df2c39f 100644
--- a/src/constants/settings.ts
+++ b/src/constants/settings.ts
@@ -33,6 +33,7 @@ export const settings = {
singleInstance: "singleInstance",
skipArtists: "skipArtists",
skippedArtists: "skippedArtists",
+ theme: "theme",
trayIcon: "trayIcon",
updateFrequency: "updateFrequency",
windowBounds: {
diff --git a/src/pages/settings/preload.ts b/src/pages/settings/preload.ts
index fb64769..965ccf8 100644
--- a/src/pages/settings/preload.ts
+++ b/src/pages/settings/preload.ts
@@ -1,8 +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,
@@ -21,8 +23,45 @@ let adBlock: HTMLInputElement,
singleInstance: HTMLInputElement,
skipArtists: HTMLInputElement,
skippedArtists: HTMLInputElement,
+ theme: HTMLSelectElement,
trayIcon: HTMLInputElement,
updateFrequency: HTMLInputElement;
+function getThemeFiles() {
+ const selectElement = document.getElementById("themesList") as HTMLSelectElement;
+ const builtInThemes = getThemeListFromDirectory(process.resourcesPath);
+ const userThemes = getThemeListFromDirectory(`${app.getPath("userData")}/themes`);
+
+ 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());
+
+ allThemes.forEach((option) => {
+ selectElement.add(option, null);
+ });
+}
+
+function handleFileUploads() {
+ const fileMessage = document.getElementById("file-message");
+ fileMessage.innerText = "or drag and drop files here";
+
+ 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}`;
+ fs.copyFileSync(file.path, destination, null);
+ });
+ fileMessage.innerText = `${e.target.files.length} files successfully uploaded`;
+ getThemeFiles();
+ });
+}
/**
* Sync the UI forms with the current settings
@@ -30,7 +69,7 @@ let adBlock: HTMLInputElement,
function refreshSettings() {
adBlock.checked = settingsStore.get(settings.adBlock);
api.checked = settingsStore.get(settings.api);
- customCSS.value = settingsStore.get(settings.customCSS);
+ customCSS.value = settingsStore.get(settings.customCSS).join("\n");
disableBackgroundThrottle.checked = settingsStore.get(settings.disableBackgroundThrottle);
disableHardwareMediaKeys.checked = settingsStore.get(settings.flags.disableHardwareMediaKeys);
enableCustomHotkeys.checked = settingsStore.get(settings.enableCustomHotkeys);
@@ -44,6 +83,7 @@ function refreshSettings() {
port.value = settingsStore.get(settings.apiSettings.port);
singleInstance.checked = settingsStore.get(settings.singleInstance);
skipArtists.checked = settingsStore.get(settings.skipArtists);
+ theme.value = settingsStore.get(settings.theme);
skippedArtists.value = settingsStore.get(settings.skippedArtists).join("\n");
trayIcon.checked = settingsStore.get(settings.trayIcon);
updateFrequency.value = settingsStore.get(settings.updateFrequency);
@@ -75,10 +115,13 @@ function restart() {
* Bind UI components to functions after DOMContentLoaded
*/
window.addEventListener("DOMContentLoaded", () => {
- function get(id: string): HTMLInputElement {
- return document.getElementById(id) as HTMLInputElement;
+ function get(id: string): T {
+ return document.getElementById(id) as T;
}
+ getThemeFiles();
+ handleFileUploads();
+
document.getElementById("close").addEventListener("click", hide);
document.getElementById("restart").addEventListener("click", restart);
document.querySelectorAll(".external-link").forEach((elem) =>
@@ -105,6 +148,13 @@ window.addEventListener("DOMContentLoaded", () => {
});
}
+ function addSelectListener(source: HTMLSelectElement, key: string) {
+ source.addEventListener("change", () => {
+ settingsStore.set(key, source.value);
+ ipcRenderer.send(globalEvents.storeChanged);
+ });
+ }
+
ipcRenderer.on("refreshData", () => {
refreshSettings();
});
@@ -127,6 +177,7 @@ window.addEventListener("DOMContentLoaded", () => {
notifications = get("notifications");
playBackControl = get("playBackControl");
port = get("port");
+ theme = get("themesList");
trayIcon = get("trayIcon");
skipArtists = get("skipArtists");
skippedArtists = get("skippedArtists");
@@ -152,6 +203,7 @@ window.addEventListener("DOMContentLoaded", () => {
addInputListener(skipArtists, settings.skipArtists);
addTextAreaListener(skippedArtists, settings.skippedArtists);
addInputListener(singleInstance, settings.singleInstance);
+ addSelectListener(theme, settings.theme);
addInputListener(trayIcon, settings.trayIcon);
addInputListener(updateFrequency, settings.updateFrequency);
});
diff --git a/src/pages/settings/settings.html b/src/pages/settings/settings.html
index b784a02..583b24b 100644
--- a/src/pages/settings/settings.html
+++ b/src/pages/settings/settings.html
@@ -35,6 +35,9 @@
+
+
+
@@ -226,6 +229,49 @@
+