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-05-26 15:18:53 +08:00
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 ,
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-24 22:47:05 +08:00
mainWindow . loadURL ( 'http://127.0.0.1:' + kernelPort + '/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-24 22:47:05 +08:00
if ( url . startsWith ( 'http://127.0.0.1:' + kernelPort ) ) {
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 )
} )
2022-10-24 12:58:20 +08:00
// IFrame 块不跟随重定向 https://github.com/siyuan-note/siyuan/issues/6327
2022-10-24 12:56:10 +08:00
mainWindow . webContents . on ( 'will-redirect' , ( event , url , isInPlace , isMainFrame ) => {
2022-10-24 22:47:05 +08:00
if ( url . startsWith ( 'http://127.0.0.1:' + kernelPort ) ) {
2022-10-24 12:17:21 +08:00
return
}
2022-10-24 12:17:54 +08:00
2022-10-24 12:56:10 +08:00
if ( ! isMainFrame ) {
event . preventDefault ( )
return
}
2022-10-24 12:17:21 +08:00
} )
2022-05-26 15:18:53 +08:00
mainWindow . on ( 'close' , ( event ) => {
if ( mainWindow && ! mainWindow . isDestroyed ( ) ) {
mainWindow . webContents . send ( 'siyuan-save-close' , false )
}
event . preventDefault ( )
} )
2022-09-25 21:52:23 +08:00
nativeTheme . on ( 'updated' , ( ) => {
mainWindow . webContents . send ( 'siyuan-update-theme' , {
theme : nativeTheme . shouldUseDarkColors ? 'dark' : 'light' ,
init : false ,
} )
} )
2022-05-26 15:18:53 +08:00
// 监听主题切换
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-09-25 21:52:23 +08:00
mainWindow . webContents . send ( 'siyuan-update-theme' , {
theme : nativeTheme . shouldUseDarkColors ? 'dark' : 'light' ,
init : true ,
} )
2022-10-24 22:47:05 +08:00
await fetch ( 'http://127.0.0.1:' + kernelPort + '/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 = [
{
label : 'Show Window' ,
click : ( ) => {
if ( mainWindow . isMinimized ( ) ) {
mainWindow . restore ( )
}
mainWindow . show ( )
} ,
} ,
2022-05-27 18:24:59 +08:00
{
label : 'Hide Window' ,
click : ( ) => {
mainWindow . hide ( )
} ,
} ,
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 )
} ,
} ]
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 ,
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
cmds . push ( '--resident' , 'false' )
if ( isDevEnv ) {
cmds . push ( '--mode' , 'dev' )
2022-10-25 11:06:50 +08:00
cmds . push ( '--port' , '6806' )
2022-10-25 10:44:04 +08:00
}
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 )
const cp = require ( 'child_process' )
const kernelProcess = cp . spawn ( kernelPath ,
cmds , {
2022-10-24 21:25:45 +08:00
detached : false , // 桌面端内核进程不再以游离模式拉起 https://github.com/siyuan-note/siyuan/issues/6336
2022-05-26 15:18:53 +08:00
stdio : 'ignore' ,
} ,
)
2022-10-24 22:47:05 +08:00
writeLog ( 'booted kernel process [pid=' + kernelProcess . pid + ']' )
2022-05-26 15:18:53 +08:00
kernelProcess . on ( 'close' , ( code ) => {
if ( 0 !== code ) {
writeLog ( ` kernel exited with code [ ${ code } ] ` )
switch ( code ) {
case 20 :
2022-07-19 21:24:49 +08:00
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> ` )
2022-05-26 15:18:53 +08:00
break
case 21 :
2022-10-24 22:47:05 +08:00
showErrorWindow ( '⚠️ ' + kernelPort + ' 端口不可用 The port ' + kernelPort + ' is unavailable' ,
'<div>思源需要监听 ' + kernelPort + ' 端口,请确保该端口可用且不是其他程序的保留端口。可尝试使用管理员运行命令:' +
'<pre><code>net stop winnat\nnetsh interface ipv4 add excludedportrange protocol=tcp startport=' + kernelPort + ' numberofports=1\nnet start winnat</code></pre></div>' +
'<div>SiYuan needs to listen to port ' + kernelPort + ', 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=' + kernelPort + ' numberofports=1\nnet start winnat</code></pre></div>' )
2022-05-26 15:18:53 +08:00
break
case 22 :
2022-09-08 16:37:59 +08:00
showErrorWindow (
'⚠️ 创建配置目录失败 Failed to create config directory' ,
2022-07-19 21:24:49 +08:00
` <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> ` )
2022-05-26 15:18:53 +08:00
break
case 23 :
2022-09-08 16:37:59 +08:00
showErrorWindow (
'⚠️ 无法读写块树文件 Failed to access blocktree file' ,
2022-10-08 11:12:31 +08:00
` <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> ` )
2022-05-26 15:18:53 +08:00
break
case 0 :
case 1 : // Fatal error
break
default :
2022-07-19 21:24:49 +08:00
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 > ` )
break
}
bootWindow . destroy ( )
resolve ( false )
}
} )
const sleep = ( ms ) => {
return new Promise ( resolve => setTimeout ( resolve , ms ) )
}
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' ) )
const ret = portJSON [ kernelProcess . pid . toString ( ) ]
if ( ret ) {
gotPort = true
return ret
}
await sleep ( 100 )
} catch ( e ) {
await sleep ( 100 )
} finally {
count ++
if ( 64 < count ) {
writeLog ( 'get kernel port failed [pid=' + kernelProcess . pid + ']' )
bootWindow . destroy ( )
resolve ( false )
}
}
}
}
kernelPort = await getKernelPort ( )
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-24 22:47:05 +08:00
const apiResult = await fetch ( 'http://127.0.0.1:' + kernelPort + '/api/system/version' )
2022-05-26 15:18:53 +08:00
apiData = await apiResult . json ( )
gotVersion = true
bootWindow . setResizable ( false )
2022-10-24 22:47:05 +08:00
bootWindow . loadURL ( 'http://127.0.0.1:' + kernelPort + '/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-24 22:47:05 +08:00
await sleep ( 200 )
2022-05-26 15:18:53 +08:00
} 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 } ] ` )
2022-10-24 22:47:05 +08:00
fetch ( 'http://127.0.0.1:' + kernelPort + '/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-24 22:47:05 +08:00
const progressResult = await fetch ( 'http://127.0.0.1:' + kernelPort + '/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-24 22:47:05 +08:00
fetch ( 'http://127.0.0.1:' + kernelPort + '/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' )
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 ,
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' )
}
firstOpenWindow . loadFile (
initHTMLPath , {
query : {
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 ) => {
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 )
}
} )
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-10-24 22:47:05 +08:00
fetch ( 'http://127.0.0.1:' + kernelPort + '/api/sync/performSync' , { method : 'POST' } )
2022-05-26 15:18:53 +08:00
} )
powerMonitor . on ( 'resume' , ( ) => {
2022-07-19 21:24:49 +08:00
writeLog ( 'system resume' )
2022-10-24 22:47:05 +08:00
fetch ( 'http://127.0.0.1:' + kernelPort + '/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-24 22:47:05 +08:00
fetch ( 'http://127.0.0.1:' + kernelPort + '/api/system/exit' , { method : 'POST' } )
2022-07-12 11:35:34 +08:00
} )