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 ,
ipcMain ,
globalShortcut ,
Tray ,
} = require ( 'electron' )
const path = require ( 'path' )
const fs = require ( 'fs' )
2023-01-04 23:03:08 +08:00
const net = require ( 'net' )
2022-05-26 15:18:53 +08:00
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' )
2023-01-05 19:29:48 +08:00
let bootWindow
2022-05-26 15:18:53 +08:00
let firstOpen = false
2023-01-05 11:48:03 +08:00
let workspaces = [ ] // workspaceDir, id, browserWindow, tray
2023-01-04 23:03:08 +08:00
let kernelPort = 6806
2022-05-26 15:18:53 +08:00
require ( '@electron/remote/main' ) . initialize ( )
if ( ! app . requestSingleInstanceLock ( ) ) {
app . quit ( )
return
}
2023-01-04 23:03:08 +08:00
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 )
2023-01-13 23:01:16 +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.' )
2023-01-04 23:03:08 +08:00
app . exit ( )
}
2023-01-06 16:44:54 +08:00
const getServer = ( port = kernelPort ) => {
return 'http://127.0.0.1:' + port
2023-01-04 23:03:08 +08:00
}
const sleep = ( ms ) => {
return new Promise ( resolve => setTimeout ( resolve , ms ) )
}
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 ( )
}
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
// 创建主窗体
2023-01-04 23:03:08 +08:00
const currentWindow = new BrowserWindow ( {
2022-05-26 15:18:53 +08:00
show : false ,
backgroundColor : '#FFF' , // 桌面端主窗体背景色设置为 `#FFF` Fix https://github.com/siyuan-note/siyuan/issues/4544
width : windowState . width ,
height : windowState . height ,
2023-01-02 16:12:03 +08:00
minWidth : 493 ,
minHeight : 376 ,
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
} )
2023-01-04 23:03:08 +08:00
require ( '@electron/remote/main' ) . enable ( currentWindow . webContents )
currentWindow . webContents . userAgent = 'SiYuan/' + appVer +
2022-09-08 16:37:59 +08:00
' https://b3log.org/siyuan Electron'
2022-07-25 22:51:01 +08:00
2023-01-04 23:03:08 +08:00
currentWindow . webContents . session . setSpellCheckerLanguages ( [ 'en-US' ] )
2022-12-30 11:27:26 +08:00
2022-07-25 22:51:01 +08:00
// 发起互联网服务请求时绕过安全策略 https://github.com/siyuan-note/siyuan/issues/5516
2023-01-04 23:03:08 +08:00
currentWindow . webContents . session . webRequest . onBeforeSendHeaders (
2022-07-25 22:51:01 +08:00
( 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 } )
} )
2023-01-04 23:03:08 +08:00
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 ]
}
2022-07-25 22:51:01 +08:00
}
2023-01-04 23:03:08 +08:00
cb ( { responseHeaders : details . responseHeaders } )
} )
2022-07-25 22:51:01 +08:00
2023-01-04 23:03:08 +08:00
currentWindow . webContents . on ( 'did-finish-load' , ( ) => {
2023-01-05 19:29:48 +08:00
let siyuanOpenURL
2022-05-26 15:18:53 +08:00
if ( 'win32' === process . platform || 'linux' === process . platform ) {
siyuanOpenURL = process . argv . find ( ( arg ) => arg . startsWith ( 'siyuan://' ) )
}
if ( siyuanOpenURL ) {
2023-01-04 23:03:08 +08:00
if ( currentWindow . isMinimized ( ) ) {
currentWindow . restore ( )
2022-05-26 15:18:53 +08:00
}
2023-01-04 23:03:08 +08:00
if ( ! currentWindow . isVisible ( ) ) {
currentWindow . show ( )
2022-05-26 15:18:53 +08:00
}
2023-01-04 23:03:08 +08:00
currentWindow . focus ( )
2022-05-26 15:18:53 +08:00
setTimeout ( ( ) => { // 等待界面js执行完毕
writeLog ( siyuanOpenURL )
2023-01-04 23:03:08 +08:00
currentWindow . webContents . send ( 'siyuan-openurl' , siyuanOpenURL )
2022-05-26 15:18:53 +08:00
} , 2000 )
}
} )
if ( windowState . isDevToolsOpened ) {
2023-01-04 23:03:08 +08:00
currentWindow . webContents . openDevTools ( { mode : 'bottom' } )
2022-05-26 15:18:53 +08:00
}
// 主界面事件监听
2023-01-04 23:03:08 +08:00
currentWindow . once ( 'ready-to-show' , ( ) => {
currentWindow . show ( )
2022-05-26 15:18:53 +08:00
if ( windowState . isMaximized ) {
2023-01-04 23:03:08 +08:00
currentWindow . maximize ( )
2022-05-26 15:18:53 +08:00
} else {
2023-01-04 23:03:08 +08:00
currentWindow . unmaximize ( )
2022-05-26 15:18:53 +08:00
}
if ( bootWindow && ! bootWindow . isDestroyed ( ) ) {
bootWindow . destroy ( )
}
} )
// 加载主界面
2023-01-04 23:03:08 +08:00
currentWindow . 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 : 'windowMenu' ,
submenu : [
{ role : 'minimize' } ,
{ role : 'zoom' } ,
{ role : 'togglefullscreen' } ,
{ type : 'separator' } ,
{ role : 'toggledevtools' } ,
{ type : 'separator' } ,
{ role : 'front' } ,
] ,
} ,
]
const menu = Menu . buildFromTemplate ( template )
Menu . setApplicationMenu ( menu )
// 当前页面链接使用浏览器打开
2023-01-04 23:03:08 +08:00
currentWindow . webContents . on ( 'will-navigate' , ( event , url ) => {
2023-01-06 16:44:54 +08:00
const currentURL = new URL ( event . sender . getURL ( ) )
if ( url . startsWith ( getServer ( currentURL . port ) ) ) {
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 )
} )
2023-01-04 23:03:08 +08:00
currentWindow . on ( 'close' , ( event ) => {
if ( currentWindow && ! currentWindow . isDestroyed ( ) ) {
currentWindow . webContents . send ( 'siyuan-save-close' , false )
2022-05-26 15:18:53 +08:00
}
event . preventDefault ( )
} )
2023-01-04 23:03:08 +08:00
workspaces . push ( {
browserWindow : currentWindow ,
id : currentWindow . id ,
2022-05-26 15:18:53 +08:00
} )
2023-01-04 23:03:08 +08:00
}
2022-12-13 00:00:49 +08:00
2023-01-13 23:01:16 +08:00
const showWindow = ( wnd ) => {
if ( ! wnd || wnd . isDestroyed ( ) ) {
return
}
if ( wnd . isMinimized ( ) ) {
wnd . restore ( )
}
if ( ! wnd . isVisible ( ) ) {
wnd . show ( )
}
wnd . focus ( )
}
2023-01-04 23:03:08 +08:00
const initKernel = ( workspace , lang ) => {
2022-05-26 15:18:53 +08:00
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 ,
} ,
} )
2023-01-04 23:03:08 +08:00
const kernelName = 'win32' === process . platform
? 'SiYuan-Kernel.exe'
: 'SiYuan-Kernel'
2022-05-26 15:18:53 +08:00
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
}
2023-01-06 10:08:29 +08:00
if ( ! isDevEnv || workspaces . length > 0 ) {
2023-01-05 21:53:31 +08:00
const getAvailablePort = ( ) => {
2023-01-05 20:03:35 +08:00
// https://gist.github.com/mikeal/1840641
2023-01-05 21:53:31 +08:00
return new Promise ( ( portResolve , portReject ) => {
2023-01-05 21:26:22 +08:00
const server = net . createServer ( )
server . on ( 'error' , error => {
writeLog ( error )
2023-01-05 22:49:53 +08:00
kernelPort = ''
2023-01-05 21:53:31 +08:00
portReject ( )
} )
server . listen ( 0 , ( ) => {
kernelPort = server . address ( ) . port
server . close ( ( ) => portResolve ( kernelPort ) )
2023-01-05 22:49:53 +08:00
} )
2023-01-05 21:26:22 +08:00
} )
2023-01-05 20:03:35 +08:00
}
2023-01-05 21:53:31 +08:00
await getAvailablePort ( )
2023-01-05 11:18:24 +08:00
}
2023-01-05 20:03:35 +08:00
writeLog ( 'got kernel port [' + kernelPort + ']' )
2023-01-05 11:18:24 +08:00
if ( ! kernelPort ) {
bootWindow . destroy ( )
resolve ( false )
return
}
const cmds = [ '--port' , kernelPort , '--wd' , appDir ]
2023-01-06 09:55:14 +08:00
if ( isDevEnv && workspaces . length === 0 ) {
2023-01-05 23:05:15 +08:00
cmds . push ( '--mode' , 'dev' )
}
2023-01-04 23:03:08 +08:00
if ( workspace ) {
cmds . push ( '--workspace' , workspace )
2023-01-06 09:49:31 +08:00
}
if ( lang ) {
2023-01-04 23:03:08 +08:00
cmds . push ( '--lang' , lang )
}
let cmd = ` ui version [ ${ appVer } ], booting kernel [ ${ kernelPath } ${ cmds . join (
' ' ) } ] `
2022-05-26 15:18:53 +08:00
writeLog ( cmd )
2023-01-04 23:03:08 +08:00
let kernelProcessPid = ''
2023-01-06 09:55:14 +08:00
if ( ! isDevEnv || workspaces . length > 0 ) {
2022-10-25 11:15:09 +08:00
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
2023-01-05 20:03:35 +08:00
writeLog ( 'booted kernel process [pid=' + kernelProcessPid + ', port=' +
kernelPort + ']' )
2022-10-25 11:15:09 +08:00
kernelProcess . on ( 'close' , ( code ) => {
2023-01-13 23:01:16 +08:00
writeLog ( ` kernel [pid= ${ kernelProcessPid } ] 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 :
2023-01-04 23:03:08 +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
2023-01-13 23:01:16 +08:00
case 24 : // 工作空间已被锁定,尝试切换到第一个打开的工作空间
if ( workspaces && 0 < workspaces . length ) {
showWindow ( workspaces [ 0 ] . browserWindow )
return
}
2023-01-05 11:45:28 +08:00
showErrorWindow (
'⚠️ 工作空间已被锁定 The workspace is locked' ,
` <div>该工作空间正在被使用。</div><div>The workspace is in use.</div> ` )
break
case 25 :
showErrorWindow (
'⚠️ 创建工作空间目录失败 Failed to create workspace directory' ,
` <div>创建工作空间目录失败。</div><div>Failed to create workspace directory.</div> ` )
break
2022-10-25 11:15:09 +08:00
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
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' )
2022-12-20 19:40:01 +08:00
2023-01-04 23:03:08 +08:00
showErrorWindow (
'⚠️ 获取内核服务端口失败 Failed to get kernel serve port' ,
2022-12-20 19:40:01 +08:00
'<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>' )
2022-05-26 15:18:53 +08:00
bootWindow . destroy ( )
resolve ( false )
2022-12-20 19:40:01 +08:00
return
2022-05-26 15:18:53 +08:00
}
}
}
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 {
2023-01-04 23:03:08 +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 ( ( ) => {
2023-01-05 19:29:48 +08:00
let resetWindowStateOnRestart = false
2023-01-04 23:03:08 +08:00
const resetTrayMenu = ( tray , lang , mainWindow ) => {
const trayMenuTemplate = [
{
label : mainWindow . isVisible ( )
? lang . hideWindow
: lang . showWindow ,
click : ( ) => {
2023-01-13 23:01:16 +08:00
showHideWindow ( tray , lang , mainWindow )
2023-01-04 23:03:08 +08:00
} ,
} ,
{
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 )
}
2023-01-13 23:01:16 +08:00
const showHideWindow = ( tray , lang , mainWindow ) => {
2023-01-04 23:03:08 +08:00
if ( ! mainWindow . isVisible ( ) ) {
if ( mainWindow . isMinimized ( ) ) {
mainWindow . restore ( )
}
mainWindow . show ( )
} else {
mainWindow . hide ( )
}
resetTrayMenu ( tray , lang , mainWindow )
}
2022-05-26 15:18:53 +08:00
ipcMain . on ( 'siyuan-first-quit' , ( ) => {
app . exit ( )
} )
2023-01-04 23:03:08 +08:00
ipcMain . on ( 'siyuan-show' , ( event , id ) => {
2023-01-13 23:01:16 +08:00
showWindow ( BrowserWindow . fromId ( id ) )
2023-01-04 23:03:08 +08:00
} )
2023-01-05 22:55:41 +08:00
ipcMain . on ( 'siyuan-config-tray' , ( event , data ) => {
workspaces . find ( item => {
if ( item . id === data . id ) {
item . browserWindow . hide ( )
if ( 'win32' === process . platform || 'linux' === process . platform ) {
resetTrayMenu ( item . tray , data . languages , item . browserWindow )
}
return true
}
} )
2023-01-04 23:03:08 +08:00
} )
ipcMain . on ( 'siyuan-export-pdf' , ( event , data ) => {
BrowserWindow . fromId ( data . id ) . webContents . send ( 'siyuan-export-pdf' , data )
} )
ipcMain . on ( 'siyuan-export-close' , ( event , id ) => {
2023-01-05 22:30:44 +08:00
BrowserWindow . fromId ( id ) . webContents . send ( 'siyuan-export-close' , id )
2023-01-04 23:03:08 +08:00
} )
2023-01-12 16:11:51 +08:00
ipcMain . on ( 'siyuan-export-prevent' , ( event , id ) => {
BrowserWindow . fromId ( id ) . webContents . on ( 'will-navigate' , ( event , url ) => {
const currentURL = new URL ( event . sender . getURL ( ) )
if ( url . startsWith ( getServer ( currentURL . port ) ) ) {
return
}
event . preventDefault ( )
shell . openExternal ( url )
} )
} )
2023-01-05 09:44:18 +08:00
ipcMain . on ( 'siyuan-quit' , ( event , id ) => {
2023-01-04 23:03:08 +08:00
const mainWindow = BrowserWindow . fromId ( id )
let tray
workspaces . find ( ( item , index ) => {
if ( item . id === id ) {
2023-01-05 09:44:18 +08:00
if ( workspaces . length > 1 ) {
mainWindow . destroy ( )
}
2023-01-04 23:03:08 +08:00
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 ) => {
2023-01-13 23:01:16 +08:00
const foundWorkspace = workspaces . find ( ( item , index ) => {
2023-01-05 11:48:03 +08:00
if ( item . workspaceDir === data . workspace ) {
2023-01-13 23:01:16 +08:00
showWindow ( item . browserWindow )
2023-01-05 11:48:03 +08:00
return true
2023-01-04 23:03:08 +08:00
}
} )
2023-01-13 23:01:16 +08:00
if ( ! foundWorkspace ) {
2023-01-05 11:48:03 +08:00
initKernel ( data . workspace , data . lang ) . then ( ( isSucc ) => {
if ( isSucc ) {
boot ( )
}
} )
}
2023-01-04 23:03:08 +08:00
} )
ipcMain . on ( 'siyuan-init' , async ( event , data ) => {
2023-01-07 22:30:55 +08:00
const exitWS = workspaces . find ( item => {
if ( data . id === item . id && item . workspaceDir ) {
return true
}
} )
if ( exitWS ) {
return
}
2023-01-04 23:03:08 +08:00
let tray
if ( 'win32' === process . platform || 'linux' === process . platform ) {
// 系统托盘
tray = new Tray ( path . join ( appDir , 'stage' , 'icon-large.png' ) )
2023-01-05 22:02:25 +08:00
tray . setToolTip ( ` ${ path . basename ( data . workspaceDir ) } - SiYuan v ${ appVer } ` )
2023-01-04 23:03:08 +08:00
const mainWindow = BrowserWindow . fromId ( data . id )
resetTrayMenu ( tray , data . languages , mainWindow )
tray . on ( 'click' , ( ) => {
2023-01-13 23:01:16 +08:00
showHideWindow ( tray , data . languages , mainWindow )
2023-01-04 23:03:08 +08:00
} )
}
workspaces . find ( item => {
if ( data . id === item . id ) {
item . workspaceDir = data . workspaceDir
item . tray = tray
return true
}
} )
2023-01-06 16:44:54 +08:00
await fetch ( getServer ( data . port ) + '/api/system/uiproc?pid=' + process . pid ,
2023-01-04 23:03:08 +08:00
{ method : 'POST' } )
} )
ipcMain . on ( 'siyuan-hotkey' , ( event , data ) => {
globalShortcut . unregisterAll ( )
if ( ! data . hotkey ) {
return
}
globalShortcut . register ( data . hotkey , ( ) => {
2023-01-05 22:49:53 +08:00
workspaces . forEach ( item => {
const mainWindow = item . browserWindow
if ( mainWindow . isMinimized ( ) ) {
mainWindow . restore ( )
if ( ! mainWindow . isVisible ( ) ) {
2023-01-04 23:03:08 +08:00
mainWindow . show ( )
}
} else {
2023-01-05 22:49:53 +08:00
if ( mainWindow . isVisible ( ) ) {
mainWindow . hide ( )
} else {
mainWindow . show ( )
2023-01-05 22:30:44 +08:00
}
2023-01-05 22:49:53 +08:00
}
if ( 'win32' === process . platform || 'linux' === process . platform ) {
resetTrayMenu ( item . tray , data . languages , mainWindow )
}
} )
2023-01-04 23:03:08 +08:00
} )
} )
2022-05-26 15:18:53 +08:00
2023-01-12 15:53:57 +08:00
ipcMain . on ( 'siyuan-lock-screen' , ( ) => {
workspaces . forEach ( item => {
2023-01-12 16:36:50 +08:00
item . browserWindow . webContents . send ( 'siyuan-lock-screen' )
2023-01-12 15:53:57 +08:00
} )
} )
2022-05-26 15:18:53 +08:00
if ( firstOpen ) {
2023-01-05 19:29:48 +08:00
const firstOpenWindow = new BrowserWindow ( {
2022-05-26 15:18:53 +08:00
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
2023-01-04 23:03:08 +08:00
let languages = app . getPreferredSystemLanguages ( )
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 ( )
// 初始化启动
2023-01-04 23:03:08 +08:00
ipcMain . on ( 'siyuan-first-init' , ( event , data ) => {
initKernel ( data . workspace , data . lang ) . then ( ( isSucc ) => {
2022-05-26 15:18:53 +08:00
if ( isSucc ) {
boot ( )
}
} )
firstOpenWindow . destroy ( )
} )
} else {
2023-01-06 09:49:31 +08:00
const getArg = ( name ) => {
for ( let i = 0 ; i < process . argv . length ; i ++ ) {
2023-01-13 23:01:16 +08:00
if ( process . argv [ i ] . startsWith ( name ) ) {
return process . argv [ i ] . split ( '=' ) [ 1 ]
2023-01-06 09:49:31 +08:00
}
}
}
2023-01-06 16:49:44 +08:00
const workspace = getArg ( '--workspace' )
2023-01-06 09:49:31 +08:00
if ( workspace ) {
2023-01-06 16:49:44 +08:00
writeLog ( 'got arg [--workspace=' + workspace + ']' )
2023-01-06 09:49:31 +08:00
}
initKernel ( workspace ) . then ( ( isSucc ) => {
2022-05-26 15:18:53 +08:00
if ( isSucc ) {
boot ( )
}
} )
}
} )
app . on ( 'open-url' , ( event , url ) => { // for macOS
if ( url . startsWith ( 'siyuan://' ) ) {
2023-01-04 23:03:08 +08:00
workspaces . forEach ( item => {
if ( item . browserWindow && ! item . browserWindow . isDestroyed ( ) ) {
item . browserWindow . webContents . send ( 'siyuan-openurl' , url )
2022-05-26 15:18:53 +08:00
}
2023-01-04 23:03:08 +08:00
} )
2022-05-26 15:18:53 +08:00
}
} )
2023-01-13 23:01:16 +08:00
app . on ( 'second-instance' , ( event , argv ) => {
writeLog ( 'second-instance [' + argv + ']' )
const siyuanURL = argv . find ( ( arg ) => arg . startsWith ( 'siyuan://' ) )
let workspace = argv . find ( ( arg ) => arg . startsWith ( '--workspace=' ) )
if ( workspace ) {
workspace = workspace . split ( '=' ) [ 1 ]
writeLog ( 'got second-instance arg [--workspace=' + workspace + ']' )
}
let foundWorkspace = false
2023-01-04 23:03:08 +08:00
workspaces . forEach ( item => {
if ( item . browserWindow && ! item . browserWindow . isDestroyed ( ) ) {
2023-01-13 23:01:16 +08:00
if ( siyuanURL ) {
item . browserWindow . webContents . send ( 'siyuan-openurl' , siyuanURL )
return
}
if ( workspace && workspace === item . workspaceDir ) {
showWindow ( item . browserWindow )
foundWorkspace = true
}
2022-05-26 15:18:53 +08:00
}
2023-01-04 23:03:08 +08:00
} )
2023-01-13 23:01:16 +08:00
if ( ! foundWorkspace ) {
initKernel ( workspace ) . then ( ( isSucc ) => {
if ( isSucc ) {
boot ( )
}
} )
}
2022-05-26 15:18:53 +08:00
} )
app . on ( 'activate' , ( ) => {
2023-01-05 09:44:18 +08:00
if ( workspaces . length > 0 ) {
const mainWindow = workspaces [ 0 ] . browserWindow
if ( mainWindow && ! mainWindow . isDestroyed ( ) ) {
mainWindow . show ( )
}
2022-05-26 15:18:53 +08:00
}
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 ) => {
2023-01-04 23:03:08 +08:00
workspaces . forEach ( item => {
if ( item . browserWindow && ! item . browserWindow . isDestroyed ( ) ) {
event . preventDefault ( )
item . browserWindow . webContents . send ( 'siyuan-save-close' , true )
}
} )
2022-05-26 15:18:53 +08:00
} )
2022-07-19 21:24:49 +08:00
const { powerMonitor } = require ( 'electron' )
2023-01-07 22:30:55 +08:00
const { write } = require ( 'fs' )
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 ( ) => {
2023-01-06 11:06:47 +08:00
// 桌面端系统休眠唤醒后判断网络连通性后再执行数据同步 https://github.com/siyuan-note/siyuan/issues/6687
2022-07-19 21:24:49 +08:00
writeLog ( 'system resume' )
2023-01-04 23:03:08 +08:00
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
}
}
}
2022-11-23 20:41:14 +08:00
let online = false
for ( let i = 0 ; i < 7 ; i ++ ) {
if ( await isOnline ( ) ) {
online = true
2023-01-04 23:03:08 +08:00
break
2022-11-23 20:41:14 +08:00
}
2023-01-04 23:03:08 +08:00
writeLog ( 'network is offline' )
2022-11-23 20:41:14 +08:00
await sleep ( 1000 )
}
if ( ! online ) {
2023-01-04 23:03:08 +08:00
writeLog ( 'network is offline, do not sync after system resume' )
return
2022-11-23 20:41:14 +08:00
}
2023-01-06 16:49:44 +08:00
workspaces . forEach ( item => {
const currentURL = new URL ( item . browserWindow . getURL ( ) )
2023-01-06 18:44:56 +08:00
const server = getServer ( currentURL . port )
2023-01-07 22:30:55 +08:00
writeLog (
'sync after system resume [' + server + '/api/sync/performSync' + ']' )
2023-01-06 18:44:56 +08:00
fetch ( server + '/api/sync/performSync' , { method : 'POST' } )
2023-01-06 16:49:44 +08:00
} )
2022-05-26 15:18:53 +08:00
} )
powerMonitor . on ( 'shutdown' , ( ) => {
2022-07-19 21:24:49 +08:00
writeLog ( 'system shutdown' )
2023-01-06 16:49:44 +08:00
workspaces . forEach ( item => {
const currentURL = new URL ( item . browserWindow . getURL ( ) )
2023-01-06 18:44:56 +08:00
fetch ( getServer ( currentURL . port ) + '/api/system/exit' , { method : 'POST' } )
2023-01-06 16:49:44 +08:00
} )
2022-07-12 11:35:34 +08:00
} )