mirror of
				https://github.com/Mastermindzh/tidal-hifi.git
				synced 2025-11-04 02:39:13 +01:00 
			
		
		
		
	added settings
This commit is contained in:
		
							
								
								
									
										12
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								README.md
									
									
									
									
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
<h1>
 | 
			
		||||
Tidal-hifi
 | 
			
		||||
<img src = "./build/icon.png" height="30" align="right" />
 | 
			
		||||
<img src = "./build/icon.png" height="40" align="right" />
 | 
			
		||||
</h1>
 | 
			
		||||
 | 
			
		||||
The web version of [listen.tidal.com](listen.tidal.com) running in electron with hifi support thanks to widevine.
 | 
			
		||||
@@ -14,6 +14,7 @@ The web version of [listen.tidal.com](listen.tidal.com) running in electron with
 | 
			
		||||
- [Installation](#installation)
 | 
			
		||||
  - [Using releases](#using-releases)
 | 
			
		||||
  - [Using source](#using-source)
 | 
			
		||||
- [features](#features)
 | 
			
		||||
- [Integrations](#integrations)
 | 
			
		||||
- [Why](#why)
 | 
			
		||||
- [Why not extend existing projects?](#why-not-extend-existing-projects)
 | 
			
		||||
@@ -36,6 +37,15 @@ To install and work with the code on this project follow these steps:
 | 
			
		||||
- npm install
 | 
			
		||||
- npm start
 | 
			
		||||
 | 
			
		||||
## features
 | 
			
		||||
 | 
			
		||||
- HiFi playback
 | 
			
		||||
- Notifications
 | 
			
		||||
- Shortcuts ([source](https://defkey.com/tidal-desktop-shortcuts))
 | 
			
		||||
- API for status and playback
 | 
			
		||||
- [Settings feature](./docs/settings.png) to disable certain functionality.
 | 
			
		||||
- Tray player (coming soon)
 | 
			
		||||
 | 
			
		||||
## Integrations
 | 
			
		||||
 | 
			
		||||
- [i3 blocks config](https://github.com/Mastermindzh/dotfiles/commit/9714b2fa1d670108ce811d5511fd3b7a43180647) - My dotfiles where I use this app to fetch currently playing music (direct commit)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								docs/settings.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/settings.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 317 KiB  | 
@@ -4,6 +4,12 @@ const globalEvents = {
 | 
			
		||||
  playPause: "playPause",
 | 
			
		||||
  next: "next",
 | 
			
		||||
  previous: "previous",
 | 
			
		||||
  updateInfo: "update-info",
 | 
			
		||||
  hideSettings: "hideSettings",
 | 
			
		||||
  showSettings: "showSettings",
 | 
			
		||||
  updateStatus: "update-status",
 | 
			
		||||
  storeChanged: "storeChanged",
 | 
			
		||||
  error: "error",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports = globalEvents;
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,8 @@
 | 
			
		||||
const settings = {
 | 
			
		||||
  notifications: "notifications",
 | 
			
		||||
  api: "api",
 | 
			
		||||
  menuBar: "menuBar",
 | 
			
		||||
  playBackControl: "playBackControl",
 | 
			
		||||
  apiSettings: {
 | 
			
		||||
    root: "apiSettings",
 | 
			
		||||
    port: "apiSettings.port",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										35
									
								
								src/main.js
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								src/main.js
									
									
									
									
									
								
							@@ -1,5 +1,12 @@
 | 
			
		||||
const { app, BrowserWindow, globalShortcut, ipcMain } = require("electron");
 | 
			
		||||
const { settings, store } = require("./scripts/settings");
 | 
			
		||||
const {
 | 
			
		||||
  settings,
 | 
			
		||||
  store,
 | 
			
		||||
  createSettingsWindow,
 | 
			
		||||
  showSettingsWindow,
 | 
			
		||||
  closeSettingsWindow,
 | 
			
		||||
  hideSettingsWindow,
 | 
			
		||||
} = require("./scripts/settings");
 | 
			
		||||
const { addTray, refreshTray } = require("./scripts/tray");
 | 
			
		||||
 | 
			
		||||
const path = require("path");
 | 
			
		||||
@@ -7,6 +14,7 @@ const tidalUrl = "https://listen.tidal.com";
 | 
			
		||||
const expressModule = require("./scripts/express");
 | 
			
		||||
const mediaKeys = require("./constants/mediaKeys");
 | 
			
		||||
const mediaInfoModule = require("./scripts/mediaInfo");
 | 
			
		||||
const globalEvents = require("./constants/globalEvents");
 | 
			
		||||
 | 
			
		||||
let mainWindow;
 | 
			
		||||
let icon = path.join(__dirname, "../assets/icon.png");
 | 
			
		||||
@@ -34,10 +42,11 @@ function createWindow(options = {}) {
 | 
			
		||||
      affinity: "window",
 | 
			
		||||
      preload: path.join(__dirname, "preload.js"),
 | 
			
		||||
      plugins: true,
 | 
			
		||||
      devTools: !app.isPackaged,
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  mainWindow.setMenuBarVisibility(false);
 | 
			
		||||
  mainWindow.setMenuBarVisibility(store.get(settings.menuBar));
 | 
			
		||||
 | 
			
		||||
  // load the Tidal website
 | 
			
		||||
  mainWindow.loadURL(tidalUrl);
 | 
			
		||||
@@ -47,7 +56,8 @@ function createWindow(options = {}) {
 | 
			
		||||
 | 
			
		||||
  // Emitted when the window is closed.
 | 
			
		||||
  mainWindow.on("closed", function() {
 | 
			
		||||
    mainWindow = null;
 | 
			
		||||
    closeSettingsWindow();
 | 
			
		||||
    app.quit();
 | 
			
		||||
  });
 | 
			
		||||
  mainWindow.on("resize", () => {
 | 
			
		||||
    let { width, height } = mainWindow.getBounds();
 | 
			
		||||
@@ -69,6 +79,7 @@ function addGlobalShortcuts() {
 | 
			
		||||
// Some APIs can only be used after this event occurs.
 | 
			
		||||
app.on("ready", () => {
 | 
			
		||||
  createWindow();
 | 
			
		||||
  createSettingsWindow();
 | 
			
		||||
  addGlobalShortcuts();
 | 
			
		||||
  addTray({ icon });
 | 
			
		||||
  refreshTray();
 | 
			
		||||
@@ -85,10 +96,24 @@ app.on("activate", function() {
 | 
			
		||||
 | 
			
		||||
// IPC
 | 
			
		||||
 | 
			
		||||
ipcMain.on("update-info", (event, arg) => {
 | 
			
		||||
ipcMain.on(globalEvents.updateInfo, (event, arg) => {
 | 
			
		||||
  mediaInfoModule.update(arg);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
ipcMain.on("update-status", (event, arg) => {
 | 
			
		||||
ipcMain.on(globalEvents.hideSettings, (event, arg) => {
 | 
			
		||||
  hideSettingsWindow();
 | 
			
		||||
});
 | 
			
		||||
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.error, (event, arg) => {
 | 
			
		||||
  console.log(event);
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										74
									
								
								src/pages/settings/preload.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								src/pages/settings/preload.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
			
		||||
let notifications;
 | 
			
		||||
let playBackControl;
 | 
			
		||||
let api;
 | 
			
		||||
let port;
 | 
			
		||||
let menuBar;
 | 
			
		||||
 | 
			
		||||
const { store, settings } = require("./../../scripts/settings");
 | 
			
		||||
const { ipcRenderer } = require("electron");
 | 
			
		||||
const globalEvents = require("./../../constants/globalEvents");
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Sync the UI forms with the current settings
 | 
			
		||||
 */
 | 
			
		||||
function refreshSettings() {
 | 
			
		||||
  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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * hide the settings window
 | 
			
		||||
 */
 | 
			
		||||
window.hide = function() {
 | 
			
		||||
  ipcRenderer.send(globalEvents.hideSettings);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Restart tidal-hifi after changes
 | 
			
		||||
 */
 | 
			
		||||
window.restart = function() {
 | 
			
		||||
  const remote = require("electron").remote;
 | 
			
		||||
  remote.app.relaunch();
 | 
			
		||||
  remote.app.exit(0);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Bind UI components to functions after DOMContentLoaded
 | 
			
		||||
 */
 | 
			
		||||
window.addEventListener("DOMContentLoaded", () => {
 | 
			
		||||
  function get(id) {
 | 
			
		||||
    return document.getElementById(id);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function addInputListener(source, key) {
 | 
			
		||||
    source.addEventListener("input", function(event, data) {
 | 
			
		||||
      if (this.value === "on") {
 | 
			
		||||
        store.set(key, source.checked);
 | 
			
		||||
      } else {
 | 
			
		||||
        store.set(key, this.value);
 | 
			
		||||
      }
 | 
			
		||||
      ipcRenderer.send(globalEvents.storeChanged);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ipcRenderer.on("refreshData", () => {
 | 
			
		||||
    refreshSettings();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  notifications = get("notifications");
 | 
			
		||||
  playBackControl = get("playBackControl");
 | 
			
		||||
  api = get("apiCheckbox");
 | 
			
		||||
  port = get("port");
 | 
			
		||||
  menuBar = get("menuBar");
 | 
			
		||||
 | 
			
		||||
  refreshSettings();
 | 
			
		||||
 | 
			
		||||
  addInputListener(notifications, settings.notifications);
 | 
			
		||||
  addInputListener(playBackControl, settings.playBackControl);
 | 
			
		||||
  addInputListener(api, settings.api);
 | 
			
		||||
  addInputListener(port, settings.apiSettings.port);
 | 
			
		||||
  addInputListener(menuBar, settings.menuBar);
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										387
									
								
								src/pages/settings/settings.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										387
									
								
								src/pages/settings/settings.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,387 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<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" />
 | 
			
		||||
</head>
 | 
			
		||||
 | 
			
		||||
<body>
 | 
			
		||||
    <div class="header">
 | 
			
		||||
        <h1 class="title">Settings</h1>
 | 
			
		||||
        <a href="javascript:hide();" class="exitWindow">
 | 
			
		||||
 | 
			
		||||
            <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>
 | 
			
		||||
 | 
			
		||||
        </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>
 | 
			
		||||
 | 
			
		||||
            <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>
 | 
			
		||||
 | 
			
		||||
                        <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>
 | 
			
		||||
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </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);
 | 
			
		||||
        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);
 | 
			
		||||
        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>
 | 
			
		||||
@@ -9,6 +9,7 @@ const hotkeys = require("./scripts/hotkeys");
 | 
			
		||||
const globalEvents = require("./constants/globalEvents");
 | 
			
		||||
const notifier = require("node-notifier");
 | 
			
		||||
const notificationPath = `${app.getPath("userData")}/notification.jpg`;
 | 
			
		||||
let currentSong = "";
 | 
			
		||||
 | 
			
		||||
const elements = {
 | 
			
		||||
  play: '*[data-test="play"]',
 | 
			
		||||
@@ -146,6 +147,10 @@ function addHotKeys() {
 | 
			
		||||
  hotkeys.add("control+r", function() {
 | 
			
		||||
    elements.click("repeat");
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  hotkeys.add("control+/", function() {
 | 
			
		||||
    ipcRenderer.send(globalEvents.showSettings);
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -185,10 +190,6 @@ function handleLogout() {
 | 
			
		||||
 */
 | 
			
		||||
function addIPCEventListeners() {
 | 
			
		||||
  window.addEventListener("DOMContentLoaded", () => {
 | 
			
		||||
    ipcRenderer.on("getPlayInfo", () => {
 | 
			
		||||
      alert(`${elements.getText("title")} - ${elements.getText("artists")}`);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    ipcRenderer.on("globalEvent", (event, args) => {
 | 
			
		||||
      switch (args) {
 | 
			
		||||
        case globalEvents.playPause:
 | 
			
		||||
@@ -221,7 +222,7 @@ function updateStatus() {
 | 
			
		||||
  if (!play) {
 | 
			
		||||
    status = statuses.playing;
 | 
			
		||||
  }
 | 
			
		||||
  ipcRenderer.send("update-status", status);
 | 
			
		||||
  ipcRenderer.send(globalEvents.updateStatus, status);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -237,33 +238,36 @@ setInterval(function() {
 | 
			
		||||
  if (getTitle() !== songDashArtistTitle) {
 | 
			
		||||
    setTitle(songDashArtistTitle);
 | 
			
		||||
 | 
			
		||||
    const image = elements.getSongIcon();
 | 
			
		||||
    if (currentSong !== songDashArtistTitle) {
 | 
			
		||||
      currentSong = songDashArtistTitle;
 | 
			
		||||
      const image = elements.getSongIcon();
 | 
			
		||||
 | 
			
		||||
    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(
 | 
			
		||||
      () => {
 | 
			
		||||
        ipcRenderer.send("update-info", options);
 | 
			
		||||
        store.get(settings.notifications) && notifier.notify(options);
 | 
			
		||||
      },
 | 
			
		||||
      () => {}
 | 
			
		||||
    );
 | 
			
		||||
      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(
 | 
			
		||||
        () => {
 | 
			
		||||
          ipcRenderer.send(globalEvents.updateInfo, options);
 | 
			
		||||
          store.get(settings.notifications) && notifier.notify(options);
 | 
			
		||||
        },
 | 
			
		||||
        () => {}
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}, 200);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,9 +3,10 @@ const { mediaInfo } = require("./mediaInfo");
 | 
			
		||||
const { store, settings } = require("./settings");
 | 
			
		||||
const globalEvents = require("./../constants/globalEvents");
 | 
			
		||||
const statuses = require("./../constants/statuses");
 | 
			
		||||
 | 
			
		||||
const expressModule = {};
 | 
			
		||||
var fs = require("fs");
 | 
			
		||||
const fs = require("fs");
 | 
			
		||||
 | 
			
		||||
let expressInstance;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Function to enable tidal-hifi's express api
 | 
			
		||||
@@ -24,17 +25,6 @@ expressModule.run = function(mainWindow) {
 | 
			
		||||
  const expressApp = express();
 | 
			
		||||
  expressApp.get("/", (req, res) => res.send("Hello World!"));
 | 
			
		||||
  expressApp.get("/current", (req, res) => res.json(mediaInfo));
 | 
			
		||||
  expressApp.get("/play", (req, res) => handleGlobalEvent(res, globalEvents.play));
 | 
			
		||||
  expressApp.get("/pause", (req, res) => handleGlobalEvent(res, globalEvents.pause));
 | 
			
		||||
  expressApp.get("/next", (req, res) => handleGlobalEvent(res, globalEvents.next));
 | 
			
		||||
  expressApp.get("/previous", (req, res) => handleGlobalEvent(res, globalEvents.previous));
 | 
			
		||||
  expressApp.get("/playpause", (req, res) => {
 | 
			
		||||
    if (mediaInfo.status == statuses.playing) {
 | 
			
		||||
      handleGlobalEvent(res, globalEvents.pause);
 | 
			
		||||
    } else {
 | 
			
		||||
      handleGlobalEvent(res, globalEvents.play);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
  expressApp.get("/image", (req, res) => {
 | 
			
		||||
    var stream = fs.createReadStream(mediaInfo.icon);
 | 
			
		||||
    stream.on("open", function() {
 | 
			
		||||
@@ -47,7 +37,34 @@ expressModule.run = function(mainWindow) {
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  expressApp.listen(store.get(settings.apiSettings.port), () => {});
 | 
			
		||||
  if (store.get(settings.playBackControl)) {
 | 
			
		||||
    expressApp.get("/play", (req, res) => handleGlobalEvent(res, globalEvents.play));
 | 
			
		||||
    expressApp.get("/pause", (req, res) => handleGlobalEvent(res, globalEvents.pause));
 | 
			
		||||
    expressApp.get("/next", (req, res) => handleGlobalEvent(res, globalEvents.next));
 | 
			
		||||
    expressApp.get("/previous", (req, res) => handleGlobalEvent(res, globalEvents.previous));
 | 
			
		||||
    expressApp.get("/playpause", (req, res) => {
 | 
			
		||||
      if (mediaInfo.status == statuses.playing) {
 | 
			
		||||
        handleGlobalEvent(res, globalEvents.pause);
 | 
			
		||||
      } else {
 | 
			
		||||
        handleGlobalEvent(res, globalEvents.play);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  if (store.get(settings.api)) {
 | 
			
		||||
    let port = store.get(settings.apiSettings.port);
 | 
			
		||||
 | 
			
		||||
    expressInstance = expressApp.listen(port, () => {});
 | 
			
		||||
    expressInstance.on("error", function(e) {
 | 
			
		||||
      let message = e.code;
 | 
			
		||||
      if (e.code === "EADDRINUSE") {
 | 
			
		||||
        message = `Port ${port} in use.`;
 | 
			
		||||
      }
 | 
			
		||||
      const { dialog } = require("electron");
 | 
			
		||||
      dialog.showErrorBox("Api failed to start.", message);
 | 
			
		||||
    });
 | 
			
		||||
  } else {
 | 
			
		||||
    expressInstance = undefined;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports = expressModule;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,20 @@
 | 
			
		||||
const Store = require("electron-store");
 | 
			
		||||
const settings = require("./../constants/settings");
 | 
			
		||||
const path = require("path");
 | 
			
		||||
const { BrowserWindow } = require("electron");
 | 
			
		||||
 | 
			
		||||
let settingsWindow;
 | 
			
		||||
 | 
			
		||||
const store = new Store({
 | 
			
		||||
  defaults: {
 | 
			
		||||
    notifications: true,
 | 
			
		||||
    api: true,
 | 
			
		||||
    playBackControl: true,
 | 
			
		||||
    menuBar: false,
 | 
			
		||||
    apiSettings: {
 | 
			
		||||
      port: 47836,
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    windowBounds: { width: 800, height: 600 },
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
@@ -15,6 +22,47 @@ const store = new Store({
 | 
			
		||||
const settingsModule = {
 | 
			
		||||
  store,
 | 
			
		||||
  settings,
 | 
			
		||||
  settingsWindow,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
settingsModule.createSettingsWindow = function() {
 | 
			
		||||
  settingsWindow = new BrowserWindow({
 | 
			
		||||
    width: 500,
 | 
			
		||||
    height: 600,
 | 
			
		||||
    show: false,
 | 
			
		||||
    frame: false,
 | 
			
		||||
    title: "Tidal-hifi - settings",
 | 
			
		||||
    webPreferences: {
 | 
			
		||||
      affinity: "window",
 | 
			
		||||
      preload: path.join(__dirname, "../pages/settings/preload.js"),
 | 
			
		||||
      plugins: true,
 | 
			
		||||
      nodeIntegration: true,
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  settingsWindow.on("close", (event) => {
 | 
			
		||||
    if (settingsWindow != null) {
 | 
			
		||||
      event.preventDefault();
 | 
			
		||||
      settingsWindow.hide();
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  settingsWindow.loadURL(`file://${__dirname}/../pages/settings/settings.html`);
 | 
			
		||||
 | 
			
		||||
  settingsModule.settingsWindow = settingsWindow;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
settingsModule.showSettingsWindow = function() {
 | 
			
		||||
  // refresh data just before showing the window
 | 
			
		||||
  settingsWindow.webContents.send("refreshData");
 | 
			
		||||
  settingsWindow.show();
 | 
			
		||||
};
 | 
			
		||||
settingsModule.hideSettingsWindow = function() {
 | 
			
		||||
  settingsWindow.hide();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
settingsModule.closeSettingsWindow = function() {
 | 
			
		||||
  settingsWindow = null;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports = settingsModule;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user