├── .gitattributes ├── .gitignore ├── app ├── assets │ └── icons │ │ ├── logo.png │ │ └── tray │ │ ├── png │ │ ├── icon.png │ │ └── icon@2x.png │ │ ├── win │ │ └── icon.ico │ │ └── osx │ │ ├── iconTemplate.png │ │ └── iconTemplate@2x.png ├── package-lock.json ├── renderer │ ├── css │ │ └── main.css │ ├── index.html │ └── js │ │ └── preload.js ├── constants.js ├── package.json └── main │ ├── index.js │ └── tray.js ├── .editorconfig ├── LICENSE ├── package.json └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | npm-debug.log 4 | package-lock.json -------------------------------------------------------------------------------- /app/assets/icons/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anshumanv/ongaku-desktop/HEAD/app/assets/icons/logo.png -------------------------------------------------------------------------------- /app/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ongaku-desktop", 3 | "version": "1.0.9", 4 | "lockfileVersion": 1 5 | } 6 | -------------------------------------------------------------------------------- /app/assets/icons/tray/png/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anshumanv/ongaku-desktop/HEAD/app/assets/icons/tray/png/icon.png -------------------------------------------------------------------------------- /app/assets/icons/tray/win/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anshumanv/ongaku-desktop/HEAD/app/assets/icons/tray/win/icon.ico -------------------------------------------------------------------------------- /app/assets/icons/tray/png/icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anshumanv/ongaku-desktop/HEAD/app/assets/icons/tray/png/icon@2x.png -------------------------------------------------------------------------------- /app/assets/icons/tray/osx/iconTemplate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anshumanv/ongaku-desktop/HEAD/app/assets/icons/tray/osx/iconTemplate.png -------------------------------------------------------------------------------- /app/assets/icons/tray/osx/iconTemplate@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anshumanv/ongaku-desktop/HEAD/app/assets/icons/tray/osx/iconTemplate@2x.png -------------------------------------------------------------------------------- /app/renderer/css/main.css: -------------------------------------------------------------------------------- 1 | html body { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | /* CSS for the webview element */ 7 | webview { 8 | display: inline-flex; 9 | width: 100%; 10 | height: 100%; 11 | } 12 | -------------------------------------------------------------------------------- /app/constants.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preferences: [ 3 | { 4 | id: 'op', 5 | name: 'Opening' 6 | }, 7 | { 8 | id: 'ed', 9 | name: 'Ending' 10 | }, 11 | { 12 | id: 'ost', 13 | name: 'OST' 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /app/renderer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
](https://sourceforge.net/projects/ongaku/)
40 |
41 | ## License
42 |
43 | MIT © [Anshuman Verma](https://twitter.com/Anshumaniac12)
44 |
--------------------------------------------------------------------------------
/app/main/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env electron
2 |
3 | const path = require('path')
4 | const electron = require('electron')
5 | const tray = require('./tray.js')
6 | require('update-electron-app')()
7 |
8 | const {
9 | app,
10 | BrowserWindow,
11 | globalShortcut,
12 | ipcMain,
13 | webContents
14 | } = electron
15 |
16 | // Prevent window and tray from being garbage collected
17 | let mainWindow
18 | let trayIcon
19 | let closing
20 |
21 | const mainURL = 'file://' + path.join(__dirname, '../renderer', 'index.html');
22 |
23 | const APP_ICON = path.join(__dirname, '../assets/icons', 'logo.png');
24 |
25 | function onTrayToggle(e) {
26 | mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show()
27 | }
28 |
29 | function onTrayClose(e) {
30 | closing = true
31 | mainWindow.close();
32 | }
33 |
34 | function onClosing (e) {
35 | // The window has asked to be closed
36 | if(!closing) {
37 | mainWindow.hide();
38 | e.preventDefault();
39 | }
40 | }
41 |
42 | function onClosed () {
43 | // Dereference the window
44 | // For multiple windows store them in an array
45 | trayIcon = null
46 | mainWindow = null
47 | }
48 |
49 | function createMainWindow () {
50 | const win = new BrowserWindow({
51 | title: 'ongaku',
52 | icon: APP_ICON,
53 | width: 1280,
54 | height: 720,
55 | minWidth: 800,
56 | minHeight: 480,
57 | autoHideMenuBar: true,
58 | show: false
59 | })
60 |
61 | // loading the mainURL in our mainWindow
62 | win.loadURL(mainURL)
63 |
64 | // Handle onClose event
65 | win.on('close', onClosing)
66 | win.on('closed', onClosed)
67 |
68 | win.on('focus', () => {
69 | win.webContents.send('focus');
70 | })
71 |
72 | // Display the contents only when they are ready-to-show
73 | win.on('ready-to-show', () => {
74 | win.show()
75 | })
76 | return win
77 | }
78 |
79 | ipcMain.on('focus', () => {
80 | if (mainWindow) {
81 | mainWindow.focus()
82 | }
83 | })
84 |
85 | app.on('window-all-closed', () => {
86 | if (process.platform !== 'darwin') {
87 | app.quit()
88 | }
89 | })
90 |
91 | app.on('activate', () => {
92 | if (!mainWindow) {
93 | mainWindow = createMainWindow()
94 | }
95 | })
96 |
97 | app.on('ready', () => {
98 | closing = false
99 | mainWindow = createMainWindow()
100 | trayIcon = tray.create(onTrayToggle, onTrayClose, mainWindow)
101 |
102 | const page = mainWindow.webContents
103 |
104 | page.on('dom-ready', () => {
105 | mainWindow.show()
106 | })
107 | })
108 |
--------------------------------------------------------------------------------
/app/main/tray.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const { app, Menu, Tray, ipcMain } = require('electron')
3 | const { preferences } = require('../constants')
4 |
5 | function createTray (onToggle, onClose, mainWindow) {
6 | const imageFolder = path.join(__dirname, '../assets/icons/tray')
7 | let trayImage
8 | if (process.platform == 'win32') {
9 | trayImage = path.join(imageFolder, 'win', 'icon.ico')
10 | } else if (process.platform == 'darwin') {
11 | trayImage = path.join(imageFolder, 'osx', 'iconTemplate.png')
12 | } else {
13 | trayImage = path.join(imageFolder, 'png', 'icon.png')
14 | }
15 |
16 | const execWV = code => (
17 | mainWindow.webContents.executeJavaScript(`
18 | webview = document.querySelector('webview');
19 | webview.executeJavaScript(\`${code}\`);
20 | `)
21 | )
22 |
23 | const musicFN = code => execWV(`
24 | mus = document.querySelector('#music');
25 | ${code}
26 | `)
27 |
28 | const togglePRF = prf => execWV(`
29 | $('input.cb-${prf}').click()
30 | `)
31 |
32 | const contextMenu = [
33 | {
34 | label: 'Toggle Show/Hide',
35 | click: onToggle
36 | },
37 | {
38 | label: 'Preferences',
39 | id: 'prf',
40 | enabled: false,
41 | submenu: []
42 | },
43 | {
44 | type: 'separator'
45 | },
46 | {
47 | label: 'Play/Pause',
48 | click() {
49 | musicFN(`
50 | if (mus.paused) {
51 | mus.play();
52 | } else {
53 | mus.pause();
54 | }
55 | `)
56 | }
57 | },
58 | {
59 | label: 'Restart',
60 | click() {
61 | musicFN(`
62 | mus.currentTime = 0;
63 | if (mus.paused) {
64 | mus.play();
65 | }
66 | `)
67 | }
68 | },
69 | {
70 | type: 'separator'
71 | },
72 | {
73 | label: 'Quit',
74 | click: onClose
75 | }
76 | ]
77 |
78 | const trayIcon = new Tray(trayImage)
79 |
80 | trayIcon.on('click', onToggle)
81 |
82 | trayIcon.setTitle('Ongaku')
83 |
84 | trayIcon.setContextMenu(Menu.buildFromTemplate(contextMenu))
85 | ipcMain.once('loaded', (e, msg) => {
86 | contextMenu.forEach((item, index) => {
87 | if (item.id === 'prf') {
88 | contextMenu[index].enabled = true
89 |
90 | preferences.forEach((prf) => {
91 | contextMenu[index].submenu.push({
92 | label: prf.name,
93 | id: prf.id,
94 | type: 'checkbox',
95 | checked: true,
96 | click() {
97 | togglePRF(prf.id)
98 | }
99 | })
100 |
101 | ipcMain.on(prf.id, (e, msg) => {
102 | contextMenu[index].submenu.forEach((item, idx) => {
103 | if (item.id === prf.id) {
104 | contextMenu[index].submenu[idx].checked = msg
105 | }
106 | })
107 |
108 | trayIcon.setContextMenu(Menu.buildFromTemplate(contextMenu))
109 | })
110 | })
111 | }
112 | })
113 |
114 | trayIcon.setContextMenu(Menu.buildFromTemplate(contextMenu))
115 | })
116 |
117 | trayIcon.setToolTip('Loading...')
118 | ipcMain.on('songName', (e, msg) => {
119 | trayIcon.setToolTip(msg)
120 | })
121 |
122 | return trayIcon
123 | }
124 |
125 | exports.create = createTray
126 |
--------------------------------------------------------------------------------
/app/renderer/js/preload.js:
--------------------------------------------------------------------------------
1 | /*
2 | This script will be loaded before other
3 | scripts run in the webview. See the docs
4 | for more info:
5 |
6 | https://electron.atom.io/docs/api/webview-tag/#preload
7 | */
8 | const { ipcRenderer } = require('electron');
9 | const { preferences } = require('../../constants')
10 |
11 | window.onload = () => {
12 | const songs = (window.osts || []).concat(window.openings, window.endings);
13 |
14 | const getSongName = (src) => {
15 | if (!src) { return ''; }
16 |
17 | const len = songs.length;
18 | for (let i = 0; i < len; i++) {
19 | let song = songs[i];
20 | if (song.link === src) {
21 | return song.name;
22 | }
23 | }
24 |
25 | return '';
26 | };
27 |
28 | const notify = (title, body) => {
29 | const options = { body };
30 | let noti = new Notification(title, options);
31 | noti.addEventListener('click', () => {
32 | ipcRenderer.send('focus', true);
33 | });
34 | };
35 |
36 | const updateToolTip = (songName, state) => {
37 | let str
38 |
39 | switch (state) {
40 | case 'playing':
41 | str = 'Now Playing'
42 | break
43 | case 'pause':
44 | str = 'Paused'
45 | break
46 | default:
47 | str = 'Unicorn' // xd
48 | }
49 |
50 | ipcRenderer.send('songName', `${str}: ${songName}`);
51 | }
52 |
53 | let currentSong;
54 | const audio = document.querySelector('audio');
55 |
56 | // reset current song so play after pause shows notification
57 | audio.addEventListener('pause', () => {
58 | updateToolTip(currentSong, 'pause')
59 |
60 | currentSong = ''
61 | });
62 |
63 | audio.addEventListener('playing', (e)=> {
64 | let src = e && e.target && e.target.currentSrc;
65 | let songName = getSongName(src);
66 | if (songName && songName !== currentSong) {
67 | currentSong = songName;
68 |
69 | notify('Now playing', currentSong);
70 | updateToolTip(currentSong, 'playing')
71 | }
72 | });
73 |
74 | // sets song name for the very first time
75 | const trayToolTip = setInterval(() => {
76 | const src = audio.currentSrc
77 | if (src) {
78 | currentSong = getSongName(src)
79 | if (currentSong) {
80 | updateToolTip(currentSong, 'playing')
81 | clearInterval(trayToolTip)
82 | }
83 | }
84 | }, 200)
85 |
86 | ipcRenderer.send('loaded', true)
87 |
88 | // create an observer instance for preferences
89 | const observer = new MutationObserver((mutations) => {
90 | mutations.forEach((mutation) => {
91 | mutation.addedNodes.forEach((node) => {
92 | if (node.classList.contains('popover')) {
93 | const prover = $('div.popover-content')
94 | preferences.forEach((prf) => {
95 | const input = prover.find(`input.cb-${prf.id}`)
96 | input.click(() => {
97 | ipcRenderer.send(prf.id, input.is(':checked'))
98 | })
99 | })
100 | }
101 | })
102 | })
103 | })
104 | observer.observe(document.querySelector('body'), { childList: true })
105 | };
106 |
--------------------------------------------------------------------------------