Compare commits
	
		
			50 Commits
		
	
	
		
			4.0.1
			...
			07be74af9f
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 07be74af9f | |||
| fc6adc25ca | |||
| 4498e8a73e | |||
| 3d2a9c3992 | |||
| af6bfaf55e | |||
|  | 8bac90e0f1 | ||
|  | 887c75f61a | ||
|  | cde7408cc4 | ||
| 05b422e045 | |||
| 35289d8216 | |||
| ea42b79cd8 | |||
| 6d859cf780 | |||
| af20092053 | |||
| 166ca353cf | |||
| b807aa2f76 | |||
|  | ef8ffe47f5 | ||
| ba7b2a5717 | |||
| 0d93bedb4d | |||
|  | 1de71aa82b | ||
|  | b2e68f5a8f | ||
|  | a2a2023853 | ||
|  | 26c8a38350 | ||
| eb93fbc35d | |||
|  | d3c56fa445 | ||
| 6998992011 | |||
| 108e1d65d4 | |||
| 1097f83911 | |||
| 8c734777cc | |||
|  | de17ac6113 | ||
|  | ced41c00d7 | ||
|  | 744016f307 | ||
|  | ad8ef71c6b | ||
| d0f9a34f9c | |||
| 3b316f2301 | |||
| c0d9cd2834 | |||
| 0620d87d8b | |||
| 57b7f9148f | |||
| 63ccff97ea | |||
| 3a4d23738f | |||
| c96bdb0d28 | |||
|  | 115d8c6c5c | ||
|  | cd2a068470 | ||
| bf260b14e0 | |||
| d161a68c95 | |||
|  | 9de8cea50e | ||
| 5f330a7c48 | |||
| 732710c3ef | |||
| 4941aae950 | |||
| 1439a11969 | |||
|  | 3a3e0e1a2d | 
							
								
								
									
										8
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -16,17 +16,17 @@ jobs: | ||||
|       - uses: actions/checkout@master | ||||
|       - uses: actions/setup-node@master | ||||
|         with: | ||||
|           node-version: 16 | ||||
|           node-version: 19 | ||||
|       - run: npm install | ||||
|       - run: npm run build | ||||
|  | ||||
|   build_on_mac: | ||||
|     runs-on: macOS-latest | ||||
|     runs-on: macos-latest | ||||
|     steps: | ||||
|       - uses: actions/checkout@master | ||||
|       - uses: actions/setup-node@master | ||||
|         with: | ||||
|           node-version: 16 | ||||
|           node-version: 19 | ||||
|       - run: npm install | ||||
|       - run: npm run build | ||||
|  | ||||
| @@ -36,6 +36,6 @@ jobs: | ||||
|       - uses: actions/checkout@master | ||||
|       - uses: actions/setup-node@master | ||||
|         with: | ||||
|           node-version: 16 | ||||
|           node-version: 19 | ||||
|       - run: npm install | ||||
|       - run: npm run build | ||||
|   | ||||
							
								
								
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -7,3 +7,8 @@ build/linux/arch/* | ||||
| !build/linux/arch/.SRCINFO | ||||
| !build/linux/arch/tidal-hifi.desktop | ||||
| !build/linux/arch/install.sh | ||||
| *.css | ||||
| *.css.map | ||||
|  | ||||
| # JetBrains IDE configuration | ||||
| .idea | ||||
|   | ||||
							
								
								
									
										68
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						| @@ -4,6 +4,74 @@ 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.1.0 | ||||
|  | ||||
| ### New features | ||||
|  | ||||
| - You can now add custom CSS in the "advanced" settings tab | ||||
| - You can now configure the updateFrequency in the settings window | ||||
|   - Default value is set to 500 and will overwrite the hardcoded value of 100 | ||||
|  | ||||
| ### Fixes | ||||
|  | ||||
| - Any songs **including** an artist listed in the `skipped artists` setting will now be skipped even if the song is a collaboration. | ||||
| - Linux desktop icons have been fixed. See [#222](https://github.com/Mastermindzh/tidal-hifi/pull/222) for details. | ||||
|  | ||||
| ## 5.0.0 | ||||
|  | ||||
| - Replaced "muting artists" with a full implementation of an Adblock mechanism | ||||
|  | ||||
|   > Disabled audio & visual ads, unlocked lyrics, suggested track, track info, unlimited skips thanks to uBlockOrigin custom filters ([source](https://github.com/uBlockOrigin/uAssets/issues/17495)) | ||||
|  | ||||
| - @thanasistrisp updated Electron to 24.1.2 and fixed the tray bug :) | ||||
|  | ||||
| ## 4.4.0 | ||||
|  | ||||
| - Updated shortcut hint on the menubar to reflect the new `ctrl+=` shortcut. | ||||
| - Reverted icon path to `icon.png` instead of the hardcoded linux path. | ||||
| - Add support to autoHide the menubar and showing it with the `alt` key. | ||||
| - Move the quit command from the system sub-menu to the main menu | ||||
| - Added single click focus/show on the tray icon | ||||
|   - Doesn't work on all platforms. Nothing I can do about that unfortunately! | ||||
| - Added a list of artists to automatically skip. | ||||
|   - I don't like the vast majority of dutch music so I added one of them to my list to test: [./docs/no-dutch-music.mp4](./docs/no-dutch-music.mp4) | ||||
|  | ||||
| ## 4.3.1 | ||||
|  | ||||
| - fix: App always requests a default-url-handler-scheme change on start | ||||
|  | ||||
| ## 4.3.0 | ||||
|  | ||||
| - Added a setting to disable background throttling ([docs](https://www.electronjs.org/docs/latest/api/browser-window)) | ||||
|  | ||||
| ## 4.2.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 | ||||
|  | ||||
| ## 4.1.2 | ||||
|  | ||||
| - Changed the category of the desktop file to AudioVideo | ||||
| - Changed desktop file name to "TIDAL Hi-Fi" | ||||
|  | ||||
| ## 4.1.1 | ||||
|  | ||||
| - Fixed `cannot read property of undefined` error because of not passing mainWindow around. | ||||
| - vincens2005, fixed inconsistent auto muting | ||||
|  | ||||
| ## 4.1.0 | ||||
|  | ||||
| - Added `tidal://` protocol support | ||||
| - Switched icon strategies to fix bugs with icons | ||||
| - Fixed tray icon bugs | ||||
|   - Menu now shows in KDE as well | ||||
|   - Toggle window is supported from tray icon | ||||
|   - regular click is still ignored, see [this issue](https://github.com/electron/electron/issues/6773) | ||||
| - Fixed about tab not showing | ||||
| - Fixed playback, mpris and API issues | ||||
|  | ||||
| ## 4.0.1 | ||||
|  | ||||
| - Updated build config to make use of a base file that doesn't build anything. | ||||
|   | ||||
							
								
								
									
										48
									
								
								README.md
									
									
									
									
									
								
							
							
						
						| @@ -1,44 +1,48 @@ | ||||
| <h1> | ||||
| Tidal-hifi | ||||
| <img src = "./build/icon.png" height="40" align="right" /> | ||||
| </h1> | ||||
| # Tidal-hifi<img src = "./build/icon.png" height="40" align="right"/> | ||||
|  | ||||
|  | ||||
|  | ||||
| The web version of [listen.tidal.com](https://listen.tidal.com) running in electron with hifi support thanks to widevine. | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Table of contents | ||||
| ## Table of Contents | ||||
|  | ||||
| <!-- toc --> | ||||
|  | ||||
| - [Installation](#installation) | ||||
|   - [Dependencies](#dependencies) | ||||
|   - [Using releases](#using-releases) | ||||
|     - [Snap install](#snap-install) | ||||
|   - [Snap](#snap) | ||||
|   - [Arch Linux](#arch-linux) | ||||
|   - [Flatpak](#flatpak) | ||||
|   - [Nix](#nix) | ||||
|   - [Using source](#using-source) | ||||
| - [features](#features) | ||||
| - [Features](#features) | ||||
| - [Integrations](#integrations) | ||||
|   - [not included](#not-included) | ||||
|   - [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) | ||||
| - [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) | ||||
|   - [Settings window](#settings-window) | ||||
|   - [User setups](#user-setups) | ||||
|  | ||||
| <!-- tocstop --> | ||||
|  | ||||
| ## 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. | ||||
|  | ||||
| ### Using releases | ||||
|  | ||||
| Various packaged versions of the software are available on the [releases](https://github.com/Mastermindzh/tidal-hifi/releases) tab. | ||||
|  | ||||
| #### Snap install | ||||
| ### Snap | ||||
|  | ||||
| To install with `snap` you need to download the pre-packaged snap-package from this repository, found under releases: | ||||
|  | ||||
| @@ -70,6 +74,14 @@ To install via [Flatpak](https://flathub.org/apps/details/com.mastermindzh.tidal | ||||
| flatpak install flathub com.mastermindzh.tidal-hifi | ||||
| ``` | ||||
|  | ||||
| ### Nix | ||||
|  | ||||
| To install with Nix run the following command: | ||||
|  | ||||
| ```sh | ||||
| nix-env -iA nixpkgs.tidal-hifi | ||||
| ``` | ||||
|  | ||||
| ### Using source | ||||
|  | ||||
| To install and work with the code on this project follow these steps: | ||||
| @@ -79,13 +91,13 @@ To install and work with the code on this project follow these steps: | ||||
| - npm install | ||||
| - npm start | ||||
|  | ||||
| ## features | ||||
| ## Features | ||||
|  | ||||
| - HiFi playback | ||||
| - Notifications | ||||
| - Custom hotkeys ([source](https://defkey.com/tidal-desktop-shortcuts)) | ||||
| - API for status and playback | ||||
| - [Mute artists automatically (defaults to "Tidal")]("./docs/muting-artists.md") | ||||
| - 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)) | ||||
| @@ -102,7 +114,7 @@ It currently includes: | ||||
| - MPRIS - MPRIS media player controls/status | ||||
| - Discord - Shows what you're listening to on Discord. | ||||
|  | ||||
| ### not included | ||||
| Not included: | ||||
|  | ||||
| - [i3 blocks config](https://github.com/Mastermindzh/dotfiles/commit/9714b2fa1d670108ce811d5511fd3b7a43180647) - My dotfiles where I use this app to fetch currently playing music (direct commit) | ||||
|  | ||||
| @@ -131,7 +143,7 @@ Whilst there are a handful of projects attempting to run Tidal on Electron they | ||||
|  | ||||
| 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/) | ||||
|   For maintaining Electron with Widevine CDM installation, Verified Media Path (VMP), and persistent licenses (StorageID) | ||||
| @@ -143,11 +155,11 @@ Inspired by [haydenjames' issue](https://github.com/Mastermindzh/tidal-hifi/issu | ||||
|  | ||||
| ## Images | ||||
|  | ||||
| ### settings window | ||||
| ### Settings window | ||||
|  | ||||
|  | ||||
|  | ||||
| ### user setups | ||||
| ### User setups | ||||
|  | ||||
| Some of our users are kind enough to share their usage pictures. | ||||
| If you want to see them or possibly even add one please do so in the following issue: [#3 - image thread](https://github.com/Mastermindzh/tidal-hifi/issues/3). | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								assets/icons/128x128.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 16 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/16x16.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 5.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/22x22.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 9.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/24x24.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 9.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/256x256.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 29 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/32x32.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 6.7 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/384x384.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 46 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/48x48.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 11 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/64x64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 11 KiB | 
| @@ -1,33 +1,40 @@ | ||||
| appId: com.rickvanlieshout.tidal-hifi | ||||
| electronVersion: 19.0.5 | ||||
| electronVersion: 24.1.2 | ||||
| electronDownload: | ||||
|   version: 19.0.5+wvcus | ||||
|   version: 24.1.2+wvcus | ||||
|   mirror: https://github.com/castlabs/electron-releases/releases/download/v | ||||
| snap: | ||||
|   plugs: | ||||
|     - default | ||||
|     - screen-inhibit-control | ||||
| linux: | ||||
|   category: Audio | ||||
|   category: AudioVideo | ||||
|   icon: assets/icons | ||||
|   target: | ||||
|     - dir | ||||
|   executableName: tidal-hifi | ||||
|   desktop: | ||||
|     Encoding: UTF-8 | ||||
|     Name: tidal-hifi | ||||
|     GenericName: tidal-hifi | ||||
|     Name: TIDAL Hi-Fi | ||||
|     GenericName: TIDAL Hi-Fi | ||||
|     Comment: The web version of listen.tidal.com running in electron with hifi support thanks to widevine. | ||||
|     Icon: assets/icon.png | ||||
|     Icon: tidal-hifi | ||||
|     StartupNotify: true | ||||
|     Terminal: false | ||||
|     Type: Application | ||||
|     Categories: Network;Application;AudioVideo;Audio;Video | ||||
|     StartupWMClass: tidal-hifi | ||||
|     X-PulseAudio-Properties: media.role=music | ||||
|     MimeType: x-scheme-handler/tidal; | ||||
|  | ||||
| mac: | ||||
|   category: public.app-category.entertainment | ||||
| win: | ||||
|   icon: build/icon.png | ||||
|   icon: icon.png | ||||
|   artifactName: "tidalhifi" | ||||
|   appId: com.rickvanlieshout.tidalhifi | ||||
|   executableName: tidalhifi | ||||
| protocols: | ||||
|   name: "tidal" | ||||
|   role: "Viewer" | ||||
|   schemes: ["tidal"] | ||||
|   | ||||
| @@ -1,6 +1,4 @@ | ||||
| extends: ./build/electron-builder.base.yml | ||||
| linux: | ||||
|   category: Audio | ||||
|   icon: ./assets/icon.png | ||||
|   target: | ||||
|     - deb | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| extends: ./build/electron-builder.base.yml | ||||
| linux: | ||||
|   category: Audio | ||||
|   icon: ./assets/icon.png | ||||
|   icon: icon.png | ||||
|   target: | ||||
|     - pacman | ||||
|   | ||||
| @@ -1,6 +1,4 @@ | ||||
| extends: ./build/electron-builder.base.yml | ||||
| linux: | ||||
|   category: Audio | ||||
|   icon: ./assets/TIDAL.icns | ||||
|   target: | ||||
|     - rpm | ||||
|   | ||||
| @@ -1,6 +1,4 @@ | ||||
| extends: ./build/electron-builder.base.yml | ||||
| linux: | ||||
|   category: Audio | ||||
|   icon: ./assets/icon.png | ||||
|   target: | ||||
|     - snap | ||||
|   | ||||
| @@ -1,9 +1,5 @@ | ||||
| electronVersion: 19.0.5 | ||||
| electronDownload: | ||||
|   version: 19.0.5+wvcus | ||||
|   mirror: https://github.com/castlabs/electron-releases/releases/download/v | ||||
| extends: ./build/electron-builder.base.yml | ||||
| linux: | ||||
|   category: Audio | ||||
|   target: | ||||
|     - pacman | ||||
|     - tar.gz | ||||
| @@ -14,7 +10,7 @@ linux: | ||||
|     - freebsd | ||||
| win: | ||||
|   target: msi | ||||
|   icon: build/icon.png | ||||
|   icon: icon.png | ||||
|   artifactName: "tidalhifi" | ||||
|   appId: com.rickvanlieshout.tidalhifi | ||||
|   executableName: tidalhifi | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								build/icon-inverted.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 102 KiB | 
							
								
								
									
										
											BIN
										
									
								
								build/icon.icns
									
									
									
									
									
										Executable file
									
								
							
							
						
						| @@ -1,11 +0,0 @@ | ||||
| # Muting artists | ||||
|  | ||||
| If you feel that some of your music is embarrassing for others you can mute specific artists in the settings window. | ||||
| This functionality is inspired by the [adblock ticket](https://github.com/Mastermindzh/tidal-hifi/issues/112), and whilst I personally feel you should simply buy Tidal, I also believe in muting sound that you don't want to hear. | ||||
|  | ||||
| Anyway, to block an artist, open the settings window (see image below) and enter a list of artists in the textarea as seen below. | ||||
| Don't forget to turn the feature on and Tidal-hifi will automatically mute the player whenever that artist is playing. | ||||
|  | ||||
| This will allow you to skip the song without anyone noticing. (you can always say "no idea, it seems to have no audio"). | ||||
|  | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								docs/no-dutch-music.mp4
									
									
									
									
									
										Normal file
									
								
							
							
						
						| Before Width: | Height: | Size: 103 KiB | 
							
								
								
									
										8998
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
							
								
								
									
										49
									
								
								package.json
									
									
									
									
									
								
							
							
						
						| @@ -1,19 +1,25 @@ | ||||
| { | ||||
|   "name": "tidal-hifi", | ||||
|   "version": "4.0.1", | ||||
|   "version": "5.1.0", | ||||
|   "description": "Tidal on Electron with widevine(hifi) support", | ||||
|   "main": "src/main.js", | ||||
|   "scripts": { | ||||
|     "start": "electron .", | ||||
|     "build": "electron-builder --publish=never -c ./build/electron-builder.yml", | ||||
|     "build-deb": "electron-builder --publish=never -c ./build/electron-builder.deb.yml", | ||||
|     "build-unpacked": "electron-builder --publish=never -c ./build/electron-builder.unpacked.yml", | ||||
|     "build-rpm": "electron-builder --publish=never -c ./build/electron-builder.rpm.yml", | ||||
|     "build-snap": "electron-builder --publish=never -c ./build/electron-builder.snap.yml", | ||||
|     "build-arch": "electron-builder --publish=never -c ./build/electron-builder.pacman.yml", | ||||
|     "build-wl": "electron-builder --publish=never -c ./build/electron-builder.yml -wl", | ||||
|     "build-mac": "electron-builder --publish=never -c ./build/electron-builder.yml -m", | ||||
|     "build-base": "electron-builder --publish=never -c ./build/electron-builder.base.yml" | ||||
|     "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", | ||||
|     "build-rpm": "npm run builder -- -c ./build/electron-builder.rpm.yml", | ||||
|     "build-snap": "npm run builder -- -c ./build/electron-builder.snap.yml", | ||||
|     "build-arch": "npm run builder -- -c ./build/electron-builder.pacman.yml", | ||||
|     "build-wl": "npm run builder -- -c ./build/electron-builder.yml -wl", | ||||
|     "build-mac": "npm run builder -- -c ./build/electron-builder.yml -m", | ||||
|     "build-base": "npm run builder -- -c ./build/electron-builder.base.yml", | ||||
|     "prestart": "npm run sass", | ||||
|     "prebuilder": "npm run sass", | ||||
|     "builder": "electron-builder --publish=never", | ||||
|     "sass": "sass ./src/pages/settings/settings.scss ./src/pages/settings/settings.css", | ||||
|     "sass-lint": "sass-lint -vc ./sass-lint.yml ./src/pages/settings/settings.scss", | ||||
|     "sass-lint-fix": "sass-lint-auto-fix ./src/pages/settings/settings.scss --config-sass-lint ./sass-lint.yml" | ||||
|   }, | ||||
|   "keywords": [ | ||||
|     "electron", | ||||
| @@ -25,19 +31,24 @@ | ||||
|   "homepage": "https://github.com/Mastermindzh/tidal-hifi", | ||||
|   "license": "MIT", | ||||
|   "dependencies": { | ||||
|     "@electron/remote": "^2.0.8", | ||||
|     "@electron/remote": "^2.0.9", | ||||
|     "discord-rpc": "^4.0.1", | ||||
|     "electron-store": "^8.0.1", | ||||
|     "express": "^4.17.1", | ||||
|     "hotkeys-js": "^3.8.7", | ||||
|     "electron-store": "^8.1.0", | ||||
|     "express": "^4.18.2", | ||||
|     "hotkeys-js": "^3.10.2", | ||||
|     "mpris-service": "^2.1.2", | ||||
|     "request": "^2.88.2" | ||||
|     "request": "^2.88.2", | ||||
|     "sass": "^1.62.0" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@mastermindzh/prettier-config": "^1.0.0", | ||||
|     "electron": "git+https://github.com/castlabs/electron-releases.git#v19.0.5+wvcus", | ||||
|     "electron-builder": "^23.2.0", | ||||
|     "prettier": "^2.5.0" | ||||
|     "electron": "git+https://github.com/castlabs/electron-releases.git#v24.1.2+wvcus", | ||||
|     "electron-builder": "^24.2.1", | ||||
|     "js-yaml": "^4.1.0", | ||||
|     "markdown-toc": "^1.2.0", | ||||
|     "prettier": "^2.8.7", | ||||
|     "sass-lint": "^1.13.1", | ||||
|     "sass-lint-auto-fix": "^0.21.2" | ||||
|   }, | ||||
|   "prettier": "@mastermindzh/prettier-config" | ||||
| } | ||||
| } | ||||
							
								
								
									
										21
									
								
								sass-lint.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,21 @@ | ||||
| rules: | ||||
|   property-sort-order: | ||||
|     - 1 | ||||
|     - order: "smacss" | ||||
|   class-name-format: | ||||
|     - 1 | ||||
|     - convention: "hyphenatedbem" | ||||
|   quotes: | ||||
|     - 1 | ||||
|     - style: "double" | ||||
|   nesting-depth: | ||||
|     - 1 | ||||
|     - max-depth: 3 | ||||
|   placeholder-in-extend: | ||||
|     - 0 | ||||
|   no-vendor-prefixes: | ||||
|     - 0 | ||||
|   empty-line-between-blocks: | ||||
|     - 0 | ||||
|   force-pseudo-nesting: | ||||
|     - 0 | ||||
| @@ -9,32 +9,36 @@ | ||||
|  *    windowBounds: { width: 800, height: 600 }, | ||||
|  */ | ||||
| const settings = { | ||||
|   notifications: "notifications", | ||||
|   adBlock: "adBlock", | ||||
|   api: "api", | ||||
|   menuBar: "menuBar", | ||||
|   playBackControl: "playBackControl", | ||||
|   muteArtists: "muteArtists", | ||||
|   mutedArtists: "mutedArtists", | ||||
|   apiSettings: { | ||||
|     root: "apiSettings", | ||||
|     port: "apiSettings.port", | ||||
|   }, | ||||
|   singleInstance: "singleInstance", | ||||
|   customCSS: "customCSS", | ||||
|   disableBackgroundThrottle: "disableBackgroundThrottle", | ||||
|   disableHardwareMediaKeys: "disableHardwareMediaKeys", | ||||
|   enableCustomHotkeys: "enableCustomHotkeys", | ||||
|   enableDiscord: "enableDiscord", | ||||
|   flags: { | ||||
|     disableHardwareMediaKeys: "flags.disableHardwareMediaKeys", | ||||
|     gpuRasterization: "flags.gpuRasterization", | ||||
|   }, | ||||
|   menuBar: "menuBar", | ||||
|   minimizeOnClose: "minimizeOnClose", | ||||
|   mpris: "mpris", | ||||
|   enableCustomHotkeys: "enableCustomHotkeys", | ||||
|   notifications: "notifications", | ||||
|   playBackControl: "playBackControl", | ||||
|   singleInstance: "singleInstance", | ||||
|   skipArtists: "skipArtists", | ||||
|   skippedArtists: "skippedArtists", | ||||
|   trayIcon: "trayIcon", | ||||
|   enableDiscord: "enableDiscord", | ||||
|   updateFrequency: "updateFrequency", | ||||
|   windowBounds: { | ||||
|     root: "windowBounds", | ||||
|     width: "windowBounds.width", | ||||
|     height: "windowBounds.height", | ||||
|   }, | ||||
|   minimizeOnClose: "minimizeOnClose", | ||||
| }; | ||||
|  | ||||
| module.exports = settings; | ||||
|   | ||||
							
								
								
									
										3
									
								
								src/constants/values.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,3 @@ | ||||
