123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053 |
- // 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");
- const net = require("net");
- 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");
- const windowStatePath = path.join(confDir, "windowState.json");
- let bootWindow;
- let firstOpen = false;
- let workspaces = []; // workspaceDir, id, browserWindow, tray
- let kernelPort = 6806;
- require("@electron/remote/main").initialize();
- if (!app.requestSingleInstanceLock()) {
- app.quit();
- return;
- }
- try {
- firstOpen = !fs.existsSync(path.join(confDir, "workspace.json"));
- if (!fs.existsSync(confDir)) {
- fs.mkdirSync(confDir, {mode: 0o755, recursive: true});
- }
- } catch (e) {
- console.error(e);
- require("electron").dialog.showErrorBox("创建配置目录失败 Failed to create config directory",
- "思源需要在用户家目录下创建配置文件夹(~/.config/siyuan),请确保该路径具有写入权限。\n\nSiYuan needs to create a configuration folder (~/.config/siyuan) in the user's home directory. Please make sure that the path has write permissions.");
- app.exit();
- }
- const getServer = (port = kernelPort) => {
- return "http://127.0.0.1:" + port;
- };
- const sleep = (ms) => {
- return new Promise(resolve => setTimeout(resolve, ms));
- };
- const showErrorWindow = (title, content) => {
- let errorHTMLPath = path.join(appDir, "app", "electron", "error.html");
- if (isDevEnv) {
- 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-large.png"),
- webPreferences: {
- 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-large.png"),
- },
- });
- errWindow.show();
- };
- const writeLog = (out) => {
- console.log(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 = () => {
- // 恢复主窗体状态
- let oldWindowState = {};
- try {
- oldWindowState = JSON.parse(fs.readFileSync(windowStatePath, "utf8"));
- } catch (e) {
- fs.writeFileSync(windowStatePath, "{}");
- }
- let defaultWidth;
- let defaultHeight;
- let workArea;
- try {
- defaultWidth = screen.getPrimaryDisplay().size.width;
- defaultHeight = screen.getPrimaryDisplay().workAreaSize.height;
- workArea = screen.getPrimaryDisplay().workArea;
- } catch (e) {
- console.error(e);
- }
- const windowState = Object.assign({}, {
- isMaximized: true,
- fullscreen: false,
- isDevToolsOpened: false,
- x: 0, y: 0,
- width: defaultWidth,
- height: defaultHeight,
- }, oldWindowState);
- 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);
- }
- if (x > workArea.width) {
- x = 0;
- }
- if (y > workArea.height) {
- y = 0;
- }
- }
- if (windowState.width < 400) {
- windowState.width = 400;
- }
- if (windowState.height < 300) {
- windowState.height = 300;
- }
- if (x < 0) {
- x = 0;
- }
- if (y < 0) {
- y = 0;
- }
- // 创建主窗体
- const currentWindow = new BrowserWindow({
- show: false,
- backgroundColor: "#FFF", // 桌面端主窗体背景色设置为 `#FFF` Fix https://github.com/siyuan-note/siyuan/issues/4544
- width: windowState.width,
- height: windowState.height,
- minWidth: 493,
- minHeight: 376,
- x,
- y,
- fullscreenable: true,
- fullscreen: windowState.fullscreen,
- trafficLightPosition: {x: 8, y: 8},
- webPreferences: {
- nodeIntegration: true,
- webviewTag: true,
- webSecurity: false,
- contextIsolation: false,
- },
- frame: "darwin" === process.platform,
- titleBarStyle: "hidden",
- icon: path.join(appDir, "stage", "icon-large.png"),
- });
- require("@electron/remote/main").enable(currentWindow.webContents);
- currentWindow.webContents.userAgent = "SiYuan/" + appVer +
- " https://b3log.org/siyuan Electron";
- currentWindow.webContents.session.setSpellCheckerLanguages(["en-US"]);
- // 发起互联网服务请求时绕过安全策略 https://github.com/siyuan-note/siyuan/issues/5516
- currentWindow.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});
- });
- currentWindow.webContents.session.webRequest.onHeadersReceived(
- (details, cb) => {
- for (let key in details.responseHeaders) {
- if ("x-frame-options" === key.toLowerCase()) {
- delete details.responseHeaders[key];
- } else if ("content-security-policy" === key.toLowerCase()) {
- delete details.responseHeaders[key];
- } else if ("access-control-allow-origin" === key.toLowerCase()) {
- delete details.responseHeaders[key];
- }
- }
- cb({responseHeaders: details.responseHeaders});
- });
- currentWindow.webContents.on("did-finish-load", () => {
- let siyuanOpenURL;
- if ("win32" === process.platform || "linux" === process.platform) {
- siyuanOpenURL = process.argv.find((arg) => arg.startsWith("siyuan://"));
- }
- if (siyuanOpenURL) {
- if (currentWindow.isMinimized()) {
- currentWindow.restore();
- }
- if (!currentWindow.isVisible()) {
- currentWindow.show();
- }
- currentWindow.focus();
- setTimeout(() => { // 等待界面js执行完毕
- writeLog(siyuanOpenURL);
- currentWindow.webContents.send("siyuan-openurl", siyuanOpenURL);
- }, 2000);
- }
- });
- if (windowState.isDevToolsOpened) {
- currentWindow.webContents.openDevTools({mode: "bottom"});
- }
- // 主界面事件监听
- currentWindow.once("ready-to-show", () => {
- currentWindow.show();
- if (windowState.isMaximized) {
- currentWindow.maximize();
- } else {
- currentWindow.unmaximize();
- }
- if (bootWindow && !bootWindow.isDestroyed()) {
- bootWindow.destroy();
- }
- });
- // 加载主界面
- currentWindow.loadURL(getServer() + "/stage/build/app/index.html?v=" +
- new Date().getTime());
- // 菜单
- 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);
- // 当前页面链接使用浏览器打开
- currentWindow.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);
- });
- currentWindow.on("close", (event) => {
- if (currentWindow && !currentWindow.isDestroyed()) {
- currentWindow.webContents.send("siyuan-save-close", false);
- }
- event.preventDefault();
- });
- workspaces.push({
- browserWindow: currentWindow,
- id: currentWindow.id,
- });
- };
- const showWindow = (wnd) => {
- if (!wnd || wnd.isDestroyed()) {
- return;
- }
- if (wnd.isMinimized()) {
- wnd.restore();
- }
- if (!wnd.isVisible()) {
- wnd.show();
- }
- wnd.focus();
- };
- const initKernel = (workspace, port, lang) => {
- 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-large.png"),
- transparent: "linux" !== process.platform,
- });
- 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;
- }
- if (!isDevEnv || workspaces.length > 0) {
- if (port && "" !== port) {
- kernelPort = port;
- } else {
- const getAvailablePort = () => {
- // https://gist.github.com/mikeal/1840641
- return new Promise((portResolve, portReject) => {
- const server = net.createServer();
- server.on("error", error => {
- writeLog(error);
- kernelPort = "";
- portReject();
- });
- server.listen(0, () => {
- kernelPort = server.address().port;
- server.close(() => portResolve(kernelPort));
- });
- });
- };
- await getAvailablePort();
- }
- }
- writeLog("got kernel port [" + kernelPort + "]");
- if (!kernelPort) {
- bootWindow.destroy();
- resolve(false);
- return;
- }
- const cmds = ["--port", kernelPort, "--wd", appDir];
- if (isDevEnv && workspaces.length === 0) {
- cmds.push("--mode", "dev");
- }
- if (workspace && "" !== workspace) {
- cmds.push("--workspace", workspace);
- }
- if (port && "" !== port) {
- cmds.push("--port", port);
- }
- if (lang && "" !== lang) {
- cmds.push("--lang", lang);
- }
- let cmd = `ui version [${appVer}], booting kernel [${kernelPath} ${cmds.join(
- " ")}]`;
- writeLog(cmd);
- let kernelProcessPid = "";
- if (!isDevEnv || workspaces.length > 0) {
- 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 + ", port=" +
- kernelPort + "]");
- kernelProcess.on("close", (code) => {
- writeLog(`kernel [pid=${kernelProcessPid}] exited with code [${code}]`);
- if (0 !== code) {
- switch (code) {
- case 20:
- showErrorWindow("⚠️ 数据库被锁定 The database is locked",
- "<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>");
- break;
- case 21:
- 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>");
- 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 24: // 工作空间已被锁定,尝试切换到第一个打开的工作空间
- if (workspaces && 0 < workspaces.length) {
- showWindow(workspaces[0].browserWindow);
- }
- 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;
- 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);
- }
- });
- }
- let gotVersion = false;
- let apiData;
- let count = 0;
- writeLog("checking kernel version");
- while (!gotVersion && count < 15) {
- try {
- const apiResult = await fetch(getServer() + "/api/system/version");
- apiData = await apiResult.json();
- gotVersion = true;
- bootWindow.setResizable(false);
- bootWindow.loadURL(getServer() + "/appearance/boot/index.html");
- bootWindow.show();
- } catch (e) {
- writeLog("get kernel version failed: " + e.message);
- await sleep(100);
- } finally {
- count++;
- if (14 < count) {
- writeLog("get kernel ver failed");
- 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);
- }
- }
- }
- if (!gotVersion) {
- return;
- }
- 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(getServer() + "/api/system/exit", {method: "POST"});
- bootWindow.destroy();
- resolve(false);
- } else {
- let progressing = false;
- while (!progressing) {
- try {
- const progressResult = await fetch(
- getServer() + "/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(getServer() + "/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.commandLine.appendSwitch("enable-features", "PlatformHEVCDecoderSupport");
- app.setPath("userData", app.getPath("userData") + "-Electron"); // `~/.config` 下 Electron 相关文件夹名称改为 `SiYuan-Electron` https://github.com/siyuan-note/siyuan/issues/3349
- app.whenReady().then(() => {
- let resetWindowStateOnRestart = false;
- const resetTrayMenu = (tray, lang, mainWindow) => {
- const trayMenuTemplate = [
- {
- label: mainWindow.isVisible()
- ? lang.hideWindow
- : lang.showWindow,
- click: () => {
- showHideWindow(tray, lang, mainWindow);
- },
- },
- {
- label: lang.officialWebsite,
- click: () => {
- shell.openExternal("https://b3log.org/siyuan/");
- },
- },
- {
- label: lang.openSource,
- click: () => {
- shell.openExternal("https://github.com/siyuan-note/siyuan");
- },
- },
- {
- label: lang.resetWindow,
- type: "checkbox",
- click: v => {
- resetWindowStateOnRestart = v.checked;
- mainWindow.webContents.send("siyuan-save-close", true);
- },
- },
- {
- label: lang.quit,
- click: () => {
- mainWindow.webContents.send("siyuan-save-close", true);
- },
- },
- ];
- if ("win32" === process.platform) {
- // Windows 端支持窗口置顶 https://github.com/siyuan-note/siyuan/issues/6860
- trayMenuTemplate.splice(1, 0, {
- label: mainWindow.isAlwaysOnTop()
- ? lang.cancelWindowTop
- : lang.setWindowTop,
- click: () => {
- if (!mainWindow.isAlwaysOnTop()) {
- mainWindow.setAlwaysOnTop(true);
- } else {
- mainWindow.setAlwaysOnTop(false);
- }
- resetTrayMenu(tray, lang, mainWindow);
- },
- });
- }
- const contextMenu = Menu.buildFromTemplate(trayMenuTemplate);
- tray.setContextMenu(contextMenu);
- };
- const hideWindow = (wnd) => {
- // 通过 `Alt+M` 最小化后焦点回到先前的窗口 https://github.com/siyuan-note/siyuan/issues/7275
- wnd.minimize();
- wnd.hide();
- };
- const showHideWindow = (tray, lang, mainWindow) => {
- if (!mainWindow.isVisible()) {
- if (mainWindow.isMinimized()) {
- mainWindow.restore();
- }
- mainWindow.show();
- } else {
- hideWindow(mainWindow);
- }
- resetTrayMenu(tray, lang, mainWindow);
- };
- ipcMain.on("siyuan-first-quit", () => {
- app.exit();
- });
- ipcMain.on("siyuan-show", (event, id) => {
- showWindow(BrowserWindow.fromId(id));
- });
- ipcMain.on("siyuan-config-tray", (event, data) => {
- workspaces.find(item => {
- if (item.id === data.id) {
- hideWindow(item.browserWindow);
- if ("win32" === process.platform || "linux" === process.platform) {
- resetTrayMenu(item.tray, data.languages, item.browserWindow);
- }
- return true;
- }
- });
- });
- ipcMain.on("siyuan-closetab", (event, data) => {
- BrowserWindow.getAllWindows().forEach(item => {
- item.webContents.send("siyuan-closetab", data);
- });
- });
- ipcMain.on("siyuan-export-pdf", (event, data) => {
- BrowserWindow.fromId(data.id).webContents.send("siyuan-export-pdf", data);
- });
- ipcMain.on("siyuan-export-close", (event, id) => {
- BrowserWindow.fromId(id).webContents.send("siyuan-export-close", id);
- });
- 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);
- });
- });
- ipcMain.on("siyuan-quit", (event, id) => {
- const mainWindow = BrowserWindow.fromId(id);
- let tray;
- workspaces.find((item, index) => {
- if (item.id === id) {
- if (workspaces.length > 1) {
- mainWindow.destroy();
- }
- tray = item.tray;
- workspaces.splice(index, 1);
- return true;
- }
- });
- if (tray && "win32" === process.platform) {
- tray.destroy();
- }
- if (workspaces.length === 0) {
- try {
- if (resetWindowStateOnRestart) {
- fs.writeFileSync(windowStatePath, "{}");
- } else {
- const bounds = mainWindow.getBounds();
- fs.writeFileSync(windowStatePath, JSON.stringify({
- isMaximized: mainWindow.isMaximized(),
- fullscreen: mainWindow.isFullScreen(),
- isDevToolsOpened: mainWindow.webContents.isDevToolsOpened(),
- x: bounds.x,
- y: bounds.y,
- width: bounds.width,
- height: bounds.height,
- }));
- }
- } catch (e) {
- writeLog(e);
- }
- app.exit();
- globalShortcut.unregisterAll();
- writeLog("exited ui");
- }
- });
- ipcMain.on("siyuan-openwindow", (event, data) => {
- const win = new BrowserWindow({
- show: true,
- backgroundColor: "#FFF",
- trafficLightPosition: {x: 8, y: 13},
- width: screen.getPrimaryDisplay().size.width * 0.7,
- height: screen.getPrimaryDisplay().size.height * 0.9,
- minWidth: 493,
- minHeight: 376,
- fullscreenable: true,
- frame: "darwin" === process.platform,
- icon: path.join(appDir, "stage", "icon-large.png"),
- titleBarStyle: "hidden",
- webPreferences: {
- contextIsolation: false,
- nodeIntegration: true,
- webviewTag: true,
- webSecurity: false,
- },
- });
- win.loadURL(data);
- require("@electron/remote/main").enable(win.webContents);
- });
- ipcMain.on("siyuan-open-workspace", (event, data) => {
- const foundWorkspace = workspaces.find((item) => {
- if (item.workspaceDir === data.workspace) {
- showWindow(item.browserWindow);
- return true;
- }
- });
- if (!foundWorkspace) {
- initKernel(data.workspace, "", data.lang).then((isSucc) => {
- if (isSucc) {
- boot();
- }
- });
- }
- });
- ipcMain.on("siyuan-init", async (event, data) => {
- const exitWS = workspaces.find(item => {
- if (data.id === item.id && item.workspaceDir) {
- return true;
- }
- });
- if (exitWS) {
- return;
- }
- let tray;
- if ("win32" === process.platform || "linux" === process.platform) {
- // 系统托盘
- tray = new Tray(path.join(appDir, "stage", "icon-large.png"));
- tray.setToolTip(`${path.basename(data.workspaceDir)} - SiYuan v${appVer}`);
- const mainWindow = BrowserWindow.fromId(data.id);
- resetTrayMenu(tray, data.languages, mainWindow);
- tray.on("click", () => {
- showHideWindow(tray, data.languages, mainWindow);
- });
- }
- workspaces.find(item => {
- if (data.id === item.id) {
- item.workspaceDir = data.workspaceDir;
- item.tray = tray;
- return true;
- }
- });
- await fetch(getServer(data.port) + "/api/system/uiproc?pid=" + process.pid,
- {method: "POST"});
- });
- ipcMain.on("siyuan-hotkey", (event, data) => {
- globalShortcut.unregisterAll();
- if (!data.hotkey) {
- return;
- }
- globalShortcut.register(data.hotkey, () => {
- workspaces.forEach(item => {
- const mainWindow = item.browserWindow;
- if (mainWindow.isMinimized()) {
- mainWindow.restore();
- if (!mainWindow.isVisible()) {
- mainWindow.show();
- }
- } else {
- if (mainWindow.isVisible()) {
- if (1 === workspaces.length) { // 改进 `Alt+M` 激活窗口 https://github.com/siyuan-note/siyuan/issues/7258
- if (!mainWindow.isFocused()) {
- mainWindow.show();
- } else {
- hideWindow(mainWindow);
- }
- } else {
- hideWindow(mainWindow);
- }
- } else {
- mainWindow.show();
- }
- }
- if ("win32" === process.platform || "linux" === process.platform) {
- resetTrayMenu(item.tray, data.languages, mainWindow);
- }
- });
- });
- });
- ipcMain.on("siyuan-lock-screen", () => {
- BrowserWindow.getAllWindows().forEach(item => {
- item.webContents.send("siyuan-lock-screen");
- });
- });
- if (firstOpen) {
- const firstOpenWindow = new BrowserWindow({
- width: screen.getPrimaryDisplay().size.width * 0.6,
- height: screen.getPrimaryDisplay().workAreaSize.height * 0.8,
- frame: false,
- icon: path.join(appDir, "stage", "icon-large.png"),
- transparent: "linux" !== process.platform,
- webPreferences: {
- 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");
- }
- // 改进桌面端初始化时使用的外观语言 https://github.com/siyuan-note/siyuan/issues/6803
- let languages = app.getPreferredSystemLanguages();
- let language = languages && 0 < languages.length && "zh-Hans-CN" ===
- languages[0] ? "zh_CN" : "en_US";
- firstOpenWindow.loadFile(
- initHTMLPath, {
- query: {
- lang: language,
- home: app.getPath("home"),
- v: appVer,
- icon: path.join(appDir, "stage", "icon-large.png"),
- },
- });
- firstOpenWindow.show();
- // 初始化启动
- ipcMain.on("siyuan-first-init", (event, data) => {
- initKernel(data.workspace, "", data.lang).then((isSucc) => {
- if (isSucc) {
- boot();
- }
- });
- firstOpenWindow.destroy();
- });
- } else {
- const getArg = (name) => {
- for (let i = 0; i < process.argv.length; i++) {
- if (process.argv[i].startsWith(name)) {
- return process.argv[i].split("=")[1];
- }
- }
- };
- const workspace = getArg("--workspace");
- if (workspace) {
- writeLog("got arg [--workspace=" + workspace + "]");
- }
- const port = getArg("--port");
- if (port) {
- writeLog("got arg [--port=" + port + "]");
- }
- initKernel(workspace, port, "").then((isSucc) => {
- if (isSucc) {
- boot();
- }
- });
- }
- });
- app.on("open-url", (event, url) => { // for macOS
- if (url.startsWith("siyuan://")) {
- workspaces.forEach(item => {
- if (item.browserWindow && !item.browserWindow.isDestroyed()) {
- item.browserWindow.webContents.send("siyuan-openurl", url);
- }
- });
- }
- });
- app.on("second-instance", (event, argv) => {
- writeLog("second-instance [" + argv + "]");
- let workspace = argv.find((arg) => arg.startsWith("--workspace="));
- if (workspace) {
- workspace = workspace.split("=")[1];
- writeLog("got second-instance arg [--workspace=" + workspace + "]");
- }
- let port = argv.find((arg) => arg.startsWith("--port="));
- if (port) {
- port = port.split("=")[1];
- writeLog("got second-instance arg [--port=" + port + "]");
- } else {
- port = 0;
- }
- const foundWorkspace = workspaces.find(item => {
- if (item.browserWindow && !item.browserWindow.isDestroyed()) {
- if (workspace && workspace === item.workspaceDir) {
- showWindow(item.browserWindow);
- return true;
- }
- }
- });
- if (foundWorkspace) {
- return;
- }
- if (workspace) {
- initKernel(workspace, port, "").then((isSucc) => {
- if (isSucc) {
- boot();
- }
- });
- return;
- }
- const siyuanURL = argv.find((arg) => arg.startsWith("siyuan://"));
- workspaces.forEach(item => {
- if (item.browserWindow && !item.browserWindow.isDestroyed() && siyuanURL) {
- item.browserWindow.webContents.send("siyuan-openurl", siyuanURL);
- }
- });
- if (!siyuanURL && 0 < workspaces.length) {
- showWindow(workspaces[0].browserWindow);
- }
- });
- app.on("activate", () => {
- if (workspaces.length > 0) {
- const mainWindow = workspaces[0].browserWindow;
- if (mainWindow && !mainWindow.isDestroyed()) {
- mainWindow.show();
- }
- }
- if (BrowserWindow.getAllWindows().length === 0) {
- boot();
- }
- });
- // 在编辑器内打开链接的处理,比如 iframe 上的打开链接。
- app.on("web-contents-created", (webContentsCreatedEvent, contents) => {
- contents.setWindowOpenHandler((details) => {
- shell.openExternal(details.url);
- return {action: "deny"};
- });
- });
- app.on("before-quit", (event) => {
- workspaces.forEach(item => {
- if (item.browserWindow && !item.browserWindow.isDestroyed()) {
- event.preventDefault();
- item.browserWindow.webContents.send("siyuan-save-close", true);
- }
- });
- });
- const {powerMonitor} = require("electron");
- powerMonitor.on("suspend", () => {
- writeLog("system suspend");
- });
- powerMonitor.on("resume", async () => {
- // 桌面端系统休眠唤醒后判断网络连通性后再执行数据同步 https://github.com/siyuan-note/siyuan/issues/6687
- writeLog("system resume");
- const isOnline = async () => {
- try {
- const result = await fetch("https://www.baidu.com", {timeout: 1000});
- return 200 === result.status;
- } catch (e) {
- try {
- const result = await fetch("https://icanhazip.com", {timeout: 1000});
- return 200 === result.status;
- } catch (e) {
- return false;
- }
- }
- };
- let online = false;
- for (let i = 0; i < 7; i++) {
- if (await isOnline()) {
- online = true;
- break;
- }
- writeLog("network is offline");
- await sleep(1000);
- }
- if (!online) {
- writeLog("network is offline, do not sync after system resume");
- return;
- }
- workspaces.forEach(item => {
- const currentURL = new URL(item.browserWindow.getURL());
- const server = getServer(currentURL.port);
- writeLog(
- "sync after system resume [" + server + "/api/sync/performSync" + "]");
- fetch(server + "/api/sync/performSync", {method: "POST"});
- });
- });
- powerMonitor.on("shutdown", () => {
- writeLog("system shutdown");
- workspaces.forEach(item => {
- const currentURL = new URL(item.browserWindow.getURL());
- fetch(getServer(currentURL.port) + "/api/system/exit", {method: "POST"});
- });
- });
|