mirror of
https://github.com/ducbao414/win32.run.git
synced 2025-12-19 18:52:50 +09:00
init the awkward code
This commit is contained in:
287
static/html/jspaint/src/electron-main.js
Normal file
287
static/html/jspaint/src/electron-main.js
Normal file
@@ -0,0 +1,287 @@
|
||||
const { app, shell, session, dialog, ipcMain, BrowserWindow } = require('electron');
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
app.enableSandbox();
|
||||
|
||||
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
|
||||
if (require('electron-squirrel-startup')) { // eslint-disable-line global-require
|
||||
app.quit();
|
||||
}
|
||||
|
||||
// Reloading and dev tools shortcuts
|
||||
const { isPackaged } = app;
|
||||
const isDev = process.env.ELECTRON_DEBUG === "1" || !isPackaged;
|
||||
if (isDev) {
|
||||
require('electron-debug')({ showDevTools: false });
|
||||
}
|
||||
|
||||
// @TODO: let user apply this setting somewhere in the UI (togglable)
|
||||
// (Note: it would be better to use REG.EXE to apply the change, rather than a .reg file)
|
||||
// This registry modification changes the right click > Edit option for images in Windows Explorer
|
||||
const reg_contents = `Windows Registry Editor Version 5.00
|
||||
|
||||
[HKEY_CLASSES_ROOT\\SystemFileAssociations\\image\\shell\\edit\\command]
|
||||
@="\\"${process.argv[0].replace(/\\/g, "\\\\")}\\" ${isPackaged ? "" : '\\".\\" '}\\"%1\\""
|
||||
`; // oof that's a lot of escaping \\
|
||||
//// \\\\
|
||||
// /\ /\ /\ /\ /\ /\ /\ \\
|
||||
// //\\ //\\ //\\ //\\ //\\ //\\ //\\ \\
|
||||
// || || || || || || || \\
|
||||
//\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\
|
||||
const reg_file_path = path.join(
|
||||
isPackaged ? path.dirname(process.argv[0]) : ".",
|
||||
`set-jspaint${isPackaged ? "" : "-DEV-MODE"}-as-default-image-editor.reg`
|
||||
);
|
||||
if (process.platform == "win32" && isPackaged) {
|
||||
fs.writeFile(reg_file_path, reg_contents, (err) => {
|
||||
if (err) {
|
||||
return console.error(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// In case of XSS holes, don't give the page free reign over the filesystem!
|
||||
// Only allow allow access to files explicitly opened by the user.
|
||||
const allowed_file_paths = [];
|
||||
|
||||
let initial_file_path;
|
||||
if (process.argv.length >= 2) {
|
||||
// in production, "path/to/jspaint.exe" "maybe/a/file.png"
|
||||
// in development, "path/to/electron.exe" "." "maybe/a/file.png"
|
||||
const initial_file_path = process.argv[isPackaged ? 1 : 2];
|
||||
allowed_file_paths.push(initial_file_path);
|
||||
}
|
||||
|
||||
// Keep a global reference of the window object, if you don't, the window will
|
||||
// be closed automatically when the JavaScript object is garbage collected.
|
||||
// @TODO: It's been several electron versions. I doubt this is still necessary. (It was from a boilerplate.)
|
||||
let mainWindow;
|
||||
|
||||
const createWindow = () => {
|
||||
// Create the browser window.
|
||||
mainWindow = new BrowserWindow({
|
||||
useContentSize: true,
|
||||
autoHideMenuBar: true, // it adds height for a native menu bar unless we hide it here
|
||||
// setMenu(null) below is too late; it's already decided on the size by then
|
||||
width: 800,
|
||||
height: 600,
|
||||
minWidth: 260,
|
||||
minHeight: 360,
|
||||
icon: path.join(__dirname, "../images/icons",
|
||||
process.platform === "win32" ?
|
||||
"jspaint.ico" :
|
||||
process.platform === "darwin" ?
|
||||
"jspaint.icns" :
|
||||
"48x48.png"
|
||||
),
|
||||
title: "JS Paint",
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, "/electron-injected.js"),
|
||||
contextIsolation: false,
|
||||
},
|
||||
});
|
||||
|
||||
// @TODO: maybe use the native menu for the "Modern" theme, or a "Native" theme
|
||||
mainWindow.setMenu(null);
|
||||
|
||||
// and load the index.html of the app.
|
||||
mainWindow.loadURL(`file://${__dirname}/../index.html`);
|
||||
|
||||
// Emitted when the window is closed.
|
||||
mainWindow.on('closed', () => {
|
||||
// Dereference the window object, usually you would store windows
|
||||
// in an array if your app supports multi windows, this is the time
|
||||
// when you should delete the corresponding element.
|
||||
mainWindow = null;
|
||||
});
|
||||
|
||||
// Emitted before the window is closed.
|
||||
mainWindow.on('close', (event) => {
|
||||
// Don't need to check mainWindow.isDocumentEdited(),
|
||||
// because the (un)edited state is handled by the renderer process, in are_you_sure().
|
||||
// Note: if the web contents are not responding, this will make the app harder to close.
|
||||
// Similarly, if there's an error, the app will be harder to close (perhaps worse as it's less likely to show a Not Responding dialog).
|
||||
// And this also prevents it from closing with Ctrl+C in the terminal, which is arguably a feature.
|
||||
mainWindow.webContents.send('close-window-prompt');
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
// Open links without target=_blank externally.
|
||||
mainWindow.webContents.on('will-navigate', (e, url) => {
|
||||
// check that the URL is not part of the app
|
||||
if (!url.includes("file://")) {
|
||||
e.preventDefault();
|
||||
shell.openExternal(url);
|
||||
}
|
||||
});
|
||||
// Open links with target=_blank externally.
|
||||
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
||||
// check that the URL is not part of the app
|
||||
if (!url.includes("file://")) {
|
||||
shell.openExternal(url);
|
||||
}
|
||||
return { action: "deny" };
|
||||
});
|
||||
|
||||
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
|
||||
callback({
|
||||
responseHeaders: {
|
||||
...details.responseHeaders,
|
||||
// connect-src needs data: for loading from localStorage,
|
||||
// and maybe blob: for loading from IndexedDB in the future.
|
||||
// (It uses fetch().)
|
||||
// Note: this should mirror the CSP in index.html, except maybe for firebase stuff.
|
||||
"Content-Security-Policy": [`
|
||||
default-src 'self';
|
||||
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
|
||||
img-src 'self' data: blob: http: https:;
|
||||
font-src 'self' https://fonts.gstatic.com;
|
||||
connect-src * data: blob:;
|
||||
`],
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
ipcMain.on("get-env-info", (event) => {
|
||||
event.returnValue = {
|
||||
isDev,
|
||||
isMacOS: process.platform === "darwin",
|
||||
initialFilePath: initial_file_path,
|
||||
};
|
||||
});
|
||||
ipcMain.on("set-represented-filename", (event, filePath) => {
|
||||
if (allowed_file_paths.includes(filePath)) {
|
||||
mainWindow.setRepresentedFilename(filePath);
|
||||
}
|
||||
});
|
||||
ipcMain.on("set-document-edited", (event, isEdited) => {
|
||||
mainWindow.setDocumentEdited(isEdited);
|
||||
});
|
||||
ipcMain.handle("show-save-dialog", async (event, options) => {
|
||||
const { filePath, canceled } = await dialog.showSaveDialog(mainWindow, {
|
||||
title: options.title,
|
||||
// defaultPath: options.defaultPath,
|
||||
defaultPath: options.defaultPath || path.basename(options.defaultFileName),
|
||||
filters: options.filters,
|
||||
});
|
||||
const fileName = path.basename(filePath);
|
||||
allowed_file_paths.push(filePath);
|
||||
return { filePath, fileName, canceled };
|
||||
});
|
||||
ipcMain.handle("show-open-dialog", async (event, options) => {
|
||||
const { filePaths, canceled } = await dialog.showOpenDialog(mainWindow, {
|
||||
title: options.title,
|
||||
defaultPath: options.defaultPath,
|
||||
filters: options.filters,
|
||||
properties: options.properties,
|
||||
});
|
||||
allowed_file_paths.push(...filePaths);
|
||||
return { filePaths, canceled };
|
||||
});
|
||||
ipcMain.handle("write-file", async (event, file_path, data) => {
|
||||
if (!allowed_file_paths.includes(file_path)) {
|
||||
return { responseCode: "ACCESS_DENIED" };
|
||||
}
|
||||
// make sure data is an ArrayBuffer, so you can't use an options object for (unknown) evil reasons
|
||||
if (data instanceof ArrayBuffer) {
|
||||
try {
|
||||
await fs.promises.writeFile(file_path, Buffer.from(data));
|
||||
} catch (error) {
|
||||
return { responseCode: "WRITE_FAILED", error };
|
||||
}
|
||||
return { responseCode: "SUCCESS" };
|
||||
} else {
|
||||
return { responseCode: "INVALID_DATA" };
|
||||
}
|
||||
});
|
||||
ipcMain.handle("read-file", async (event, file_path) => {
|
||||
if (!allowed_file_paths.includes(file_path)) {
|
||||
return { responseCode: "ACCESS_DENIED" };
|
||||
}
|
||||
try {
|
||||
const buffer = await fs.promises.readFile(file_path);
|
||||
return { responseCode: "SUCCESS", data: new Uint8Array(buffer), fileName: path.basename(file_path) };
|
||||
} catch (error) {
|
||||
return { responseCode: "READ_FAILED", error };
|
||||
}
|
||||
});
|
||||
ipcMain.handle("set-wallpaper", async (event, data) => {
|
||||
const image_path = path.join(app.getPath("userData"), "bg.png"); // Note: used without escaping
|
||||
if (!(data instanceof ArrayBuffer)) {
|
||||
return { responseCode: "INVALID_DATA" };
|
||||
}
|
||||
data = new Uint8Array(data);
|
||||
const png_magic_bytes = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
|
||||
for (let i = 0; i < png_magic_bytes.length; i++) {
|
||||
if (data[i] !== png_magic_bytes[i]) {
|
||||
console.log("Found bytes:", data.slice(0, png_magic_bytes.length), "but expected:", png_magic_bytes);
|
||||
return { responseCode: "INVALID_PNG_DATA" };
|
||||
}
|
||||
}
|
||||
try {
|
||||
await fs.promises.writeFile(image_path, Buffer.from(data));
|
||||
} catch (error) {
|
||||
return { responseCode: "WRITE_TEMP_PNG_FAILED", error };
|
||||
}
|
||||
|
||||
// The wallpaper module actually has support for Xfce, but it's not general enough.
|
||||
const bash_for_xfce = `xfconf-query -c xfce4-desktop -l | grep last-image | while read path; do xfconf-query -c xfce4-desktop -p $path -s '${image_path}'; done`;
|
||||
const { lookpath } = require("lookpath");
|
||||
if (await lookpath("xfconf-query") && await lookpath("grep")) {
|
||||
const exec = require("util").promisify(require('child_process').exec);
|
||||
try {
|
||||
await exec(bash_for_xfce);
|
||||
} catch (error) {
|
||||
console.error("Error setting wallpaper for Xfce:", error);
|
||||
return { responseCode: "XFCONF_FAILED", error };
|
||||
}
|
||||
return { responseCode: "SUCCESS" };
|
||||
} else {
|
||||
// Note: { scale: "center" } is only supported on macOS.
|
||||
// I worked around this by providing an image with a transparent margin on other platforms,
|
||||
// in setWallpaperCentered.
|
||||
return new Promise((resolve, reject) => {
|
||||
require("wallpaper").set(image_path, { scale: "center" }, error => {
|
||||
if (error) {
|
||||
resolve({ responseCode: "SET_WALLPAPER_FAILED", error });
|
||||
} else {
|
||||
resolve({ responseCode: "SUCCESS" });
|
||||
}
|
||||
});
|
||||
});
|
||||
// Newer promise-based wallpaper API that I can't import:
|
||||
// try {
|
||||
// await setWallpaper(image_path, { scale: "center" });
|
||||
// } catch (error) {
|
||||
// return { responseCode: "SET_WALLPAPER_FAILED", error };
|
||||
// }
|
||||
// return { responseCode: "SUCCESS" };
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// This method will be called when Electron has finished
|
||||
// initialization and is ready to create browser windows.
|
||||
// Some APIs can only be used after this event occurs.
|
||||
app.on('ready', createWindow);
|
||||
|
||||
// Quit when all windows are closed.
|
||||
app.on('window-all-closed', () => {
|
||||
// On OS X it is common for applications and their menu bar
|
||||
// to stay active until the user quits explicitly with Cmd + Q
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
// On OS X it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (mainWindow === null) {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
|
||||
// In this file you can include the rest of your app's specific main process
|
||||
// code. You can also put them in separate files and import them here.
|
||||
Reference in New Issue
Block a user