Compare commits
	
		
			134 Commits
		
	
	
		
			1.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 | ||
| fa9ab22867 | |||
| 207a61d199 | |||
|  | 7b18322e17 | ||
| 8f47756244 | |||
|  | cdcf9431bf | ||
| 374f3da740 | |||
| 3965ada0a2 | |||
| 79ff02d06c | |||
| 7b2afd2290 | |||
| 5fde20ace1 | |||
| 7f5f5e7f62 | |||
| 6a1a1efe74 | |||
| 94e1bb1780 | |||
| d66dd8cc9e | |||
|  | de97ac8a00 | ||
|  | 82ac5edf22 | ||
|  | 909c8ee8ba | ||
|  | 15b6b13e14 | ||
| 89589b75e1 | |||
| 6a7b3eefd4 | |||
|  | 0583c4a188 | ||
|  | 8855e7f89f | ||
|  | 4fbb598c50 | ||
| 101fe967d3 | |||
|  | 53468e0dc3 | ||
| 53cecbcd18 | |||
| 5a65f60cc5 | |||
| d51d5cdc24 | |||
| 0dec967e71 | |||
|  | c940d0991d | ||
| 662ef6ad7b | |||
| 5313ab13d3 | |||
| f43f227191 | |||
|  | ae25d88e94 | ||
| 5ef6074015 | |||
| 8fea5265e7 | |||
| 8d2e03ca6b | |||
| 1074de228b | |||
|  | 64d1aa4041 | ||
|  | 6608330ed3 | ||
| aa562c4a30 | |||
| d6f63ac560 | |||
| cdc0f49789 | |||
| 2f290f83fd | |||
| d34ddfeb75 | |||
| b7f163c1a1 | |||
|  | 791a92a446 | ||
|  | 10c1e57680 | ||
|  | c65d1a56c8 | ||
|  | d8f2dbd0c2 | ||
|  | 43ce85bb28 | ||
|  | 8201e23e4b | ||
|  | 3cc288e014 | ||
|  | 08ec7fadac | ||
|  | 7a30b125ec | ||
|  | cac5db123f | ||
|  | 59f8b2d0b5 | ||
|  | 81a536bbdb | ||
|  | 5e952e3899 | ||
|  | df887b8628 | ||
|  | 31d90a342c | ||
|  | 8607337580 | ||
| 4fe42a3671 | |||
| 148d1746ad | |||
| ae51f9610c | |||
| 5eb3b8d95f | |||
| ebdae6bc88 | |||
|  | ab25bf16b2 | ||
| 31670d0c2b | |||
| 9ca3d3b37d | |||
| fb9082e995 | |||
| 87a4ff3fc5 | |||
| 1e5b7d61f5 | |||
| 8177e6e3ca | |||
| 9f26db22fc | |||
| e2ea4d13c4 | |||
| c222113cf1 | |||
| bdab6c3a17 | |||
| 813beec863 | |||
| 591d42b31a | |||
| beacedd64b | |||
|  | e13af7a2d5 | ||
| 73cba7f761 | |||
| 71208bbf81 | 
							
								
								
									
										12
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -9,20 +9,24 @@ jobs: | ||||
|   build_on_linux: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: update apt | ||||
|         run: sudo apt-get update | ||||
|       - name: Install libarchive-tools | ||||
|         run: sudo apt-get install -y libarchive-tools | ||||
|       - uses: actions/checkout@master | ||||
|       - uses: actions/setup-node@master | ||||
|         with: | ||||
|           node-version: 12 | ||||
|           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: 12 | ||||
|           node-version: 19 | ||||
|       - run: npm install | ||||
|       - run: npm run build | ||||
|  | ||||
| @@ -32,6 +36,6 @@ jobs: | ||||
|       - uses: actions/checkout@master | ||||
|       - uses: actions/setup-node@master | ||||
|         with: | ||||
|           node-version: 12 | ||||
|           node-version: 19 | ||||
|       - run: npm install | ||||
|       - run: npm run build | ||||
|   | ||||
							
								
								
									
										12
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -9,10 +9,14 @@ jobs: | ||||
|   build_on_linux: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: update apt | ||||
|         run: sudo apt-get update | ||||
|       - name: Install libarchive-tools | ||||
|         run: sudo apt-get install -y libarchive-tools | ||||
|       - uses: actions/checkout@master | ||||
|       - uses: actions/setup-node@master | ||||
|         with: | ||||
|           node-version: 12 | ||||
|           node-version: 16 | ||||
|       - run: npm install | ||||
|       - run: npm run build | ||||
|       - uses: actions/upload-artifact@master | ||||
| @@ -26,13 +30,13 @@ jobs: | ||||
|       - uses: actions/checkout@master | ||||
|       - uses: actions/setup-node@master | ||||
|         with: | ||||
|           node-version: 12 | ||||
|           node-version: 16 | ||||
|       - run: npm install | ||||
|       - run: npm run build | ||||
|       - uses: actions/upload-artifact@master | ||||
|         with: | ||||
|           name: mac-builds | ||||
|           path: dist/ | ||||
|           path: ./dist/ | ||||
|  | ||||
|   build_on_win: | ||||
|     runs-on: windows-latest | ||||
| @@ -40,7 +44,7 @@ jobs: | ||||
|       - uses: actions/checkout@master | ||||
|       - uses: actions/setup-node@master | ||||
|         with: | ||||
|           node-version: 12 | ||||
|           node-version: 16 | ||||
|       - run: npm install | ||||
|       - run: npm run build | ||||
|       - uses: actions/upload-artifact@master | ||||
|   | ||||
							
								
								
									
										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 | ||||
|   | ||||
							
								
								
									
										3
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,3 @@ | ||||
| { | ||||
|   "cSpell.words": ["hifi", "rescrobbler", "widevine"] | ||||
| } | ||||
							
								
								
									
										218
									
								
								CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,218 @@ | ||||
