feat: electron tray & menu & touchbar supported

master
kunkka 4 years ago
parent 249ad01cf1
commit 9db57acb71

@ -1,2 +1,4 @@
VUE_APP_NETEASE_API_URL=http://localhost:3000 VUE_APP_NETEASE_API_URL=http://127.0.0.1:3000
VUE_APP_ELECTRON_API_URL=http://127.0.0.1:10754
VUE_APP_ENABLE_SENTRY=false VUE_APP_ENABLE_SENTRY=false
DEV_SERVER_PORT=20201

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -25,6 +25,8 @@
import Navbar from "./components/Navbar.vue"; import Navbar from "./components/Navbar.vue";
import Player from "./components/Player.vue"; import Player from "./components/Player.vue";
import GlobalEvents from "vue-global-events"; import GlobalEvents from "vue-global-events";
const electron = window.require('electron')
const ipcRenderer = electron.ipcRenderer
export default { export default {
name: "App", name: "App",
@ -33,6 +35,45 @@ export default {
Player, Player,
GlobalEvents, GlobalEvents,
}, },
created(){
// listens to the main process 'changeRouteTo' event and changes the route from
// inside this Vue instance, according to what path the main process requires.
// responds to Menu click() events at the main process and changes the route accordingly.
ipcRenderer.on('changeRouteTo', (event, path) => {
console.log(event)
this.$router.push(path)
})
ipcRenderer.on('play', () => {
this.$refs.player.play()
})
ipcRenderer.on('next', () => {
this.$refs.player.next()
})
ipcRenderer.on('previous', () => {
this.$refs.player.previous()
})
ipcRenderer.on('increaseVolume', () => {
if (this.$refs.player.volume + 0.1 >= 1) {
return this.$refs.player.volume = 1
}
this.$refs.player.volume += 0.1
})
ipcRenderer.on('decreaseVolume', () => {
if (this.$refs.player.volume - 0.1 <= 0) {
return this.$refs.player.volume = 0
}
this.$refs.player.volume -= 0.1
})
ipcRenderer.on('like', () => {
this.$refs.player.likeCurrentSong()
})
ipcRenderer.on('repeat', () => {
this.$refs.player.repeat()
})
ipcRenderer.on('shuffle', () => {
this.$refs.player.shuffle()
})
},
methods: { methods: {
play(e) { play(e) {
e.preventDefault(); e.preventDefault();

@ -8,14 +8,11 @@ import {
BrowserWindow, BrowserWindow,
ipcMain, ipcMain,
dialog, dialog,
Tray,
globalShortcut, globalShortcut,
} from "electron"; } from "electron";
import { createProtocol } from "vue-cli-plugin-electron-builder/lib"; import { createProtocol } from "vue-cli-plugin-electron-builder/lib";
import installExtension, { VUEJS_DEVTOOLS } from "electron-devtools-installer"; import installExtension, { VUEJS_DEVTOOLS } from "electron-devtools-installer";
// maybe use for modify app menu
// import contextMenu from 'electron-context-menu'
const isDevelopment = process.env.NODE_ENV !== "production"; const isDevelopment = process.env.NODE_ENV !== "production";
// Keep a global reference of the window object, if you don't, the window will // Keep a global reference of the window object, if you don't, the window will
@ -29,26 +26,41 @@ protocol.registerSchemesAsPrivileged([
const iconString = path.join(__static, "img/icons/apple-touch-icon.png") const iconString = path.join(__static, "img/icons/apple-touch-icon.png")
let bounceId = app.dock.bounce() let bounceId = app.dock.bounce()
// app.dock.setBadge('Yes Play Music')
app.dock.setIcon(iconString) app.dock.setIcon(iconString)
function createWindow() { function createWindow() {
require('./electron/services') const touchbar = require('./electron/touchbar.js')
// TODO Set the tray icon, need a white icon const tray = require('./electron/tray.js')
// const trayIcon = path.join(__static, "img/icons/32x32.png") const createMenu = require('./electron/menu.js')
// const tray = new Tray(trayIcon) tray.on('click', function () {
// Create the browser window. if (win.isVisible()) {
win.hide()
} else {
win.show()
}
})
win = new BrowserWindow({ win = new BrowserWindow({
width: 1440, width: 1440,
height: 768, height: 768,
icon: iconString, icon: iconString,
titleBarStyle: 'default',
webPreferences: { webPreferences: {
webSecurity: false, webSecurity: false,
// See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
nodeIntegration: true, nodeIntegration: true,
}, },
preload: path.join(__dirname, "./electron/preload.js"), preload: path.join(__dirname, "./electron/preload.js"),
}); });
try {
createMenu(win)
win.setTouchBar(touchbar)
win.setAutoHideCursor(true)
app.dock.cancelBounce(bounceId)
// autoUpdater.checkForUpdatesAndNotify()
} catch (error) {
console.log(error)
}
if (process.env.WEBPACK_DEV_SERVER_URL) { if (process.env.WEBPACK_DEV_SERVER_URL) {
// Load the url of the dev server if in development mode // Load the url of the dev server if in development mode
win.loadURL(process.env.WEBPACK_DEV_SERVER_URL); win.loadURL(process.env.WEBPACK_DEV_SERVER_URL);
@ -57,9 +69,6 @@ function createWindow() {
createProtocol("app"); createProtocol("app");
// Load the index.html when not in development // Load the index.html when not in development
win.loadURL("app://./index.html"); win.loadURL("app://./index.html");
app.dock.cancelBounce(bounceId)
// autoUpdater.checkForUpdatesAndNotify()
} }
win.on("closed", () => { win.on("closed", () => {
@ -67,8 +76,6 @@ function createWindow() {
}); });
} }
// Quit when all windows are closed. // Quit when all windows are closed.
app.on("window-all-closed", () => { app.on("window-all-closed", () => {
// On macOS it is common for applications and their menu bar // On macOS it is common for applications and their menu bar
@ -90,6 +97,8 @@ app.on("activate", () => {
// initialization and is ready to create browser windows. // initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs. // Some APIs can only be used after this event occurs.
app.on("ready", async () => { app.on("ready", async () => {
// 启动 api 服务器
require('./electron/services.js')
if (isDevelopment && !process.env.IS_TEST) { if (isDevelopment && !process.env.IS_TEST) {
// Install Vue Devtools // Install Vue Devtools
try { try {
@ -149,14 +158,6 @@ if (isDevelopment) {
function initialize() { function initialize() {
const shouldQuit = !app.requestSingleInstanceLock(); const shouldQuit = !app.requestSingleInstanceLock();
if (shouldQuit) return app.quit(); if (shouldQuit) return app.quit();
// loadComponent()
} }
/**
* 注册主线程文件里的所有js
*/
// function loadComponent () {
// require('./electron/menu.js')
// }
initialize(); initialize();

@ -1,121 +0,0 @@
"use strict";
const { app, ipcMain, Menu, MenuItem, BrowserWindow, globalShortcut } = require('electron')
let loginWindow, senders, win
function openWindow(url) {
win = new BrowserWindow({
height: 500,
width: 350,
useContentSize: true,
transparent: false,
frame: false,
darkTheme: true,
backgroundColor: "#FFF",
});
win.loadURL(url);
win.on("closed", () => {
loginWindow = null;
});
return win;
}
const menu = new Menu();
const settingsMenu = {
playMenu: function () {
const settings = {
paly: {
label: "播放",
click: function () {
senders.send("play-start");
},
},
addPlayList: {
label: "添加到播放列表",
click: function () {
senders.send("add-play-list");
},
},
collect: {
label: "收藏",
submenu: [
{
label: "创建新歌单",
click: function () {
senders.send("i-like-star");
},
},
],
},
share: {
label: "分享",
},
copyLink: {
label: "复制链接",
},
};
menu.append(new MenuItem(settings.paly));
menu.append(new MenuItem(settings.addPlayList));
menu.append(new MenuItem({ type: "separator" }));
menu.append(new MenuItem(settings.collect));
menu.append(new MenuItem(settings.share));
menu.append(new MenuItem(settings.copyLink));
},
};
function command(mainWindow, winURL) {
// 显示播放菜单
settingsMenu.playMenu();
// 接收显示菜单指令
ipcMain.on("show-content-menu", (event) => {
senders = event.sender;
const win = BrowserWindow.fromWebContents(senders);
menu.popup(win);
});
// 设置app名称
app.setName("网易云音乐App");
// 关闭window窗口
ipcMain.on("window-close", (event) => {
app.quit();
});
// 最大化window窗口
ipcMain.on("window-max", (event) => {
if (mainWindow.isMaximized()) {
mainWindow.unmaximize();
} else {
mainWindow.maximize();
}
});
// 最小化window窗口
ipcMain.on("window-min", (event) => {
if (!mainWindow.isMinimized()) {
mainWindow.minimize();
}
});
// 新建登录窗口
ipcMain.on("open-login-window", (event, url) => {
if (loginWindow) {
loginWindow.focus();
} else {
loginWindow = openWindow(url);
}
});
// 关闭登录窗口
ipcMain.on("close-login-window", (event) => {
loginWindow.close();
});
// 触发调试 Shift+i
globalShortcut.register("Shift+i", () => {
require("electron-debug")({ showDevTools: true });
});
}
app.on("ready", async () => {
openWindow();
command(win)
});

@ -1,161 +1,203 @@
const { Menu, app } = require("electron"); const { app, Menu } = require('electron')
// import { autoUpdater } from "electron-updater"
// const version = app.getVersion();
const version = app.getVersion(); const isMac = process.platform === 'darwin'
let win; function createMenu(win) {
let updateSource = "menu"; // 更新事件触发来源 menu:通过菜单触发 vue:通过vue页面触发 let menu = null
let template = [ const template = [
...(isMac ? [{
label: app.name,
submenu: [
{ role: 'about' },
{ type: 'separator' },
{ role: 'services' },
{ type: 'separator' },
{ type: 'separator' },
{
label: 'Preferences...',
accelerator: (() => isMac ? 'CmdOrCtrl+,' : 'Ctrl+,')(),
click: () => {
win.webContents.send("changeRouteTo", "/settings")
},
role: 'preferences'
},
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hideothers' },
{ role: 'unhide' },
{ type: 'separator' },
{ role: 'quit' }
]
}] : []),
{ {
label: "编辑", label: 'Edit',
submenu: [ submenu: [
{ role: 'undo' },
{ role: 'redo' },
{ type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' },
...(isMac ? [
{ role: 'delete' },
{ role: 'selectAll' },
{ type: 'separator' },
{ {
label: "剪切", label: 'Speech',
accelerator: (() => { submenu: [
if (process.platform === "darwin") { { role: 'startspeaking' },
return "CmdOrCtrl+X"; { role: 'stopspeaking' }
} else { ]
return "Ctrl+X";
} }
})(), ] : [
role: "cut", { role: 'delete' },
{ type: 'separator' },
{ role: 'selectAll' }
])
]
}, },
{ {
label: "复制", label: 'Controls',
accelerator: (() => { submenu: [
if (process.platform === "darwin") { {
return "CmdOrCtrl+C"; label: 'Play',
} else { accelerator: 'Space',
return "Ctrl+C"; click: () => {
} win.webContents.send("play")
})(), },
role: "copy",
}, },
{ {
label: "粘贴", label: 'Next',
accelerator: (() => { accelerator: 'CmdOrCtrl+Right',
if (process.platform === "darwin") { click: () => {
return "CmdOrCtrl+V"; win.webContents.send("next")
} else {
return "Ctrl+V";
}
})(),
role: "paste",
}, },
],
}, },
{ {
label: "工具", label: 'Previous',
submenu: [ accelerator: 'CmdOrCtrl+Left',
click: () => {
win.webContents.send("previous")
},
},
{ {
label: "刷新", label: 'Increase Volume',
accelerator: (() => { accelerator: 'CmdOrCtrl+Up',
if (process.platform === "darwin") { click: () => {
return "CmdOrCtrl+R"; win.webContents.send("increaseVolume")
} else {
return "F5";
}
})(),
click: (item, focusedWindow) => {
if (focusedWindow) {
focusedWindow.reload();
}
}, },
}, },
{ {
label: "全屏", label: 'Decrease Volume',
accelerator: (() => { accelerator: 'CmdOrCtrl+Down',
if (process.platform === "darwin") { click: () => {
return "Ctrl+Command+F"; win.webContents.send("decreaseVolume")
} else {
return "F11";
}
})(),
click: (item, focusedWindow) => {
if (focusedWindow) {
focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
}
}, },
}, },
{ {
label: "检查", label: 'Like',
accelerator: "F12", accelerator: 'CmdOrCtrl+L',
click: (item, focusedWindow) => { click: () => {
if (focusedWindow) { win.webContents.send("like")
focusedWindow.toggleDevTools();
}
}, },
}, },
], {
label: 'Repeat',
accelerator: 'Alt+R',
click: () => {
win.webContents.send("repeat")
},
},
{
label: 'Shuffle',
accelerator: 'Alt+S',
click: () => {
win.webContents.send("shuffle")
}, },
];
function findReopenMenuItem() {
const menu = Menu.getApplicationMenu();
if (!menu) return;
let reopenMenuItem;
menu.items.forEach((item) => {
if (item.submenu) {
item.submenu.items.forEach((item) => {
if (item.key === "reopenMenuItem") {
reopenMenuItem = item;
}
});
}
});
return reopenMenuItem;
} }
]
// mac 添加退出 },
if (process.platform === "darwin") { {
const name = app.getName(); label: 'Window',
template.unshift({
label: name + " v" + version,
submenu: [ submenu: [
{ role: 'minimize' },
{ role: 'zoom' },
{ role: 'reload' },
{ role: 'forcereload' },
{ role: 'toggledevtools' },
{ type: 'separator' },
{ role: 'togglefullscreen' },
...(isMac ? [
{ type: 'separator' },
{ role: 'front' },
{ type: 'separator' },
{ {
label: "退出", role: 'window',
accelerator: "Command+Q", id: 'window',
label: 'Yes Play Music',
type: 'checkbox',
checked: true,
click: () => { click: () => {
app.quit(); const current = menu.getMenuItemById('window')
}, if (current.checked === false) {
win.hide()
} else {
win.show()
}
}, },
],
});
} }
// win 添加更新菜单 ] : [
if (process.platform === "win32") { { role: 'close' }
template.push({ ])
label: "帮助", ]
},
{
label: 'Help',
submenu: [ submenu: [
{ {
label: `当前版本 v${version}`, label: 'Github',
enabled: false, click: async () => {
const { shell } = require('electron')
await shell.openExternal('https://github.com/qier222/YesPlayMusic')
}
}, },
{ {
label: "检查更新", label: 'Electron',
accelerator: "Ctrl+U", click: async () => {
click: (item, focusedWindow) => { const { shell } = require('electron')
// 执行自动更新检查 await shell.openExternal('https://electronjs.org')
win = focusedWindow; }
updateSource = "menu";
autoUpdater.checkForUpdates();
},
}, },
], ]
});
} }
]
app.on('ready', () => { // for window
const menu = Menu.buildFromTemplate(template) // if (process.platform === "win32") {
// template.push({
// label: "Help",
// submenu: [
// {
// label: `Current version v${version}`,
// enabled: false,
// },
// {
// label: "Check for update",
// accelerator: "Ctrl+U",
// click: (item, focusedWindow) => {
// win = focusedWindow;
// updateSource = "menu";
// autoUpdater.checkForUpdates();
// },
// },
// ],
// });
// }
menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu) Menu.setApplicationMenu(menu)
}) }
module.exports = createMenu
app.on('browser-window-created', () => {
let reopenMenuItem = findReopenMenuItem()
if (reopenMenuItem) reopenMenuItem.enabled = false
})
app.on('window-all-closed', () => {
let reopenMenuItem = findReopenMenuItem()
if (reopenMenuItem) reopenMenuItem.enabled = true
})

@ -1,5 +1,4 @@
const express = require("express"); const express = require("express");
const path = require("path");
const bodyParser = require('body-parser') const bodyParser = require('body-parser')
const cache = require('../../napi/util/apicache').middleware const cache = require('../../napi/util/apicache').middleware
const fileUpload = require('express-fileupload') const fileUpload = require('express-fileupload')
@ -50,8 +49,8 @@ Object.keys(routes).forEach(route => {
app.use(route, routes[route]) app.use(route, routes[route])
}) })
const port = process.env.PORT || 3000 const port = process.env.PORT || 10754
const host = process.env.HOST || '' const host = process.env.HOST || '127.0.0.1'
app.server = app.listen(port, host, () => { app.server = app.listen(port, host, () => {
console.log(`server running @ http://${host ? host : 'localhost'}:${port}`) console.log(`server running @ http://${host ? host : 'localhost'}:${port}`)

@ -1,4 +1,8 @@
const { app, BrowserWindow, TouchBar } = require('electron') const {
app,
BrowserWindow,
TouchBar
} = require('electron')
const { TouchBarLabel, TouchBarButton, TouchBarSpacer } = TouchBar const { TouchBarLabel, TouchBarButton, TouchBarSpacer } = TouchBar
@ -88,16 +92,16 @@ const touchBar = new TouchBar({
] ]
}) })
let window // let window
app.whenReady().then(() => { // app.whenReady().then(() => {
window = new BrowserWindow({ // window = new BrowserWindow({
frame: false, // frame: false,
titleBarStyle: 'hiddenInset', // titleBarStyle: 'hiddenInset',
width: 200, // backgroundColor: '#000'
height: 200, // })
backgroundColor: '#000' // window.loadURL('about:blank')
}) // window.setTouchBar(touchBar)
window.loadURL('about:blank') // })
window.setTouchBar(touchBar)
}) module.exports = touchBar

@ -0,0 +1,24 @@
const path = require('path')
const { Menu, Tray } = require('electron')
let tray = null
const macIcon = path.join(__static, "img/icons/menu.png")
const winIcon = path.join(__static, "img/icons/icon.ico")
tray = new Tray(macIcon)
// Temporary no need for menu.
// const contextMenu = Menu.buildFromTemplate([
// { label: 'Item1', type: 'radio' },
// { label: 'Item2', type: 'radio' }
// ])
// Make a change to the context menu
// contextMenu.items[1].checked = false
// tray.setToolTip('Yes Play Music')
// Call this again for Linux because we modified the context menu
// tray.setContextMenu(contextMenu)
module.exports = tray

@ -1,7 +1,15 @@
import axios from "axios"; import axios from "axios";
let baseURL = ''
// Web 和 Electron 跑在不同端口避免同时启动时冲突
if (process.env.IS_ELECTRON) {
baseURL = process.env.VUE_APP_ELECTRON_API_URL
} else {
baseURL = process.env.VUE_APP_NETEASE_API_URL
}
const service = axios.create({ const service = axios.create({
baseURL: process.env.VUE_APP_NETEASE_API_URL, baseURL,
withCredentials: true, withCredentials: true,
timeout: 15000, timeout: 15000,
}); });

Loading…
Cancel
Save