mirror of
https://github.com/siyuan-note/siyuan.git
synced 2026-01-15 05:08:51 +01:00
678 lines
22 KiB
JavaScript
678 lines
22 KiB
JavaScript
|
|
// SiYuan - Build Your Eternal Digital Garden
|
|||
|
|
// Copyright (c) 2020-present, b3log.org
|
|||
|
|
//
|
|||
|
|
// This program is free software: you can redistribute it and/or modify
|
|||
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|||
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|||
|
|
// (at your option) any later version.
|
|||
|
|
//
|
|||
|
|
// This program is distributed in the hope that it will be useful,
|
|||
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|||
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|||
|
|
// GNU Affero General Public License for more details.
|
|||
|
|
//
|
|||
|
|
// You should have received a copy of the GNU Affero General Public License
|
|||
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
|
|
|
|||
|
|
const {
|
|||
|
|
app,
|
|||
|
|
BrowserWindow,
|
|||
|
|
shell,
|
|||
|
|
Menu,
|
|||
|
|
screen,
|
|||
|
|
nativeTheme,
|
|||
|
|
ipcMain,
|
|||
|
|
globalShortcut,
|
|||
|
|
Tray,
|
|||
|
|
} = require('electron')
|
|||
|
|
const path = require('path')
|
|||
|
|
const fs = require('fs')
|
|||
|
|
const fetch = require('electron-fetch').default
|
|||
|
|
process.noAsar = true
|
|||
|
|
const appDir = path.dirname(app.getAppPath())
|
|||
|
|
const isDevEnv = process.env.NODE_ENV === 'development'
|
|||
|
|
const appVer = app.getVersion()
|
|||
|
|
const confDir = path.join(app.getPath('home'), '.config', 'siyuan')
|
|||
|
|
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
|
|||
|
|
require('@electron/remote/main').initialize()
|
|||
|
|
|
|||
|
|
if (!app.requestSingleInstanceLock()) {
|
|||
|
|
app.quit()
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const showErrorWindow = (title, content) => {
|
|||
|
|
let errorHTMLPath = path.join(appDir, 'app', 'electron', 'error.html')
|
|||
|
|
if (isDevEnv) {
|
|||
|
|
errorHTMLPath = path.join(appDir, 'electron', 'error.html')
|
|||
|
|
}
|
|||
|
|
const errWindow = new BrowserWindow({
|
|||
|
|
width: screen.getPrimaryDisplay().size.width / 2,
|
|||
|
|
height: screen.getPrimaryDisplay().workAreaSize.height / 2,
|
|||
|
|
frame: false,
|
|||
|
|
icon: path.join(appDir, 'stage', 'icon.png'),
|
|||
|
|
webPreferences: {
|
|||
|
|
nativeWindowOpen: true,
|
|||
|
|
nodeIntegration: true,
|
|||
|
|
webviewTag: true,
|
|||
|
|
webSecurity: false,
|
|||
|
|
contextIsolation: false,
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
require('@electron/remote/main').enable(errWindow.webContents)
|
|||
|
|
errWindow.loadFile(errorHTMLPath, {
|
|||
|
|
query: {
|
|||
|
|
home: app.getPath('home'),
|
|||
|
|
v: appVer,
|
|||
|
|
title: title,
|
|||
|
|
content: content,
|
|||
|
|
icon: path.join(appDir, 'stage', 'icon.png'),
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
errWindow.show()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
firstOpen = !fs.existsSync(path.join(confDir, 'workspace.json'))
|
|||
|
|
if (!fs.existsSync(confDir)) {
|
|||
|
|
fs.mkdirSync(confDir, {mode: 0o755, recursive: true})
|
|||
|
|
}
|
|||
|
|
const documents = path.join(app.getPath('home'), "Documents")
|
|||
|
|
if (!fs.existsSync(documents)) {
|
|||
|
|
fs.mkdirSync(documents, {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) => {
|
|||
|
|
const logFile = path.join(confDir, 'app.log')
|
|||
|
|
let log = ''
|
|||
|
|
const maxLogLines = 1024
|
|||
|
|
try {
|
|||
|
|
if (fs.existsSync(logFile)) {
|
|||
|
|
log = fs.readFileSync(logFile).toString()
|
|||
|
|
let lines = log.split('\n')
|
|||
|
|
if (maxLogLines < lines.length) {
|
|||
|
|
log = lines.slice(maxLogLines / 2, maxLogLines).join('\n') + '\n'
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
out = out.toString()
|
|||
|
|
out = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '') + ' ' +
|
|||
|
|
out
|
|||
|
|
log += out + '\n'
|
|||
|
|
fs.writeFileSync(logFile, log)
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error(e)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const boot = () => {
|
|||
|
|
const windowStatePath = path.join(confDir, 'windowState.json')
|
|||
|
|
|
|||
|
|
// 恢复主窗体状态
|
|||
|
|
let oldWindowState = {}
|
|||
|
|
try {
|
|||
|
|
oldWindowState = JSON.parse(fs.readFileSync(windowStatePath, 'utf8'))
|
|||
|
|
} catch (e) {
|
|||
|
|
fs.writeFileSync(windowStatePath, '{}')
|
|||
|
|
}
|
|||
|
|
const defaultWidth = screen.getPrimaryDisplay().size.width * 4 / 5
|
|||
|
|
const defaultHeight = screen.getPrimaryDisplay().workAreaSize.height * 4 / 5
|
|||
|
|
const windowState = Object.assign({}, {
|
|||
|
|
isMaximized: true,
|
|||
|
|
fullscreen: false,
|
|||
|
|
isDevToolsOpened: false,
|
|||
|
|
x: 0, y: 0,
|
|||
|
|
width: defaultWidth,
|
|||
|
|
height: defaultHeight,
|
|||
|
|
}, oldWindowState)
|
|||
|
|
|
|||
|
|
// 窗口大小等同于或大于 workArea 时,缩小会隐藏到左下角
|
|||
|
|
const workArea = screen.getPrimaryDisplay().workArea
|
|||
|
|
if (windowState.width >= workArea.width || windowState.height >=
|
|||
|
|
workArea.height) {
|
|||
|
|
windowState.width = Math.min(defaultWidth, workArea.width)
|
|||
|
|
windowState.height = Math.min(defaultHeight, workArea.height)
|
|||
|
|
}
|
|||
|
|
if (windowState.width < 256) {
|
|||
|
|
windowState.width = Math.min(defaultWidth, workArea.width)
|
|||
|
|
}
|
|||
|
|
if (windowState.height < 256) {
|
|||
|
|
windowState.height = Math.min(defaultHeight, workArea.height)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let x = windowState.x, y = windowState.y
|
|||
|
|
if (x > workArea.width || x < 0) {
|
|||
|
|
x = 0
|
|||
|
|
}
|
|||
|
|
if (y > workArea.height || y < 0) {
|
|||
|
|
y = 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 创建主窗体
|
|||
|
|
mainWindow = new BrowserWindow({
|
|||
|
|
show: false,
|
|||
|
|
backgroundColor: '#FFF', // 桌面端主窗体背景色设置为 `#FFF` Fix https://github.com/siyuan-note/siyuan/issues/4544
|
|||
|
|
width: windowState.width,
|
|||
|
|
height: windowState.height,
|
|||
|
|
x: x, y: y,
|
|||
|
|
fullscreenable: true,
|
|||
|
|
fullscreen: windowState.fullscreen,
|
|||
|
|
webPreferences: {
|
|||
|
|
nodeIntegration: true,
|
|||
|
|
nativeWindowOpen: true,
|
|||
|
|
webviewTag: true,
|
|||
|
|
webSecurity: false,
|
|||
|
|
contextIsolation: false,
|
|||
|
|
},
|
|||
|
|
frame: 'darwin' === process.platform,
|
|||
|
|
titleBarStyle: 'hidden',
|
|||
|
|
icon: path.join(appDir, 'stage', 'icon.png'),
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
require('@electron/remote/main').enable(mainWindow.webContents)
|
|||
|
|
mainWindow.webContents.userAgent = 'SiYuan/' + appVer +
|
|||
|
|
' https://b3log.org/siyuan ' + mainWindow.webContents.userAgent
|
|||
|
|
mainWindow.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 (!mainWindow.isVisible()) {
|
|||
|
|
mainWindow.show()
|
|||
|
|
}
|
|||
|
|
mainWindow.focus()
|
|||
|
|
setTimeout(() => { // 等待界面js执行完毕
|
|||
|
|
writeLog(siyuanOpenURL)
|
|||
|
|
mainWindow.webContents.send('siyuan-openurl', siyuanOpenURL)
|
|||
|
|
siyuanOpenURL = null
|
|||
|
|
}, 2000)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if (windowState.isDevToolsOpened) {
|
|||
|
|
mainWindow.webContents.openDevTools({mode: 'bottom'})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 主界面事件监听
|
|||
|
|
mainWindow.once('ready-to-show', () => {
|
|||
|
|
mainWindow.show()
|
|||
|
|
if (windowState.isMaximized) {
|
|||
|
|
mainWindow.maximize()
|
|||
|
|
} else {
|
|||
|
|
mainWindow.unmaximize()
|
|||
|
|
}
|
|||
|
|
if (bootWindow && !bootWindow.isDestroyed()) {
|
|||
|
|
bootWindow.destroy()
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 加载主界面
|
|||
|
|
const loadURL = 'http://127.0.0.1:6806/stage/build/app/index.html?v=' +
|
|||
|
|
new Date().getTime()
|
|||
|
|
mainWindow.loadURL(loadURL)
|
|||
|
|
|
|||
|
|
// 菜单
|
|||
|
|
const productName = 'SiYuan'
|
|||
|
|
const template = [
|
|||
|
|
{
|
|||
|
|
label: productName,
|
|||
|
|
submenu: [
|
|||
|
|
{
|
|||
|
|
label: `About ${productName}`,
|
|||
|
|
role: 'about',
|
|||
|
|
},
|
|||
|
|
{type: 'separator'},
|
|||
|
|
{role: 'services'},
|
|||
|
|
{type: 'separator'},
|
|||
|
|
{
|
|||
|
|
label: `Hide ${productName}`,
|
|||
|
|
role: 'hide',
|
|||
|
|
},
|
|||
|
|
{role: 'hideOthers'},
|
|||
|
|
{role: 'unhide'},
|
|||
|
|
{type: 'separator'},
|
|||
|
|
{
|
|||
|
|
label: `Quit ${productName}`,
|
|||
|
|
role: 'quit',
|
|||
|
|
},
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
role: 'editMenu',
|
|||
|
|
submenu: [
|
|||
|
|
{role: 'cut'},
|
|||
|
|
{role: 'copy'},
|
|||
|
|
{role: 'paste'},
|
|||
|
|
{role: 'pasteAndMatchStyle', accelerator: 'CmdOrCtrl+Shift+C'},
|
|||
|
|
{role: 'selectAll'},
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
role: 'viewMenu',
|
|||
|
|
submenu: [
|
|||
|
|
{role: 'resetZoom'},
|
|||
|
|
{role: 'zoomIn', accelerator: 'CommandOrControl+='},
|
|||
|
|
{role: 'zoomOut'},
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
role: 'windowMenu',
|
|||
|
|
submenu: [
|
|||
|
|
{role: 'minimize'},
|
|||
|
|
{role: 'zoom'},
|
|||
|
|
{role: 'togglefullscreen'},
|
|||
|
|
{type: 'separator'},
|
|||
|
|
{role: 'toggledevtools'},
|
|||
|
|
{type: 'separator'},
|
|||
|
|
{role: 'front'},
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
]
|
|||
|
|
const menu = Menu.buildFromTemplate(template)
|
|||
|
|
Menu.setApplicationMenu(menu)
|
|||
|
|
// 当前页面链接使用浏览器打开
|
|||
|
|
mainWindow.webContents.on('will-navigate', (event, url) => {
|
|||
|
|
if (url.startsWith('http://127.0.0.1:6806')) {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
event.preventDefault()
|
|||
|
|
shell.openExternal(url)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
mainWindow.on('close', (event) => {
|
|||
|
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
|||
|
|
mainWindow.webContents.send('siyuan-save-close', false)
|
|||
|
|
}
|
|||
|
|
event.preventDefault()
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 监听主题切换
|
|||
|
|
ipcMain.on('siyuan-config-theme', (event, theme) => {
|
|||
|
|
nativeTheme.themeSource = theme
|
|||
|
|
})
|
|||
|
|
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-quit', () => {
|
|||
|
|
try {
|
|||
|
|
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) {
|
|||
|
|
}
|
|||
|
|
app.exit()
|
|||
|
|
globalShortcut.unregisterAll()
|
|||
|
|
writeLog('exited ui')
|
|||
|
|
})
|
|||
|
|
ipcMain.on('siyuan-init', async () => {
|
|||
|
|
await fetch('http://127.0.0.1:6806/api/system/uiproc?pid=' + process.pid,
|
|||
|
|
{method: 'POST'})
|
|||
|
|
})
|
|||
|
|
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()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if ('win32' === process.platform || 'linux' === process.platform) {
|
|||
|
|
// 系统托盘
|
|||
|
|
tray = new Tray(path.join(appDir, 'stage', 'icon.png'))
|
|||
|
|
tray.setToolTip('SiYuan')
|
|||
|
|
const trayMenuTemplate = [
|
|||
|
|
{
|
|||
|
|
label: 'Show Window',
|
|||
|
|
click: () => {
|
|||
|
|
if (mainWindow.isMinimized()) {
|
|||
|
|
mainWindow.restore()
|
|||
|
|
}
|
|||
|
|
mainWindow.show()
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: 'Quit',
|
|||
|
|
click: () => {
|
|||
|
|
mainWindow.webContents.send('siyuan-save-close', true)
|
|||
|
|
},
|
|||
|
|
}]
|
|||
|
|
const contextMenu = Menu.buildFromTemplate(trayMenuTemplate)
|
|||
|
|
tray.setContextMenu(contextMenu)
|
|||
|
|
tray.on('click', () => {
|
|||
|
|
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()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const initKernel = (initData) => {
|
|||
|
|
return new Promise(async (resolve) => {
|
|||
|
|
bootWindow = new BrowserWindow({
|
|||
|
|
width: screen.getPrimaryDisplay().size.width / 2,
|
|||
|
|
height: screen.getPrimaryDisplay().workAreaSize.height / 2,
|
|||
|
|
frame: false,
|
|||
|
|
icon: path.join(appDir, 'stage', 'icon.png'),
|
|||
|
|
transparent: 'linux' !== process.platform,
|
|||
|
|
webPreferences: {
|
|||
|
|
nativeWindowOpen: true,
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
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', `<div>内核可执行文件丢失,请重新安装思源,并将思源加入杀毒软件信任列表。</div><div>The kernel binary is not found, please reinstall SiYuan and add SiYuan into the trust list of your antivirus software.</div>`)
|
|||
|
|
bootWindow.destroy()
|
|||
|
|
resolve(false)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let cmd = `ui version [${appVer}], booting kernel [${kernelPath} --wd=${appDir}]`
|
|||
|
|
const cmds = ['--wd', appDir]
|
|||
|
|
if (initData) {
|
|||
|
|
const initDatas = initData.split('-')
|
|||
|
|
cmds.push('--workspace', initDatas[0])
|
|||
|
|
cmds.push('--lang', initDatas[1])
|
|||
|
|
cmd = `ui version [${appVer}], booting kernel [${kernelPath} --wd=${appDir} --workspace=${initDatas[0]} --lang=${initDatas[1]}]`
|
|||
|
|
}
|
|||
|
|
writeLog(cmd)
|
|||
|
|
const cp = require('child_process')
|
|||
|
|
const kernelProcess = cp.spawn(kernelPath,
|
|||
|
|
cmds, {
|
|||
|
|
detached: true,
|
|||
|
|
stdio: 'ignore',
|
|||
|
|
},
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
kernelProcess.on('close', (code) => {
|
|||
|
|
if (0 !== code) {
|
|||
|
|
writeLog(`kernel exited with code [${code}]`)
|
|||
|
|
switch (code) {
|
|||
|
|
case 20:
|
|||
|
|
showErrorWindow('⚠️ 数据库被锁定 The database is locked', `<div>数据库文件正在被其他程序锁定。如果你使用了第三方同步盘,请在思源运行期间关闭同步。</div><div>The database file is being locked by another program. If you use a third-party sync disk, please turn off sync while SiYuan is running.</div>`)
|
|||
|
|
break
|
|||
|
|
case 21:
|
|||
|
|
showErrorWindow('⚠️ 6806 端口不可用 The port 6806 is unavailable', '<div>思源需要监听 6806 端口,请确保该端口可用且不是其他程序的保留端口。可尝试使用管理员运行命令:' +
|
|||
|
|
'<pre><code>net stop winnat\nnetsh interface ipv4 add excludedportrange protocol=tcp startport=6806 numberofports=1\nnet start winnat</code></pre></div>' +
|
|||
|
|
'<div>SiYuan needs to listen to port 6806, please make sure this port is available, and not a reserved port by other software. Try running the command as an administrator: ' +
|
|||
|
|
'<pre><code>net stop winnat\nnetsh interface ipv4 add excludedportrange protocol=tcp startport=6806 numberofports=1\nnet start winnat</code></pre></div>')
|
|||
|
|
break
|
|||
|
|
case 22:
|
|||
|
|
showErrorWindow('⚠️ 创建配置目录失败 Failed to create config directory', `<div>思源需要在用户家目录下创建配置文件夹(~/.config/siyuan),请确保该路径具有写入权限。</div><div>SiYuan needs to create a configuration folder (~/.config/siyuan) in the user\'s home directory. Please make sure that the path has write permissions.</div>`)
|
|||
|
|
break
|
|||
|
|
case 23:
|
|||
|
|
showErrorWindow('⚠️ 无法读写块树文件 Failed to access blocktree file', `<div>块树文件正在被其他程序锁定。如果你使用了第三方同步盘,请在思源运行期间关闭同步。</div><div>The block tree file is being locked by another program. If you use a third-party sync disk, please turn off the sync while SiYuan is running.</div>`)
|
|||
|
|
break
|
|||
|
|
case 0:
|
|||
|
|
case 1: // Fatal error
|
|||
|
|
break
|
|||
|
|
default:
|
|||
|
|
showErrorWindow('⚠️ 内核因未知原因退出 The kernel exited for unknown reasons', `<div>思源内核因未知原因退出 [code=${code}],请尝试重启操作系统后再启动思源。如果该问题依然发生,请检查杀毒软件是否阻止思源内核启动。</div>
|
|||
|
|
<div>SiYuan Kernel exited for unknown reasons [code=${code}], please try to reboot your operating system and then start SiYuan again. If occurs this problem still, please check your anti-virus software whether kill the SiYuan Kernel.</div>`)
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
bootWindow.destroy()
|
|||
|
|
resolve(false)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
kernelProcess.unref()
|
|||
|
|
writeLog('booted kernel process')
|
|||
|
|
|
|||
|
|
const sleep = (ms) => {
|
|||
|
|
return new Promise(resolve => setTimeout(resolve, ms))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let gotVersion = false
|
|||
|
|
let apiData
|
|||
|
|
let count = 0
|
|||
|
|
writeLog('checking kernel version')
|
|||
|
|
while (!gotVersion) {
|
|||
|
|
try {
|
|||
|
|
const apiResult = await fetch(
|
|||
|
|
'http://127.0.0.1:6806/api/system/version')
|
|||
|
|
apiData = await apiResult.json()
|
|||
|
|
gotVersion = true
|
|||
|
|
bootWindow.setResizable(false)
|
|||
|
|
bootWindow.loadURL('http://127.0.0.1:6806/appearance/boot/index.html')
|
|||
|
|
bootWindow.show()
|
|||
|
|
} catch (e) {
|
|||
|
|
writeLog('get kernel version failed: ' + e.message)
|
|||
|
|
await sleep(100)
|
|||
|
|
} finally {
|
|||
|
|
count++
|
|||
|
|
if (64 < count) {
|
|||
|
|
writeLog('get kernel ver failed')
|
|||
|
|
bootWindow.destroy()
|
|||
|
|
resolve(false)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (0 === apiData.code) {
|
|||
|
|
writeLog('got kernel version [' + apiData.data + ']')
|
|||
|
|
if (!isDevEnv && apiData.data !== appVer) {
|
|||
|
|
writeLog(
|
|||
|
|
`kernel [${apiData.data}] is running, shutdown it now and then start kernel [${appVer}]`)
|
|||
|
|
fetch('http://127.0.0.1:6806/api/system/exit', {method: 'POST'})
|
|||
|
|
bootWindow.destroy()
|
|||
|
|
resolve(false)
|
|||
|
|
} else {
|
|||
|
|
let progressing = false
|
|||
|
|
while (!progressing) {
|
|||
|
|
try {
|
|||
|
|
const progressResult = await fetch(
|
|||
|
|
'http://127.0.0.1:6806/api/system/bootProgress')
|
|||
|
|
const progressData = await progressResult.json()
|
|||
|
|
if (progressData.data.progress >= 100) {
|
|||
|
|
resolve(true)
|
|||
|
|
progressing = true
|
|||
|
|
} else {
|
|||
|
|
await sleep(100)
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
writeLog('get boot progress failed: ' + e.message)
|
|||
|
|
fetch('http://127.0.0.1:6806/api/system/exit', {method: 'POST'})
|
|||
|
|
bootWindow.destroy()
|
|||
|
|
resolve(false)
|
|||
|
|
progressing = true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
writeLog(`get kernel version failed: ${apiData.code}, ${apiData.msg}`)
|
|||
|
|
resolve(false)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
app.setAsDefaultProtocolClient('siyuan')
|
|||
|
|
app.commandLine.appendSwitch('disable-web-security')
|
|||
|
|
app.commandLine.appendSwitch('auto-detect', 'false')
|
|||
|
|
app.commandLine.appendSwitch('no-proxy-server')
|
|||
|
|
app.setPath('userData', app.getPath('userData') + '-Electron') // `~/.config` 下 Electron 相关文件夹名称改为 `SiYuan-Electron` https://github.com/siyuan-note/siyuan/issues/3349
|
|||
|
|
|
|||
|
|
app.whenReady().then(() => {
|
|||
|
|
ipcMain.on('siyuan-first-quit', () => {
|
|||
|
|
app.exit()
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if (firstOpen) {
|
|||
|
|
firstOpenWindow = new BrowserWindow({
|
|||
|
|
width: screen.getPrimaryDisplay().size.width / 2,
|
|||
|
|
height: screen.getPrimaryDisplay().workAreaSize.height / 2,
|
|||
|
|
frame: false,
|
|||
|
|
icon: path.join(appDir, 'stage', 'icon.png'),
|
|||
|
|
transparent: 'linux' !== process.platform,
|
|||
|
|
webPreferences: {
|
|||
|
|
nativeWindowOpen: true,
|
|||
|
|
nodeIntegration: true,
|
|||
|
|
webviewTag: true,
|
|||
|
|
webSecurity: false,
|
|||
|
|
contextIsolation: false,
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
require('@electron/remote/main').enable(firstOpenWindow.webContents)
|
|||
|
|
let initHTMLPath = path.join(appDir, 'app', 'electron', 'init.html')
|
|||
|
|
if (isDevEnv) {
|
|||
|
|
initHTMLPath = path.join(appDir, 'electron', 'init.html')
|
|||
|
|
}
|
|||
|
|
firstOpenWindow.loadFile(
|
|||
|
|
initHTMLPath, {
|
|||
|
|
query: {
|
|||
|
|
home: app.getPath('home'),
|
|||
|
|
v: appVer,
|
|||
|
|
icon: path.join(appDir, 'stage', 'icon.png'),
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
firstOpenWindow.show()
|
|||
|
|
// 初始化启动
|
|||
|
|
ipcMain.on('siyuan-first-init', (event, initData) => {
|
|||
|
|
initKernel(initData).then((isSucc) => {
|
|||
|
|
if (isSucc) {
|
|||
|
|
boot()
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
firstOpenWindow.destroy()
|
|||
|
|
})
|
|||
|
|
} else {
|
|||
|
|
initKernel().then((isSucc) => {
|
|||
|
|
if (isSucc) {
|
|||
|
|
boot()
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
app.on('open-url', (event, url) => { // for macOS
|
|||
|
|
if (url.startsWith('siyuan://')) {
|
|||
|
|
siyuanOpenURL = url
|
|||
|
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
|||
|
|
if (mainWindow.isMinimized()) {
|
|||
|
|
mainWindow.restore()
|
|||
|
|
}
|
|||
|
|
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()
|
|||
|
|
}
|
|||
|
|
if (!mainWindow.isVisible()) {
|
|||
|
|
mainWindow.show()
|
|||
|
|
}
|
|||
|
|
mainWindow.focus()
|
|||
|
|
mainWindow.webContents.send('siyuan-openurl',
|
|||
|
|
commandLine.find((arg) => arg.startsWith('siyuan://')))
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
app.on('activate', () => {
|
|||
|
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
|||
|
|
mainWindow.show()
|
|||
|
|
}
|
|||
|
|
if (BrowserWindow.getAllWindows().length === 0) {
|
|||
|
|
boot()
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 在编辑器内打开链接的处理,比如 iframe 上的打开链接。
|
|||
|
|
app.on('web-contents-created', (webContentsCreatedEvent, contents) => {
|
|||
|
|
contents.on('new-window', (newWindowEvent, url) => {
|
|||
|
|
newWindowEvent.preventDefault()
|
|||
|
|
shell.openExternal(url)
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
app.on('before-quit', (event) => {
|
|||
|
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
|||
|
|
event.preventDefault()
|
|||
|
|
mainWindow.webContents.send('siyuan-save-close', true)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const {powerMonitor} = require("electron");
|
|||
|
|
|
|||
|
|
powerMonitor.on('suspend', () => {
|
|||
|
|
writeLog("system suspend");
|
|||
|
|
fetch("http://127.0.0.1:6806/api/sync/performSync", {method: "POST"});
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
powerMonitor.on('resume', () => {
|
|||
|
|
writeLog("system resume");
|
|||
|
|
fetch("http://127.0.0.1:6806/api/sync/performSync", {method: "POST"});
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
powerMonitor.on('shutdown', () => {
|
|||
|
|
writeLog("system shutdown");
|
|||
|
|
fetch('http://127.0.0.1:6806/api/system/exit', {method: 'POST'})
|
|||
|
|
})
|