2022-05-26 15:18:53 +08:00
// 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' )
2022-10-01 23:38:26 +08:00
const windowStatePath = path . join ( confDir , 'windowState.json' )
2022-10-24 22:47:05 +08:00
const portJSONPath = path . join ( confDir , 'port.json' )
2022-05-26 15:18:53 +08:00
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
2022-10-01 23:38:26 +08:00
let resetWindowStateOnRestart = false
2022-10-24 22:47:05 +08:00
let kernelPort = "6806"
2022-10-29 21:47:10 +08:00
const localhost = "127.0.0.1"
2022-05-26 15:18:53 +08:00
require ( '@electron/remote/main' ) . initialize ( )
if ( ! app . requestSingleInstanceLock ( ) ) {
app . quit ( )
return
}
2022-10-27 09:47:03 +08:00
const getServer = ( ) => {
return "http://" + localhost + ":" + kernelPort
}
2022-05-26 15:18:53 +08:00
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 ,
2022-07-19 10:53:21 +08:00
icon : path . join ( appDir , 'stage' , 'icon-large.png' ) ,
2022-05-26 15:18:53 +08:00
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 ,
2022-07-19 10:53:21 +08:00
icon : path . join ( appDir , 'stage' , 'icon-large.png' ) ,
2022-05-26 15:18:53 +08:00
} ,
} )
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 )
2022-10-24 12:17:21 +08:00
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.' )
2022-05-26 15:18:53 +08:00
app . exit ( )
}
const writeLog = ( out ) => {
2022-10-24 22:47:05 +08:00
console . log ( out )
2022-05-26 15:18:53 +08:00
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 = ( ) => {
// 恢复主窗体状态
let oldWindowState = { }
try {
oldWindowState = JSON . parse ( fs . readFileSync ( windowStatePath , 'utf8' ) )
} catch ( e ) {
fs . writeFileSync ( windowStatePath , '{}' )
}
2022-07-19 21:24:49 +08:00
let defaultWidth
let defaultHeight
let workArea
try {
defaultWidth = screen . getPrimaryDisplay ( ) . size . width * 4 / 5
defaultHeight = screen . getPrimaryDisplay ( ) . workAreaSize . height * 4 / 5
workArea = screen . getPrimaryDisplay ( ) . workArea
} catch ( e ) {
console . error ( e )
}
2022-05-26 15:18:53 +08:00
const windowState = Object . assign ( { } , {
isMaximized : true ,
fullscreen : false ,
isDevToolsOpened : false ,
x : 0 , y : 0 ,
width : defaultWidth ,
height : defaultHeight ,
} , oldWindowState )
2022-07-19 21:24:49 +08:00
let x = windowState . x
let y = windowState . y
if ( workArea ) {
// 窗口大小等同于或大于 workArea 时,缩小会隐藏到左下角
if ( windowState . width >= workArea . width || windowState . height >=
workArea . height ) {
windowState . width = Math . min ( defaultWidth , workArea . width )
windowState . height = Math . min ( defaultHeight , workArea . height )
}
2022-10-01 22:54:02 +08:00
if ( x > workArea . width ) {
2022-07-19 21:24:49 +08:00
x = 0
}
2022-10-01 22:54:02 +08:00
if ( y > workArea . height ) {
2022-07-19 21:24:49 +08:00
y = 0
}
2022-05-26 15:18:53 +08:00
}
2022-10-01 22:52:45 +08:00
if ( windowState . width < 400 ) {
windowState . width = 400
}
if ( windowState . height < 300 ) {
windowState . height = 300
}
2022-10-01 22:54:02 +08:00
if ( x < 0 ) {
x = 0
}
if ( y < 0 ) {
y = 0
}
2022-05-26 15:18:53 +08:00
// 创建主窗体
mainWindow = new BrowserWindow ( {
show : false ,
backgroundColor : '#FFF' , // 桌面端主窗体背景色设置为 `#FFF` Fix https://github.com/siyuan-note/siyuan/issues/4544
width : windowState . width ,
height : windowState . height ,
2022-07-19 21:24:49 +08:00
x ,
y ,
2022-05-26 15:18:53 +08:00
fullscreenable : true ,
fullscreen : windowState . fullscreen ,
2022-10-19 09:53:08 +08:00
trafficLightPosition : { x : 8 , y : 8 } ,
2022-05-26 15:18:53 +08:00
webPreferences : {
nodeIntegration : true ,
nativeWindowOpen : true ,
webviewTag : true ,
webSecurity : false ,
contextIsolation : false ,
} ,
frame : 'darwin' === process . platform ,
titleBarStyle : 'hidden' ,
2022-07-19 10:53:21 +08:00
icon : path . join ( appDir , 'stage' , 'icon-large.png' ) ,
2022-05-26 15:18:53 +08:00
} )
require ( '@electron/remote/main' ) . enable ( mainWindow . webContents )
2022-09-08 16:37:59 +08:00
mainWindow . webContents . userAgent = 'SiYuan/' + appVer +
' https://b3log.org/siyuan Electron'
2022-07-25 22:51:01 +08:00
// 发起互联网服务请求时绕过安全策略 https://github.com/siyuan-note/siyuan/issues/5516
mainWindow . webContents . session . webRequest . onBeforeSendHeaders (
( details , cb ) => {
if ( - 1 < details . url . indexOf ( 'bili' ) ) {
// B 站不移除 Referer https://github.com/siyuan-note/siyuan/issues/94
cb ( { requestHeaders : details . requestHeaders } )
return
}
for ( let key in details . requestHeaders ) {
if ( 'referer' === key . toLowerCase ( ) ) {
delete details . requestHeaders [ key ]
}
}
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 ]
}
}
cb ( { responseHeaders : details . responseHeaders } )
} )
2022-05-26 15:18:53 +08:00
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 ( )
}
} )
// 加载主界面
2022-10-27 09:47:03 +08:00
mainWindow . loadURL ( getServer ( ) + '/stage/build/app/index.html?v=' +
2022-07-12 11:35:34 +08:00
new Date ( ) . getTime ( ) )
2022-05-26 15:18:53 +08:00
// 菜单
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 ) => {
2022-10-27 09:47:03 +08:00
if ( url . startsWith ( getServer ( ) ) ) {
2022-05-26 15:18:53 +08:00
return
}
2022-10-24 12:17:54 +08:00
2022-05-26 15:18:53 +08:00
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 ( )
}
} )
2022-09-08 16:37:59 +08:00
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 )
} )
2022-05-26 15:18:53 +08:00
ipcMain . on ( 'siyuan-quit' , ( ) => {
try {
2022-10-01 23:38:26 +08:00
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 ,
} ) )
}
2022-05-26 15:18:53 +08:00
} catch ( e ) {
2022-10-01 23:38:26 +08:00
writeLog ( e )
2022-05-26 15:18:53 +08:00
}
app . exit ( )
globalShortcut . unregisterAll ( )
writeLog ( 'exited ui' )
} )
ipcMain . on ( 'siyuan-init' , async ( ) => {
2022-10-27 09:47:03 +08:00
await fetch ( getServer ( ) + '/api/system/uiproc?pid=' + process . pid ,
2022-05-26 15:18:53 +08:00
{ 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 ) {
// 系统托盘
2022-07-19 10:53:21 +08:00
tray = new Tray ( path . join ( appDir , 'stage' , 'icon-large.png' ) )
2022-10-01 23:38:26 +08:00
tray . setToolTip ( 'SiYuan v' + appVer )
2022-05-26 15:18:53 +08:00
const trayMenuTemplate = [
2022-05-30 09:54:04 +08:00
{
label : 'Official Website' ,
click : ( ) => {
shell . openExternal ( 'https://b3log.org/siyuan/' )
2022-07-19 21:24:49 +08:00
} ,
2022-05-30 09:54:04 +08:00
} ,
{
label : 'Open Source' ,
click : ( ) => {
shell . openExternal ( 'https://github.com/siyuan-note/siyuan' )
2022-07-19 21:24:49 +08:00
} ,
2022-05-30 09:54:04 +08:00
} ,
2022-05-30 09:58:24 +08:00
{
label : '中文反馈' ,
click : ( ) => {
shell . openExternal ( 'https://ld246.com/article/1649901726096' )
2022-07-19 21:24:49 +08:00
} ,
2022-05-30 09:58:24 +08:00
} ,
2022-10-01 23:38:26 +08:00
{
label : 'Reset Window on restart' ,
type : 'checkbox' ,
click : v => {
2022-10-14 12:13:38 +08:00
resetWindowStateOnRestart = v . checked
2022-10-01 23:38:26 +08:00
} ,
} ,
2022-05-26 15:18:53 +08:00
{
label : 'Quit' ,
click : ( ) => {
mainWindow . webContents . send ( 'siyuan-save-close' , true )
} ,
2022-12-12 23:21:14 +08:00
}
]
2022-12-12 23:52:10 +08:00
const showWndMenu = {
label : 'Hide Window' ,
click : ( ) => {
showHideWnd ( )
} ,
}
trayMenuTemplate . splice ( 0 , 0 , showWndMenu )
const showHideWnd = ( ) => {
if ( ! mainWindow . isVisible ( ) ) {
if ( mainWindow . isMinimized ( ) ) {
mainWindow . restore ( )
}
showWndMenu . label = "Hide Window"
trayMenuTemplate . splice ( 0 , 1 , showWndMenu )
const contextMenu = Menu . buildFromTemplate ( trayMenuTemplate )
tray . setContextMenu ( contextMenu )
mainWindow . show ( )
} else {
mainWindow . hide ( )
showWndMenu . label = "Show Window"
trayMenuTemplate . splice ( 0 , 1 , showWndMenu )
const contextMenu = Menu . buildFromTemplate ( trayMenuTemplate )
tray . setContextMenu ( contextMenu )
}
}
2022-12-12 23:21:14 +08:00
let changeWndTop = { }
if ( 'win32' === process . platform ) {
// Windows 平台提供窗口置顶功能
changeWndTop = {
2022-12-12 23:52:10 +08:00
label : 'Set Window top' ,
2022-12-12 23:21:14 +08:00
click : ( ) => {
if ( ! mainWindow . isAlwaysOnTop ( ) ) {
mainWindow . setAlwaysOnTop ( true )
2022-12-12 23:52:10 +08:00
changeWndTop . label = 'Cancel Window top'
2022-12-12 23:21:14 +08:00
trayMenuTemplate . splice ( trayMenuTemplate . length - 2 , 1 , changeWndTop )
const contextMenu = Menu . buildFromTemplate ( trayMenuTemplate )
tray . setContextMenu ( contextMenu )
} else {
mainWindow . setAlwaysOnTop ( false )
2022-12-12 23:52:10 +08:00
changeWndTop . label = 'Set Window top'
2022-12-12 23:21:14 +08:00
trayMenuTemplate . splice ( trayMenuTemplate . length - 2 , 1 , changeWndTop )
const contextMenu = Menu . buildFromTemplate ( trayMenuTemplate )
tray . setContextMenu ( contextMenu )
}
} ,
} ;
trayMenuTemplate . splice ( trayMenuTemplate . length - 1 , 0 , changeWndTop )
}
2022-05-26 15:18:53 +08:00
const contextMenu = Menu . buildFromTemplate ( trayMenuTemplate )
tray . setContextMenu ( contextMenu )
tray . on ( 'click' , ( ) => {
2022-12-12 23:52:10 +08:00
showHideWnd ( )
2022-05-26 15:18:53 +08:00
} )
}
}
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 ,
2022-07-19 10:53:21 +08:00
icon : path . join ( appDir , 'stage' , 'icon-large.png' ) ,
2022-05-26 15:18:53 +08:00
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 ) ) {
2022-07-19 21:24:49 +08:00
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> ` )
2022-05-26 15:18:53 +08:00
bootWindow . destroy ( )
resolve ( false )
return
}
const cmds = [ '--wd' , appDir ]
2022-10-25 10:44:04 +08:00
if ( isDevEnv ) {
cmds . push ( '--mode' , 'dev' )
}
2022-05-26 15:18:53 +08:00
if ( initData ) {
const initDatas = initData . split ( '-' )
cmds . push ( '--workspace' , initDatas [ 0 ] )
cmds . push ( '--lang' , initDatas [ 1 ] )
}
2022-10-25 11:06:50 +08:00
let cmd = ` ui version [ ${ appVer } ], booting kernel [ ${ kernelPath } ${ cmds . join ( ' ' ) } ] `
2022-05-26 15:18:53 +08:00
writeLog ( cmd )
2022-10-25 11:15:09 +08:00
let kernelProcessPid = ""
if ( ! isDevEnv ) {
const cp = require ( 'child_process' )
const kernelProcess = cp . spawn ( kernelPath ,
cmds , {
detached : false , // 桌面端内核进程不再以游离模式拉起 https://github.com/siyuan-note/siyuan/issues/6336
stdio : 'ignore' ,
} ,
)
kernelProcessPid = kernelProcess . pid
writeLog ( 'booted kernel process [pid=' + kernelProcessPid + ']' )
kernelProcess . on ( 'close' , ( code ) => {
2022-10-26 11:08:32 +08:00
writeLog ( ` kernel exited with code [ ${ code } ] ` )
2022-10-25 11:15:09 +08:00
if ( 0 !== code ) {
switch ( code ) {
case 20 :
showErrorWindow ( '⚠️ 数据库被锁定 The database is locked' ,
2022-12-07 18:21:54 +08:00
` <div>数据库文件正在被其他进程占用, 请检查是否同时存在多个内核进程( SiYuan Kernel) 服务相同的工作空间。</div><div>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.</div> ` )
2022-10-25 11:15:09 +08:00
break
case 21 :
2022-10-25 14:52:39 +08:00
showErrorWindow ( '⚠️ 监听端口 ' + kernelPort + ' 失败 Failed to listen to port ' + kernelPort ,
'<div>监听 ' + kernelPort + ' 端口失败,请确保程序拥有网络权限并不受防火墙和杀毒软件阻止。</div><div>Failed to listen to port ' + kernelPort + ', please make sure the program has network permissions and is not blocked by firewalls and antivirus software.</div>' )
2022-10-25 11:15:09 +08:00
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>块树文件正在被其他程序锁定或者已经损坏,请删除 工作空间/temp/ 文件夹后重启</div><div>The block tree file is being locked by another program or is corrupted, please delete the workspace/temp/ folder and restart.</div> ` )
break
case 0 :
case 1 : // Fatal error
break
default :
showErrorWindow (
'⚠️ 内核因未知原因退出 The kernel exited for unknown reasons' ,
` <div>思源内核因未知原因退出 [code= ${ code } ],请尝试重启操作系统后再启动思源。如果该问题依然发生,请检查杀毒软件是否阻止思源内核启动。</div>
2022-05-26 15:18:53 +08:00
< 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 . < / d i v > ` )
2022-10-25 11:15:09 +08:00
break
}
2022-05-26 15:18:53 +08:00
2022-10-25 11:15:09 +08:00
bootWindow . destroy ( )
resolve ( false )
}
} )
}
2022-05-26 15:18:53 +08:00
2022-10-24 22:47:05 +08:00
const getKernelPort = async ( ) => {
2022-10-25 10:26:47 +08:00
if ( isDevEnv ) {
return kernelPort
}
2022-10-24 22:47:05 +08:00
await sleep ( 200 )
let gotPort = false
let count = 0
while ( ! gotPort ) {
try {
const portJSON = JSON . parse ( fs . readFileSync ( portJSONPath , 'utf8' ) )
2022-10-25 11:15:09 +08:00
const ret = portJSON [ kernelProcessPid ]
2022-10-24 22:47:05 +08:00
if ( ret ) {
gotPort = true
return ret
}
await sleep ( 100 )
} catch ( e ) {
await sleep ( 100 )
} finally {
count ++
if ( 64 < count ) {
2022-11-25 20:38:32 +08:00
writeLog ( 'get kernel port failed [pid=' + kernelProcessPid + '], try to use 6806' )
2022-12-07 21:08:22 +08:00
return
2022-10-24 22:47:05 +08:00
}
}
}
}
kernelPort = await getKernelPort ( )
2022-12-07 21:08:22 +08:00
if ( ! kernelPort ) {
showErrorWindow ( '⚠️ 获取内核服务端口失败 Failed to get kernel serve port' ,
'<div>获取内核服务端口失败,请确保程序拥有网络权限并不受防火墙和杀毒软件阻止。</div><div>Failed to get kernel serve port, please make sure the program has network permissions and is not blocked by firewalls and antivirus software.</div>' )
bootWindow . destroy ( )
resolve ( false )
}
2022-10-24 22:47:05 +08:00
writeLog ( "got kernel port [" + kernelPort + "]" )
2022-05-26 15:18:53 +08:00
let gotVersion = false
let apiData
let count = 0
writeLog ( 'checking kernel version' )
while ( ! gotVersion ) {
try {
2022-10-27 09:47:03 +08:00
const apiResult = await fetch ( getServer ( ) + '/api/system/version' )
2022-05-26 15:18:53 +08:00
apiData = await apiResult . json ( )
gotVersion = true
bootWindow . setResizable ( false )
2022-10-27 09:47:03 +08:00
bootWindow . loadURL ( getServer ( ) + '/appearance/boot/index.html' )
2022-05-26 15:18:53 +08:00
bootWindow . show ( )
} catch ( e ) {
writeLog ( 'get kernel version failed: ' + e . message )
2022-10-25 11:19:42 +08:00
await sleep ( 100 )
2022-05-26 15:18:53 +08:00
} finally {
count ++
2022-10-25 11:19:42 +08:00
if ( 14 < count ) {
2022-05-26 15:18:53 +08:00
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 } ] ` )
2022-10-27 09:47:03 +08:00
fetch ( getServer ( ) + '/api/system/exit' , { method : 'POST' } )
2022-05-26 15:18:53 +08:00
bootWindow . destroy ( )
resolve ( false )
} else {
let progressing = false
while ( ! progressing ) {
try {
2022-10-27 09:47:03 +08:00
const progressResult = await fetch ( getServer ( ) + '/api/system/bootProgress' )
2022-05-26 15:18:53 +08:00
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 )
2022-10-27 09:47:03 +08:00
fetch ( getServer ( ) + '/api/system/exit' , { method : 'POST' } )
2022-05-26 15:18:53 +08:00
bootWindow . destroy ( )
resolve ( false )
progressing = true
}
}
}
} else {
writeLog ( ` get kernel version failed: ${ apiData . code } , ${ apiData . msg } ` )
resolve ( false )
}
} )
}
app . setAsDefaultProtocolClient ( 'siyuan' )
2022-10-30 13:53:38 +08:00
2022-05-26 15:18:53 +08:00
app . commandLine . appendSwitch ( 'disable-web-security' )
app . commandLine . appendSwitch ( 'auto-detect' , 'false' )
app . commandLine . appendSwitch ( 'no-proxy-server' )
2022-10-30 13:53:38 +08:00
app . commandLine . appendSwitch ( 'enable-features' , 'PlatformHEVCDecoderSupport' )
2022-05-26 15:18:53 +08:00
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 ,
2022-07-19 10:53:21 +08:00
icon : path . join ( appDir , 'stage' , 'icon-large.png' ) ,
2022-05-26 15:18:53 +08:00
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' )
}
2022-12-07 11:01:29 +08:00
2022-12-07 11:04:52 +08:00
// 改进桌面端初始化时使用的外观语言 https://github.com/siyuan-note/siyuan/issues/6803
2022-12-07 11:01:29 +08:00
let languages = app . getPreferredSystemLanguages ( ) ;
2022-12-07 21:08:22 +08:00
let language = languages && 0 < languages . length && "zh-Hans-CN" === languages [ 0 ] ? "zh_CN" : "en_US" ;
2022-05-26 15:18:53 +08:00
firstOpenWindow . loadFile (
initHTMLPath , {
query : {
2022-12-07 11:01:29 +08:00
lang : language ,
2022-05-26 15:18:53 +08:00
home : app . getPath ( 'home' ) ,
v : appVer ,
2022-07-19 10:53:21 +08:00
icon : path . join ( appDir , 'stage' , 'icon-large.png' ) ,
2022-05-26 15:18:53 +08:00
} ,
} )
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 ) => {
2022-11-25 10:00:22 +08:00
contents . setWindowOpenHandler ( ( details ) => {
shell . openExternal ( details . url )
2022-11-25 10:01:52 +08:00
return { action : 'deny' }
2022-11-25 10:00:22 +08:00
} )
2022-05-26 15:18:53 +08:00
} )
app . on ( 'before-quit' , ( event ) => {
if ( mainWindow && ! mainWindow . isDestroyed ( ) ) {
event . preventDefault ( )
mainWindow . webContents . send ( 'siyuan-save-close' , true )
}
} )
2022-07-19 21:24:49 +08:00
const { powerMonitor } = require ( 'electron' )
2022-05-26 15:18:53 +08:00
powerMonitor . on ( 'suspend' , ( ) => {
2022-07-19 21:24:49 +08:00
writeLog ( 'system suspend' )
2022-05-26 15:18:53 +08:00
} )
2022-11-23 20:41:14 +08:00
powerMonitor . on ( 'resume' , async ( ) => {
2022-07-19 21:24:49 +08:00
writeLog ( 'system resume' )
2022-11-23 20:41:14 +08:00
let online = false
for ( let i = 0 ; i < 7 ; i ++ ) {
if ( await isOnline ( ) ) {
online = true
break ;
}
writeLog ( "network is offline" )
await sleep ( 1000 )
}
if ( ! online ) {
writeLog ( "network is offline, do not sync after system resume" )
return ;
}
writeLog ( "sync after system resume" )
// 桌面端系统休眠唤醒后同步延时 7s 后再执行 https://github.com/siyuan-note/siyuan/issues/6687
2022-10-27 09:47:03 +08:00
fetch ( getServer ( ) + '/api/sync/performSync' , { method : 'POST' } )
2022-05-26 15:18:53 +08:00
} )
powerMonitor . on ( 'shutdown' , ( ) => {
2022-07-19 21:24:49 +08:00
writeLog ( 'system shutdown' )
2022-10-27 09:47:03 +08:00
fetch ( getServer ( ) + '/api/system/exit' , { method : 'POST' } )
2022-07-12 11:35:34 +08:00
} )
2022-11-23 20:41:14 +08:00
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 ) {
2022-11-23 20:43:37 +08:00
try {
const result = await fetch ( "https://www.baidu.com" , { timeout : 1000 } )
return 200 === result . status
} catch ( e ) {
return false ;
}
2022-11-23 20:41:14 +08:00
}
}