| module.exports = { | ||||
|   name: "tidal-hifi", | ||||
| }; | ||||
							
								
								
									
										49
									
								
								src/main.js
									
									
									
									
									
								
							
							
						
						| @@ -1,5 +1,13 @@ | ||||
| require("@electron/remote/main").initialize(); | ||||
| const { app, BrowserWindow, components, globalShortcut, ipcMain } = require("electron"); | ||||
| const { | ||||
|   app, | ||||
|   BrowserWindow, | ||||
|   components, | ||||
|   globalShortcut, | ||||
|   ipcMain, | ||||
|   protocol, | ||||
|   session, | ||||
| } = require("electron"); | ||||
| const { | ||||
|   settings, | ||||
|   store, | ||||
| @@ -21,6 +29,7 @@ const flagValues = require("./constants/flags"); | ||||
|  | ||||
| let mainWindow; | ||||
| let icon = path.join(__dirname, "../assets/icon.png"); | ||||
| const PROTOCOL_PREFIX = "tidal"; | ||||
|  | ||||
| setFlags(); | ||||
|  | ||||
| @@ -48,7 +57,10 @@ function setFlags() { | ||||
|  * | ||||
|  */ | ||||
| function syncMenuBarWithStore() { | ||||
|   mainWindow.setMenuBarVisibility(store.get(settings.menuBar)); | ||||
|   const fixedMenuBar = store.get(settings.menuBar); | ||||
|  | ||||
|   mainWindow.autoHideMenuBar = !fixedMenuBar; | ||||
|   mainWindow.setMenuBarVisibility(fixedMenuBar); | ||||
| } | ||||
|  | ||||
| /** | ||||
| @@ -77,19 +89,26 @@ function createWindow(options = {}) { | ||||
|     height: store && store.get(settings.windowBounds.height), | ||||
|     icon, | ||||
|     backgroundColor: options.backgroundColor, | ||||
|     autoHideMenuBar: true, | ||||
|     webPreferences: { | ||||
|       sandbox: false, | ||||
|       preload: path.join(__dirname, "preload.js"), | ||||
|       plugins: true, | ||||
|       devTools: true, // I like tinkering, others might too | ||||
|     }, | ||||
|   }); | ||||
|   require("@electron/remote/main").enable(mainWindow.webContents); | ||||
|  | ||||
|   registerHttpProtocols(); | ||||
|   syncMenuBarWithStore(); | ||||
|  | ||||
|   // load the Tidal website | ||||
|   mainWindow.loadURL(tidalUrl); | ||||
|  | ||||
|   if (store.get(settings.disableBackgroundThrottle)) { | ||||
|     // prevent setInterval lag | ||||
|     mainWindow.webContents.setBackgroundThrottling(false); | ||||
|   } | ||||
|  | ||||
|   // run stuff after first load | ||||
|   mainWindow.webContents.once("did-finish-load", () => {}); | ||||
|  | ||||
| @@ -113,6 +132,15 @@ function createWindow(options = {}) { | ||||
|   }); | ||||
| } | ||||
|  | ||||
| function registerHttpProtocols() { | ||||
|   protocol.registerHttpProtocol(PROTOCOL_PREFIX, (request, _callback) => { | ||||
|     mainWindow.loadURL(`${tidalUrl}/${request.url.substring(PROTOCOL_PREFIX.length + 3)}`); | ||||
|   }); | ||||
|   if (!app.isDefaultProtocolClient(PROTOCOL_PREFIX)) { | ||||
|     app.setAsDefaultProtocolClient(PROTOCOL_PREFIX); | ||||
|   } | ||||
| } | ||||
|  | ||||
| function addGlobalShortcuts() { | ||||
|   Object.keys(mediaKeys).forEach((key) => { | ||||
|     globalShortcut.register(`${key}`, () => { | ||||
| @@ -127,14 +155,23 @@ function addGlobalShortcuts() { | ||||
| app.on("ready", async () => { | ||||
|   if (isMainInstanceOrMultipleInstancesAllowed()) { | ||||
|     await components.whenReady(); | ||||
|  | ||||
|     // Adblock | ||||
|     if (store.get(settings.adBlock)) { | ||||
|       const filter = { urls: ["https://listen.tidal.com/*"] }; | ||||
|       session.defaultSession.webRequest.onBeforeRequest(filter, (details, callback) => { | ||||
|         if (details.url.match(/\/users\/.*\d\?country/)) callback({ cancel: true }); | ||||
|         else callback({ cancel: false }); | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     createWindow(); | ||||
|     addMenu(); | ||||
|     addMenu(mainWindow); | ||||
|     createSettingsWindow(); | ||||
|     addGlobalShortcuts(); | ||||
|     store.get(settings.trayIcon) && addTray({ icon }) && refreshTray(); | ||||
|     store.get(settings.trayIcon) && addTray(mainWindow, { icon }) && refreshTray(); | ||||
|     store.get(settings.api) && expressModule.run(mainWindow); | ||||
|     store.get(settings.enableDiscord) && discordModule.initRPC(); | ||||
|     // mainWindow.webContents.openDevTools(); | ||||
|   } else { | ||||
|     app.quit(); | ||||
|   } | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								src/pages/settings/fonts/NotoSans-Bold.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								src/pages/settings/fonts/NotoSans-Light.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								src/pages/settings/fonts/NotoSans-Regular.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								src/pages/settings/fonts/NotoSans-SemiBold.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -1,18 +1,22 @@ | ||||
| let trayIcon, | ||||
|   minimizeOnClose, | ||||
|   mpris, | ||||
| let adBlock, | ||||
|   api, | ||||
|   customCSS, | ||||
|   disableBackgroundThrottle, | ||||
|   disableHardwareMediaKeys, | ||||
|   enableCustomHotkeys, | ||||
|   enableDiscord, | ||||
|   muteArtists, | ||||
|   gpuRasterization, | ||||
|   menuBar, | ||||
|   minimizeOnClose, | ||||
|   mpris, | ||||
|   notifications, | ||||
|   playBackControl, | ||||
|   api, | ||||
|   port, | ||||
|   menuBar, | ||||
|   mutedArtists, | ||||
|   singleInstance, | ||||
|   disableHardwareMediaKeys, | ||||
|   gpuRasterization; | ||||
|   skipArtists, | ||||
|   skippedArtists, | ||||
|   trayIcon, | ||||
|   updateFrequency; | ||||
|  | ||||
| const { store, settings } = require("./../../scripts/settings"); | ||||
| const { ipcRenderer } = require("electron"); | ||||
| @@ -23,21 +27,25 @@ const { app } = remote; | ||||
|  * Sync the UI forms with the current settings | ||||
|  */ | ||||
| function refreshSettings() { | ||||
|   notifications.checked = store.get(settings.notifications); | ||||
|   playBackControl.checked = store.get(settings.playBackControl); | ||||
|   adBlock.checked = store.get(settings.adBlock); | ||||
|   api.checked = store.get(settings.api); | ||||
|   port.value = store.get(settings.apiSettings.port); | ||||
|   menuBar.checked = store.get(settings.menuBar); | ||||
|   trayIcon.checked = store.get(settings.trayIcon); | ||||
|   mpris.checked = store.get(settings.mpris); | ||||
|   customCSS.value = store.get(settings.customCSS); | ||||
|   disableBackgroundThrottle.checked = store.get("disableBackgroundThrottle"); | ||||
|   disableHardwareMediaKeys.checked = store.get(settings.flags.disableHardwareMediaKeys); | ||||
|   enableCustomHotkeys.checked = store.get(settings.enableCustomHotkeys); | ||||
|   enableDiscord.checked = store.get(settings.enableDiscord); | ||||
|   minimizeOnClose.checked = store.get(settings.minimizeOnClose); | ||||
|   muteArtists.checked = store.get(settings.muteArtists); | ||||
|   mutedArtists.value = store.get(settings.mutedArtists).join("\n"); | ||||
|   singleInstance.checked = store.get(settings.singleInstance); | ||||
|   disableHardwareMediaKeys.checked = store.get(settings.flags.disableHardwareMediaKeys); | ||||
|   gpuRasterization.checked = store.get(settings.flags.gpuRasterization); | ||||
|   menuBar.checked = store.get(settings.menuBar); | ||||
|   minimizeOnClose.checked = store.get(settings.minimizeOnClose); | ||||
|   mpris.checked = store.get(settings.mpris); | ||||
|   notifications.checked = store.get(settings.notifications); | ||||
|   playBackControl.checked = store.get(settings.playBackControl); | ||||
|   port.value = store.get(settings.apiSettings.port); | ||||
|   singleInstance.checked = store.get(settings.singleInstance); | ||||
|   skipArtists.checked = store.get(settings.skipArtists); | ||||
|   skippedArtists.value = store.get(settings.skippedArtists).join("\n"); | ||||
|   trayIcon.checked = store.get(settings.trayIcon); | ||||
|   updateFrequency.value = store.get(settings.updateFrequency); | ||||
| } | ||||
|  | ||||
| /** | ||||
| @@ -60,7 +68,7 @@ function hide() { | ||||
|  */ | ||||
| function restart() { | ||||
|   app.relaunch(); | ||||
|   app.quit(); | ||||
|   app.exit(); | ||||
| } | ||||
|  | ||||
| /** | ||||
| @@ -73,7 +81,7 @@ window.addEventListener("DOMContentLoaded", () => { | ||||
|  | ||||
|   document.getElementById("close").addEventListener("click", hide); | ||||
|   document.getElementById("restart").addEventListener("click", restart); | ||||
|   document.querySelectorAll("#openExternal").forEach((elem) => | ||||
|   document.querySelectorAll(".external-link").forEach((elem) => | ||||
|     elem.addEventListener("click", function (event) { | ||||
|       openExternal(event.target.getAttribute("data-url")); | ||||
|     }) | ||||
| @@ -105,37 +113,45 @@ window.addEventListener("DOMContentLoaded", () => { | ||||
|     document.getElementById(tab).click(); | ||||
|   }); | ||||
|  | ||||
|   notifications = get("notifications"); | ||||
|   playBackControl = get("playBackControl"); | ||||
|   adBlock = get("adBlock"); | ||||
|   api = get("apiCheckbox"); | ||||
|   port = get("port"); | ||||
|   menuBar = get("menuBar"); | ||||
|   trayIcon = get("trayIcon"); | ||||
|   minimizeOnClose = get("minimizeOnClose"); | ||||
|   mpris = get("mprisCheckbox"); | ||||
|   customCSS = get("customCSS"); | ||||
|   disableBackgroundThrottle = get("disableBackgroundThrottle"); | ||||
|   disableHardwareMediaKeys = get("disableHardwareMediaKeys"); | ||||
|   enableCustomHotkeys = get("enableCustomHotkeys"); | ||||
|   enableDiscord = get("enableDiscord"); | ||||
|   muteArtists = get("muteArtists"); | ||||
|   mutedArtists = get("mutedArtists"); | ||||
|   singleInstance = get("singleInstance"); | ||||
|   disableHardwareMediaKeys = get("disableHardwareMediaKeys"); | ||||
|   gpuRasterization = get("gpuRasterization"); | ||||
|   menuBar = get("menuBar"); | ||||
|   minimizeOnClose = get("minimizeOnClose"); | ||||
|   mpris = get("mprisCheckbox"); | ||||
|   notifications = get("notifications"); | ||||
|   playBackControl = get("playBackControl"); | ||||
|   port = get("port"); | ||||
|   trayIcon = get("trayIcon"); | ||||
|   skipArtists = get("skipArtists"); | ||||
|   skippedArtists = get("skippedArtists"); | ||||
|   singleInstance = get("singleInstance"); | ||||
|   updateFrequency = get("updateFrequency"); | ||||
|  | ||||
|   refreshSettings(); | ||||
|  | ||||
|   addInputListener(notifications, settings.notifications); | ||||
|   addInputListener(playBackControl, settings.playBackControl); | ||||
|   addInputListener(adBlock, settings.adBlock); | ||||
|   addInputListener(api, settings.api); | ||||
|   addInputListener(port, settings.apiSettings.port); | ||||
|   addInputListener(menuBar, settings.menuBar); | ||||
|   addInputListener(trayIcon, settings.trayIcon); | ||||
|   addInputListener(mpris, settings.mpris); | ||||
|   addTextAreaListener(customCSS, settings.customCSS); | ||||
|   addInputListener(disableBackgroundThrottle, settings.disableBackgroundThrottle); | ||||
|   addInputListener(disableHardwareMediaKeys, settings.flags.disableHardwareMediaKeys); | ||||
|   addInputListener(enableCustomHotkeys, settings.enableCustomHotkeys); | ||||
|   addInputListener(enableDiscord, settings.enableDiscord); | ||||
|   addInputListener(minimizeOnClose, settings.minimizeOnClose); | ||||
|   addInputListener(muteArtists, settings.muteArtists); | ||||
|   addTextAreaListener(mutedArtists, settings.mutedArtists); | ||||
|   addInputListener(singleInstance, settings.singleInstance); | ||||
|   addInputListener(disableHardwareMediaKeys, settings.flags.disableHardwareMediaKeys); | ||||
|   addInputListener(gpuRasterization, settings.flags.gpuRasterization); | ||||
|   addInputListener(menuBar, settings.menuBar); | ||||
|   addInputListener(minimizeOnClose, settings.minimizeOnClose); | ||||
|   addInputListener(mpris, settings.mpris); | ||||
|   addInputListener(notifications, settings.notifications); | ||||
|   addInputListener(playBackControl, settings.playBackControl); | ||||
|   addInputListener(port, settings.apiSettings.port); | ||||
|   addInputListener(skipArtists, settings.skipArtists); | ||||
|   addTextAreaListener(skippedArtists, settings.skippedArtists); | ||||
|   addInputListener(singleInstance, settings.singleInstance); | ||||
|   addInputListener(trayIcon, settings.trayIcon); | ||||
|   addInputListener(updateFrequency, settings.updateFrequency); | ||||
| }); | ||||
|   | ||||
| @@ -2,539 +2,302 @@ | ||||
| <html lang="en"> | ||||
|  | ||||
| <head> | ||||
|     <title>Tidal-hifi settings</title> | ||||
|     <meta charset="UTF-8" /> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||||
|     <meta http-equiv="X-UA-Compatible" content="ie=edge" /> | ||||
|   <title>Tidal Hi-Fi settings</title> | ||||
|   <meta charset="UTF-8" /> | ||||
|   <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||||
|   <meta http-equiv="X-UA-Compatible" content="ie=edge" /> | ||||
|   <link rel="stylesheet" href="./settings.css" /> | ||||
| </head> | ||||
|  | ||||
| <body> | ||||
|     <div class="header"> | ||||
|         <h1 class="title">Settings</h1> | ||||
|         <a id="close" style="cursor: pointer;" class="exitWindow"> | ||||
| <body class="settings-window"> | ||||
|   <div class="settings-window__wrapper"> | ||||
|     <div class="settings-window__drag-area"></div> | ||||
|     <a id="close" class="settings-window__close-button" title="Close settings"> | ||||
|       <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 348.333 348.334" class="settings-window__svg-icon"> | ||||
|         <path fill="white" d="M336.559,68.611L231.016,174.165l105.543,105.549c15.699,15.705,15.699,41.145,0,56.85 | ||||
|             c-7.844,7.844-18.128,11.769-28.407,11.769c-10.296,0-20.581-3.919-28.419-11.769L174.167,231.003L68.609,336.563 | ||||
|             c-7.843,7.844-18.128,11.769-28.416,11.769c-10.285,0-20.563-3.919-28.413-11.769c-15.699-15.698-15.699-41.139,0-56.85 | ||||
|             l105.54-105.549L11.774,68.611c-15.699-15.699-15.699-41.145,0-56.844c15.696-15.687,41.127-15.687,56.829,0l105.563,105.554 | ||||
|             L279.721,11.767c15.705-15.687,41.139-15.687,56.832,0C352.258,27.466,352.258,52.912,336.559,68.611z" /> | ||||
|       </svg> | ||||
|     </a> | ||||
|  | ||||
|             <svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="18px" height="18px" viewBox="0 0 348.333 348.334"> | ||||
|                 <g> | ||||
|                     <path fill="white" d="M336.559,68.611L231.016,174.165l105.543,105.549c15.699,15.705,15.699,41.145,0,56.85 | ||||
|               c-7.844,7.844-18.128,11.769-28.407,11.769c-10.296,0-20.581-3.919-28.419-11.769L174.167,231.003L68.609,336.563 | ||||
|               c-7.843,7.844-18.128,11.769-28.416,11.769c-10.285,0-20.563-3.919-28.413-11.769c-15.699-15.698-15.699-41.139,0-56.85 | ||||
|               l105.54-105.549L11.774,68.611c-15.699-15.699-15.699-41.145,0-56.844c15.696-15.687,41.127-15.687,56.829,0l105.563,105.554 | ||||
|               L279.721,11.767c15.705-15.687,41.139-15.687,56.832,0C352.258,27.466,352.258,52.912,336.559,68.611z" /> | ||||
|             </svg> | ||||
|     <main class="settings"> | ||||
|       <input type="radio" name="tab" id="general" checked /> | ||||
|       <label for="general">General</label> | ||||
|  | ||||
|         </a> | ||||
|     </div> | ||||
|     <div class="body"> | ||||
|         <div class="tabset"> | ||||
|             <!-- Tab 1 --> | ||||
|             <input type="radio" name="tabset" id="general" checked /> | ||||
|             <label for="general">General</label> | ||||
|             <!-- Tab 2 --> | ||||
|             <input type="radio" name="tabset" id="api" /> | ||||
|             <label for="api">Api</label> | ||||
|       <input type="radio" name="tab" id="api" /> | ||||
|       <label for="api">API</label> | ||||
|  | ||||
|             <!-- Integrations tab --> | ||||
|             <input type="radio" name="tabset" id="integrations" /> | ||||
|             <label for="integrations">Integrations</label> | ||||
|       <input type="radio" name="tab" id="integrations" /> | ||||
|       <label for="integrations">Integrations</label> | ||||
|  | ||||
|             <!-- advanced tab --> | ||||
|             <input type="radio" name="tabset" id="advanced" /> | ||||
|             <label for="advanced">Advanced</label> | ||||
|             <!-- about tab --> | ||||
|             <input type="radio" name="tabset" id="about" /> | ||||
|             <label for="about">About</label> | ||||
|       <input type="radio" name="tab" id="advanced" /> | ||||
|       <label for="advanced">Advanced</label> | ||||
|  | ||||
|             <div class="tab-panels"> | ||||
|                 <section id="general" class="tab-panel"> | ||||
|                     <div class="section"> | ||||
|                         <h3>Playback</h3> | ||||
|                         <div class="option"> | ||||
|                             <h4>Notifications</h4> | ||||
|                             <p> | ||||
|                                 Whether to show a notification when a new song starts. | ||||
|                             </p> | ||||
|                             <label class="switch"> | ||||
|                                 <input id="notifications" type="checkbox"> | ||||
|                                 <span class="slider round"></span> | ||||
|                             </label> | ||||
|                         </div> | ||||
|                         <div class="option"> | ||||
|                             <h4>Mute Artists automatically</h4> | ||||
|                             <p> | ||||
|                                 The following list of artists (1 per line) will be muted automatically. | ||||
|                             </p> | ||||
|                             <label class="switch" style="margin-bottom:10px"> | ||||
|                               <input id="muteArtists" type="checkbox"> | ||||
|                               <span class="slider round" ></span> | ||||
|                           </label> | ||||
|                           <textarea id="mutedArtists" cols="40" rows="5"></textarea> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                     <div class="section"> | ||||
|                         <h3>UI</h3> | ||||
|                         <div class="option"> | ||||
|                             <h4>Menubar</h4> | ||||
|                             <p> | ||||
|                                 Show Tidal-hifi's menu bar | ||||
|                             </p> | ||||
|                             <label class="switch"> | ||||
|                                 <input id="menuBar" type="checkbox"> | ||||
|                                 <span class="slider round"></span> | ||||
|                             </label> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                     <div class="section"> | ||||
|                       <h3>System</h3> | ||||
|                       <div class="option"> | ||||
|                           <h4>Tray icon</h4> | ||||
|                           <p> | ||||
|                               Show Tidal-hifi's tray icon<br /> | ||||
|                           </p> | ||||
|                           <label class="switch"> | ||||
|                               <input id="trayIcon" type="checkbox"> | ||||
|                               <span class="slider round"></span> | ||||
|                           </label> | ||||
|                       </div> | ||||
|                       <div class="option"> | ||||
|                         <h4>Minimize on Close</h4> | ||||
|                         <p> | ||||
|                             Minimize window on close instead <br /> | ||||
|                         </p> | ||||
|                         <label class="switch"> | ||||
|                             <input id="minimizeOnClose" type="checkbox"> | ||||
|                             <span class="slider round"></span> | ||||
|                         </label> | ||||
|                       </div> | ||||
|                       <div class="option"> | ||||
|                         <h4>Hotkeys</h4> | ||||
|                         <p> | ||||
|                             Enables extra hotkeys to achieve feature parity with the <a id="openExternal" style="text-decoration: underline; cursor: pointer;" data-url= "https://defkey.com/tidal-desktop-shortcuts">desktop apps</a><br /> | ||||
|                         </p> | ||||
|                         <label class="switch"> | ||||
|                             <input id="enableCustomHotkeys" type="checkbox"> | ||||
|                             <span class="slider round"></span> | ||||
|                         </label> | ||||
|                       </div> | ||||
|                       <div class="option"> | ||||
|                         <h4>Single instance</h4> | ||||
|                         <p> | ||||
|                             Prevent opening multiple tidal-hifi instances | ||||
|                         </p> | ||||
|                         <label class="switch"> | ||||
|                             <input id="singleInstance" type="checkbox"> | ||||
|                             <span class="slider round"></span> | ||||
|                         </label> | ||||
|                       </div> | ||||
|                   </div> | ||||
|                 </section> | ||||
|                 <section id="api" class="tab-panel"> | ||||
|                     <div class="section"> | ||||
|                         <h3>Api</h3> | ||||
|                         <p style="margin-bottom: 15px;"> | ||||
|                             Tidal-hifi has a web api built in to allow users to get current song information. You can optionally enable playback control as well. | ||||
|                         </p> | ||||
|       <input type="radio" name="tab" id="about" /> | ||||
|       <label for="about">About</label> | ||||
|  | ||||
|                         <div class="option"> | ||||
|                             <h4>Web API</h4> | ||||
|                             <p> | ||||
|                                 Whether to enable the Tidal-hifi web api | ||||
|                             </p> | ||||
|                             <label class="switch"> | ||||
|                                 <input id="apiCheckbox" type="checkbox"> | ||||
|                                 <span class="slider round"></span> | ||||
|                             </label> | ||||
|                         </div> | ||||
|                         <div class="option"> | ||||
|                             <h4 style="margin-bottom: 5px;">API port</h4> | ||||
|                             <input id="port" type="text" class="freeTextInput" name="port"> | ||||
|                         </div> | ||||
|                         <div class="option"> | ||||
|                             <h4>Playback control</h4> | ||||
|                             <p> | ||||
|                                 Whether to enable playback control from the api | ||||
|                             </p> | ||||
|                             <label class="switch"> | ||||
|                                 <input id="playBackControl" type="checkbox"> | ||||
|                                 <span class="slider round"></span> | ||||
|                             </label> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </section> | ||||
|                 <section id="integrations" class="tab-panel"> | ||||
|                   <div class="section"> | ||||
|                       <h3>integrations</h3> | ||||
|                       <p style="margin-bottom: 15px;"> | ||||
|                           Tidal-hifi is extensible through the use of integrations. You can enable or disable integrations here | ||||
|                       </p> | ||||
|                       <div class="option"> | ||||
|                           <h4>MPRIS-player</h4> | ||||
|                           <p> | ||||
|                               Whether to enable the MPRIS media player controls for Linux systems | ||||
|                           </p> | ||||
|                           <label class="switch"> | ||||
|                               <input id="mprisCheckbox" type="checkbox"> | ||||
|                               <span class="slider round"></span> | ||||
|                           </label> | ||||
|                       </div> | ||||
|                       <div class="option"> | ||||
|                         <h4>Discord RPC</h4> | ||||
|                         <p> | ||||
|                             Show what you're listening to on Discord | ||||
|                         </p> | ||||
|                         <label class="switch"> | ||||
|                             <input id="enableDiscord" type="checkbox"> | ||||
|                             <span class="slider round"></span> | ||||
|                         </label> | ||||
|                     </div> | ||||
|                   </div> | ||||
|               </section> | ||||
|  | ||||
|               <section id="advanced" class="tab-panel"> | ||||
|                 <div class="section"> | ||||
|                   <h3>Flags</h3> | ||||
|                   <div class="option"> | ||||
|                     <h4>Disable hardware media keys</h4> | ||||
|                     <p> | ||||
|                         Disable built-in media keys. <br /> | ||||
|                         Also prevents certain desktop environments from recognizing the chrome MPRIS client separetely from the custom MPRIS client. | ||||
|                     </p> | ||||
|                     <label class="switch"> | ||||
|                         <input id="disableHardwareMediaKeys" type="checkbox"> | ||||
|                         <span class="slider round"></span> | ||||
|                     </label> | ||||
|                   </div> | ||||
|                   <div class="option"> | ||||
|                     <h4>Enable GPU rasterization</h4> | ||||
|                     <p> | ||||
|                       Move a part of the rendering to the GPU for increased performance. | ||||
|                     </p> | ||||
|                     <label class="switch"> | ||||
|                         <input id="gpuRasterization" type="checkbox"> | ||||
|                         <span class="slider round"></span> | ||||
|                     </label> | ||||
|                   </div> | ||||
|                 </div> | ||||
|               </section> | ||||
|  | ||||
|               <!-- about --> | ||||
|               <section id="about" class="tab-panel"> | ||||
|                 <div class="section"> | ||||
|                   <img alt="tidal icon" style="width: 100px; height: auto; display: block; margin: 0 auto; margin-bottom: 20px; margin-top: 20px;" src = "./icon.png"> | ||||
|                   <p style="max-width: 350px; display:block; margin: 0 auto; text-align: center;"> | ||||
|                     <a id="openExternal" style="text-decoration: underline; cursor: pointer;" data-url="https://github.com/Mastermindzh/tidal-hifi">Tidal-hifi</a> is made by <a id="openExternal" data-url="https://www.rickvanlieshout.com" style="text-decoration: underline; cursor: pointer;">Rick van Lieshout</a>.<br /> | ||||
|                     It uses <a style="text-decoration: underline; cursor: pointer;" id="openExternal" data-url="https://castlabs.com/">Castlabs'</a> versions of Electron for widevine support. | ||||
|                   </p> | ||||
|                 </div> | ||||
|               </section> | ||||
|  | ||||
|               <small>Some settings require a restart of Tidal-hifi. To do so, click the button below:</small> | ||||
|               <button id="restart" style ="width: 100%">Restart Tidal-hifi</button> | ||||
|       <div class="tabs"> | ||||
|         <section id="general-section" class="tabs__section"> | ||||
|           <div class="group"> | ||||
|             <p class="group__title">Playback</p> | ||||
|             <div class="group__option"> | ||||
|               <div class="group__description"> | ||||
|                 <h4>Notifications</h4> | ||||
|                 <p>Show a notification when a new song starts.</p> | ||||
|               </div> | ||||
|               <label class="switch"> | ||||
|                 <input id="notifications" type="checkbox" /> | ||||
|                 <span class="switch__slider"></span> | ||||
|               </label> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
|             <div class="group__option"> | ||||
|               <div class="group__description"> | ||||
|                 <h4>Skip Artists automatically</h4> | ||||
|                 <p>The following list of artists (1 per line) will be skipped automatically.</p> | ||||
|               </div> | ||||
|               <label class="switch"> | ||||
|                 <input id="skipArtists" type="checkbox" /> | ||||
|                 <span class="switch__slider"></span> | ||||
|               </label> | ||||
|             </div> | ||||
|             <textarea id="skippedArtists" class="textarea" cols="40" rows="5" spellcheck="false"></textarea> | ||||
|             <div class="group__option"> | ||||
|               <div class="group__description"> | ||||
|                 <h4>Block ads</h4> | ||||
|                 <p> | ||||
|                   Disabled audio & visual ads, unlocked lyrics, suggested track, track info, | ||||
|                   unlimited skips | ||||
|                 </p> | ||||
|               </div> | ||||
|               <label class="switch"> | ||||
|                 <input id="adBlock" type="checkbox" /> | ||||
|                 <span class="switch__slider"></span> | ||||
|               </label> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="group"> | ||||
|             <p class="group__title">UI</p> | ||||
|             <div class="group__option"> | ||||
|               <div class="group__description"> | ||||
|                 <h4>Fixed menubar</h4> | ||||
|                 <p>Always show TIDAL Hi-Fi's menu bar.</p> | ||||
|               </div> | ||||
|               <label class="switch"> | ||||
|                 <input id="menuBar" type="checkbox" /> | ||||
|                 <span class="switch__slider"></span> | ||||
|               </label> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="group"> | ||||
|             <p class="group__title">System</p> | ||||
|             <div class="group__option"> | ||||
|               <div class="group__description"> | ||||
|                 <h4>Tray icon</h4> | ||||
|                 <p>Show TIDAL Hi-Fi's tray icon.</p> | ||||
|               </div> | ||||
|               <label class="switch"> | ||||
|                 <input id="trayIcon" type="checkbox" /> | ||||
|                 <span class="switch__slider"></span> | ||||
|               </label> | ||||
|             </div> | ||||
|             <div class="group__option"> | ||||
|               <div class="group__description"> | ||||
|                 <h4>Minimize on Close</h4> | ||||
|                 <p>Minimize window on close instead.</p> | ||||
|               </div> | ||||
|               <label class="switch"> | ||||
|                 <input id="minimizeOnClose" type="checkbox" /> | ||||
|                 <span class="switch__slider"></span> | ||||
|               </label> | ||||
|             </div> | ||||
|             <div class="group__option"> | ||||
|               <div class="group__description"> | ||||
|                 <h4>Hotkeys</h4> | ||||
|                 <p> | ||||
|                   Enable extra hotkeys to achieve feature parity with the | ||||
|                   <a class="external-link" data-url="https://defkey.com/tidal-desktop-shortcuts">desktop apps</a>. | ||||
|                 </p> | ||||
|               </div> | ||||
|               <label class="switch"> | ||||
|                 <input id="enableCustomHotkeys" type="checkbox" /> | ||||
|                 <span class="switch__slider"></span> | ||||
|               </label> | ||||
|             </div> | ||||
|             <div class="group__option"> | ||||
|               <div class="group__description"> | ||||
|                 <h4>Single instance</h4> | ||||
|                 <p>Prevent opening multiple TIDAL Hi-Fi's instances.</p> | ||||
|               </div> | ||||
|               <label class="switch"> | ||||
|                 <input id="singleInstance" type="checkbox" /> | ||||
|                 <span class="switch__slider"></span> | ||||
|               </label> | ||||
|             </div> | ||||
|           </div> | ||||
|         </section> | ||||
|  | ||||
|         <section id="api-section" class="tabs__section"> | ||||
|           <div class="group"> | ||||
|             <p class="group__title">API</p> | ||||
|             <div class="group__description"> | ||||
|               <p> | ||||
|                 TIDAL Hi-Fi has a built-in web API to allow users to get current song information. | ||||
|                 You can optionally enable playback control as well. | ||||
|               </p> | ||||
|             </div> | ||||
|             <div class="group__option"> | ||||
|               <div class="group__description"> | ||||
|                 <h4>Web API</h4> | ||||
|                 <p>Enable the TIDAL Hi-Fi web API.</p> | ||||
|               </div> | ||||
|               <label class="switch"> | ||||
|                 <input id="apiCheckbox" type="checkbox" /> | ||||
|                 <span class="switch__slider"></span> | ||||
|               </label> | ||||
|             </div> | ||||
|             <div class="group__option"> | ||||
|               <div class="group__description"> | ||||
|                 <label for="port">API port</label> | ||||
|                 <input id="port" type="number" class="text-input" name="port" /> | ||||
|               </div> | ||||
|             </div> | ||||
|             <div class="group__option"> | ||||
|               <div class="group__description"> | ||||
|                 <h4>Playback control</h4> | ||||
|                 <p>Enable playback control from the web API.</p> | ||||
|               </div> | ||||
|               <label class="switch"> | ||||
|                 <input id="playBackControl" type="checkbox" /> | ||||
|                 <span class="switch__slider"></span> | ||||
|               </label> | ||||
|             </div> | ||||
|           </div> | ||||
|         </section> | ||||
|  | ||||
|         <section id="integrations-section" class="tabs__section"> | ||||
|           <div class="group"> | ||||
|             <p class="group__title">Integrations</p> | ||||
|             <div class="group__description"> | ||||
|               <p> | ||||
|                 TIDAL Hi-Fi is extensible through the use of integrations. You can enable or | ||||
|                 disable them here. | ||||
|               </p> | ||||
|             </div> | ||||
|             <div class="group__option"> | ||||
|               <div class="group__description"> | ||||
|                 <h4>MPRIS</h4> | ||||
|                 <p> | ||||
|                   Enable MPRIS interface which provides a mechanism for discovery, querying and | ||||
|                   basic playback control on Linux systems. | ||||
|                 </p> | ||||
|               </div> | ||||
|               <label class="switch"> | ||||
|                 <input id="mprisCheckbox" type="checkbox" /> | ||||
|                 <span class="switch__slider"></span> | ||||
|               </label> | ||||
|             </div> | ||||
|             <div class="group__option"> | ||||
|               <div class="group__description"> | ||||
|                 <h4>Discord RPC</h4> | ||||
|                 <p>Show what you're listening to on Discord.</p> | ||||
|               </div> | ||||
|               <label class="switch"> | ||||
|                 <input id="enableDiscord" type="checkbox" /> | ||||
|                 <span class="switch__slider"></span> | ||||
|               </label> | ||||
|             </div> | ||||
|           </div> | ||||
|         </section> | ||||
|  | ||||
|         <section id="advanced-section" class="tabs__section"> | ||||
|           <div class="group"> | ||||
|             <p class="group__title">Settings</p> | ||||
|             <div class="group__option"> | ||||
|               <div class="group__description"> | ||||
|                 <h4>Update frequency</h4> | ||||
|                 <p> | ||||
|                   The amount of time, in milliseconds, that tidal-hifi 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. | ||||
|                 </p> | ||||
|                 <input id="updateFrequency" type="number" class="text-input" name="updateFrequency" /> | ||||
|               </div> | ||||
|             </div> | ||||
|             <div class="group__option"> | ||||
|               <div class="group__description"> | ||||
|                 <h4>Custom CSS</h4> | ||||
|                 <p> | ||||
|                   The css that you put in here will be injected into a style tag in the head of the document. | ||||
|                 </p> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|           <textarea id="customCSS" class="textarea" cols="40" rows="8" spellcheck="false"></textarea> | ||||
|  | ||||
|           <div class="group"> | ||||
|             <p class="group__title">Flags</p> | ||||
|             <div class="group__option"> | ||||
|               <div class="group__description"> | ||||
|                 <h4>Disable hardware built-in media keys</h4> | ||||
|                 <p> | ||||
|                   Also prevents certain desktop environments from recognizing the chrome MPRIS | ||||
|                   client separately from the custom MPRIS client. | ||||
|                 </p> | ||||
|               </div> | ||||
|               <label class="switch"> | ||||
|                 <input id="disableHardwareMediaKeys" type="checkbox" /> | ||||
|                 <span class="switch__slider"></span> | ||||
|               </label> | ||||
|             </div> | ||||
|             <div class="group__option"> | ||||
|               <div class="group__description"> | ||||
|                 <h4>Enable GPU rasterization</h4> | ||||
|                 <p>Move a part of the rendering to the GPU for increased performance.</p> | ||||
|               </div> | ||||
|               <label class="switch"> | ||||
|                 <input id="gpuRasterization" type="checkbox" /> | ||||
|                 <span class="switch__slider"></span> | ||||
|               </label> | ||||
|             </div> | ||||
|             <div class="group__option"> | ||||
|               <div class="group__description"> | ||||
|                 <h4>Disable Background Throttling</h4> | ||||
|                 <p> | ||||
|                   Makes app more responsive while in the background, at the cost of performance. | ||||
|                 </p> | ||||
|               </div> | ||||
|               <label class="switch"> | ||||
|                 <input id="disableBackgroundThrottle" type="checkbox" /> | ||||
|                 <span class="switch__slider"></span> | ||||
|               </label> | ||||
|             </div> | ||||
|           </div> | ||||
|         </section> | ||||
|  | ||||
|         <section id="about-section" class="tabs__section about-section"> | ||||
|           <img alt="tidal icon" class="about-section__icon" src="./icon.png" /> | ||||
|           <p class="about-section__text"> | ||||
|             <a class="external-link" data-url="https://github.com/Mastermindzh/tidal-hifi">TIDAL Hi-Fi</a> | ||||
|             is made by | ||||
|             <a class="external-link" data-url="https://www.rickvanlieshout.com"> | ||||
|               Rick van Lieshout</a>. <br />It uses | ||||
|             <a class="external-link" data-url="https://castlabs.com/">Castlabs'</a> | ||||
|             version of Electron for widevine support. | ||||
|           </p> | ||||
|         </section> | ||||
|  | ||||
|         <footer class="footer"> | ||||
|           <p class="footer__note"> | ||||
|             Some settings may require a restart of TIDAL Hi-Fi. To do so, click the button below: | ||||
|           </p> | ||||
|           <button class="footer__button" id="restart">Restart TIDAL Hi-Fi</button> | ||||
|         </footer> | ||||
|       </div> | ||||
|     </main> | ||||
|   </div> | ||||
| </body> | ||||
|  | ||||
| <style> | ||||
|     .header { | ||||
|         -webkit-user-select: none; | ||||
|         -webkit-app-region: drag; | ||||
|     } | ||||
|     .header a { | ||||
|       -webkit-app-region: no-drag; | ||||
|     } | ||||
|  | ||||
|     * { | ||||
|         margin: 0%; | ||||
|         padding: 0%; | ||||
|         color: #ffffff; | ||||
|         font-weight: 400; | ||||
|         font-stretch: normal; | ||||
|         -webkit-font-smoothing: antialiased; | ||||
|         font-family: nationale, nationale-regular, Helvetica, sans-serif; | ||||
|     } | ||||
|  | ||||
|     html, | ||||
|     body { | ||||
|         height: 100%; | ||||
|         background-color: black; | ||||
|         display: flex; | ||||
|         flex-direction: column; | ||||
|     } | ||||
|  | ||||
|     h2 { | ||||
|         font-size: 1.2rem; | ||||
|     } | ||||
|  | ||||
|     small { | ||||
|         font-style: italic; | ||||
|         color: #72777f; | ||||
|     } | ||||
|  | ||||
|     .header { | ||||
|         background-color: #242528; | ||||
|         border-bottom: 1px solid #5a5a5a; | ||||
|         height: 50px; | ||||
|     } | ||||
|  | ||||
|     .title { | ||||
|         float: left; | ||||
|         line-height: 50px; | ||||
|         margin-left: 15px; | ||||
|     } | ||||
|  | ||||
|     .accent { | ||||
|         color: #0ff; | ||||
|     } | ||||
|  | ||||
|     .exitWindow { | ||||
|         border: none; | ||||
|         outline: none; | ||||
|         text-decoration: none; | ||||
|         font-size: 1.4rem; | ||||
|         float: right; | ||||
|         margin-right: 15px; | ||||
|         height: 50px; | ||||
|         line-height: 50px; | ||||
|     } | ||||
|  | ||||
|     .exitWindow:focus { | ||||
|       border: none; | ||||
|       outline: none; | ||||
|     } | ||||
|  | ||||
|     .exitWindow svg { | ||||
|         height: 50px; | ||||
|         color: white; | ||||
|     } | ||||
|  | ||||
|     .section { | ||||
|         padding-top: 10px; | ||||
|         padding-bottom: 10px; | ||||
|         border-bottom: 1px solid rgba(246, 245, 255, .1); | ||||
|     } | ||||
|  | ||||
|     .section .option { | ||||
|         margin-bottom: 15px; | ||||
|     } | ||||
|  | ||||
|     .section .option p { | ||||
|         max-width: 75%; | ||||
|         float: left | ||||
|     } | ||||
|  | ||||
|     .section .option label { | ||||
|         float: right; | ||||
|     } | ||||
|  | ||||
|     .section:after, | ||||
|     .section .option:after { | ||||
|         content: ""; | ||||
|         display: table; | ||||
|         clear: both; | ||||
|     } | ||||
|  | ||||
|     .section h3 { | ||||
|         margin-bottom: 15px; | ||||
|     } | ||||
|  | ||||
|     .section h4 { | ||||
|         font-size: 0.9rem; | ||||
|     } | ||||
|  | ||||
|     .section p { | ||||
|         color: #72777f; | ||||
|     } | ||||
|  | ||||
|     .bottom-border { | ||||
|         border-bottom: 1px solid #0ff; | ||||
|     } | ||||
|  | ||||
|     .body { | ||||
|         padding: 15px; | ||||
|         flex: 1 1 auto; | ||||
|         position: relative; | ||||
|         overflow-y: auto; | ||||
|     } | ||||
|  | ||||
|     .body::-webkit-scrollbar-track { | ||||
|         -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); | ||||
|         box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); | ||||
|         border-radius: 5px; | ||||
|         background-color: 2a2a2a; | ||||
|     } | ||||
|  | ||||
|     .body::-webkit-scrollbar { | ||||
|         width: 10px; | ||||
|         background-color: #2a2a2a; | ||||
|     } | ||||
|  | ||||
|     .body::-webkit-scrollbar-thumb { | ||||
|         border-radius: 10px; | ||||
|         -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .3); | ||||
|         box-shadow: inset 0 0 6px rgba(0, 0, 0, .3); | ||||
|         background-color: #5a5a5a; | ||||
|     } | ||||
|     /* Tabs */ | ||||
|  | ||||
|     .tabset > input[type="radio"] { | ||||
|         position: absolute; | ||||
|         left: -200vw; | ||||
|     } | ||||
|  | ||||
|     .tabset .tab-panel { | ||||
|         display: none; | ||||
|     } | ||||
|  | ||||
|     .tabset > input:first-child:checked ~ .tab-panels > .tab-panel:first-child, | ||||
|     .tabset > input:nth-child(3):checked ~ .tab-panels > .tab-panel:nth-child(2), | ||||
|     .tabset > input:nth-child(5):checked ~ .tab-panels > .tab-panel:nth-child(3), | ||||
|     .tabset > input:nth-child(7):checked ~ .tab-panels > .tab-panel:nth-child(4) | ||||
|      { | ||||
|         display: block; | ||||
|     } | ||||
|  | ||||
|     .tabset > label { | ||||
|         position: relative; | ||||
|         display: inline-block; | ||||
|         padding: 15px 0px 10px; | ||||
|         border-bottom: 0; | ||||
|         cursor: pointer; | ||||
|     } | ||||
|  | ||||
|     .tabset > input + label { | ||||
|         color: #e0e0e0; | ||||
|         margin-right: 30px; | ||||
|     } | ||||
|  | ||||
|     .tabset > input:checked + label { | ||||
|         color: #0ff; | ||||
|         border-bottom: 2px solid #0ff; | ||||
|     } | ||||
|  | ||||
|     .tab-panel { | ||||
|         padding: 10px 0; | ||||
|     } | ||||
|     /* switches */ | ||||
|     /* The switch - the box around the slider */ | ||||
|  | ||||
|     .switch { | ||||
|         position: relative; | ||||
|         display: inline-block; | ||||
|         width: 50px; | ||||
|         height: 28px; | ||||
|     } | ||||
|     /* Hide default HTML checkbox */ | ||||
|  | ||||
|     .switch input { | ||||
|         opacity: 0; | ||||
|         width: 0; | ||||
|         height: 0; | ||||
|     } | ||||
|     /* The slider */ | ||||
|  | ||||
|     .slider { | ||||
|         position: absolute; | ||||
|         cursor: pointer; | ||||
|         top: 0; | ||||
|         left: 0; | ||||
|         right: 0; | ||||
|         bottom: 0; | ||||
|         background-color: rgba(246, 245, 255, .1); | ||||
|         -webkit-transition: .4s; | ||||
|         transition: .4s; | ||||
|     } | ||||
|  | ||||
|     .slider:before { | ||||
|         position: absolute; | ||||
|         content: ""; | ||||
|         height: 24px; | ||||
|         width: 24px; | ||||
|         left: 2px; | ||||
|         bottom: 2px; | ||||
|         background-color: white; | ||||
|         -webkit-transition: .4s; | ||||
|         transition: .4s; | ||||
|     } | ||||
|  | ||||
|     input:checked + .slider { | ||||
|         background-color: #0ff; | ||||
|     } | ||||
|  | ||||
|     input:focus + .slider { | ||||
|         box-shadow: 0 0 1px #0ff; | ||||
|     } | ||||
|  | ||||
|     input:checked + .slider:before { | ||||
|         -webkit-transform: translateX(22px); | ||||
|         -ms-transform: translateX(22px); | ||||
|         transform: translateX(22px); | ||||
|     } | ||||
|     /* Rounded sliders */ | ||||
|  | ||||
|     .slider.round { | ||||
|         border-radius: 34px; | ||||
|     } | ||||
|  | ||||
|     .slider.round:before { | ||||
|         border-radius: 50%; | ||||
|     } | ||||
|     /* input field */ | ||||
|  | ||||
|     input { | ||||
|         background: transparent; | ||||
|         border: 0; | ||||
|         border-bottom: 1px solid rgba(246, 245, 255, .1); | ||||
|         color: rgba(229, 238, 255, .6); | ||||
|         width: 100%; | ||||
|         display: block; | ||||
|         padding: 0 0 12px; | ||||
|     } | ||||
|  | ||||
|     .freeTextInput:focus { | ||||
|         outline: none; | ||||
|         border-bottom: 1px solid #0ff; | ||||
|     } | ||||
|  | ||||
|     /* buttons */ | ||||
|     button{ | ||||
|       border:none; | ||||
|       background:none; | ||||
|       align-items: center; | ||||
|       background-color: rgba(229,238,255,.2); | ||||
|       display: inline-flex; | ||||
|       justify-content: center; | ||||
|       border-radius: 12px; | ||||
|       height: 48px; | ||||
|       line-height: 49px; | ||||
|       padding: 0 24px; | ||||
|       text-align: center; | ||||
|       overflow: hidden; | ||||
|       text-overflow: ellipsis; | ||||
|       white-space: nowrap; | ||||
|       transition: background .35s ease; | ||||
|       min-height: 0; | ||||
|       min-width: 0; | ||||
|       font-size: 1.14286rem; | ||||
|       font-family: nationale,nationale-regular,Helvetica,sans-serif; | ||||
|       margin-top: 10px; | ||||
|       cursor: pointer; | ||||
|     } | ||||
|     button:hover{ | ||||
|       background-color: rgba(229,238,255,.3); | ||||
|     } | ||||
|     textarea { | ||||
|       color: #72777f; | ||||
|       background: #242528; | ||||
|       border: 0; | ||||
|       width: 100%; | ||||
|       color: rgba(229, 238, 255, .6); | ||||
|       padding: 0 0 12px; | ||||
|       display:block; | ||||
|     } | ||||
|     textarea:focus { | ||||
|       outline: none; | ||||
|       border: 0; | ||||
|       border-bottom: 1px solid #0ff; | ||||
|     } | ||||
| </style> | ||||
| </html> | ||||
| </html> | ||||
							
								
								
									
										363
									
								
								src/pages/settings/settings.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,363 @@ | ||||
| // --- Variables --- | ||||
|  | ||||
| $black: #17171a; | ||||
| $grey-333: #333; | ||||
| $white: #f9f9f9; | ||||
|  | ||||
| $tidal-blue: #0ff; | ||||
| $tidal-grey: #72777f; | ||||
| $tidal-grey-darker: #404248; | ||||
| $tidal-grey-darker-focus: #55585f; | ||||
| $tidal-grey-darkest: #242528; | ||||
|  | ||||
| // --- Fonts --- | ||||
|  | ||||
| @font-face { | ||||
|   font-family: "Noto Sans"; | ||||
|   font-weight: 300; | ||||
|   src: url("fonts/NotoSans-Light.ttf") format("truetype"); | ||||
| } | ||||
|  | ||||
| @font-face { | ||||
|   font-family: "Noto Sans"; | ||||
|   font-weight: normal; | ||||
|   src: url("fonts/NotoSans-Regular.ttf") format("truetype"); | ||||
| } | ||||
|  | ||||
| @font-face { | ||||
|   font-family: "Noto Sans"; | ||||
|   font-weight: 600; | ||||
|   src: url("fonts/NotoSans-SemiBold.ttf") format("truetype"); | ||||
| } | ||||
|  | ||||
| @font-face { | ||||
|   font-family: "Noto Sans"; | ||||
|   font-weight: bold; | ||||
|   src: url("fonts/NotoSans-Bold.ttf") format("truetype"); | ||||
| } | ||||
|  | ||||
| $font1: "Noto Sans", Helvetica, sans-serif; | ||||
|  | ||||
| // --- Mixins --- | ||||
|  | ||||
| @mixin drag($enabled: true) { | ||||
|   @if $enabled { | ||||
|     -webkit-app-region: drag; | ||||
|   } | ||||
|  | ||||
|   @else { | ||||
|     -webkit-app-region: no-drag; | ||||
|   } | ||||
| } | ||||
|  | ||||
| button { | ||||
|   cursor: pointer; | ||||
| } | ||||
|  | ||||
| // --- Settings window --- | ||||
|  | ||||
| html { | ||||
|   height: 100%; | ||||
| } | ||||
|  | ||||
| .external-link { | ||||
|   @extend button; | ||||
|   text-decoration: underline; | ||||
| } | ||||
|  | ||||
| .settings-window { | ||||
|   height: 100%; | ||||
|   margin: 0; | ||||
|   color: $white; | ||||
|   font-family: $font1; | ||||
|  | ||||
|   &__wrapper { | ||||
|     height: 100%; | ||||
|     background: $black; | ||||
|     box-shadow: inset 0 0 2px 0 $tidal-grey; | ||||
|     overflow: hidden; | ||||
|   } | ||||
|  | ||||
|   &__drag-area { | ||||
|     @include drag; | ||||
|     position: absolute; | ||||
|     width: 100%; | ||||
|     height: 50px; | ||||
|     z-index: 0; | ||||
|     user-select: none; | ||||
|   } | ||||
|  | ||||
|   &__close-button { | ||||
|     @extend button; | ||||
|     @include drag(false); | ||||
|     position: absolute; | ||||
|     top: 12px; | ||||
|     right: 10px; | ||||
|     padding: 10px; | ||||
|     border-radius: 100%; | ||||
|     z-index: 1; | ||||
|  | ||||
|     &:hover { | ||||
|       background: $grey-333; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   &__svg-icon { | ||||
|     display: block; | ||||
|     width: 18px; | ||||
|     height: 18px; | ||||
|     opacity: .7; | ||||
|   } | ||||
|  | ||||
|   // --- Settings tabs --- | ||||
| } | ||||
|  | ||||
| .settings { | ||||
|   height: 100%; | ||||
|   margin: 20px 0; | ||||
|   padding-left: 15px; | ||||
|   font-size: 0; | ||||
|  | ||||
|   input { | ||||
|     &[type="radio"] { | ||||
|       margin-right: -18px; | ||||
|       transform: scale(0); | ||||
|       outline: none; | ||||
|     } | ||||
|  | ||||
|     &+label { | ||||
|       @include drag(false); | ||||
|       display: inline-block; | ||||
|       position: relative; | ||||
|       margin-right: 35px; | ||||
|       padding-bottom: 8px; | ||||
|       border-bottom: 0; | ||||
|       font-size: 16px; | ||||
|       cursor: pointer; | ||||
|       z-index: 1; | ||||
|       user-select: none; | ||||
|     } | ||||
|  | ||||
|     &:checked+label { | ||||
|       border-bottom: 2px solid $tidal-blue; | ||||
|       color: $tidal-blue; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| .tabs { | ||||
|   height: calc(100% - 70px); | ||||
|   padding-right: 15px; | ||||
|   font-size: 16px; | ||||
|   overflow: auto; | ||||
|  | ||||
|   &__section { | ||||
|     display: none; | ||||
|   } | ||||
|  | ||||
|   @for $i from 1 to 6 { | ||||
|     .settings>input:nth-child(#{$i*2-1}):checked~&>.tabs__section:nth-child(#{$i}) { | ||||
|       display: block; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   &::-webkit-scrollbar { | ||||
|     width: 10px; | ||||
|  | ||||
|     &-thumb { | ||||
|       border-radius: 10px; | ||||
|       background-color: $tidal-grey-darker; | ||||
|       box-shadow: inset 0 0 10px 2px $tidal-grey-darkest; | ||||
|     } | ||||
|  | ||||
|     // --- Settings group --- | ||||
|   } | ||||
| } | ||||
|  | ||||
| .group { | ||||
|   padding: 10px 0; | ||||
|   border-bottom: 1px solid $grey-333; | ||||
|  | ||||
|   &:last-child { | ||||
|     border: 0; | ||||
|   } | ||||
|  | ||||
|   &__title { | ||||
|     margin-bottom: 10px; | ||||
|     font-size: 16px; | ||||
|     font-weight: bold; | ||||
|   } | ||||
|  | ||||
|   &__option { | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|   } | ||||
|  | ||||
|   &__description { | ||||
|     flex-grow: 1; | ||||
|  | ||||
|     h4, | ||||
|     label { | ||||
|       display: block; | ||||
|       margin-top: 10px; | ||||
|       margin-bottom: 0; | ||||
|       font-size: 14px; | ||||
|       font-weight: 600; | ||||
|     } | ||||
|  | ||||
|     p { | ||||
|       margin-top: 5px; | ||||
|       margin-bottom: 8px; | ||||
|       color: $tidal-grey; | ||||
|       font-size: 14px; | ||||
|     } | ||||
|  | ||||
|     .text-input { | ||||
|       display: block; | ||||
|       width: 100%; | ||||
|       margin-bottom: 10px; | ||||
|       padding: 5px 0; | ||||
|       transition: .2s; | ||||
|       border: 0; | ||||
|       border-bottom: solid 1px $grey-333; | ||||
|       outline: none; | ||||
|       background: transparent; | ||||
|       color: $tidal-grey; | ||||
|       font-size: 14px; | ||||
|  | ||||
|       &:focus { | ||||
|         border-color: $tidal-blue; | ||||
|         color: $white; | ||||
|       } | ||||
|  | ||||
|       // --- Switch slider component --- | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| .switch { | ||||
|   $this: &; | ||||
|   position: relative; | ||||
|   min-width: 50px; | ||||
|   height: 28px; | ||||
|  | ||||
|   margin-left: 10px; | ||||
|  | ||||
|   input { | ||||
|     transform: scale(0); | ||||
|     outline: none; | ||||
|  | ||||
|     &:checked+#{$this}__slider { | ||||
|       background-color: $tidal-blue; | ||||
|  | ||||
|       &::before { | ||||
|         transform: translateX(22px); | ||||
|         background-color: white; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     &:focus+#{$this}__slider { | ||||
|       box-shadow: inset 0 0 0 1px $tidal-blue; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   &__slider { | ||||
|     @extend button; | ||||
|     position: absolute; | ||||
|     top: 0; | ||||
|     right: 0; | ||||
|     bottom: 0; | ||||
|     left: 0; | ||||
|     transition: .4s; | ||||
|     border-radius: 40px; | ||||
|     background-color: $tidal-grey-darkest; | ||||
|  | ||||
|     &::before { | ||||
|       position: absolute; | ||||
|       bottom: 2px; | ||||
|       left: 2px; | ||||
|       width: 24px; | ||||
|       height: 24px; | ||||
|       transition: .4s; | ||||
|       border-radius: 50%; | ||||
|       background-color: $white; | ||||
|       content: ""; | ||||
|     } | ||||
|  | ||||
|     // --- Textarea component | ||||
|   } | ||||
| } | ||||
|  | ||||
| .textarea { | ||||
|   min-width: 100%; | ||||
|   max-width: 100%; | ||||
|   min-height: 50px; | ||||
|   max-height: 100px; | ||||
|   padding: 8px; | ||||
|   transition: .2s; | ||||
|   border: 0; | ||||
|   border-bottom: 1px solid transparent; | ||||
|   background: $tidal-grey-darkest; | ||||
|   color: $tidal-grey; | ||||
|   font-size: 13px; | ||||
|   box-sizing: border-box; | ||||
|  | ||||
|   &:focus { | ||||
|     border-color: $tidal-blue; | ||||
|     outline: none; | ||||
|     color: $white; | ||||
|   } | ||||
|  | ||||
|   // --- About section --- | ||||
| } | ||||
|  | ||||
| .about-section { | ||||
|   padding-top: 120px; | ||||
|   text-align: center; | ||||
|  | ||||
|   &__icon { | ||||
|     display: inline-block; | ||||
|     width: 100px; | ||||
|   } | ||||
|  | ||||
|   &__text { | ||||
|     display: block; | ||||
|     max-width: 350px; | ||||
|     margin: 20px auto 0; | ||||
|   } | ||||
|  | ||||
|   // --- Footer --- | ||||
| } | ||||
|  | ||||
| .footer { | ||||
|   position: sticky; | ||||
|   top: calc(100% - 120px); | ||||
|   height: 100px; | ||||
|   padding-top: 20px; | ||||
|   text-align: center; | ||||
|  | ||||
|   &__note { | ||||
|     max-width: 300px; | ||||
|     margin: 0 auto 15px; | ||||
|     color: $tidal-grey; | ||||
|     font-size: 12px; | ||||
|   } | ||||
|  | ||||
|   &__button { | ||||
|     @extend button; | ||||
|     display: block; | ||||
|     height: 48px; | ||||
|     margin: auto; | ||||
|     padding: 0 24px; | ||||
|     transition: .2s; | ||||
|     border: 0; | ||||
|     border-radius: 12px; | ||||
|     background: $tidal-grey-darker; | ||||
|     color: $white; | ||||
|     font-size: 16px; | ||||
|  | ||||
|     &:hover { | ||||
|       background: $tidal-grey-darker-focus; | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										202
									
								
								src/preload.js
									
									
									
									
									
								
							
							
						
						| @@ -1,22 +1,18 @@ | ||||
| const { setTitle } = require("./scripts/window-functions"); | ||||
| const { dialog, process, Notification } = require('@electron/remote'); | ||||
| const { dialog, process, Notification } = require("@electron/remote"); | ||||
| const { store, settings } = require("./scripts/settings"); | ||||
| const { ipcRenderer } = require("electron"); | ||||
| const { app } = require('@electron/remote'); | ||||
| const { app } = require("@electron/remote"); | ||||
| const { downloadFile } = require("./scripts/download"); | ||||
| const statuses = require("./constants/statuses"); | ||||
| const hotkeys = require("./scripts/hotkeys"); | ||||
| const globalEvents = require("./constants/globalEvents"); | ||||
| const { skipArtists, updateFrequency, customCSS } = require("./constants/settings"); | ||||
| const notificationPath = `${app.getPath("userData")}/notification.jpg`; | ||||
| const appName = "Tidal Hifi"; | ||||
| let currentSong = ""; | ||||
| let player; | ||||
| let currentPlayStatus = statuses.paused; | ||||
| let progressBarTime; | ||||
| let currentTimeChanged = false; | ||||
| let currentTime; | ||||
| let currentURL = undefined; | ||||
| let isMutedArtist = false; | ||||
|  | ||||
| const elements = { | ||||
|   play: '*[data-test="play"]', | ||||
| @@ -69,16 +65,27 @@ const elements = { | ||||
|     return ""; | ||||
|   }, | ||||
|  | ||||
|   getArtists: function () { | ||||
|   /** | ||||
|    * returns an array of all artists in the current song | ||||
|    * @returns {Array} artists | ||||
|    */ | ||||
|   getArtistsArray: function () { | ||||
|     const footer = this.get("footer"); | ||||
|  | ||||
|     if (footer) { | ||||
|       const artists = footer.querySelector(this["artists"]); | ||||
|       if (artists) { | ||||
|         return artists.innerText; | ||||
|       } | ||||
|       const artists = footer.querySelectorAll(this.artists); | ||||
|       if (artists) return Array.from(artists).map((artist) => artist.textContent); | ||||
|     } | ||||
|     return []; | ||||
|   }, | ||||
|  | ||||
|   /** | ||||
|    * unify the artists array into a string separated by commas | ||||
|    * @param {Array} artistsArray | ||||
|    * @returns {String} artists | ||||
|    */ | ||||
|   getArtistsString: function (artistsArray) { | ||||
|     if (artistsArray.length > 0) return artistsArray.join(", "); | ||||
|     return "unknown artist(s)"; | ||||
|   }, | ||||
|  | ||||
| @@ -136,6 +143,29 @@ const elements = { | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| function addCustomCss() { | ||||
|   window.addEventListener("DOMContentLoaded", () => { | ||||
|     const style = document.createElement("style"); | ||||
|     style.innerHTML = store.get(customCSS); | ||||
|     document.head.appendChild(style); | ||||
|   }); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Get the update frequency from the store | ||||
|  * make sure it returns a number, if not use the default | ||||
|  */ | ||||
| function getUpdateFrequency() { | ||||
|   const storeValue = store.get(updateFrequency); | ||||
|   const defaultValue = 500; | ||||
|  | ||||
|   if (!isNaN(storeValue)) { | ||||
|     return storeValue; | ||||
|   } else { | ||||
|     return defaultValue; | ||||
|   } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Play or pause the current song | ||||
|  */ | ||||
| @@ -304,6 +334,7 @@ function updateMediaInfo(options, notify) { | ||||
|           "xesam:album": options.album, | ||||
|           "mpris:artUrl": options.image, | ||||
|           "mpris:length": convertDuration(options.duration) * 1000 * 1000, | ||||
|           "mpris:trackid": "/org/mpris/MediaPlayer2/track/" + getTrackID(), | ||||
|         }, | ||||
|       }; | ||||
|       player.playbackStatus = options.status == statuses.paused ? "Paused" : "Playing"; | ||||
| @@ -313,19 +344,21 @@ function updateMediaInfo(options, notify) { | ||||
|  | ||||
| /** | ||||
|  * Checks if Tidal is playing a video or song by grabbing the "a" element from the title. | ||||
|  * If it's a song it sets the track URL as currentURL, If it's a video it will set currentURL to undefined. | ||||
|  * If it's a song it returns the track URL, if not it will return undefined | ||||
|  */ | ||||
| function updateURL() { | ||||
| function getTrackURL() { | ||||
|   const id = getTrackID(); | ||||
|   return `https://tidal.com/browse/track/${id}`; | ||||
| } | ||||
|  | ||||
| function getTrackID() { | ||||
|   const URLelement = elements.get("title").querySelector("a"); | ||||
|   switch (URLelement) { | ||||
|     case null: | ||||
|       currentURL = undefined; | ||||
|       break; | ||||
|     default: | ||||
|       const id = URLelement.href.replace(/[^0-9]/g, ""); | ||||
|       currentURL = `https://tidal.com/browse/track/${id}`; | ||||
|       break; | ||||
|   if (URLelement !== null) { | ||||
|     const id = URLelement.href.replace(/\D/g, ""); | ||||
|     return id; | ||||
|   } | ||||
|  | ||||
|   return window.location; | ||||
| } | ||||
|  | ||||
| /** | ||||
| @@ -333,103 +366,78 @@ function updateURL() { | ||||
|  */ | ||||
| setInterval(function () { | ||||
|   const title = elements.getText("title"); | ||||
|   const artists = elements.getArtists(); | ||||
|   const artistsArray = elements.getArtistsArray(); | ||||
|   const artistsString = elements.getArtistsString(artistsArray); | ||||
|   skipArtistsIfFoundInSkippedArtistsList(artistsArray); | ||||
|  | ||||
|   const album = elements.getAlbumName(); | ||||
|   const current = elements.getText("current"); | ||||
|   const duration = elements.getText("duration"); | ||||
|   const progressBarcurrentTime = elements.get("bar").getAttribute("aria-valuenow"); | ||||
|   const songDashArtistTitle = `${title} - ${artists}`; | ||||
|   const songDashArtistTitle = `${title} - ${artistsString}`; | ||||
|   const currentStatus = getCurrentlyPlayingStatus(); | ||||
|   const options = { | ||||
|     title, | ||||
|     message: artists, | ||||
|     message: artistsString, | ||||
|     album: album, | ||||
|     status: currentStatus, | ||||
|     url: currentURL, | ||||
|     current: current, | ||||
|     duration: duration, | ||||
|     url: getTrackURL(), | ||||
|     current, | ||||
|     duration, | ||||
|     "app-name": appName, | ||||
|   }; | ||||
|  | ||||
|   const playStatusChanged = currentStatus !== currentPlayStatus; | ||||
|   const progressBarTimeChanged = progressBarcurrentTime !== progressBarTime; | ||||
|   const titleOrArtistChanged = currentSong !== songDashArtistTitle; | ||||
|  | ||||
|   muteArtistIfFoundInMutedArtistsList(); | ||||
|   // update title, url and play info with new info | ||||
|   setTitle(songDashArtistTitle); | ||||
|   getTrackURL(); | ||||
|   currentSong = songDashArtistTitle; | ||||
|   currentPlayStatus = currentStatus; | ||||
|  | ||||
|   if (titleOrArtistChanged || playStatusChanged || progressBarTimeChanged || currentTimeChanged) { | ||||
|     // update title, url and play info with new info | ||||
|     setTitle(songDashArtistTitle); | ||||
|     updateURL(); | ||||
|     currentSong = songDashArtistTitle; | ||||
|     currentPlayStatus = currentStatus; | ||||
|   const image = elements.getSongIcon(); | ||||
|  | ||||
|     // check progress bar value and make sure current stays up to date after switch | ||||
|     if (progressBarTime != progressBarcurrentTime && !titleOrArtistChanged) { | ||||
|       progressBarTime = progressBarcurrentTime; | ||||
|       currentTime = options.current; | ||||
|       options.duration = duration; | ||||
|       currentTimeChanged = true; | ||||
|   new Promise((resolve) => { | ||||
|     if (image.startsWith("http")) { | ||||
|       options.image = image; | ||||
|       downloadFile(image, notificationPath).then( | ||||
|         () => { | ||||
|           options.icon = notificationPath; | ||||
|           resolve(); | ||||
|         }, | ||||
|         () => { | ||||
|           // if the image can't be downloaded then continue without it | ||||
|           resolve(); | ||||
|         } | ||||
|       ); | ||||
|     } else { | ||||
|       // if the image can't be found on the page continue without it | ||||
|       resolve(); | ||||
|     } | ||||
|  | ||||
|     if (currentTimeChanged) { | ||||
|       if (options.current == currentTime && currentStatus != "paused") return; | ||||
|       currentTime = options.current; | ||||
|       currentTimeChanged = false; | ||||
|     } | ||||
|  | ||||
|     // make sure current is set to 0 if title changes | ||||
|     if (titleOrArtistChanged) { | ||||
|       options.current = "0:00"; | ||||
|       currentTime = options.current; | ||||
|       progressBarTime = progressBarcurrentTime; | ||||
|     } | ||||
|  | ||||
|     const image = elements.getSongIcon(); | ||||
|  | ||||
|     new Promise((resolve) => { | ||||
|       if (image.startsWith("http")) { | ||||
|         options.image = image; | ||||
|         downloadFile(image, notificationPath).then( | ||||
|           () => { | ||||
|             options.icon = notificationPath; | ||||
|             resolve(); | ||||
|           }, | ||||
|           () => { | ||||
|             // if the image can't be downloaded then continue without it | ||||
|             resolve(); | ||||
|           } | ||||
|         ); | ||||
|       } else { | ||||
|         // if the image can't be found on the page continue without it | ||||
|         resolve(); | ||||
|       } | ||||
|     }).then( | ||||
|       () => { | ||||
|         updateMediaInfo(options, titleOrArtistChanged); | ||||
|       }, | ||||
|       () => {} | ||||
|     ); | ||||
|   } | ||||
|   }).then( | ||||
|     () => { | ||||
|       updateMediaInfo(options, titleOrArtistChanged); | ||||
|     }, | ||||
|     () => {} | ||||
|   ); | ||||
|  | ||||
|   /** | ||||
|    * Checks whether the current artist is included in the "muted artists" list and if so it will automatically mute the player | ||||
|    * automatically skip a song if the artists are found in the list of artists to skip | ||||
|    * @param {*} artists array of artists | ||||
|    */ | ||||
|   function muteArtistIfFoundInMutedArtistsList() { | ||||
|     if (store.get(settings.muteArtists)) { | ||||
|       const mutedArtists = store.get(settings.mutedArtists); | ||||
|       if (mutedArtists.find((artist) => artist === artists) !== undefined) { | ||||
|         if (!elements.isMuted()) { | ||||
|           isMutedArtist = true; | ||||
|           elements.click("volume"); | ||||
|   function skipArtistsIfFoundInSkippedArtistsList(artists) { | ||||
|     if (store.get(skipArtists)) { | ||||
|       const skippedArtists = store.get(settings.skippedArtists); | ||||
|       if (skippedArtists.length > 0) { | ||||
|         const artistsToSkip = skippedArtists.map((artist) => artist); | ||||
|         const artistNames = Object.values(artists).map((artist) => artist); | ||||
|         const foundArtist = artistNames.some((artist) => artistsToSkip.includes(artist)); | ||||
|         if (foundArtist) { | ||||
|           elements.click("next"); | ||||
|         } | ||||
|       } else if (currentStatus === statuses.playing && isMutedArtist && elements.isMuted()) { | ||||
|         elements.click("volume"); | ||||
|         isMutedArtist = false; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| }, 500); | ||||
| }, getUpdateFrequency()); | ||||
|  | ||||
| if (process.platform === "linux" && store.get(settings.mpris)) { | ||||
|   try { | ||||
| @@ -486,7 +494,7 @@ if (process.platform === "linux" && store.get(settings.mpris)) { | ||||
|     console.log("player api not working"); | ||||
|   } | ||||
| } | ||||
|  | ||||
| addCustomCss(); | ||||
| addHotKeys(); | ||||
| addIPCEventListeners(); | ||||
| addFullScreenListeners(); | ||||
|   | ||||
| @@ -1,13 +1,14 @@ | ||||
| const { Menu, app } = require("electron"); | ||||
| const { showSettingsWindow } = require("./settings"); | ||||
| const isMac = process.platform === "darwin"; | ||||
| const { name } = require("./../constants/values"); | ||||
|  | ||||
| const settingsMenuEntry = { | ||||
|   label: "Settings", | ||||
|   click() { | ||||
|     showSettingsWindow(); | ||||
|   }, | ||||
|   accelerator: "Control+/", | ||||
|   accelerator: "Control+=", | ||||
| }; | ||||
|  | ||||
| const quitMenuEntry = { | ||||
| @@ -15,97 +16,108 @@ const quitMenuEntry = { | ||||
|   click() { | ||||
|     app.exit(0); | ||||
|   }, | ||||
|   accelerator: "Control+Q" | ||||
|   accelerator: "Control+Q", | ||||
| }; | ||||
|  | ||||
| const mainMenu = [ | ||||
|   ...(isMac | ||||
|     ? [ | ||||
|         { | ||||
|           label: app.name, | ||||
|           submenu: [ | ||||
|             { role: "about" }, | ||||
|             settingsMenuEntry, | ||||
|             { type: "separator" }, | ||||
|             { role: "services" }, | ||||
|             { type: "separator" }, | ||||
|             { role: "hide" }, | ||||
|             { role: "hideothers" }, | ||||
|             { role: "unhide" }, | ||||
|             { type: "separator" }, | ||||
|             quitMenuEntry, | ||||
|           ], | ||||
|         }, | ||||
|       ] | ||||
|     : []), | ||||
|   { | ||||
|     label: "File", | ||||
|     submenu: [settingsMenuEntry, isMac ? { role: "close" } : quitMenuEntry], | ||||
|   }, | ||||
|   { | ||||
|     label: "Edit", | ||||
|     submenu: [ | ||||
|       { role: "undo" }, | ||||
|       { role: "redo" }, | ||||
|       { type: "separator" }, | ||||
|       { role: "cut" }, | ||||
|       { role: "copy" }, | ||||
|       { role: "paste" }, | ||||
|       ...(isMac | ||||
|         ? [ | ||||
|             { role: "pasteAndMatchStyle" }, | ||||
|             { role: "delete" }, | ||||
|             { role: "selectAll" }, | ||||
|             { type: "separator" }, | ||||
|             { | ||||
|               label: "Speech", | ||||
|               submenu: [{ role: "startspeaking" }, { role: "stopspeaking" }], | ||||
|             }, | ||||
|           ] | ||||
|         : [{ role: "delete" }, { type: "separator" }, { role: "selectAll" }]), | ||||
|       { type: "separator" }, | ||||
|       settingsMenuEntry, | ||||
|     ], | ||||
|   }, | ||||
|   { | ||||
|     label: "View", | ||||
|     submenu: [ | ||||
|       { role: "reload" }, | ||||
|       { role: "forcereload" }, | ||||
|       { type: "separator" }, | ||||
|       { role: "resetzoom" }, | ||||
|       { role: "zoomin" }, | ||||
|       { role: "zoomout" }, | ||||
|       { type: "separator" }, | ||||
|       { role: "togglefullscreen" }, | ||||
|     ], | ||||
|   }, | ||||
|   { | ||||
|     label: "Window", | ||||
|     submenu: [ | ||||
|       { role: "minimize" }, | ||||
|       ...(isMac | ||||
|         ? [{ type: "separator" }, { role: "front" }, { type: "separator" }, { role: "window" }] | ||||
|         : [{ role: "close" }]), | ||||
|     ], | ||||
|   }, | ||||
|   settingsMenuEntry, | ||||
|   { | ||||
|     label: "About", | ||||
|     click() { | ||||
|       showSettingsWindow("about"); | ||||
| const menuModule = {}; | ||||
|  | ||||
| menuModule.getMenu = function (mainWindow) { | ||||
|   const toggleWindow = { | ||||
|     label: "Toggle Window", | ||||
|     click: function () { | ||||
|       mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show(); | ||||
|     }, | ||||
|   }, | ||||
| ]; | ||||
|   }; | ||||
|  | ||||
| const menuModule = { mainMenu }; | ||||
|   const mainMenu = [ | ||||
|     ...(isMac | ||||
|       ? [ | ||||
|           { | ||||
|             label: name, | ||||
|             submenu: [ | ||||
|               { role: "about" }, | ||||
|               settingsMenuEntry, | ||||
|               { type: "separator" }, | ||||
|               { role: "services" }, | ||||
|               { type: "separator" }, | ||||
|               { role: "hide" }, | ||||
|               { role: "hideothers" }, | ||||
|               { role: "unhide" }, | ||||
|               { type: "separator" }, | ||||
|               quitMenuEntry, | ||||
|             ], | ||||
|           }, | ||||
|         ] | ||||
|       : []), | ||||
|     { | ||||
|       label: "File", | ||||
|       submenu: [settingsMenuEntry, isMac ? { role: "close" } : quitMenuEntry], | ||||
|     }, | ||||
|     { | ||||
|       label: "Edit", | ||||
|       submenu: [ | ||||
|         { role: "undo" }, | ||||
|         { role: "redo" }, | ||||
|         { type: "separator" }, | ||||
|         { role: "cut" }, | ||||
|         { role: "copy" }, | ||||
|         { role: "paste" }, | ||||
|         ...(isMac | ||||
|           ? [ | ||||
|               { role: "pasteAndMatchStyle" }, | ||||
|               { role: "delete" }, | ||||
|               { role: "selectAll" }, | ||||
|               { type: "separator" }, | ||||
|               { | ||||
|                 label: "Speech", | ||||
|                 submenu: [{ role: "startspeaking" }, { role: "stopspeaking" }], | ||||
|               }, | ||||
|             ] | ||||
|           : [{ role: "delete" }, { type: "separator" }, { role: "selectAll" }]), | ||||
|         { type: "separator" }, | ||||
|         settingsMenuEntry, | ||||
|       ], | ||||
|     }, | ||||
|     { | ||||
|       label: "View", | ||||
|       submenu: [ | ||||
|         { role: "reload" }, | ||||
|         { role: "forcereload" }, | ||||
|         { type: "separator" }, | ||||
|         { role: "resetzoom" }, | ||||
|         { role: "zoomin" }, | ||||
|         { role: "zoomout" }, | ||||
|         { type: "separator" }, | ||||
|         { role: "togglefullscreen" }, | ||||
|         { role: "toggledevtools" }, | ||||
|       ], | ||||
|     }, | ||||
|     { | ||||
|       label: "Window", | ||||
|       submenu: [ | ||||
|         { role: "minimize" }, | ||||
|         toggleWindow, | ||||
|         ...(isMac | ||||
|           ? [{ type: "separator" }, { role: "front" }, { type: "separator" }, { role: "window" }] | ||||
|           : [{ role: "close" }]), | ||||
|       ], | ||||
|     }, | ||||
|     settingsMenuEntry, | ||||
|     { | ||||
|       label: "About", | ||||
|       click() { | ||||
|         showSettingsWindow("about"); | ||||
|       }, | ||||
|     }, | ||||
|     toggleWindow, | ||||
|     quitMenuEntry, | ||||
|   ]; | ||||
|  | ||||
| menuModule.getMenu = function () { | ||||
|   return Menu.buildFromTemplate(mainMenu); | ||||
| }; | ||||
|  | ||||
| menuModule.addMenu = function () { | ||||
|   Menu.setApplicationMenu(menuModule.getMenu()); | ||||
| menuModule.addMenu = function (mainWindow) { | ||||
|   Menu.setApplicationMenu(menuModule.getMenu(mainWindow)); | ||||
| }; | ||||
|  | ||||
| module.exports = menuModule; | ||||
|   | ||||
| @@ -7,27 +7,31 @@ let settingsWindow; | ||||
|  | ||||
| const store = new Store({ | ||||
|   defaults: { | ||||
|     notifications: true, | ||||
|     adBlock: false, | ||||
|     api: true, | ||||
|     playBackControl: true, | ||||
|     muteArtists: false, | ||||
|     mutedArtists: ["TIDAL"], | ||||
|     menuBar: true, | ||||
|     apiSettings: { | ||||
|       port: 47836, | ||||
|     }, | ||||
|     singleInstance: true, | ||||
|     customCSS: "", | ||||
|     disableBackgroundThrottle: true, | ||||
|     disableHardwareMediaKeys: false, | ||||
|     trayIcon: true, | ||||
|     minimizeOnClose: false, | ||||
|     mpris: false, | ||||
|     enableCustomHotkeys: false, | ||||
|     enableDiscord: false, | ||||
|     windowBounds: { width: 800, height: 600 }, | ||||
|     flags: { | ||||
|       gpuRasterization: true, | ||||
|       disableHardwareMediaKeys: false, | ||||
|     }, | ||||
|     menuBar: true, | ||||
|     minimizeOnClose: false, | ||||
|     mpris: false, | ||||
|     notifications: true, | ||||
|     playBackControl: true, | ||||
|     singleInstance: true, | ||||
|     skipArtists: false, | ||||
|     skippedArtists: [""], | ||||
|     trayIcon: true, | ||||
|     updateFrequency: 500, | ||||
|     windowBounds: { width: 800, height: 600 }, | ||||
|   }, | ||||
|   migrations: { | ||||
|     "3.1.0": (migrationStore) => { | ||||
| @@ -48,11 +52,13 @@ const settingsModule = { | ||||
|  | ||||
| settingsModule.createSettingsWindow = function () { | ||||
|   settingsWindow = new BrowserWindow({ | ||||
|     width: 500, | ||||
|     width: 700, | ||||
|     height: 600, | ||||
|     resizable: false, | ||||
|     show: false, | ||||
|     transparent: true, | ||||
|     frame: false, | ||||
|     title: "Tidal-hifi - settings", | ||||
|     title: "TIDAL Hi-Fi settings", | ||||
|     webPreferences: { | ||||
|       preload: path.join(__dirname, "../pages/settings/preload.js"), | ||||
|       plugins: true, | ||||
|   | ||||
| @@ -1,48 +1,33 @@ | ||||
| const { Tray, app } = require("electron"); | ||||
| const { Menu } = require("electron"); | ||||
| const { getMenu, mainMenu } = require("./menu"); | ||||
| const { store, settings } = require("./settings"); | ||||
| const { Tray } = require("electron"); | ||||
| const { getMenu } = require("./menu"); | ||||
| const trayModule = {}; | ||||
| let tray; | ||||
|  | ||||
| trayModule.addTray = function (options = { icon: "" }) { | ||||
| trayModule.addTray = function (mainWindow, options = { icon: "" }) { | ||||
|   tray = new Tray(options.icon); | ||||
|   tray.setIgnoreDoubleClickEvents(true); | ||||
|   tray.setToolTip("Tidal-hifi"); | ||||
|  | ||||
|   const menu = getMenu(mainWindow); | ||||
|  | ||||
|   tray.setContextMenu(menu); | ||||
|  | ||||
|   tray.on("click", function () { | ||||
|     if (mainWindow.isVisible()) { | ||||
|       if (!mainWindow.isFocused()) { | ||||
|         mainWindow.focus(); | ||||
|       } else { | ||||
|         mainWindow.hide(); | ||||
|       } | ||||
|     } else { | ||||
|       mainWindow.show(); | ||||
|     } | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| trayModule.refreshTray = function (mainWindow) { | ||||
|   if (!tray) { | ||||
|     trayModule.addTray(); | ||||
|   } | ||||
|  | ||||
|   tray.on("click", function (e) { | ||||
|     if (mainWindow) { | ||||
|       mainWindow.show(); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   tray.setToolTip("Tidal-hifi"); | ||||
|  | ||||
|   if (mainWindow && store.get(settings.minimizeOnClose)) { | ||||
|     tray.setContextMenu( | ||||
|       Menu.buildFromTemplate([ | ||||
|         { | ||||
|           label: "Toggle Window", | ||||
|           click: function () { | ||||
|             mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show(); | ||||
|           }, | ||||
|         }, | ||||
|         { | ||||
|           label: "Quit", | ||||
|           click: function () { | ||||
|             mainWindow.destroy(); | ||||
|             app.quit(); | ||||
|           }, | ||||
|         }, | ||||
|         ...mainMenu, //we add menu items from the other context | ||||
|       ]) | ||||
|     ); | ||||
|   } else { | ||||
|     tray.setContextMenu(getMenu()); | ||||
|     trayModule.addTray(mainWindow); | ||||
|   } | ||||
| }; | ||||
|  | ||||
|   | ||||