| # Changelog | ||||
|  | ||||
| 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. | ||||
|   - This fixes the issue of unwanted extra build targets that were introduced with the electron-builder update | ||||
|  | ||||
| ## 4.0.0 | ||||
|  | ||||
| - Updated to Electron 19.0.5 | ||||
|  | ||||
| ## 3.1.1 | ||||
|  | ||||
| - Media update timeout set to 500 instead of 200 | ||||
| - Updated property name for duration because of a tidal update | ||||
| - flag for "disable hardware media keys" now working again | ||||
|  | ||||
| ## 3.1.0 | ||||
|  | ||||
| - Added a separate advanced options settings panel with flags | ||||
|   - Added gpu-rasterization flag | ||||
| - config setting `disableHardwareMediaKeys` moved to `flags.disableHardwareMediaKeys`, it will be migrated automatically | ||||
|  | ||||
| ## 3.0.0 | ||||
|  | ||||
| - Updated to Electron 15 | ||||
| - Fixed the develop "build-unpacked" command | ||||
| - Added setting to disable multiple tidal-hifi windows (defaults to true) | ||||
| - Added setting to disable HardwareMediaKeyHandling (defaults to false) | ||||
|  | ||||
| ## 2.8.2 | ||||
|  | ||||
| - Updated dependencies | ||||
| - Downgraded packaged version of electron to 8.5.2, doesn't seem to like a newer build | ||||
| - Fixed the annoying (and useless) terminal warning about `allowRendererProcessReuse` | ||||
|  | ||||
| ## 2.8.1 | ||||
|  | ||||
| - Mar0xy fixed some build issues (thanks!) | ||||
| - vincens2005 fixed the quit button in the menubar | ||||
|  | ||||
| ## 2.8.0 | ||||
|  | ||||
| - Added the ability to mute artists automatically | ||||
| - Added better error handling for discord rpc | ||||
|  | ||||
| ## 2.7.2 | ||||
|  | ||||
| - Disabled sandboxing to fix a display compositor issue on Linux. | ||||
|  | ||||
| ## 2.7.1 | ||||
|  | ||||
| - Fixed bug: Triggering full screen from the Tidal web app would cause the menubar to be visible even if it was disabled in the settings | ||||
|  | ||||
| ## 2.7.0 | ||||
|  | ||||
| - Switched to the native Notifier (removed node-notifier) | ||||
| - Album art now also has a name, based on [best effort](https://github.com/Mastermindzh/tidal-hifi/pull/88#pullrequestreview-840814847) | ||||
|  | ||||
| ## 2.6.0 | ||||
|  | ||||
| - Add album images to media info and discord | ||||
|  | ||||
| ## 2.5.0 | ||||
|  | ||||
| - Notify-send now correctly shows "Tidal HiFi" as the program name | ||||
| - Updated dependencies (including electron itself) | ||||
|  | ||||
| ### known issues | ||||
|  | ||||
| - Requires older version of nodejs due to electron-builder (use lts/gallium) | ||||
|  | ||||
| ### builds | ||||
|  | ||||
| updated to nodejs 16 in actions | ||||
|  | ||||
| ## 2.4.0 | ||||
|  | ||||
| - Added more MPRIS settings | ||||
| - Added instruction for rescrobbler to get last.fm working without sandbox mode | ||||
|  | ||||
| ## 2.3.0 | ||||
|  | ||||
| - Added a setting to minimize to tray on app close (off by default) | ||||
| - Added the main menu to the tray icon | ||||
|  | ||||
| ## 2.2.1 | ||||
|  | ||||
| - artists is now gotten specifically from the footer. This fixes the [unknown artists bug](https://github.com/Mastermindzh/tidal-hifi/issues/45). | ||||
| - the discord module will check whether the artists is empty and if so substitute it with a default message. This is to prevent sending an empty state to Discord (which it doesn't support). fixes [#45](https://github.com/Mastermindzh/tidal-hifi/issues/54) | ||||
|  | ||||
| ### removed arch build details from source control | ||||
|  | ||||
| moved to: [https://github.com/Mastermindzh/tidal-hifi-aur](https://github.com/Mastermindzh/tidal-hifi-aur) | ||||
|  | ||||
| ## 2.2.0 | ||||
|  | ||||
| - The discord integration now adds a time remaining field based on the song duration | ||||
| - All fields (current, remaining, and url are also available in the API\*) | ||||
| - the artist field is now correctly identified | ||||
|  | ||||
| \* current time only updates on play/pause. | ||||
|  | ||||
| ## 2.1.1 | ||||
|  | ||||
| - The discord integration now doesn't send an update every 15 seconds it sends an update whenever the media info changes | ||||
| - consolidated updating the media info changes with the status changes into a single global event | ||||
|  | ||||
| ## 2.1.0 | ||||
|  | ||||
| - [Mar0xy](https://github.com/Mar0xy) added Discord integration. | ||||
| - Several versions have been bumped to fix vulnerabilities | ||||
|  | ||||
| ## 2.0.0 | ||||
|  | ||||
| ### Breaking changes | ||||
|  | ||||
| - Changed settings hotkey from "ctrl+/" to "ctrl+=" to avoid a conflict with the default Tidal hotkeys | ||||
|  | ||||
| ## Other changes | ||||
|  | ||||
| - Added a setting to disable custom hotkeys | ||||
| - Fixed the bug that the previous song hotkey would register 3 times. (Twice due to a duplicate block of code + once from the default tidal hotkey) | ||||
|  | ||||
| ## 1.3.0 | ||||
|  | ||||
| -- re-enabled MPRIS-service wit the electron downloader fixes | ||||
|  | ||||
| ## 1.2.0 | ||||
|  | ||||
| - Added the ability to disable the tray icon | ||||
|  | ||||
| ## 1.1.1 | ||||
|  | ||||
| Bugfixes: | ||||
|  | ||||
| - Arch AUR install failed before, it is fixed now by using the included build scripts | ||||
|  | ||||
| ## 1.1.0 | ||||
|  | ||||
| - updated to electron 8.0.0 | ||||
| - Added a beta-version of the MPRIS service | ||||
|  | ||||
| - Bugfixes: | ||||
|   - icon on gnome not showing in launcher | ||||
|   - app not remembering size on startup | ||||
							
								
								
									
										108
									
								
								README.md
									
									
									
									
									
								
							
							
						
						| @@ -1,44 +1,59 @@ | ||||
| <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](listen.tidal.com) running in electron with hifi support thanks to widevine. | ||||
|  | ||||
|  | ||||
| The web version of [listen.tidal.com](https://listen.tidal.com) running in electron with hifi support thanks to widevine. | ||||
|  | ||||
|  | ||||
|  | ||||
| ## 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) | ||||
|   - [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) | ||||
|  | ||||
| <!-- 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: | ||||
|  | ||||
| 1) Download:  | ||||
| ```sh  | ||||
| 1. Download | ||||
|  | ||||
| ```sh | ||||
| wget <URI> #for instance: https://github.com/Mastermindzh/tidal-hifi/releases/download/1.0/tidal-hifi_1.0.0_amd64.snap | ||||
| ``` | ||||
|  | ||||
| 2) Install:  | ||||
| 2. Install | ||||
|  | ||||
| ```sh | ||||
| snap install --dangerous <path> #for instance: tidal-hifi_1.0.0_amd64.snap | ||||
| ``` | ||||
| @@ -48,33 +63,68 @@ snap install --dangerous <path> #for instance: tidal-hifi_1.0.0_amd64.snap | ||||
| Arch Linux users can use the AUR to install tidal-hifi: | ||||
|  | ||||
| ```sh | ||||
| trizen tidal-hifi | ||||
| trizen tidal-hifi-bin | ||||
| ``` | ||||
|  | ||||
| ### Flatpak | ||||
|  | ||||
| To install via [Flatpak](https://flathub.org/apps/details/com.mastermindzh.tidal-hifi) run the following command: | ||||
|  | ||||
| ```sh | ||||
| 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: | ||||
|  | ||||
| - git clone https://github.com/Mastermindzh/tidal-hifi.git | ||||
| - git clone [https://github.com/Mastermindzh/tidal-hifi.git](https://github.com/Mastermindzh/tidal-hifi.git) | ||||
| - cd tidal-hifi | ||||
| - npm install | ||||
| - npm start | ||||
|  | ||||
| ## features | ||||
| ## Features | ||||
|  | ||||
| - HiFi playback | ||||
| - Notifications | ||||
| - Shortcuts ([source](https://defkey.com/tidal-desktop-shortcuts)) | ||||
| - Custom hotkeys ([source](https://defkey.com/tidal-desktop-shortcuts)) | ||||
| - API for status and playback | ||||
| - [Settings feature](./docs/settings.png) to disable certain functionality. (`ctrl+/`) | ||||
| - Tray(/mini) player (coming soon) | ||||
| - Disabled audio & visual ads, unlocked lyrics, suggested track, track info, and unlimited skips thanks to uBlockOrigin custom filters ([source](https://github.com/uBlockOrigin/uAssets/issues/17495)) | ||||
| - Custom [integrations](#integrations) | ||||
| - [Settings feature](./docs/settings.png) to disable certain functionality. (`ctrl+=` or `ctrl+0`) | ||||
| - AlbumArt in integrations ([best-effort](https://github.com/Mastermindzh/tidal-hifi/pull/88#pullrequestreview-840814847)) | ||||
|  | ||||
| ## Integrations | ||||
|  | ||||
| Tidal-hifi comes with several integrations out of the box. | ||||
| You can find these in the settings menu (`ctrl + =` by default) under the "integrations" tab. | ||||
|  | ||||
|  | ||||
|  | ||||
| It currently includes: | ||||
|  | ||||
| - MPRIS - MPRIS media player controls/status | ||||
| - Discord - Shows what you're listening to on Discord. | ||||
|  | ||||
| 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) | ||||
|  | ||||
| ### Known bugs | ||||
| - [Last.fm login doesn't work](https://github.com/Mastermindzh/tidal-hifi/issues/4). | ||||
|  | ||||
| #### last.fm doesn't work out of the box. Use rescrobbler as a workaround | ||||
|  | ||||
| The last.fm login doesn't work, as is evident from the following issue: [Last.fm login doesn't work](https://github.com/Mastermindzh/tidal-hifi/issues/4). | ||||
| However, in that same issue you can read about a workaround using [rescrobbler](https://github.com/InputUsername/rescrobbled). | ||||
| For now that will be the default workaround. | ||||
|  | ||||
| ## Why | ||||
|  | ||||
| @@ -93,7 +143,23 @@ 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) | ||||
|  | ||||
| ## Buy me a coffee? Please don't | ||||
|  | ||||
| Instead spend some money on a charity I care for: [kwf.nl](https://www.kwf.nl/donatie/donation). | ||||
| Inspired by [haydenjames' issue](https://github.com/Mastermindzh/tidal-hifi/issues/27#issuecomment-704198429) | ||||
|  | ||||
| ## Images | ||||
|  | ||||
| ### Settings window | ||||
|  | ||||
|  | ||||
|  | ||||
| ### 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 | 
							
								
								
									
										40
									
								
								build/electron-builder.base.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,40 @@ | ||||
| appId: com.rickvanlieshout.tidal-hifi | ||||
| electronVersion: 24.1.2 | ||||
| electronDownload: | ||||
|   version: 24.1.2+wvcus | ||||
|   mirror: https://github.com/castlabs/electron-releases/releases/download/v | ||||
| snap: | ||||
|   plugs: | ||||
|     - default | ||||
|     - screen-inhibit-control | ||||
| linux: | ||||
|   category: AudioVideo | ||||
|   icon: assets/icons | ||||
|   target: | ||||
|     - dir | ||||
|   executableName: tidal-hifi | ||||
|   desktop: | ||||
|     Encoding: UTF-8 | ||||
|     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: 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: 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.yml | ||||
| extends: ./build/electron-builder.base.yml | ||||
| linux: | ||||
|   category: Audio | ||||
|   icon: ./assets/icon.png | ||||
|   target: | ||||
|     - deb | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| extends: ./build/electron-builder.yml | ||||
| 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.yml | ||||
| extends: ./build/electron-builder.base.yml | ||||
| linux: | ||||
|     category: Audio | ||||
|     icon: ./assets/TIDAL.icns | ||||
|     target: | ||||
|         - rpm | ||||
|   target: | ||||
|     - rpm | ||||
|   | ||||
| @@ -1,6 +1,4 @@ | ||||
| extends: ./build/electron-builder.yml | ||||
| extends: ./build/electron-builder.base.yml | ||||
| linux: | ||||
|   category: Audio | ||||
|   icon: ./assets/icon.png | ||||
|   target: | ||||
|     - snap | ||||
|   | ||||
							
								
								
									
										4
									
								
								build/electron-builder.unpacked.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,4 @@ | ||||
| extends: ./build/electron-builder.base.yml | ||||
| linux: | ||||
|   target: | ||||
|     - dir | ||||
| @@ -1,20 +1,16 @@ | ||||
| appId: com.rickvanlieshout.tidal-hifi | ||||
| electronDownload: | ||||
|   mirror: https://github.com/castlabs/electron-releases/releases/download/v | ||||
| snap: | ||||
|   plugs: | ||||
|     - default | ||||
|     - screen-inhibit-control | ||||
| extends: ./build/electron-builder.base.yml | ||||
| linux: | ||||
|   category: Audio | ||||
|   target: | ||||
|     # - pacman | ||||
|     - pacman | ||||
|     - tar.gz | ||||
|     - deb | ||||
|     - rpm | ||||
|     - AppImage | ||||
|     - snap | ||||
| mac: | ||||
|   category: public.app-category.entertainment | ||||
|     - freebsd | ||||
| win: | ||||
|   target: msi | ||||
|   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,20 +0,0 @@ | ||||
| pkgbase = tidal-hifi-git | ||||
| 	pkgdesc = The web version of listen.tidal.com running in electron with hifi support thanks to widevine. | ||||
| 	pkgver = 1.0 | ||||
| 	pkgrel = 2 | ||||
| 	url = https://github.com/Mastermindzh/tidal-hifi | ||||
| 	arch = x86_64 | ||||
| 	license = custom:MIT | ||||
| 	makedepends = npm | ||||
| 	makedepends = git | ||||
| 	depends = libxss | ||||
| 	depends = nss | ||||
| 	depends = gtk3 | ||||
| 	provides = tidal-hifi | ||||
| 	source = https://github.com/Mastermindzh/tidal-hifi/archive/1.0.zip | ||||
| 	source = tidal-hifi.desktop | ||||
| 	sha512sums = 5b3af830c4043f90ebe9b988ee47214e8b21fb26451baad543e9cd1dcba8a7a1a6b5c751212fa6f9edc791ea9c40fb2122c08a1985c7d75152817b27348f1680 | ||||
| 	sha512sums = fa5fa918ea890baa5f500db3153a6eff3d63966528ffa3349acda3ea02fbecb1ea78a1ba1d23ef7402de2228fc0a483252e0b7e72c73cfb25ed401bedaf856f5 | ||||
|  | ||||
| pkgname = tidal-hifi-git | ||||
|  | ||||
| @@ -1,56 +0,0 @@ | ||||
| # Maintainer: Rick van Lieshout <info@rickvanlieshout.com> | ||||
|  | ||||
| _pkgname=tidal-hifi | ||||
| pkgname="$_pkgname-git" | ||||
| pkgver=1.0 | ||||
| pkgrel=2 | ||||
| pkgdesc="The web version of listen.tidal.com running in electron with hifi support thanks to widevine." | ||||
| arch=("x86_64") | ||||
| url="https://github.com/Mastermindzh/tidal-hifi" | ||||
| license=("custom:MIT") | ||||
|  | ||||
| depends=("libxss" "nss" "gtk3") | ||||
| makedepends=("npm" "git") | ||||
| provides=("$_pkgname") | ||||
|  | ||||
| source=("https://github.com/Mastermindzh/tidal-hifi/archive/$pkgver.zip" | ||||
|         "${_pkgname}.desktop") | ||||
| sha512sums=('5b3af830c4043f90ebe9b988ee47214e8b21fb26451baad543e9cd1dcba8a7a1a6b5c751212fa6f9edc791ea9c40fb2122c08a1985c7d75152817b27348f1680' | ||||
|             'fa5fa918ea890baa5f500db3153a6eff3d63966528ffa3349acda3ea02fbecb1ea78a1ba1d23ef7402de2228fc0a483252e0b7e72c73cfb25ed401bedaf856f5') | ||||
|  | ||||
| cdToPkg(){ | ||||
|     cd "tidal-hifi-$pkgver" | ||||
| } | ||||
|  | ||||
| prepare() { | ||||
|     cdToPkg | ||||
|  | ||||
|     # install build dependencies | ||||
|     npm install | ||||
| } | ||||
|  | ||||
| build() { | ||||
|     cdToPkg | ||||
|  | ||||
|     # We are not using the systems Electron as we need castlab's Electron. | ||||
|     npx electron-builder --linux dir | ||||
| } | ||||
|  | ||||
| package() { | ||||
|     cdToPkg | ||||
|  | ||||
|     install -d "${pkgdir}/opt/${_pkgname}/" "${pkgdir}/usr/bin" "${pkgdir}/usr/share/doc" "${pkgdir}/usr/share/licenses" | ||||
|  | ||||
|     cp -r dist/linux-unpacked/* "${pkgdir}/opt/${_pkgname}/" | ||||
|     chmod +x "${pkgdir}/opt/${_pkgname}/${_pkgname}" | ||||
|  | ||||
|     ln -s "/opt/${_pkgname}/${_pkgname}" "${pkgdir}/usr/bin/${_pkgname}" | ||||
|  | ||||
|     install -Dm 644 "build/icon.png" "${pkgdir}/usr/share/pixmaps/${_pkgname}.png" | ||||
|     install -Dm 644 "${srcdir}/${_pkgname}.desktop" "${pkgdir}/usr/share/applications/${_pkgname}.desktop" | ||||
|  | ||||
|     install -Dm 644 "README.md" "${pkgdir}/usr/share/doc/${pkgname}/README.md" | ||||
|     install -Dm 644 "LICENSE" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" | ||||
|     ln -s "/opt/${_pkgname}/LICENSE.electron.txt" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE.electron.txt" | ||||
|     ln -s "/opt/${_pkgname}/LICENSES.chromium.html" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSES.chromium.html" | ||||
| } | ||||
| @@ -1,18 +0,0 @@ | ||||
| #!/bin/bash | ||||
| # Will generate a correctly formatted SRCINFO file | ||||
|  | ||||
| SCRIPT_DIST=".SRCINFO" | ||||
|  | ||||
| # generate SRCINFO | ||||
| makepkg --printsrcinfo > $SCRIPT_DIST | ||||
|  | ||||
| # replace pkgbase with tidal-hifi-git | ||||
| pkgName="tidal-hifi-git" | ||||
| sed -i "1s/.*/pkgbase = $pkgName/" $SCRIPT_DIST | ||||
|  | ||||
| # replace pkgbase with tidal-hifi-git | ||||
| sed -i '/^pkgname/ d' $SCRIPT_DIST | ||||
| echo "pkgname = $pkgName" >> $SCRIPT_DIST | ||||
|  | ||||
| # remove double line breaks and replace with single line breaks | ||||
| sed -i '/^$/N;/^\n$/D' $SCRIPT_DIST | ||||
| @@ -1,12 +0,0 @@ | ||||
| [Desktop Entry] | ||||
| Encoding=UTF-8 | ||||
| Name=tidal-hifi | ||||
| GenericName=tidal-hifi | ||||
| Comment=The web version of listen.tidal.com running in electron with hifi support thanks to widevine. | ||||
| Exec=tidal-hifi %u | ||||
| Icon=tidal-hifi.png | ||||
| StartupNotify=true | ||||
| Terminal=false | ||||
| Type=Application | ||||
| Categories=Network;Application;Audio;Video | ||||
| StartupWMClass=tidal-hifi | ||||
							
								
								
									
										
											BIN
										
									
								
								docs/integrations.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 47 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/no-dutch-music.mp4
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								docs/settings-preview.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 726 KiB | 
							
								
								
									
										9624
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
							
								
								
									
										51
									
								
								package.json
									
									
									
									
									
								
							
							
						
						| @@ -1,17 +1,25 @@ | ||||
| { | ||||
|   "name": "tidal-hifi", | ||||
|   "version": "1.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-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": "npm run build-without-release -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": "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", | ||||
| @@ -20,20 +28,27 @@ | ||||
|     "linux" | ||||
|   ], | ||||
|   "author": "Rick van Lieshout <info@rickvanlieshout.com> (http://rickvanlieshout.com)", | ||||
|   "homepage": "https://github.com/Mastermindzh/tidal-hifi", | ||||
|   "license": "MIT", | ||||
|   "dependencies": { | ||||
|     "electron-store": "^5.1.1", | ||||
|     "express": "^4.17.1", | ||||
|     "hotkeys-js": "^3.7.6", | ||||
|     "node-notifier": "^6.0.0", | ||||
|     "request": "^2.88.2" | ||||
|     "@electron/remote": "^2.0.9", | ||||
|     "discord-rpc": "^4.0.1", | ||||
|     "electron-store": "^8.1.0", | ||||
|     "express": "^4.18.2", | ||||
|     "hotkeys-js": "^3.10.2", | ||||
|     "mpris-service": "^2.1.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#v6.1.0-wvvmp", | ||||
|     "electron-builder": "^21.2.0", | ||||
|     "electron-reload": "^1.5.0", | ||||
|     "prettier": "^2.0.4" | ||||
|     "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 | ||||
							
								
								
									
										6
									
								
								src/constants/flags.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,6 @@ | ||||
| const flags = { | ||||
|   gpuRasterization: [{ flag: "enable-gpu-rasterization", value: undefined }], | ||||
|   disableHardwareMediaKeys: [{ flag: "disable-features", value: "HardwareMediaKeyHandling" }], | ||||
| }; | ||||
|  | ||||
| module.exports = flags; | ||||
| @@ -6,8 +6,8 @@ const globalEvents = { | ||||
|   previous: "previous", | ||||
|   updateInfo: "update-info", | ||||
|   hideSettings: "hideSettings", | ||||
|   refreshMenuBar: "refreshMenubar", | ||||
|   showSettings: "showSettings", | ||||
|   updateStatus: "update-status", | ||||
|   storeChanged: "storeChanged", | ||||
|   error: "error", | ||||
| }; | ||||
|   | ||||
| @@ -9,14 +9,31 @@ | ||||
|  *    windowBounds: { width: 800, height: 600 }, | ||||
|  */ | ||||
| const settings = { | ||||
|   notifications: "notifications", | ||||
|   adBlock: "adBlock", | ||||
|   api: "api", | ||||
|   menuBar: "menuBar", | ||||
|   playBackControl: "playBackControl", | ||||
|   apiSettings: { | ||||
|     root: "apiSettings", | ||||
|     port: "apiSettings.port", | ||||
|   }, | ||||
|   customCSS: "customCSS", | ||||
|   disableBackgroundThrottle: "disableBackgroundThrottle", | ||||
|   disableHardwareMediaKeys: "disableHardwareMediaKeys", | ||||
|   enableCustomHotkeys: "enableCustomHotkeys", | ||||
|   enableDiscord: "enableDiscord", | ||||
|   flags: { | ||||
|     disableHardwareMediaKeys: "flags.disableHardwareMediaKeys", | ||||
|     gpuRasterization: "flags.gpuRasterization", | ||||
|   }, | ||||
|   menuBar: "menuBar", | ||||
|   minimizeOnClose: "minimizeOnClose", | ||||
|   mpris: "mpris", | ||||
|   notifications: "notifications", | ||||
|   playBackControl: "playBackControl", | ||||
|   singleInstance: "singleInstance", | ||||
|   skipArtists: "skipArtists", | ||||
|   skippedArtists: "skippedArtists", | ||||
|   trayIcon: "trayIcon", | ||||
|   updateFrequency: "updateFrequency", | ||||
|   windowBounds: { | ||||
|     root: "windowBounds", | ||||
|     width: "windowBounds.width", | ||||
|   | ||||
							
								
								
									
										3
									
								
								src/constants/values.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,3 @@ | ||||
| module.exports = { | ||||
|   name: "tidal-hifi", | ||||
| }; | ||||
							
								
								
									
										167
									
								
								src/main.js
									
									
									
									
									
								
							
							
						
						| @@ -1,4 +1,13 @@ | ||||
| const { app, BrowserWindow, globalShortcut, ipcMain } = require("electron"); | ||||
| require("@electron/remote/main").initialize(); | ||||
| const { | ||||
|   app, | ||||
|   BrowserWindow, | ||||
|   components, | ||||
|   globalShortcut, | ||||
|   ipcMain, | ||||
|   protocol, | ||||
|   session, | ||||
| } = require("electron"); | ||||
| const { | ||||
|   settings, | ||||
|   store, | ||||
| @@ -9,24 +18,66 @@ const { | ||||
| } = require("./scripts/settings"); | ||||
| const { addTray, refreshTray } = require("./scripts/tray"); | ||||
| const { addMenu } = require("./scripts/menu"); | ||||
|  | ||||
| const path = require("path"); | ||||
| const tidalUrl = "https://listen.tidal.com"; | ||||
| const expressModule = require("./scripts/express"); | ||||
| const mediaKeys = require("./constants/mediaKeys"); | ||||
| const mediaInfoModule = require("./scripts/mediaInfo"); | ||||
| const discordModule = require("./scripts/discord"); | ||||
| const globalEvents = require("./constants/globalEvents"); | ||||
| const flagValues = require("./constants/flags"); | ||||
|  | ||||
| let mainWindow; | ||||
| let icon = path.join(__dirname, "../assets/icon.png"); | ||||
| const PROTOCOL_PREFIX = "tidal"; | ||||
|  | ||||
| setFlags(); | ||||
|  | ||||
| function setFlags() { | ||||
|   const flags = store.get().flags; | ||||
|   if (flags) { | ||||
|     for (const [key, value] of Object.entries(flags)) { | ||||
|       if (value) { | ||||
|         flagValues[key].forEach((flag) => { | ||||
|           console.log(`enabling command line switch ${flag.flag} with value ${flag.value}`); | ||||
|           app.commandLine.appendSwitch(flag.flag, flag.value); | ||||
|         }); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Fix Display Compositor issue. | ||||
|    */ | ||||
|   app.commandLine.appendSwitch("disable-seccomp-filter-sandbox"); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Enable live reload in development builds | ||||
|  * Update the menuBarVisbility according to the store value | ||||
|  * | ||||
|  */ | ||||
| if (!app.isPackaged) { | ||||
|   require("electron-reload")(`${__dirname}`, { | ||||
|     electron: require(`${__dirname}/../node_modules/electron`), | ||||
|   }); | ||||
| function syncMenuBarWithStore() { | ||||
|   const fixedMenuBar = store.get(settings.menuBar); | ||||
|  | ||||
|   mainWindow.autoHideMenuBar = !fixedMenuBar; | ||||
|   mainWindow.setMenuBarVisibility(fixedMenuBar); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Determine whether the current window is the main window | ||||
|  * if singleInstance is requested. | ||||
|  * If singleInstance isn't requested simply return true | ||||
|  * @returns true if singInstance is not requested, otherwise true/false based on whether the current window is the main window | ||||
|  */ | ||||
| function isMainInstanceOrMultipleInstancesAllowed() { | ||||
|   if (store.get(settings.singleInstance)) { | ||||
|     const gotTheLock = app.requestSingleInstanceLock(); | ||||
|  | ||||
|     if (!gotTheLock) { | ||||
|       return false; | ||||
|     } | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| function createWindow(options = {}) { | ||||
| @@ -34,29 +85,43 @@ function createWindow(options = {}) { | ||||
|   mainWindow = new BrowserWindow({ | ||||
|     x: options.x, | ||||
|     y: options.y, | ||||
|     width: 1024, | ||||
|     height: 800, | ||||
|     width: store && store.get(settings.windowBounds.width), | ||||
|     height: store && store.get(settings.windowBounds.height), | ||||
|     icon, | ||||
|     tray: true, | ||||
|     backgroundColor: options.backgroundColor, | ||||
|     autoHideMenuBar: true, | ||||
|     webPreferences: { | ||||
|       affinity: "window", | ||||
|       sandbox: false, | ||||
|       preload: path.join(__dirname, "preload.js"), | ||||
|       plugins: true, | ||||
|       devTools: !app.isPackaged, | ||||
|       devTools: true, // I like tinkering, others might too | ||||
|     }, | ||||
|   }); | ||||
|  | ||||
|   mainWindow.setMenuBarVisibility(store.get(settings.menuBar)); | ||||
|   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", () => {}); | ||||
|  | ||||
|   mainWindow.on("close", function (event) { | ||||
|     if (!app.isQuiting && store.get(settings.minimizeOnClose)) { | ||||
|       event.preventDefault(); | ||||
|       mainWindow.hide(); | ||||
|       refreshTray(mainWindow); | ||||
|     } | ||||
|     return false; | ||||
|   }); | ||||
|   // Emitted when the window is closed. | ||||
|   mainWindow.on("closed", function() { | ||||
|   mainWindow.on("closed", function () { | ||||
|     closeSettingsWindow(); | ||||
|     app.quit(); | ||||
|   }); | ||||
| @@ -67,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}`, () => { | ||||
| @@ -78,17 +152,32 @@ function addGlobalShortcuts() { | ||||
| // This method will be called when Electron has finished | ||||
| // initialization and is ready to create browser windows. | ||||
| // Some APIs can only be used after this event occurs. | ||||
| app.on("ready", () => { | ||||
|   createWindow(); | ||||
|   addMenu(); | ||||
|   createSettingsWindow(); | ||||
|   addGlobalShortcuts(); | ||||
|   addTray({ icon }); | ||||
|   refreshTray(); | ||||
|   store.get(settings.api) && expressModule.run(mainWindow); | ||||
| 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(mainWindow); | ||||
|     createSettingsWindow(); | ||||
|     addGlobalShortcuts(); | ||||
|     store.get(settings.trayIcon) && addTray(mainWindow, { icon }) && refreshTray(); | ||||
|     store.get(settings.api) && expressModule.run(mainWindow); | ||||
|     store.get(settings.enableDiscord) && discordModule.initRPC(); | ||||
|   } else { | ||||
|     app.quit(); | ||||
|   } | ||||
| }); | ||||
|  | ||||
| app.on("activate", function() { | ||||
| app.on("activate", function () { | ||||
|   // On OS X it's common to re-create a window in the app when the | ||||
|   // dock icon is clicked and there are no other windows open. | ||||
|   if (mainWindow === null) { | ||||
| @@ -96,26 +185,36 @@ app.on("activate", function() { | ||||
|   } | ||||
| }); | ||||
|  | ||||
| // IPC | ||||
| app.on("browser-window-created", (_, window) => { | ||||
|   require("@electron/remote/main").enable(window.webContents); | ||||
| }); | ||||
|  | ||||
| ipcMain.on(globalEvents.updateInfo, (event, arg) => { | ||||
| // IPC | ||||
| ipcMain.on(globalEvents.updateInfo, (_event, arg) => { | ||||
|   mediaInfoModule.update(arg); | ||||
| }); | ||||
|  | ||||
| ipcMain.on(globalEvents.hideSettings, (event, arg) => { | ||||
| ipcMain.on(globalEvents.hideSettings, (_event, _arg) => { | ||||
|   hideSettingsWindow(); | ||||
| }); | ||||
| ipcMain.on(globalEvents.showSettings, (event, arg) => { | ||||
| ipcMain.on(globalEvents.showSettings, (_event, _arg) => { | ||||
|   showSettingsWindow(); | ||||
| }); | ||||
|  | ||||
| ipcMain.on(globalEvents.updateStatus, (event, arg) => { | ||||
|   mediaInfoModule.updateStatus(arg); | ||||
| }); | ||||
| ipcMain.on(globalEvents.storeChanged, (event, arg) => { | ||||
|   mainWindow.setMenuBarVisibility(store.get(settings.menuBar)); | ||||
| ipcMain.on(globalEvents.refreshMenuBar, (_event, _arg) => { | ||||
|   syncMenuBarWithStore(); | ||||
| }); | ||||
|  | ||||
| ipcMain.on(globalEvents.error, (event, arg) => { | ||||
| ipcMain.on(globalEvents.storeChanged, (_event, _arg) => { | ||||
|   syncMenuBarWithStore(); | ||||
|  | ||||
|   if (store.get(settings.enableDiscord) && !discordModule.rpc) { | ||||
|     discordModule.initRPC(); | ||||
|   } else if (!store.get(settings.enableDiscord) && discordModule.rpc) { | ||||
|     discordModule.unRPC(); | ||||
|   } | ||||
| }); | ||||
|  | ||||
| ipcMain.on(globalEvents.error, (event, _arg) => { | ||||
|   console.log(event); | ||||
| }); | ||||
|   | ||||
							
								
								
									
										
											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,47 +1,75 @@ | ||||
| let notifications; | ||||
| let playBackControl; | ||||
| let api; | ||||
| let port; | ||||
| let menuBar; | ||||
| let adBlock, | ||||
|   api, | ||||
|   customCSS, | ||||
|   disableBackgroundThrottle, | ||||
|   disableHardwareMediaKeys, | ||||
|   enableCustomHotkeys, | ||||
|   enableDiscord, | ||||
|   gpuRasterization, | ||||
|   menuBar, | ||||
|   minimizeOnClose, | ||||
|   mpris, | ||||
|   notifications, | ||||
|   playBackControl, | ||||
|   port, | ||||
|   singleInstance, | ||||
|   skipArtists, | ||||
|   skippedArtists, | ||||
|   trayIcon, | ||||
|   updateFrequency; | ||||
|  | ||||
| const { store, settings } = require("./../../scripts/settings"); | ||||
| const { ipcRenderer } = require("electron"); | ||||
| const globalEvents = require("./../../constants/globalEvents"); | ||||
|  | ||||
| const remote = require("@electron/remote"); | ||||
| const { app } = remote; | ||||
| /** | ||||
|  * Sync the UI forms with the current settings | ||||
|  */ | ||||
| function refreshSettings() { | ||||
|   adBlock.checked = store.get(settings.adBlock); | ||||
|   api.checked = store.get(settings.api); | ||||
|   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); | ||||
|   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); | ||||
|   api.checked = store.get(settings.api); | ||||
|   port.value = store.get(settings.apiSettings.port); | ||||
|   menuBar.checked = store.get(settings.menuBar); | ||||
|   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); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Open an url in the default browsers | ||||
|  */ | ||||
| window.openExternal = function(url) { | ||||
| function openExternal(url) { | ||||
|   const { shell } = require("electron"); | ||||
|   shell.openExternal(url); | ||||
| }; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * hide the settings window | ||||
|  */ | ||||
| window.hide = function() { | ||||
| function hide() { | ||||
|   ipcRenderer.send(globalEvents.hideSettings); | ||||
| }; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Restart tidal-hifi after changes | ||||
|  */ | ||||
| window.restart = function() { | ||||
|   const remote = require("electron").remote; | ||||
|   remote.app.relaunch(); | ||||
|   remote.app.exit(0); | ||||
| }; | ||||
| function restart() { | ||||
|   app.relaunch(); | ||||
|   app.exit(); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Bind UI components to functions after DOMContentLoaded | ||||
| @@ -51,8 +79,16 @@ window.addEventListener("DOMContentLoaded", () => { | ||||
|     return document.getElementById(id); | ||||
|   } | ||||
|  | ||||
|   document.getElementById("close").addEventListener("click", hide); | ||||
|   document.getElementById("restart").addEventListener("click", restart); | ||||
|   document.querySelectorAll(".external-link").forEach((elem) => | ||||
|     elem.addEventListener("click", function (event) { | ||||
|       openExternal(event.target.getAttribute("data-url")); | ||||
|     }) | ||||
|   ); | ||||
|  | ||||
|   function addInputListener(source, key) { | ||||
|     source.addEventListener("input", function(event, data) { | ||||
|     source.addEventListener("input", function (_event, _data) { | ||||
|       if (this.value === "on") { | ||||
|         store.set(key, source.checked); | ||||
|       } else { | ||||
| @@ -62,25 +98,60 @@ window.addEventListener("DOMContentLoaded", () => { | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   function addTextAreaListener(source, key) { | ||||
|     source.addEventListener("input", function (_event, _data) { | ||||
|       store.set(key, source.value.split("\n")); | ||||
|       ipcRenderer.send(globalEvents.storeChanged); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   ipcRenderer.on("refreshData", () => { | ||||
|     refreshSettings(); | ||||
|   }); | ||||
|  | ||||
|   ipcRenderer.on("goToTab", (event, tab) => { | ||||
|   ipcRenderer.on("goToTab", (_, tab) => { | ||||
|     document.getElementById(tab).click(); | ||||
|   }); | ||||
|  | ||||
|   adBlock = get("adBlock"); | ||||
|   api = get("apiCheckbox"); | ||||
|   customCSS = get("customCSS"); | ||||
|   disableBackgroundThrottle = get("disableBackgroundThrottle"); | ||||
|   disableHardwareMediaKeys = get("disableHardwareMediaKeys"); | ||||
|   enableCustomHotkeys = get("enableCustomHotkeys"); | ||||
|   enableDiscord = get("enableDiscord"); | ||||
|   gpuRasterization = get("gpuRasterization"); | ||||
|   menuBar = get("menuBar"); | ||||
|   minimizeOnClose = get("minimizeOnClose"); | ||||
|   mpris = get("mprisCheckbox"); | ||||
|   notifications = get("notifications"); | ||||
|   playBackControl = get("playBackControl"); | ||||
|   api = get("apiCheckbox"); | ||||
|   port = get("port"); | ||||
|   menuBar = get("menuBar"); | ||||
|   trayIcon = get("trayIcon"); | ||||
|   skipArtists = get("skipArtists"); | ||||
|   skippedArtists = get("skippedArtists"); | ||||
|   singleInstance = get("singleInstance"); | ||||
|   updateFrequency = get("updateFrequency"); | ||||
|  | ||||
|   refreshSettings(); | ||||
|  | ||||
|   addInputListener(adBlock, settings.adBlock); | ||||
|   addInputListener(api, settings.api); | ||||
|   addTextAreaListener(customCSS, settings.customCSS); | ||||
|   addInputListener(disableBackgroundThrottle, settings.disableBackgroundThrottle); | ||||
|   addInputListener(disableHardwareMediaKeys, settings.flags.disableHardwareMediaKeys); | ||||
|   addInputListener(enableCustomHotkeys, settings.enableCustomHotkeys); | ||||
|   addInputListener(enableDiscord, settings.enableDiscord); | ||||
|   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(api, settings.api); | ||||
|   addInputListener(port, settings.apiSettings.port); | ||||
|   addInputListener(menuBar, settings.menuBar); | ||||
|   addInputListener(skipArtists, settings.skipArtists); | ||||
|   addTextAreaListener(skippedArtists, settings.skippedArtists); | ||||
|   addInputListener(singleInstance, settings.singleInstance); | ||||
|   addInputListener(trayIcon, settings.trayIcon); | ||||
|   addInputListener(updateFrequency, settings.updateFrequency); | ||||
| }); | ||||
|   | ||||
| @@ -2,399 +2,302 @@ | ||||
| <html lang="en"> | ||||
|  | ||||
| <head> | ||||
|     <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 href="javascript:hide();" 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="tab1" checked /> | ||||
|             <label for="tab1">General</label> | ||||
|             <!-- Tab 2 --> | ||||
|             <input type="radio" name="tabset" id="tab2" /> | ||||
|             <label for="tab2">Api</label> | ||||
|             <!-- Tab 3 --> | ||||
|             <input type="radio" name="tabset" id="tab3" /> | ||||
|             <label for="tab3">About</label> | ||||
|       <input type="radio" name="tab" id="api" /> | ||||
|       <label for="api">API</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> | ||||
|                     <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> | ||||
|                 </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. | ||||
|                             <br /> | ||||
|                             <br /> | ||||
|                             <small>* api changes require a restart to update</small> | ||||
|                         </p> | ||||
|       <input type="radio" name="tab" id="integrations" /> | ||||
|       <label for="integrations">Integrations</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> | ||||
|                     <button onClick="restart()">Restart Tidal-hifi</button> | ||||
|                 </section> | ||||
|                 <section id="general" class="tab-panel"> | ||||
|                   <div class="section"> | ||||
|                     <img 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 href ="javascript:openExternal('https://github.com/Mastermindzh/tidal-hifi');">Tidal-hifi</a> is made by <a href ="javascript:openExternal('https://www.rickvanlieshout.com')">Rick van Lieshout</a>.<br /> | ||||
|                       It uses <a href="javascript:openExternal('https://castlabs.com/');">castlabs</a> versions of Electron for widevine support. | ||||
|                     </p> | ||||
|       <input type="radio" name="tab" id="advanced" /> | ||||
|       <label for="advanced">Advanced</label> | ||||
|  | ||||
|                   </div> | ||||
|                 </section> | ||||
|       <input type="radio" name="tab" id="about" /> | ||||
|       <label for="about">About</label> | ||||
|  | ||||
|       <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; | ||||
|         text-decoration: none; | ||||
|         font-size: 1.4rem; | ||||
|         float: right; | ||||
|         margin-right: 15px; | ||||
|         height: 50px; | ||||
|         line-height: 50px; | ||||
|     } | ||||
|  | ||||
|     .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) { | ||||
|         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); | ||||
|     } | ||||
| </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; | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										419
									
								
								src/preload.js
									
									
									
									
									
								
							
							
						
						| @@ -1,15 +1,18 @@ | ||||
| const { setTitle, getTitle } = require("./scripts/window-functions"); | ||||
| const { dialog } = require("electron").remote; | ||||
| const { setTitle } = require("./scripts/window-functions"); | ||||
| 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 notifier = require("node-notifier"); | ||||
| 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; | ||||
|  | ||||
| const elements = { | ||||
|   play: '*[data-test="play"]', | ||||
| @@ -17,7 +20,7 @@ const elements = { | ||||
|   next: '*[data-test="next"]', | ||||
|   previous: 'button[data-test="previous"]', | ||||
|   title: '*[data-test^="footer-track-title"]', | ||||
|   artists: '*[class^="mediaArtists"]', | ||||
|   artists: '*[data-test^="grid-item-detail-text-title-artist"]', | ||||
|   home: '*[data-test="menu--home"]', | ||||
|   back: '[class^="backwardButton"]', | ||||
|   forward: '[class^="forwardButton"]', | ||||
| @@ -28,37 +31,96 @@ const elements = { | ||||
|   account: '*[data-test^="profile-image-button"]', | ||||
|   settings: '*[data-test^="open-settings"]', | ||||
|   media: '*[data-test="current-media-imagery"]', | ||||
|   image: '*[class^="image--"]', | ||||
|  | ||||
|   image: "img", | ||||
|   current: '*[data-test="current-time"]', | ||||
|   duration: '*[data-test="duration"]', | ||||
|   bar: '*[data-test="progress-bar"]', | ||||
|   footer: "#footerPlayer", | ||||
|   album_header_title: '.header-details [data-test="title"]', | ||||
|   playing_title: 'span[data-test="table-cell-title"].css-geqnfr', | ||||
|   album_name_cell: '[data-test="table-cell-album"]', | ||||
|   tracklist_row: '[data-test="tracklist-row"]', | ||||
|   volume: '*[data-test="volume"]', | ||||
|   /** | ||||
|    * Get an element from the dom | ||||
|    * @param {*} key key in elements object to fetch | ||||
|    */ | ||||
|   get: function(key) { | ||||
|   get: function (key) { | ||||
|     return window.document.querySelector(this[key.toLowerCase()]); | ||||
|   }, | ||||
|  | ||||
|   /** | ||||
|    * Get the icon of the current song | ||||
|    */ | ||||
|   getSongIcon: function() { | ||||
|   getSongIcon: function () { | ||||
|     const figure = this.get("media"); | ||||
|  | ||||
|     if (figure) { | ||||
|       const mediaElement = figure.querySelector(this["image"]); | ||||
|       if (mediaElement) { | ||||
|         return mediaElement.src; | ||||
|         return mediaElement.src.replace("80x80", "640x640"); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return ""; | ||||
|   }, | ||||
|  | ||||
|   /** | ||||
|    * 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.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)"; | ||||
|   }, | ||||
|  | ||||
|   getAlbumName: function () { | ||||
|     //If listening to an album, get its name from the header title | ||||
|     if (window.location.href.includes("/album/")) { | ||||
|       const albumName = window.document.querySelector(this.album_header_title); | ||||
|       if (albumName) { | ||||
|         return albumName.textContent; | ||||
|       } | ||||
|       //If listening to a playlist or a mix, get album name from the list | ||||
|     } else if ( | ||||
|       window.location.href.includes("/playlist/") || | ||||
|       window.location.href.includes("/mix/") | ||||
|     ) { | ||||
|       if (currentPlayStatus === statuses.playing) { | ||||
|         const row = window.document.querySelector(this.playing_title).closest(this.tracklist_row); | ||||
|         if (row) { | ||||
|           return row.querySelector(this.album_name_cell).textContent; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return ""; | ||||
|   }, | ||||
|  | ||||
|   isMuted: function () { | ||||
|     return this.get("volume").getAttribute("aria-checked") === "false"; // it's muted if aria-checked is false | ||||
|   }, | ||||
|  | ||||
|   /** | ||||
|    * Shorthand function to get the text of a dom element | ||||
|    * @param {*} key key in elements object to fetch | ||||
|    */ | ||||
|   getText: function(key) { | ||||
|   getText: function (key) { | ||||
|     const element = this.get(key); | ||||
|     return element ? element.textContent : ""; | ||||
|   }, | ||||
| @@ -67,7 +129,7 @@ const elements = { | ||||
|    * Shorthand function to click a dom element | ||||
|    * @param {*} key key in elements object to fetch | ||||
|    */ | ||||
|   click: function(key) { | ||||
|   click: function (key) { | ||||
|     this.get(key).click(); | ||||
|     return this; | ||||
|   }, | ||||
| @@ -76,11 +138,34 @@ const elements = { | ||||
|    * Shorthand function to focus a dom element | ||||
|    * @param {*} key key in elements object to fetch | ||||
|    */ | ||||
|   focus: function(key) { | ||||
|   focus: function (key) { | ||||
|     return this.get(key).focus(); | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| 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 | ||||
|  */ | ||||
| @@ -100,55 +185,41 @@ function playPause() { | ||||
|  * https://defkey.com/tidal-desktop-shortcuts | ||||
|  */ | ||||
| function addHotKeys() { | ||||
|   hotkeys.add("Control+p", function() { | ||||
|     elements.click("account").click("settings"); | ||||
|   }); | ||||
|   hotkeys.add("Control+l", function() { | ||||
|     handleLogout(); | ||||
|   }); | ||||
|   if (store.get(settings.enableCustomHotkeys)) { | ||||
|     hotkeys.add("Control+p", function () { | ||||
|       elements.click("account").click("settings"); | ||||
|     }); | ||||
|     hotkeys.add("Control+l", function () { | ||||
|       handleLogout(); | ||||
|     }); | ||||
|  | ||||
|   hotkeys.add("Control+h", function() { | ||||
|     elements.click("home"); | ||||
|   }); | ||||
|     hotkeys.add("Control+h", function () { | ||||
|       elements.click("home"); | ||||
|     }); | ||||
|  | ||||
|   hotkeys.add("backspace", function() { | ||||
|     elements.click("back"); | ||||
|   }); | ||||
|     hotkeys.add("backspace", function () { | ||||
|       elements.click("back"); | ||||
|     }); | ||||
|  | ||||
|   hotkeys.add("shift+backspace", function() { | ||||
|     elements.click("forward"); | ||||
|   }); | ||||
|     hotkeys.add("shift+backspace", function () { | ||||
|       elements.click("forward"); | ||||
|     }); | ||||
|  | ||||
|   hotkeys.add("control+f", function() { | ||||
|     elements.focus("search"); | ||||
|   }); | ||||
|     hotkeys.add("control+u", function () { | ||||
|       // reloading window without cache should show the update bar if applicable | ||||
|       window.location.reload(true); | ||||
|     }); | ||||
|  | ||||
|   hotkeys.add("control+u", function() { | ||||
|     // reloading window without cache should show the update bar if applicable | ||||
|     window.location.reload(true); | ||||
|   }); | ||||
|     hotkeys.add("control+r", function () { | ||||
|       elements.click("repeat"); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   hotkeys.add("control+left", function() { | ||||
|     elements.click("previous"); | ||||
|   // always add the hotkey for the settings window | ||||
|   hotkeys.add("control+=", function () { | ||||
|     ipcRenderer.send(globalEvents.showSettings); | ||||
|   }); | ||||
|  | ||||
|   hotkeys.add("control+right", function() { | ||||
|     elements.click("next"); | ||||
|   }); | ||||
|  | ||||
|   hotkeys.add("control+right", function() { | ||||
|     elements.click("next"); | ||||
|   }); | ||||
|  | ||||
|   hotkeys.add("control+s", function() { | ||||
|     elements.click("shuffle"); | ||||
|   }); | ||||
|  | ||||
|   hotkeys.add("control+r", function() { | ||||
|     elements.click("repeat"); | ||||
|   }); | ||||
|  | ||||
|   hotkeys.add("control+/", function() { | ||||
|   hotkeys.add("control+0", function () { | ||||
|     ipcRenderer.send(globalEvents.showSettings); | ||||
|   }); | ||||
| } | ||||
| @@ -169,10 +240,10 @@ function handleLogout() { | ||||
|       buttons: logoutOptions, | ||||
|       defaultId: 2, | ||||
|     }, | ||||
|     function(response) { | ||||
|     function (response) { | ||||
|       if (logoutOptions.indexOf("Yes, please") == response) { | ||||
|         for (i = 0; i < window.localStorage.length; i++) { | ||||
|           key = window.localStorage.key(i); | ||||
|         for (let i = 0; i < window.localStorage.length; i++) { | ||||
|           const key = window.localStorage.key(i); | ||||
|           if (key.startsWith("_TIDAL_activeSession")) { | ||||
|             window.localStorage.removeItem(key); | ||||
|             i = window.localStorage.length + 1; | ||||
| @@ -184,13 +255,19 @@ function handleLogout() { | ||||
|   ); | ||||
| } | ||||
|  | ||||
| function addFullScreenListeners() { | ||||
|   window.document.addEventListener("fullscreenchange", () => { | ||||
|     ipcRenderer.send(globalEvents.refreshMenuBar); | ||||
|   }); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Add ipc event listeners. | ||||
|  * Some actions triggered outside of the site need info from the site. | ||||
|  */ | ||||
| function addIPCEventListeners() { | ||||
|   window.addEventListener("DOMContentLoaded", () => { | ||||
|     ipcRenderer.on("globalEvent", (event, args) => { | ||||
|     ipcRenderer.on("globalEvent", (_event, args) => { | ||||
|       switch (args) { | ||||
|         case globalEvents.playPause: | ||||
|           playPause(); | ||||
| @@ -215,61 +292,209 @@ function addIPCEventListeners() { | ||||
| /** | ||||
|  * Update the current status of tidal (e.g playing or paused) | ||||
|  */ | ||||
| function updateStatus() { | ||||
|   const play = elements.get("play"); | ||||
|   let status = statuses.paused; | ||||
|   // if play button is NOT visible tidal is playing | ||||
|   if (!play) { | ||||
| function getCurrentlyPlayingStatus() { | ||||
|   let pause = elements.get("pause"); | ||||
|   let status = undefined; | ||||
|  | ||||
|   // if pause button is visible tidal is playing | ||||
|   if (pause) { | ||||
|     status = statuses.playing; | ||||
|   } else { | ||||
|     status = statuses.paused; | ||||
|   } | ||||
|   ipcRenderer.send(globalEvents.updateStatus, status); | ||||
|   return status; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Convert the duration from MM:SS to seconds | ||||
|  * @param {*} duration | ||||
|  */ | ||||
| function convertDuration(duration) { | ||||
|   const parts = duration.split(":"); | ||||
|   return parseInt(parts[1]) + 60 * parseInt(parts[0]); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Update Tidal-hifi's media info | ||||
|  * | ||||
|  * @param {*} options | ||||
|  */ | ||||
| function updateMediaInfo(options, notify) { | ||||
|   if (options) { | ||||
|     ipcRenderer.send(globalEvents.updateInfo, options); | ||||
|     if (store.get(settings.notifications) && notify) { | ||||
|       new Notification({ title: options.title, body: options.message, icon: options.icon }).show(); | ||||
|     } | ||||
|     if (player) { | ||||
|       player.metadata = { | ||||
|         ...player.metadata, | ||||
|         ...{ | ||||
|           "xesam:title": options.title, | ||||
|           "xesam:artist": [options.message], | ||||
|           "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"; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Checks if Tidal is playing a video or song by grabbing the "a" element from the title. | ||||
|  * If it's a song it returns the track URL, if not it will return undefined | ||||
|  */ | ||||
| function getTrackURL() { | ||||
|   const id = getTrackID(); | ||||
|   return `https://tidal.com/browse/track/${id}`; | ||||
| } | ||||
|  | ||||
| function getTrackID() { | ||||
|   const URLelement = elements.get("title").querySelector("a"); | ||||
|   if (URLelement !== null) { | ||||
|     const id = URLelement.href.replace(/\D/g, ""); | ||||
|     return id; | ||||
|   } | ||||
|  | ||||
|   return window.location; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Watch for song changes and update title + notify | ||||
|  */ | ||||
| setInterval(function() { | ||||
| setInterval(function () { | ||||
|   const title = elements.getText("title"); | ||||
|   const artists = elements.getText("artists"); | ||||
|   const songDashArtistTitle = `${title} - ${artists}`; | ||||
|   const artistsArray = elements.getArtistsArray(); | ||||
|   const artistsString = elements.getArtistsString(artistsArray); | ||||
|   skipArtistsIfFoundInSkippedArtistsList(artistsArray); | ||||
|  | ||||
|   updateStatus(); | ||||
|   const album = elements.getAlbumName(); | ||||
|   const current = elements.getText("current"); | ||||
|   const duration = elements.getText("duration"); | ||||
|   const songDashArtistTitle = `${title} - ${artistsString}`; | ||||
|   const currentStatus = getCurrentlyPlayingStatus(); | ||||
|   const options = { | ||||
|     title, | ||||
|     message: artistsString, | ||||
|     album: album, | ||||
|     status: currentStatus, | ||||
|     url: getTrackURL(), | ||||
|     current, | ||||
|     duration, | ||||
|     "app-name": appName, | ||||
|   }; | ||||
|  | ||||
|   if (getTitle() !== songDashArtistTitle) { | ||||
|     setTitle(songDashArtistTitle); | ||||
|   const titleOrArtistChanged = currentSong !== songDashArtistTitle; | ||||
|  | ||||
|     if (currentSong !== songDashArtistTitle) { | ||||
|       currentSong = songDashArtistTitle; | ||||
|       const image = elements.getSongIcon(); | ||||
|   // update title, url and play info with new info | ||||
|   setTitle(songDashArtistTitle); | ||||
|   getTrackURL(); | ||||
|   currentSong = songDashArtistTitle; | ||||
|   currentPlayStatus = currentStatus; | ||||
|  | ||||
|       const options = { | ||||
|         title, | ||||
|         message: artists, | ||||
|       }; | ||||
|       new Promise((resolve, reject) => { | ||||
|         if (image.startsWith("http")) { | ||||
|           downloadFile(image, notificationPath).then( | ||||
|             () => { | ||||
|               options.icon = notificationPath; | ||||
|               resolve(); | ||||
|             }, | ||||
|             () => { | ||||
|               reject(); | ||||
|             } | ||||
|           ); | ||||
|         } else { | ||||
|           reject(); | ||||
|         } | ||||
|       }).then( | ||||
|   const image = elements.getSongIcon(); | ||||
|  | ||||
|   new Promise((resolve) => { | ||||
|     if (image.startsWith("http")) { | ||||
|       options.image = image; | ||||
|       downloadFile(image, notificationPath).then( | ||||
|         () => { | ||||
|           ipcRenderer.send(globalEvents.updateInfo, options); | ||||
|           store.get(settings.notifications) && notifier.notify(options); | ||||
|           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); | ||||
|     }, | ||||
|     () => {} | ||||
|   ); | ||||
|  | ||||
|   /** | ||||
|    * automatically skip a song if the artists are found in the list of artists to skip | ||||
|    * @param {*} artists array of artists | ||||
|    */ | ||||
|   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"); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| }, 200); | ||||
| }, getUpdateFrequency()); | ||||
|  | ||||
| if (process.platform === "linux" && store.get(settings.mpris)) { | ||||
|   try { | ||||
|     const Player = require("mpris-service"); | ||||
|     player = Player({ | ||||
|       name: "tidal-hifi", | ||||
|       identity: "tidal-hifi", | ||||
|       supportedUriSchemes: ["file"], | ||||
|       supportedMimeTypes: [ | ||||
|         "audio/mpeg", | ||||
|         "audio/flac", | ||||
|         "audio/x-flac", | ||||
|         "application/ogg", | ||||
|         "audio/wav", | ||||
|       ], | ||||
|       supportedInterfaces: ["player"], | ||||
|       desktopEntry: "tidal-hifi", | ||||
|     }); | ||||
|  | ||||
|     // Events | ||||
|     var events = { | ||||
|       next: "next", | ||||
|       previous: "previous", | ||||
|       pause: "pause", | ||||
|       playpause: "playpause", | ||||
|       stop: "stop", | ||||
|       play: "play", | ||||
|       loopStatus: "repeat", | ||||
|       shuffle: "shuffle", | ||||
|       seek: "seek", | ||||
|     }; | ||||
|     Object.keys(events).forEach(function (eventName) { | ||||
|       player.on(eventName, function () { | ||||
|         const eventValue = events[eventName]; | ||||
|         switch (events[eventValue]) { | ||||
|           case events.playpause: | ||||
|             playPause(); | ||||
|             break; | ||||
|  | ||||
|           default: | ||||
|             elements.click(eventValue); | ||||
|         } | ||||
|       }); | ||||
|     }); | ||||
|     // Override get position function | ||||
|     player.getPosition = function () { | ||||
|       return convertDuration(elements.getText("current")) * 1000 * 1000; | ||||
|     }; | ||||
|  | ||||
|     player.on("quit", function () { | ||||
|       app.quit(); | ||||
|     }); | ||||
|   } catch (exception) { | ||||
|     console.log("player api not working"); | ||||
|   } | ||||
| } | ||||
| addCustomCss(); | ||||
| addHotKeys(); | ||||
| addIPCEventListeners(); | ||||
| addFullScreenListeners(); | ||||
|   | ||||
							
								
								
									
										95
									
								
								src/scripts/discord.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,95 @@ | ||||
| const discordrpc = require("discord-rpc"); | ||||
| const { app, ipcMain } = require("electron"); | ||||
| const globalEvents = require("../constants/globalEvents"); | ||||
| const clientId = "833617820704440341"; | ||||
| const mediaInfoModule = require("./mediaInfo"); | ||||
| const discordModule = []; | ||||
|  | ||||
| function timeToSeconds(timeArray) { | ||||
|   let minutes = timeArray[0] * 1; | ||||
|   let seconds = minutes * 60 + timeArray[1] * 1; | ||||
|   return seconds; | ||||
| } | ||||
|  | ||||
| let rpc; | ||||
| const observer = (event, arg) => { | ||||
|   if (mediaInfoModule.mediaInfo.status == "paused" && rpc) { | ||||
|     rpc.setActivity(idleStatus); | ||||
|   } else if (rpc) { | ||||
|     const currentSeconds = timeToSeconds(mediaInfoModule.mediaInfo.current.split(":")); | ||||
|     const durationSeconds = timeToSeconds(mediaInfoModule.mediaInfo.duration.split(":")); | ||||
|     const date = new Date(); | ||||
|     const now = (date.getTime() / 1000) | 0; | ||||
|     const remaining = date.setSeconds(date.getSeconds() + (durationSeconds - currentSeconds)); | ||||
|     if (mediaInfoModule.mediaInfo.url) { | ||||
|       rpc.setActivity({ | ||||
|         ...idleStatus, | ||||
|         ...{ | ||||
|           details: `Listening to ${mediaInfoModule.mediaInfo.title}`, | ||||
|           state: mediaInfoModule.mediaInfo.artist | ||||
|             ? mediaInfoModule.mediaInfo.artist | ||||
|             : "unknown artist(s)", | ||||
|           startTimestamp: parseInt(now), | ||||
|           endTimestamp: parseInt(remaining), | ||||
|           largeImageKey: mediaInfoModule.mediaInfo.image, | ||||
|           largeImageText: mediaInfoModule.mediaInfo.album | ||||
|             ? mediaInfoModule.mediaInfo.album | ||||
|             : `${idleStatus.largeImageText}`, | ||||
|           buttons: [{ label: "Play on Tidal", url: mediaInfoModule.mediaInfo.url }], | ||||
|         }, | ||||
|       }); | ||||
|     } else { | ||||
|       rpc.setActivity({ | ||||
|         ...idleStatus, | ||||
|         ...{ | ||||
|           details: `Watching ${mediaInfoModule.mediaInfo.title}`, | ||||
|           state: mediaInfoModule.mediaInfo.artist, | ||||
|           startTimestamp: parseInt(now), | ||||
|           endTimestamp: parseInt(remaining), | ||||
|         }, | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
| }; | ||||
|  | ||||
| const idleStatus = { | ||||
|   details: `Browsing Tidal`, | ||||
|   largeImageKey: "tidal-hifi-icon", | ||||
|   largeImageText: `Tidal HiFi ${app.getVersion()}`, | ||||
|   instance: false, | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Set up the discord rpc and listen on globalEvents.updateInfo | ||||
|  */ | ||||
| discordModule.initRPC = function () { | ||||
|   rpc = new discordrpc.Client({ transport: "ipc" }); | ||||
|   rpc.login({ clientId }).then( | ||||
|     () => { | ||||
|       discordModule.rpc = rpc; | ||||
|  | ||||
|       rpc.on("ready", () => { | ||||
|         rpc.setActivity(idleStatus); | ||||
|       }); | ||||
|       ipcMain.on(globalEvents.updateInfo, observer); | ||||
|     }, | ||||
|     () => { | ||||
|       console.error("Can't connect to Discord, is it running?"); | ||||
|     } | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Remove any RPC connection with discord and remove the event listener on globalEvents.updateInfo | ||||
|  */ | ||||
| discordModule.unRPC = function () { | ||||
|   if (rpc) { | ||||
|     rpc.clearActivity(); | ||||
|     rpc.destroy(); | ||||
|     rpc = false; | ||||
|     discordModule.rpc = undefined; | ||||
|     ipcMain.removeListener(globalEvents.updateInfo, observer); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| module.exports = discordModule; | ||||
| @@ -3,8 +3,13 @@ const statuses = require("./../constants/statuses"); | ||||
| const mediaInfo = { | ||||
|   title: "", | ||||
|   artist: "", | ||||
|   album: "", | ||||
|   icon: "", | ||||
|   status: statuses.paused, | ||||
|   url: "", | ||||
|   current: "", | ||||
|   duration: "", | ||||
|   image: "tidal-hifi-icon" | ||||
| }; | ||||
| const mediaInfoModule = { | ||||
|   mediaInfo, | ||||
| @@ -13,19 +18,16 @@ const mediaInfoModule = { | ||||
| /** | ||||
|  * Update artist and song info in the mediaInfo constant | ||||
|  */ | ||||
| mediaInfoModule.update = function(arg) { | ||||
| mediaInfoModule.update = function (arg) { | ||||
|   mediaInfo.title = propOrDefault(arg.title); | ||||
|   mediaInfo.artist = propOrDefault(arg.message); | ||||
|   mediaInfo.album = propOrDefault(arg.album); | ||||
|   mediaInfo.icon = propOrDefault(arg.icon); | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Update tidal's status in the mediaInfo constant | ||||
|  */ | ||||
| mediaInfoModule.updateStatus = function(status) { | ||||
|   if (Object.values(statuses).includes(status)) { | ||||
|     mediaInfo.status = status; | ||||
|   } | ||||
|   mediaInfo.url = propOrDefault(arg.url); | ||||
|   mediaInfo.status = propOrDefault(arg.status); | ||||
|   mediaInfo.current = propOrDefault(arg.current); | ||||
|   mediaInfo.duration = propOrDefault(arg.duration); | ||||
|   mediaInfo.image = propOrDefault(arg.image); | ||||
| }; | ||||
|  | ||||
| /** | ||||
|   | ||||
| @@ -1,107 +1,123 @@ | ||||
| const { Menu } = require("electron"); | ||||
| 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 mainMenu = [ | ||||
|   ...(isMac | ||||
|     ? [ | ||||
|         { | ||||
|           label: app.name, | ||||
|           submenu: [ | ||||
|             { role: "about" }, | ||||
|             settingsMenuEntry, | ||||
|             { type: "separator" }, | ||||
|             { role: "services" }, | ||||
|             { type: "separator" }, | ||||
|             { role: "hide" }, | ||||
|             { role: "hideothers" }, | ||||
|             { role: "unhide" }, | ||||
|             { type: "separator" }, | ||||
|             { role: "quit" }, | ||||
|           ], | ||||
|         }, | ||||
|       ] | ||||
|     : []), | ||||
|   // { role: 'fileMenu' } | ||||
|   { | ||||
|     label: "File", | ||||
|     submenu: [settingsMenuEntry, isMac ? { role: "close" } : { role: "quit" }], | ||||
| const quitMenuEntry = { | ||||
|   label: "Quit", | ||||
|   click() { | ||||
|     app.exit(0); | ||||
|   }, | ||||
|   // { role: 'editMenu' } | ||||
|   { | ||||
|     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, | ||||
|     ], | ||||
|   }, | ||||
|   // { role: 'viewMenu' } | ||||
|   { | ||||
|     label: "View", | ||||
|     submenu: [ | ||||
|       { role: "reload" }, | ||||
|       { role: "forcereload" }, | ||||
|       { type: "separator" }, | ||||
|       { role: "resetzoom" }, | ||||
|       { role: "zoomin" }, | ||||
|       { role: "zoomout" }, | ||||
|       { type: "separator" }, | ||||
|       { role: "togglefullscreen" }, | ||||
|     ], | ||||
|   }, | ||||
|   // { role: 'windowMenu' } | ||||
|   { | ||||
|     label: "Window", | ||||
|     submenu: [ | ||||
|       { role: "minimize" }, | ||||
|       ...(isMac | ||||
|         ? [{ type: "separator" }, { role: "front" }, { type: "separator" }, { role: "window" }] | ||||
|         : [{ role: "close" }]), | ||||
|     ], | ||||
|   }, | ||||
|   settingsMenuEntry, | ||||
|   { | ||||
|     label: "About", | ||||
|     click() { | ||||
|       showSettingsWindow("tab3"); | ||||
|   accelerator: "Control+Q", | ||||
| }; | ||||
|  | ||||
| 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,16 +7,41 @@ let settingsWindow; | ||||
|  | ||||
| const store = new Store({ | ||||
|   defaults: { | ||||
|     notifications: true, | ||||
|     adBlock: false, | ||||
|     api: true, | ||||
|     playBackControl: true, | ||||
|     menuBar: true, | ||||
|     apiSettings: { | ||||
|       port: 47836, | ||||
|     }, | ||||
|  | ||||
|     customCSS: "", | ||||
|     disableBackgroundThrottle: true, | ||||
|     disableHardwareMediaKeys: false, | ||||
|     enableCustomHotkeys: false, | ||||
|     enableDiscord: false, | ||||
|     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) => { | ||||
|       console.log("running migrations for 3.1.0"); | ||||
|       migrationStore.set( | ||||
|         settings.flags.disableHardwareMediaKeys, | ||||
|         migrationStore.get("disableHardwareMediaKeys") ?? false | ||||
|       ); | ||||
|     }, | ||||
|   }, | ||||
| }); | ||||
|  | ||||
| const settingsModule = { | ||||
| @@ -25,15 +50,16 @@ const settingsModule = { | ||||
|   settingsWindow, | ||||
| }; | ||||
|  | ||||
| settingsModule.createSettingsWindow = function() { | ||||
| 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: { | ||||
|       affinity: "window", | ||||
|       preload: path.join(__dirname, "../pages/settings/preload.js"), | ||||
|       plugins: true, | ||||
|       nodeIntegration: true, | ||||
| @@ -52,18 +78,18 @@ settingsModule.createSettingsWindow = function() { | ||||
|   settingsModule.settingsWindow = settingsWindow; | ||||
| }; | ||||
|  | ||||
| settingsModule.showSettingsWindow = function(tab = "tab1") { | ||||
| settingsModule.showSettingsWindow = function (tab = "general") { | ||||
|   settingsWindow.webContents.send("goToTab", tab); | ||||
|  | ||||
|   // refresh data just before showing the window | ||||
|   settingsWindow.webContents.send("refreshData"); | ||||
|   settingsWindow.show(); | ||||
| }; | ||||
| settingsModule.hideSettingsWindow = function() { | ||||
| settingsModule.hideSettingsWindow = function () { | ||||
|   settingsWindow.hide(); | ||||
| }; | ||||
|  | ||||
| settingsModule.closeSettingsWindow = function() { | ||||
| settingsModule.closeSettingsWindow = function () { | ||||
|   settingsWindow = null; | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -3,17 +3,32 @@ 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() { | ||||
|   tray.on("click", function(e) { | ||||
|     // do nothing on click | ||||
|   }); | ||||
|  | ||||
|   tray.setToolTip("Tidal-hifi"); | ||||
|   tray.setContextMenu(getMenu()); | ||||
| trayModule.refreshTray = function (mainWindow) { | ||||
|   if (!tray) { | ||||
|     trayModule.addTray(mainWindow); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| module.exports = trayModule; | ||||
|   | ||||
							
								
								
									
										18
									
								
								stale.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,18 @@ | ||||
| # Number of days of inactivity before an issue becomes stale | ||||
| daysUntilStale: 30 | ||||
| # Number of days of inactivity before a stale issue is closed | ||||
| daysUntilClose: 2 | ||||
| # Issues with these labels will never be considered stale | ||||
| exemptLabels: | ||||
|   - good first issue | ||||
|   - waiting for support | ||||
|   - security | ||||
| # Label to use when marking an issue as stale | ||||
| staleLabel: stale | ||||
| # Comment to post when marking an issue as stale. Set to `false` to disable | ||||
| markComment: > | ||||
|   This issue has been automatically marked as stale because it has not had | ||||
|   recent activity. It will be closed if no further activity occurs. Thank you | ||||
|   for your contributions. | ||||
| # Comment to post when closing a stale issue. Set to `false` to disable | ||||
| closeComment: false | ||||