diff --git a/app/electron/init.html b/app/electron/init.html index e28631878..e034fbb09 100644 --- a/app/electron/init.html +++ b/app/electron/init.html @@ -358,8 +358,10 @@ if (!fs.existsSync(initPath)) { fs.mkdirSync(initPath, {mode: 0o755, recursive: true}) } - const lang = document.querySelector('.lang').value - ipcRenderer.send('siyuan-first-init', `${initPath}-${lang}`) + ipcRenderer.send('siyuan-first-init', { + workspace: initPath, + lang: document.querySelector('.lang').value + }) } }) }) diff --git a/app/electron/main.js b/app/electron/main.js index 11a7f1c83..98cec7dbf 100644 --- a/app/electron/main.js +++ b/app/electron/main.js @@ -27,7 +27,7 @@ const { } = require('electron') const path = require('path') const fs = require('fs') -const net = require("net"); +const net = require('net') const fetch = require('electron-fetch').default process.noAsar = true const appDir = path.dirname(app.getAppPath()) @@ -35,14 +35,13 @@ const isDevEnv = process.env.NODE_ENV === 'development' const appVer = app.getVersion() const confDir = path.join(app.getPath('home'), '.config', 'siyuan') const windowStatePath = path.join(confDir, 'windowState.json') -let tray // 托盘必须使用全局变量,以防止被垃圾回收 https://www.electronjs.org/docs/faq#my-apps-windowtray-disappeared-after-a-few-minutes -let mainWindow // 从托盘处激活报错 https://github.com/siyuan-note/siyuan/issues/769 let firstOpenWindow, bootWindow -let closeButtonBehavior = 0 let siyuanOpenURL let firstOpen = false let resetWindowStateOnRestart = false -const localhost = "127.0.0.1" +let workspaces = [] +const localhost = '127.0.0.1' +let kernelPort = 6806 require('@electron/remote/main').initialize() if (!app.requestSingleInstanceLock()) { @@ -50,6 +49,28 @@ if (!app.requestSingleInstanceLock()) { return } +try { + firstOpen = !fs.existsSync(path.join(confDir, 'workspace.json')) + if (!fs.existsSync(confDir)) { + fs.mkdirSync(confDir, {mode: 0o755, recursive: true}) + } +} catch (e) { + console.error(e) + require('electron'). + dialog. + showErrorBox('创建配置目录失败 Failed to create config directory', + '思源需要在用户家目录下创建配置文件夹(~/.config/siyuan),请确保该路径具有写入权限。\n\nSiYuan needs to create a configuration folder (~/.config/siyuan) in the user\'s home directory. Please make sure that the path has write permissions.') + app.exit() +} + +const getServer = () => { + return 'http://' + localhost + ':' + kernelPort +} + +const sleep = (ms) => { + return new Promise(resolve => setTimeout(resolve, ms)) +} + const showErrorWindow = (title, content) => { let errorHTMLPath = path.join(appDir, 'app', 'electron', 'error.html') if (isDevEnv) { @@ -81,18 +102,6 @@ const showErrorWindow = (title, content) => { errWindow.show() } -try { - firstOpen = !fs.existsSync(path.join(confDir, 'workspace.json')) - if (!fs.existsSync(confDir)) { - fs.mkdirSync(confDir, {mode: 0o755, recursive: true}) - } -} catch (e) { - console.error(e) - require('electron').dialog.showErrorBox('创建配置目录失败 Failed to create config directory', - '思源需要在用户家目录下创建配置文件夹(~/.config/siyuan),请确保该路径具有写入权限。\n\nSiYuan needs to create a configuration folder (~/.config/siyuan) in the user\'s home directory. Please make sure that the path has write permissions.') - app.exit() -} - const writeLog = (out) => { console.log(out) const logFile = path.join(confDir, 'app.log') @@ -173,7 +182,7 @@ const boot = () => { } // 创建主窗体 - mainWindow = new BrowserWindow({ + const currentWindow = new BrowserWindow({ show: false, backgroundColor: '#FFF', // 桌面端主窗体背景色设置为 `#FFF` Fix https://github.com/siyuan-note/siyuan/issues/4544 width: windowState.width, @@ -196,15 +205,14 @@ const boot = () => { titleBarStyle: 'hidden', icon: path.join(appDir, 'stage', 'icon-large.png'), }) - - require('@electron/remote/main').enable(mainWindow.webContents) - mainWindow.webContents.userAgent = 'SiYuan/' + appVer + + require('@electron/remote/main').enable(currentWindow.webContents) + currentWindow.webContents.userAgent = 'SiYuan/' + appVer + ' https://b3log.org/siyuan Electron' - mainWindow.webContents.session.setSpellCheckerLanguages(['en-US']) + currentWindow.webContents.session.setSpellCheckerLanguages(['en-US']) // 发起互联网服务请求时绕过安全策略 https://github.com/siyuan-note/siyuan/issues/5516 - mainWindow.webContents.session.webRequest.onBeforeSendHeaders( + currentWindow.webContents.session.webRequest.onBeforeSendHeaders( (details, cb) => { if (-1 < details.url.indexOf('bili')) { // B 站不移除 Referer https://github.com/siyuan-note/siyuan/issues/94 @@ -219,50 +227,51 @@ const boot = () => { } cb({requestHeaders: details.requestHeaders}) }) - mainWindow.webContents.session.webRequest.onHeadersReceived((details, cb) => { - for (let key in details.responseHeaders) { - if ('x-frame-options' === key.toLowerCase()) { - delete details.responseHeaders[key] - } else if ('content-security-policy' === key.toLowerCase()) { - delete details.responseHeaders[key] - } else if ('access-control-allow-origin' === key.toLowerCase()) { - delete details.responseHeaders[key] + currentWindow.webContents.session.webRequest.onHeadersReceived( + (details, cb) => { + for (let key in details.responseHeaders) { + if ('x-frame-options' === key.toLowerCase()) { + delete details.responseHeaders[key] + } else if ('content-security-policy' === key.toLowerCase()) { + delete details.responseHeaders[key] + } else if ('access-control-allow-origin' === key.toLowerCase()) { + delete details.responseHeaders[key] + } } - } - cb({responseHeaders: details.responseHeaders}) - }) + cb({responseHeaders: details.responseHeaders}) + }) - mainWindow.webContents.on('did-finish-load', () => { + currentWindow.webContents.on('did-finish-load', () => { if ('win32' === process.platform || 'linux' === process.platform) { siyuanOpenURL = process.argv.find((arg) => arg.startsWith('siyuan://')) } if (siyuanOpenURL) { - if (mainWindow.isMinimized()) { - mainWindow.restore() + if (currentWindow.isMinimized()) { + currentWindow.restore() } - if (!mainWindow.isVisible()) { - mainWindow.show() + if (!currentWindow.isVisible()) { + currentWindow.show() } - mainWindow.focus() + currentWindow.focus() setTimeout(() => { // 等待界面js执行完毕 writeLog(siyuanOpenURL) - mainWindow.webContents.send('siyuan-openurl', siyuanOpenURL) + currentWindow.webContents.send('siyuan-openurl', siyuanOpenURL) siyuanOpenURL = null }, 2000) } }) if (windowState.isDevToolsOpened) { - mainWindow.webContents.openDevTools({mode: 'bottom'}) + currentWindow.webContents.openDevTools({mode: 'bottom'}) } // 主界面事件监听 - mainWindow.once('ready-to-show', () => { - mainWindow.show() + currentWindow.once('ready-to-show', () => { + currentWindow.show() if (windowState.isMaximized) { - mainWindow.maximize() + currentWindow.maximize() } else { - mainWindow.unmaximize() + currentWindow.unmaximize() } if (bootWindow && !bootWindow.isDestroyed()) { bootWindow.destroy() @@ -270,7 +279,7 @@ const boot = () => { }) // 加载主界面 - mainWindow.loadURL(getServer() + '/stage/build/app/index.html?v=' + + currentWindow.loadURL(getServer() + '/stage/build/app/index.html?v=' + new Date().getTime()) // 菜单 @@ -333,7 +342,7 @@ const boot = () => { const menu = Menu.buildFromTemplate(template) Menu.setApplicationMenu(menu) // 当前页面链接使用浏览器打开 - mainWindow.webContents.on('will-navigate', (event, url) => { + currentWindow.webContents.on('will-navigate', (event, url) => { if (url.startsWith(getServer())) { return } @@ -342,218 +351,48 @@ const boot = () => { shell.openExternal(url) }) - mainWindow.on('close', (event) => { - if (mainWindow && !mainWindow.isDestroyed()) { - mainWindow.webContents.send('siyuan-save-close', false) + currentWindow.on('close', (event) => { + if (currentWindow && !currentWindow.isDestroyed()) { + currentWindow.webContents.send('siyuan-save-close', false) } event.preventDefault() }) - // 监听主题切换 - ipcMain.on('siyuan-config-theme', (event, theme) => { - nativeTheme.themeSource = theme + workspaces.push({ + browserWindow: currentWindow, + id: currentWindow.id, }) - ipcMain.on('siyuan-config-close', (event, close) => { - closeButtonBehavior = close - }) - ipcMain.on('siyuan-config-tray', () => { - mainWindow.hide() - }) - ipcMain.on('siyuan-config-closetray', () => { - if ('win32' === process.platform) { - tray.destroy() - } - }) - ipcMain.on('siyuan-export-pdf', (event, data) => { - mainWindow.webContents.send('siyuan-export-pdf', data) - }) - ipcMain.on('siyuan-export-close', (event, data) => { - mainWindow.webContents.send('siyuan-export-close', data) - }) - ipcMain.on('siyuan-quit', () => { - try { - if (resetWindowStateOnRestart) { - fs.writeFileSync(windowStatePath, '{}') - } else { - const bounds = mainWindow.getBounds() - fs.writeFileSync(windowStatePath, JSON.stringify({ - isMaximized: mainWindow.isMaximized(), - fullscreen: mainWindow.isFullScreen(), - isDevToolsOpened: mainWindow.webContents.isDevToolsOpened(), - x: bounds.x, - y: bounds.y, - width: bounds.width, - height: bounds.height, - })) - } - } catch (e) { - writeLog(e) - } - app.exit() - globalShortcut.unregisterAll() - writeLog('exited ui') - }) - - let trayMenu = { - "showWindow": "Show Window", - "hideWindow": "Hide Window", - "setWindowTop": "Set Window top", - "cancelWindowTop": "Cancel Window top", - "officialWebsite": "Visit official website", - "openSource": "Visit Project on Github", - "resetWindow": "Reset Window on restart", - "quit": "Quit application" - } - ipcMain.on('siyuan-init', async (event, languages) => { - trayMenu = languages['_trayMenu']; - resetTrayMenu() - await fetch(getServer() + '/api/system/uiproc?pid=' + process.pid, - {method: 'POST'}) - }) - - const resetTrayMenu = () => { - if ('win32' !== process.platform && 'linux' !== process.platform) { - return - } - - const trayMenuTemplate = buildTrayMenuTemplate() - const contextMenu = Menu.buildFromTemplate(trayMenuTemplate) - tray.setContextMenu(contextMenu) - } - - const buildShowWndMenu = () => { - const ret = { - label: trayMenu.hideWindow, - click: () => { - showHideWnd() - }, - } - - if (mainWindow.isVisible()) { - ret.label = trayMenu.hideWindow - } else { - ret.label = trayMenu.showWindow - } - return ret - } - - const showHideWnd = () => { - if (!mainWindow.isVisible()) { - if (mainWindow.isMinimized()) { - mainWindow.restore() - } - mainWindow.show() - } else { - mainWindow.hide() - } - - resetTrayMenu() - } - - const buildSetWndTopMenu = () => { - const ret = { - label: trayMenu.setWindowTop, - click: () => { - setCancelWndTop() - }, - } - if (mainWindow.isAlwaysOnTop()) { - ret.label = trayMenu.cancelWindowTop - } else { - ret.label = trayMenu.setWindowTop - } - return ret; - } - const setCancelWndTop = () => { - if (!mainWindow.isAlwaysOnTop()) { - mainWindow.setAlwaysOnTop(true) - } else { - mainWindow.setAlwaysOnTop(false) - } - - resetTrayMenu() - } - - const buildTrayMenuTemplate = () => { - let ret = [ - buildShowWndMenu(), - { - label: trayMenu.officialWebsite, - click: () => { - shell.openExternal('https://b3log.org/siyuan/') - }, - }, - { - label: trayMenu.openSource, - click: () => { - shell.openExternal('https://github.com/siyuan-note/siyuan') - }, - }, - { - label: trayMenu.resetWindow, - type: 'checkbox', - click: v => { - resetWindowStateOnRestart = v.checked - mainWindow.webContents.send('siyuan-save-close', true) - }, - }, - { - label: trayMenu.quit, - click: () => { - mainWindow.webContents.send('siyuan-save-close', true) - }, - } - ] - - if ('win32' === process.platform) { - // Windows 端支持窗口置顶 https://github.com/siyuan-note/siyuan/issues/6860 - ret.splice(1, 0, buildSetWndTopMenu()) - } - return ret; - } - - ipcMain.on('siyuan-hotkey', (event, hotkey) => { - globalShortcut.unregisterAll() - if (!hotkey) { - return - } - globalShortcut.register(hotkey, () => { - if (mainWindow.isMinimized()) { - mainWindow.restore() - if (!mainWindow.isVisible()) { - mainWindow.show() - } - } else { - if (mainWindow.isVisible()) { - if (!mainWindow.isFocused()) { - mainWindow.show() - } else { - mainWindow.hide() - } - } else { - mainWindow.show() - } - } - - resetTrayMenu() - }) - }) - - if ('win32' === process.platform || 'linux' === process.platform) { - // 系统托盘 - - tray = new Tray(path.join(appDir, 'stage', 'icon-large.png')) - tray.setToolTip('SiYuan v' + appVer) - - const trayMenuTemplate = buildTrayMenuTemplate() - const contextMenu = Menu.buildFromTemplate(trayMenuTemplate) - tray.setContextMenu(contextMenu) - tray.on('click', () => { - showHideWnd() - }) - } } -const initKernel = (initData) => { +const initKernel = (workspace, lang) => { + const getKernelPort = async () => { + // TODO if (isDevEnv) { + // writeLog("got kernel port [" + kernelPort + "]") + // return kernelPort + // } + + // 改进桌面端拉起内核 https://github.com/siyuan-note/siyuan/issues/6894 + const getAvailablePort = (port = kernelPort) => { + // https://gist.github.com/mikeal/1840641 + let tryGetPortCount = 0 + const server = net.createServer() + return new Promise((resolve, reject) => server.on('error', error => { + writeLog(error) + if (2048 < ++tryGetPortCount) { + writeLog('failed to get available port [tryCount=' + tryGetPortCount + + ', port=' + port + ']') + reject(error) + return + } + server.listen(++port) + }).on('listening', () => { + writeLog('found an available port [' + port + ']') + server.close(() => resolve(port)) + }).listen(port, '127.0.0.1')) + } + kernelPort = await getAvailablePort() + writeLog('got kernel available port [' + kernelPort + ']') + return kernelPort + } return new Promise(async (resolve) => { bootWindow = new BrowserWindow({ width: screen.getPrimaryDisplay().size.width / 2, @@ -566,7 +405,9 @@ const initKernel = (initData) => { }, }) - const kernelName = 'win32' === process.platform ? 'SiYuan-Kernel.exe' : 'SiYuan-Kernel' + const kernelName = 'win32' === process.platform + ? 'SiYuan-Kernel.exe' + : 'SiYuan-Kernel' const kernelPath = path.join(appDir, 'kernel', kernelName) if (!fs.existsSync(kernelPath)) { showErrorWindow('⚠️ 内核文件丢失 Kernel is missing', @@ -578,18 +419,19 @@ const initKernel = (initData) => { const availablePort = await getKernelPort() const cmds = ['--port', availablePort, '--wd', appDir] - if (isDevEnv) { - cmds.push('--mode', 'dev') + // TODO if (isDevEnv) { + // cmds.push('--mode', 'dev') + // } + if (workspace) { + cmds.push('--workspace', workspace) + cmds.push('--lang', lang) } - if (initData) { - const initDatas = initData.split('-') - cmds.push('--workspace', initDatas[0]) - cmds.push('--lang', initDatas[1]) - } - let cmd = `ui version [${appVer}], booting kernel [${kernelPath} ${cmds.join(' ')}]` + let cmd = `ui version [${appVer}], booting kernel [${kernelPath} ${cmds.join( + ' ')}]` writeLog(cmd) - let kernelProcessPid = "" - if (!isDevEnv) { + let kernelProcessPid = '' + // TODO + if (isDevEnv) { const cp = require('child_process') const kernelProcess = cp.spawn(kernelPath, cmds, { @@ -609,8 +451,12 @@ const initKernel = (initData) => { `
数据库文件正在被其他进程占用,请检查是否同时存在多个内核进程(SiYuan Kernel)服务相同的工作空间。
The database file is being occupied by other processes, please check whether there are multiple kernel processes (SiYuan Kernel) serving the same workspace at the same time.
`) break case 21: - showErrorWindow('⚠️ 监听端口 ' + kernelPort + ' 失败 Failed to listen to port ' + kernelPort, - '
监听 ' + kernelPort + ' 端口失败,请确保程序拥有网络权限并不受防火墙和杀毒软件阻止。
Failed to listen to port ' + kernelPort + ', please make sure the program has network permissions and is not blocked by firewalls and antivirus software.
') + showErrorWindow('⚠️ 监听端口 ' + kernelPort + + ' 失败 Failed to listen to port ' + kernelPort, + '
监听 ' + kernelPort + + ' 端口失败,请确保程序拥有网络权限并不受防火墙和杀毒软件阻止。
Failed to listen to port ' + + kernelPort + + ', please make sure the program has network permissions and is not blocked by firewalls and antivirus software.
') break case 22: showErrorWindow( @@ -659,7 +505,8 @@ const initKernel = (initData) => { if (14 < count) { writeLog('get kernel ver failed') - showErrorWindow('⚠️ 获取内核服务端口失败 Failed to get kernel serve port', + showErrorWindow( + '⚠️ 获取内核服务端口失败 Failed to get kernel serve port', '
获取内核服务端口失败,请确保程序拥有网络权限并不受防火墙和杀毒软件阻止。
Failed to get kernel serve port, please make sure the program has network permissions and is not blocked by firewalls and antivirus software.
') bootWindow.destroy() resolve(false) @@ -680,7 +527,8 @@ const initKernel = (initData) => { let progressing = false while (!progressing) { try { - const progressResult = await fetch(getServer() + '/api/system/bootProgress') + const progressResult = await fetch( + getServer() + '/api/system/bootProgress') const progressData = await progressResult.json() if (progressData.data.progress >= 100) { resolve(true) @@ -714,9 +562,199 @@ app.commandLine.appendSwitch('enable-features', 'PlatformHEVCDecoderSupport') app.setPath('userData', app.getPath('userData') + '-Electron') // `~/.config` 下 Electron 相关文件夹名称改为 `SiYuan-Electron` https://github.com/siyuan-note/siyuan/issues/3349 app.whenReady().then(() => { + const resetTrayMenu = (tray, lang, mainWindow) => { + const trayMenuTemplate = [ + { + label: mainWindow.isVisible() + ? lang.hideWindow + : lang.showWindow, + click: () => { + showHideWnd(tray, lang, mainWindow) + }, + }, + { + label: lang.officialWebsite, + click: () => { + shell.openExternal('https://b3log.org/siyuan/') + }, + }, + { + label: lang.openSource, + click: () => { + shell.openExternal('https://github.com/siyuan-note/siyuan') + }, + }, + { + label: lang.resetWindow, + type: 'checkbox', + click: v => { + resetWindowStateOnRestart = v.checked + mainWindow.webContents.send('siyuan-save-close', true) + }, + }, + { + label: lang.quit, + click: () => { + mainWindow.webContents.send('siyuan-save-close', true) + }, + }, + ] + + if ('win32' === process.platform) { + // Windows 端支持窗口置顶 https://github.com/siyuan-note/siyuan/issues/6860 + trayMenuTemplate.splice(1, 0, { + label: mainWindow.isAlwaysOnTop() + ? lang.cancelWindowTop + : lang.setWindowTop, + click: () => { + if (!mainWindow.isAlwaysOnTop()) { + mainWindow.setAlwaysOnTop(true) + } else { + mainWindow.setAlwaysOnTop(false) + } + resetTrayMenu(tray, lang, mainWindow) + }, + }) + } + const contextMenu = Menu.buildFromTemplate(trayMenuTemplate) + tray.setContextMenu(contextMenu) + } + const showHideWnd = (tray, lang, mainWindow) => { + if (!mainWindow.isVisible()) { + if (mainWindow.isMinimized()) { + mainWindow.restore() + } + mainWindow.show() + } else { + mainWindow.hide() + } + + resetTrayMenu(tray, lang, mainWindow) + } + ipcMain.on('siyuan-first-quit', () => { app.exit() }) + ipcMain.on('siyuan-show', (event, id) => { + const mainWindow = BrowserWindow.fromId(id) + if (mainWindow.isMinimized()) { + mainWindow.restore() + } + if (!mainWindow.isVisible()) { + mainWindow.show() + } + mainWindow.focus() + }) + ipcMain.on('siyuan-config-theme', (event, theme) => { + nativeTheme.themeSource = theme + }) + ipcMain.on('siyuan-config-tray', (event, id) => { + BrowserWindow.fromId(id).hide() + }) + ipcMain.on('siyuan-export-pdf', (event, data) => { + BrowserWindow.fromId(data.id).webContents.send('siyuan-export-pdf', data) + }) + ipcMain.on('siyuan-export-close', (event, id) => { + BrowserWindow.fromId(id).webContents.send('siyuan-export-close', data) + }) + ipcMain.on('siyuan-quit', (id) => { + const mainWindow = BrowserWindow.fromId(id) + let tray + workspaces.find((item, index) => { + if (item.id === id) { + mainWindow.destroy() + tray = item.tray + workspaces.splice(index, 1) + return true + } + }) + if (tray && 'win32' === process.platform) { + tray.destroy() + } + if (workspaces.length === 0) { + try { + if (resetWindowStateOnRestart) { + fs.writeFileSync(windowStatePath, '{}') + } else { + const bounds = mainWindow.getBounds() + fs.writeFileSync(windowStatePath, JSON.stringify({ + isMaximized: mainWindow.isMaximized(), + fullscreen: mainWindow.isFullScreen(), + isDevToolsOpened: mainWindow.webContents.isDevToolsOpened(), + x: bounds.x, + y: bounds.y, + width: bounds.width, + height: bounds.height, + })) + } + } catch (e) { + writeLog(e) + } + app.exit() + globalShortcut.unregisterAll() + writeLog('exited ui') + } + }) + ipcMain.on('siyuan-open-workspace', (event, data) => { + initKernel(data.workspace, data.lang).then((isSucc) => { + if (isSucc) { + boot() + } + }) + }) + ipcMain.on('siyuan-init', async (event, data) => { + let tray + if ('win32' === process.platform || 'linux' === process.platform) { + // 系统托盘 + tray = new Tray(path.join(appDir, 'stage', 'icon-large.png')) + tray.setToolTip('SiYuan v' + appVer) + const mainWindow = BrowserWindow.fromId(data.id) + resetTrayMenu(tray, data.languages, mainWindow) + tray.on('click', () => { + showHideWnd(tray, data.languages, mainWindow) + }) + } + workspaces.find(item => { + if (data.id === item.id) { + item.workspaceDir = data.workspaceDir + item.tray = tray + return true + } + }) + await fetch(getServer() + '/api/system/uiproc?pid=' + process.pid, + {method: 'POST'}) + }) + ipcMain.on('siyuan-hotkey', (event, data) => { + globalShortcut.unregisterAll() + if (!data.hotkey) { + return + } + globalShortcut.register(data.hotkey, () => { + const mainWindow = BrowserWindow.fromId(data.id) + if (mainWindow.isMinimized()) { + mainWindow.restore() + if (!mainWindow.isVisible()) { + mainWindow.show() + } + } else { + if (mainWindow.isVisible()) { + if (!mainWindow.isFocused()) { + mainWindow.show() + } else { + mainWindow.hide() + } + } else { + mainWindow.show() + } + } + workspaces.find(item => { + if (item.id === data.id) { + resetTrayMenu(item.tray, data.languages, mainWindow) + return true + } + }) + }) + }) if (firstOpen) { firstOpenWindow = new BrowserWindow({ @@ -740,8 +778,9 @@ app.whenReady().then(() => { } // 改进桌面端初始化时使用的外观语言 https://github.com/siyuan-note/siyuan/issues/6803 - let languages = app.getPreferredSystemLanguages(); - let language = languages && 0 < languages.length && "zh-Hans-CN" === languages[0] ? "zh_CN" : "en_US"; + let languages = app.getPreferredSystemLanguages() + let language = languages && 0 < languages.length && 'zh-Hans-CN' === + languages[0] ? 'zh_CN' : 'en_US' firstOpenWindow.loadFile( initHTMLPath, { query: { @@ -753,8 +792,8 @@ app.whenReady().then(() => { }) firstOpenWindow.show() // 初始化启动 - ipcMain.on('siyuan-first-init', (event, initData) => { - initKernel(initData).then((isSucc) => { + ipcMain.on('siyuan-first-init', (event, data) => { + initKernel(data.workspace, data.lang).then((isSucc) => { if (isSucc) { boot() } @@ -773,34 +812,25 @@ app.whenReady().then(() => { app.on('open-url', (event, url) => { // for macOS if (url.startsWith('siyuan://')) { siyuanOpenURL = url - if (mainWindow && !mainWindow.isDestroyed()) { - if (mainWindow.isMinimized()) { - mainWindow.restore() + workspaces.forEach(item => { + if (item.browserWindow && !item.browserWindow.isDestroyed()) { + item.browserWindow.webContents.send('siyuan-openurl', url) } - if (!mainWindow.isVisible()) { - mainWindow.show() - } - mainWindow.focus() - mainWindow.webContents.send('siyuan-openurl', url) - } + }) } }) app.on('second-instance', (event, commandLine) => { - if (mainWindow && !mainWindow.isDestroyed()) { - if (mainWindow.isMinimized()) { - mainWindow.restore() + workspaces.forEach(item => { + if (item.browserWindow && !item.browserWindow.isDestroyed()) { + item.browserWindow.webContents.send('siyuan-openurl', + commandLine.find((arg) => arg.startsWith('siyuan://'))) } - if (!mainWindow.isVisible()) { - mainWindow.show() - } - mainWindow.focus() - mainWindow.webContents.send('siyuan-openurl', - commandLine.find((arg) => arg.startsWith('siyuan://'))) - } + }) }) app.on('activate', () => { + const mainWindow = workspaces[0].browserWindow if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.show() } @@ -818,10 +848,12 @@ app.on('web-contents-created', (webContentsCreatedEvent, contents) => { }) app.on('before-quit', (event) => { - if (mainWindow && !mainWindow.isDestroyed()) { - event.preventDefault() - mainWindow.webContents.send('siyuan-save-close', true) - } + workspaces.forEach(item => { + if (item.browserWindow && !item.browserWindow.isDestroyed()) { + event.preventDefault() + item.browserWindow.webContents.send('siyuan-save-close', true) + } + }) }) const {powerMonitor} = require('electron') @@ -832,23 +864,36 @@ powerMonitor.on('suspend', () => { powerMonitor.on('resume', async () => { writeLog('system resume') + const isOnline = async () => { + try { + const result = await fetch('https://icanhazip.com', {timeout: 1000}) + return 200 === result.status + } catch (e) { + try { + const result = await fetch('https://www.baidu.com', {timeout: 1000}) + return 200 === result.status + } catch (e) { + return false + } + } + } let online = false for (let i = 0; i < 7; i++) { if (await isOnline()) { online = true - break; + break } - writeLog("network is offline") + writeLog('network is offline') await sleep(1000) } if (!online) { - writeLog("network is offline, do not sync after system resume") - return; + writeLog('network is offline, do not sync after system resume') + return } - writeLog("sync after system resume") + writeLog('sync after system resume') // 桌面端系统休眠唤醒后同步延时 7s 后再执行 https://github.com/siyuan-note/siyuan/issues/6687 fetch(getServer() + '/api/sync/performSync', {method: 'POST'}) }) @@ -857,61 +902,3 @@ powerMonitor.on('shutdown', () => { writeLog('system shutdown') fetch(getServer() + '/api/system/exit', {method: 'POST'}) }) - -const sleep = (ms) => { - return new Promise(resolve => setTimeout(resolve, ms)) -} - -const isOnline = async () => { - try { - const result = await fetch("https://icanhazip.com", {timeout: 1000}) - return 200 === result.status - } catch (e) { - try { - const result = await fetch("https://www.baidu.com", {timeout: 1000}) - return 200 === result.status - } catch (e) { - return false; - } - } -} - -let kernelPort = 6806 - -const getKernelPort = async () => { - if (isDevEnv) { - writeLog("got kernel port [" + kernelPort + "]") - return kernelPort - } - - // 改进桌面端拉起内核 https://github.com/siyuan-note/siyuan/issues/6894 - kernelPort = await getAvailablePort() - writeLog("got kernel available port [" + kernelPort + "]") - return kernelPort -} - -let tryGetPortCount = 0 -const getAvailablePort = (port = kernelPort) => { - // https://gist.github.com/mikeal/1840641 - - const server = net.createServer() - return new Promise((resolve, reject) => server - .on('error', error => { - writeLog(error) - if (2048 < ++tryGetPortCount) { - writeLog('failed to get available port [tryCount=' + tryGetPortCount + ', port=' + port + ']') - reject(error) - return - } - server.listen(++port) - }) - .on('listening', () => { - writeLog('found an available port [' + port + ']') - server.close(() => resolve(port)) - }) - .listen(port, '127.0.0.1')) -} - -const getServer = () => { - return "http://" + localhost + ":" + kernelPort -} diff --git a/app/src/config/about.ts b/app/src/config/about.ts index 88507fc8c..1c9f9d19e 100644 --- a/app/src/config/about.ts +++ b/app/src/config/about.ts @@ -1,6 +1,6 @@ import {Constants} from "../constants"; /// #if !BROWSER -import {app, shell} from "electron"; +import {app, ipcRenderer, shell} from "electron"; import {dialog} from "@electron/remote"; /// #endif import {isBrowser} from "../util/functions"; @@ -242,8 +242,9 @@ export const about = { fetchPost("/api/system/setWorkspaceDir", { path: workspace }, () => { - exportLayout(false, () => { - exitSiYuan(); + ipcRenderer.send(Constants.SIYUAN_OPEN_WORKSPACE, { + workspace, + lang: window.siyuan.config.appearance.lang }); }); }); diff --git a/app/src/config/appearance.ts b/app/src/config/appearance.ts index e85fb8f41..512e4100d 100644 --- a/app/src/config/appearance.ts +++ b/app/src/config/appearance.ts @@ -274,7 +274,6 @@ export const appearance = { } /// #if !BROWSER ipcRenderer.send(Constants.SIYUAN_CONFIG_THEME, data.modeOS ? "system" : (data.mode === 1 ? "dark" : "light")); - ipcRenderer.send(Constants.SIYUAN_CONFIG_CLOSE, data.closeButtonBehavior); /// #endif if (needLoadAsset) { loadAssets(data); diff --git a/app/src/config/keymap.ts b/app/src/config/keymap.ts index ac601407b..96658e045 100644 --- a/app/src/config/keymap.ts +++ b/app/src/config/keymap.ts @@ -2,8 +2,11 @@ import {hotKey2Electron, isCtrl, isMac, updateHotkeyTip} from "../protyle/util/c import {Constants} from "../constants"; import {showMessage} from "../dialog/message"; import {fetchPost} from "../util/fetch"; -import {ipcRenderer} from "electron"; import {exportLayout} from "../layout/util"; +/// #if !BROWSER +import {getCurrentWindow} from "@electron/remote"; +import {ipcRenderer} from "electron"; +/// #endif import {confirmDialog} from "../dialog/confirmDialog"; export const keymap = { @@ -132,7 +135,11 @@ export const keymap = { data }, () => { /// #if !BROWSER - ipcRenderer.send(Constants.SIYUAN_HOTKEY, hotKey2Electron(window.siyuan.config.keymap.general.toggleWin.custom)); + ipcRenderer.send(Constants.SIYUAN_HOTKEY, { + languages: window.siyuan.languages["_trayMenu"], + id: getCurrentWindow().id, + hotkey: hotKey2Electron(window.siyuan.config.keymap.general.toggleWin.custom) + }); /// #endif }); }, @@ -219,7 +226,11 @@ export const keymap = { }, () => { window.location.reload(); /// #if !BROWSER - ipcRenderer.send(Constants.SIYUAN_HOTKEY, hotKey2Electron(window.siyuan.config.keymap.general.toggleWin.custom)); + ipcRenderer.send(Constants.SIYUAN_HOTKEY, { + languages: window.siyuan.languages["_trayMenu"], + id: getCurrentWindow().id, + hotkey: hotKey2Electron(window.siyuan.config.keymap.general.toggleWin.custom) + }); /// #endif }); }); diff --git a/app/src/constants.ts b/app/src/constants.ts index d0363fb36..64ca3ffac 100644 --- a/app/src/constants.ts +++ b/app/src/constants.ts @@ -20,10 +20,10 @@ export abstract class Constants { public static readonly SIYUAN_DROP_EDITOR: string = "application/siyuan-editor"; // 渲染进程调主进程 + public static readonly SIYUAN_SHOW: string = "siyuan-show"; public static readonly SIYUAN_CONFIG_THEME: string = "siyuan-config-theme"; - public static readonly SIYUAN_CONFIG_CLOSE: string = "siyuan-config-close"; public static readonly SIYUAN_CONFIG_TRAY: string = "siyuan-config-tray"; - public static readonly SIYUAN_CONFIG_CLOSETRAY: string = "siyuan-config-closetray"; + public static readonly SIYUAN_OPEN_WORKSPACE: string = "siyuan-open-workspace"; public static readonly SIYUAN_QUIT: string = "siyuan-quit"; public static readonly SIYUAN_HOTKEY: string = "siyuan-hotkey"; public static readonly SIYUAN_INIT: string = "siyuan-init"; diff --git a/app/src/dialog/processSystem.ts b/app/src/dialog/processSystem.ts index 38d87504d..d0b081bae 100644 --- a/app/src/dialog/processSystem.ts +++ b/app/src/dialog/processSystem.ts @@ -93,7 +93,6 @@ export const exitSiYuan = () => { buttonElement.addEventListener("click", () => { fetchPost("/api/system/exit", {force: true}, () => { /// #if !BROWSER - ipcRenderer.send(Constants.SIYUAN_CONFIG_CLOSETRAY); ipcRenderer.send(Constants.SIYUAN_QUIT); /// #else if (["ios", "android"].includes(window.siyuan.config.system.container) && (window.webkit?.messageHandlers || window.JSAndroid)) { @@ -118,7 +117,6 @@ export const exitSiYuan = () => { }, 2000); // 然后等待一段时间后再退出,避免界面主进程退出以后内核子进程被杀死 setTimeout(() => { - ipcRenderer.send(Constants.SIYUAN_CONFIG_CLOSETRAY); ipcRenderer.send(Constants.SIYUAN_QUIT); }, 4000); /// #endif @@ -129,14 +127,12 @@ export const exitSiYuan = () => { execInstallPkg: 1 // 0:默认检查新版本,1:不执行新版本安装,2:执行新版本安装 }, () => { /// #if !BROWSER - ipcRenderer.send(Constants.SIYUAN_CONFIG_CLOSETRAY); ipcRenderer.send(Constants.SIYUAN_QUIT); /// #endif }); }); } else { // 正常退出 /// #if !BROWSER - ipcRenderer.send(Constants.SIYUAN_CONFIG_CLOSETRAY); ipcRenderer.send(Constants.SIYUAN_QUIT); /// #else if (["ios", "android"].includes(window.siyuan.config.system.container) && (window.webkit?.messageHandlers || window.JSAndroid)) { diff --git a/app/src/protyle/export/index.ts b/app/src/protyle/export/index.ts index bd26642a6..ddacddf54 100644 --- a/app/src/protyle/export/index.ts +++ b/app/src/protyle/export/index.ts @@ -416,11 +416,12 @@ const renderPDF = (id: string) => { }); actionElement.querySelector('.b3-button--cancel').addEventListener('click', () => { const {ipcRenderer} = require("electron"); - ipcRenderer.send("${Constants.SIYUAN_EXPORT_CLOSE}") + ipcRenderer.send("${Constants.SIYUAN_EXPORT_CLOSE}", getCurrentWindow().id) }); actionElement.querySelector('.b3-button--text').addEventListener('click', () => { const {ipcRenderer} = require("electron"); ipcRenderer.send("${Constants.SIYUAN_EXPORT_PDF}", { + id: getCurrentWindow().id, pdfOptions:{ printBackground: true, landscape: actionElement.querySelector("#landscape").checked, diff --git a/app/src/util/assets.ts b/app/src/util/assets.ts index f1fa7b854..3700a3f20 100644 --- a/app/src/util/assets.ts +++ b/app/src/util/assets.ts @@ -235,7 +235,6 @@ export const setMode = (modeElementValue: number) => { window.siyuan.config.appearance = response.data; /// #if !BROWSER ipcRenderer.send(Constants.SIYUAN_CONFIG_THEME, response.data.modeOS ? "system" : (response.data.mode === 1 ? "dark" : "light")); - ipcRenderer.send(Constants.SIYUAN_CONFIG_CLOSE, response.data.closeButtonBehavior); /// #endif loadAssets(response.data); document.querySelector("#barMode use").setAttribute("xlink:href", `#icon${window.siyuan.config.appearance.modeOS ? "Mode" : (window.siyuan.config.appearance.mode === 0 ? "Light" : "Dark")}`); diff --git a/app/src/util/fetch.ts b/app/src/util/fetch.ts index 508079845..779de61fb 100644 --- a/app/src/util/fetch.ts +++ b/app/src/util/fetch.ts @@ -48,7 +48,6 @@ export const fetchPost = (url: string, data?: any, cb?: (response: IWebSocketDat if (url === "/api/system/exit" || url === "/api/system/setWorkspaceDir" || ( ["/api/system/setUILayout"].includes(url) && data.exit // 内核中断,点关闭处理 )) { - ipcRenderer.send(Constants.SIYUAN_CONFIG_CLOSETRAY); ipcRenderer.send(Constants.SIYUAN_QUIT); } /// #endif diff --git a/app/src/util/onGetConfig.ts b/app/src/util/onGetConfig.ts index 8605a1003..97e6e13a5 100644 --- a/app/src/util/onGetConfig.ts +++ b/app/src/util/onGetConfig.ts @@ -126,14 +126,25 @@ export const onGetConfig = (isStart: boolean) => { data: window.siyuan.config.keymap }, () => { /// #if !BROWSER - ipcRenderer.send(Constants.SIYUAN_HOTKEY, hotKey2Electron(window.siyuan.config.keymap.general.toggleWin.custom)); + ipcRenderer.send(Constants.SIYUAN_HOTKEY, { + languages: window.siyuan.languages["_trayMenu"], + id: getCurrentWindow().id, + hotkey: hotKey2Electron(window.siyuan.config.keymap.general.toggleWin.custom) + }); /// #endif }); } /// #if !BROWSER - ipcRenderer.send(Constants.SIYUAN_CONFIG_CLOSE, window.siyuan.config.appearance.closeButtonBehavior); - ipcRenderer.send(Constants.SIYUAN_INIT, window.siyuan.languages); - ipcRenderer.send(Constants.SIYUAN_HOTKEY, hotKey2Electron(window.siyuan.config.keymap.general.toggleWin.custom)); + ipcRenderer.send(Constants.SIYUAN_INIT, { + languages: window.siyuan.languages["_trayMenu"], + workspaceDir: window.siyuan.config.system.workspaceDir, + id: getCurrentWindow().id, + }); + ipcRenderer.send(Constants.SIYUAN_HOTKEY, { + languages: window.siyuan.languages["_trayMenu"], + id: getCurrentWindow().id, + hotkey: hotKey2Electron(window.siyuan.config.keymap.general.toggleWin.custom) + }); /// #endif if (!window.siyuan.config.uiLayout || (window.siyuan.config.uiLayout && !window.siyuan.config.uiLayout.left)) { window.siyuan.config.uiLayout = Constants.SIYUAN_EMPTY_LAYOUT; @@ -332,7 +343,7 @@ const winOnClose = (currentWindow: Electron.BrowserWindow, close = false) => { if (window.siyuan.config.appearance.closeButtonBehavior === 1 && !close) { // 最小化 if ("windows" === window.siyuan.config.system.os) { - ipcRenderer.send(Constants.SIYUAN_CONFIG_TRAY); + ipcRenderer.send(Constants.SIYUAN_CONFIG_TRAY, getCurrentWindow().id); } else { if (currentWindow.isFullScreen()) { currentWindow.once("leave-full-screen", () => currentWindow.hide()); @@ -359,10 +370,16 @@ const initWindow = () => { if (!/^siyuan:\/\/blocks\/\d{14}-\w{7}/.test(url)) { return; } - openFileById({ - id: url.substr(16, 22), - action: [Constants.CB_GET_FOCUS, Constants.CB_GET_CONTEXT], - zoomIn: getSearch("focus", url) === "1" + const id = url.substr(16, 22); + fetchPost("/api/block/checkBlockExist", {id}, existResponse => { + if (existResponse.data) { + openFileById({ + id, + action: [Constants.CB_GET_FOCUS, Constants.CB_GET_CONTEXT], + zoomIn: getSearch("focus", url) === "1" + }); + ipcRenderer.send(Constants.SIYUAN_SHOW, getCurrentWindow().id); + } }); }); ipcRenderer.on(Constants.SIYUAN_SAVE_CLOSE, (event, close) => { diff --git a/app/stage/auth.html b/app/stage/auth.html index dc685a1d4..65c82c249 100644 --- a/app/stage/auth.html +++ b/app/stage/auth.html @@ -432,8 +432,8 @@ const exitApp = () => { try { const {ipcRenderer} = require('electron') - ipcRenderer.send('siyuan-config-closetray') - ipcRenderer.send('siyuan-quit') + const {getCurrentWindow} = require('@electron/remote'); + ipcRenderer.send('siyuan-quit', getCurrentWindow().id) } catch (e) { if ((window.webkit && window.webkit.messageHandlers) || window.JSAndroid) { window.location.href = 'siyuan://api/system/exit'