node build fixed
This commit is contained in:
5
seanime-2.9.10/seanime-denshi/.gitignore
vendored
Normal file
5
seanime-2.9.10/seanime-denshi/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
node_modules/
|
||||
dist/
|
||||
binaries/
|
||||
.DS_Store
|
||||
web-denshi/
|
||||
156
seanime-2.9.10/seanime-denshi/README.md
Normal file
156
seanime-2.9.10/seanime-denshi/README.md
Normal file
@@ -0,0 +1,156 @@
|
||||
<p align="center">
|
||||
<img src="../seanime-web/public/logo_2.png" alt="preview" width="150px"/>
|
||||
</p>
|
||||
|
||||
<h2 align="center"><b>Seanime Denshi</b></h2>
|
||||
|
||||
<p align="center">
|
||||
Electron-based desktop client for Seanime. Embeds server and web interface. Successor to Seanime Desktop.
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="../docs/images/4/anime-entry-torrent-stream--sq.jpg" alt="preview" width="70%"/>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Go 1.24+
|
||||
- Node.js 20+ and npm
|
||||
|
||||
---
|
||||
|
||||
## Seanime Denshi vs Seanime Desktop
|
||||
|
||||
Pros:
|
||||
- Linux support
|
||||
- Better consistency accross platforms (fewer bugs)
|
||||
- Built-in player support for torrent/debrid streaming without transcoding
|
||||
|
||||
Cons:
|
||||
- Greater memory usage
|
||||
- Larger binary size (from ~80mb to ~300mb)
|
||||
|
||||
## TODO
|
||||
|
||||
- [ ] Built-in player
|
||||
- Server: Stream subtitle extraction, thumbnail generation
|
||||
- [ ] Testing on Windows (Fix titlebar in fullscreen)
|
||||
- [ ] Fix crash screen
|
||||
- [ ] Test server reconnection
|
||||
- [ ] Test updates, auto updates
|
||||
|
||||
## Development
|
||||
|
||||
### Web Interface
|
||||
|
||||
```shell
|
||||
# Working dir: ./seanime-web
|
||||
npm run dev:denshi
|
||||
```
|
||||
|
||||
### Sidecar
|
||||
|
||||
1. Build the server
|
||||
|
||||
```shell
|
||||
# Working dir: .
|
||||
|
||||
# Windows
|
||||
go build -o seanime.exe -trimpath -ldflags="-s -w" -tags=nosystray
|
||||
|
||||
# Linux, macOS
|
||||
go build -o seanime -trimpath -ldflags="-s -w"
|
||||
```
|
||||
|
||||
2. Move the binary to `./seanime-denshi/binaries`
|
||||
|
||||
3. Rename the binary:
|
||||
|
||||
- For Windows: `seanime-server-windows.exe`
|
||||
- For macOS/Intel: `seanime-server-darwin-amd64`
|
||||
- For macOS/ARM: `seanime-server-darwin-arm64`
|
||||
- For Linux/x86_64: `seanime-server-linux-amd64`
|
||||
- For Linux/ARM64: `seanime-server-linux-arm64`
|
||||
|
||||
### Electron
|
||||
|
||||
1. Setup
|
||||
|
||||
```shell
|
||||
# Working dir: ./seanime-denshi
|
||||
npm install
|
||||
```
|
||||
|
||||
2. Run
|
||||
|
||||
`TEST_DATADIR` can be used in development mode, it should point to a dummy data directory for testing purposes.
|
||||
|
||||
```shell
|
||||
# Working dir: ./seanime-desktop
|
||||
TEST_DATADIR="/path/to/data/dir" npm run dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Build
|
||||
|
||||
### Web Interface
|
||||
|
||||
```shell
|
||||
# Working dir: ./seanime-web
|
||||
npm run build
|
||||
npm run build:denshi
|
||||
```
|
||||
|
||||
Move the output `./seanime-web/out` to `./web`
|
||||
Move the output `./seanime-web/out-denshi` to `./seanime-denshi/web-denshi`
|
||||
|
||||
```shell
|
||||
# UNIX command
|
||||
mv ./seanime-web/out ./web
|
||||
mv ./seanime-web/out-denshi ./seanime-denshi/web-denshi
|
||||
```
|
||||
|
||||
### Sidecar
|
||||
|
||||
1. Build the server
|
||||
|
||||
```shell
|
||||
# Working dir: .
|
||||
|
||||
# Windows
|
||||
go build -o seanime.exe -trimpath -ldflags="-s -w" -tags=nosystray
|
||||
|
||||
# Linux, macOS
|
||||
go build -o seanime -trimpath -ldflags="-s -w"
|
||||
```
|
||||
|
||||
2. Move the binary to `./seanime-denshi/binaries`
|
||||
|
||||
3. Rename the binary:
|
||||
|
||||
- For Windows: `seanime-server-windows.exe`
|
||||
- For macOS/Intel: `seanime-server-darwin-amd64`
|
||||
- For macOS/ARM: `seanime-server-darwin-arm64`
|
||||
- For Linux/x86_64: `seanime-server-linux-amd64`
|
||||
- For Linux/ARM64: `seanime-server-linux-arm64`
|
||||
|
||||
### Electron
|
||||
|
||||
To build the desktop client for all platforms:
|
||||
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
To build for specific platforms:
|
||||
|
||||
```
|
||||
npm run build:mac
|
||||
npm run build:win
|
||||
npm run build:linux
|
||||
```
|
||||
|
||||
Output is in `./seanime-denshi/dist/...`
|
||||
BIN
seanime-2.9.10/seanime-denshi/assets/18x18.png
Normal file
BIN
seanime-2.9.10/seanime-denshi/assets/18x18.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1004 B |
BIN
seanime-2.9.10/seanime-denshi/assets/32x32.png
Normal file
BIN
seanime-2.9.10/seanime-denshi/assets/32x32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
23
seanime-2.9.10/seanime-denshi/assets/entitlements.mac.plist
Normal file
23
seanime-2.9.10/seanime-denshi/assets/entitlements.mac.plist
Normal file
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
<key>com.apple.security.inherit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
|
||||
<true/>
|
||||
<key>com.apple.security.automation.apple-events</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.downloads.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.all</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
BIN
seanime-2.9.10/seanime-denshi/assets/icon.icns
Normal file
BIN
seanime-2.9.10/seanime-denshi/assets/icon.icns
Normal file
Binary file not shown.
BIN
seanime-2.9.10/seanime-denshi/assets/icon.ico
Normal file
BIN
seanime-2.9.10/seanime-denshi/assets/icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
BIN
seanime-2.9.10/seanime-denshi/assets/icon.png
Normal file
BIN
seanime-2.9.10/seanime-denshi/assets/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
4349
seanime-2.9.10/seanime-denshi/package-lock.json
generated
Normal file
4349
seanime-2.9.10/seanime-denshi/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
117
seanime-2.9.10/seanime-denshi/package.json
Normal file
117
seanime-2.9.10/seanime-denshi/package.json
Normal file
@@ -0,0 +1,117 @@
|
||||
{
|
||||
"name": "seanime-denshi",
|
||||
"version": "2.10.0",
|
||||
"description": "Electron-based Desktop client for Seanime",
|
||||
"main": "src/main.js",
|
||||
"scripts": {
|
||||
"dev": "NODE_ENV=development electron .",
|
||||
"build": "electron-builder build",
|
||||
"build:mac": "electron-builder build --mac",
|
||||
"build:win": "electron-builder build --win",
|
||||
"build:linux": "electron-builder build --linux"
|
||||
},
|
||||
"dependencies": {
|
||||
"electron-log": "^5.0.0",
|
||||
"electron-serve": "^1.3.0",
|
||||
"electron-updater": "^6.1.7",
|
||||
"mime-types": "^2.1.35",
|
||||
"strip-ansi": "^7.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "^7.0.3",
|
||||
"electron": "^36.1.2",
|
||||
"electron-builder": "^24.13.3"
|
||||
},
|
||||
"build": {
|
||||
"appId": "app.seanime.denshi",
|
||||
"productName": "Seanime Denshi",
|
||||
"asar": true,
|
||||
"extraResources": [
|
||||
{
|
||||
"from": "binaries",
|
||||
"to": "binaries"
|
||||
}
|
||||
],
|
||||
"generateUpdatesFilesForAllChannels": true,
|
||||
"publish": {
|
||||
"provider": "generic",
|
||||
"url": "https://github.com/5rahim/seanime/releases/latest/download",
|
||||
"channel": "latest",
|
||||
"publishAutoUpdate": true,
|
||||
"useMultipleRangeRequest": false
|
||||
},
|
||||
"mac": {
|
||||
"category": "public.app-category.entertainment",
|
||||
"target": [
|
||||
{
|
||||
"target": "dmg",
|
||||
"arch": [
|
||||
"x64",
|
||||
"arm64"
|
||||
]
|
||||
}
|
||||
],
|
||||
"darkModeSupport": true,
|
||||
"notarize": false,
|
||||
"hardenedRuntime": true,
|
||||
"gatekeeperAssess": false,
|
||||
"entitlements": "assets/entitlements.mac.plist",
|
||||
"entitlementsInherit": "assets/entitlements.mac.plist",
|
||||
"artifactName": "seanime-denshi-${version}_MacOS_${arch}.${ext}"
|
||||
},
|
||||
"win": {
|
||||
"target": [
|
||||
{
|
||||
"target": "nsis",
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
}
|
||||
],
|
||||
"artifactName": "seanime-denshi-${version}_Windows_${arch}.${ext}"
|
||||
},
|
||||
"linux": {
|
||||
"target": [
|
||||
{
|
||||
"target": "AppImage",
|
||||
"arch": [
|
||||
"x64",
|
||||
"arm64"
|
||||
]
|
||||
}
|
||||
],
|
||||
"category": "Entertainment",
|
||||
"artifactName": "seanime-denshi-${version}_Linux_${arch}.${ext}"
|
||||
},
|
||||
"nsis": {
|
||||
"oneClick": false,
|
||||
"allowToChangeInstallationDirectory": true,
|
||||
"createDesktopShortcut": true,
|
||||
"createStartMenuShortcut": true,
|
||||
"shortcutName": "Seanime Denshi",
|
||||
"artifactName": "seanime-denshi-${version}_Windows_${arch}.${ext}"
|
||||
},
|
||||
"directories": {
|
||||
"buildResources": "assets",
|
||||
"output": "dist"
|
||||
},
|
||||
"files": [
|
||||
"src/**/*",
|
||||
"web-denshi/**/*",
|
||||
"assets/**/*",
|
||||
"package.json",
|
||||
"!**/node_modules/*/{CHANGELOG.md,README.md,README,readme.md,readme}",
|
||||
"!**/node_modules/*/{test,__tests__,tests,powered-test,example,examples}",
|
||||
"!**/node_modules/*.d.ts",
|
||||
"!**/node_modules/.bin",
|
||||
"!**/*.{iml,o,hprof,orig,pyc,pyo,rbc,swp,csproj,sln,xproj}",
|
||||
"!.editorconfig",
|
||||
"!**/._*",
|
||||
"!**/{.DS_Store,.git,.hg,.svn,CVS,RCS,SCCS,.gitignore,.gitattributes}",
|
||||
"!**/{__pycache__,thumbs.db,.flowconfig,.idea,.vs,.nyc_output}",
|
||||
"!**/{appveyor.yml,.travis.yml,circle.yml}",
|
||||
"!**/{npm-debug.log,yarn.lock,.yarn-integrity,.yarn-metadata.json}"
|
||||
]
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
911
seanime-2.9.10/seanime-denshi/src/main.js
Normal file
911
seanime-2.9.10/seanime-denshi/src/main.js
Normal file
@@ -0,0 +1,911 @@
|
||||
const {app, BrowserWindow, Menu, Tray, ipcMain, shell, dialog, remote, net} = require('electron');
|
||||
const path = require('path');
|
||||
const serve = require('electron-serve');
|
||||
const {spawn} = require('child_process');
|
||||
const fs = require('fs');
|
||||
let stripAnsi;
|
||||
import('strip-ansi').then(module => {
|
||||
stripAnsi = module.default;
|
||||
});
|
||||
const {autoUpdater} = require('electron-updater');
|
||||
const log = require('electron-log');
|
||||
|
||||
function setupChromiumFlags() {
|
||||
// Bypass CSP and security
|
||||
app.commandLine.appendSwitch('bypasscsp-schemes');
|
||||
app.commandLine.appendSwitch('no-sandbox');
|
||||
app.commandLine.appendSwitch('no-zygote');
|
||||
|
||||
|
||||
app.commandLine.appendSwitch('autoplay-policy', 'no-user-gesture-required');
|
||||
app.commandLine.appendSwitch('force_high_performance_gpu');
|
||||
|
||||
app.commandLine.appendSwitch('disk-cache-size', (400 * 1000 * 1000).toString());
|
||||
app.commandLine.appendSwitch('force-effective-connection-type', '4g');
|
||||
|
||||
// Disable features that can interfere with playback
|
||||
app.commandLine.appendSwitch('disable-features', ['Vulkan', 'WidgetLayering', 'ColorProviderRedirection', 'WebContentsForceDarkMode', // 'ForcedColors'
|
||||
].join(','));
|
||||
|
||||
// Color management and rendering optimizations
|
||||
// app.commandLine.appendSwitch('force-color-profile', 'srgb');
|
||||
// app.commandLine.appendSwitch('disable-color-correct-rendering');
|
||||
// app.commandLine.appendSwitch('disable-web-contents-color-extraction');
|
||||
// app.commandLine.appendSwitch('disable-color-management');
|
||||
// app.commandLine.appendSwitch('force-color-profile-interpretation', 'all-images');
|
||||
// app.commandLine.appendSwitch('force-raster-color-profile', 'srgb');
|
||||
|
||||
// Hardware acceleration and GPU optimizations
|
||||
app.commandLine.appendSwitch('force-high-performance-gpu');
|
||||
// app.commandLine.appendSwitch('enable-gpu-rasterization');
|
||||
app.commandLine.appendSwitch('enable-zero-copy');
|
||||
app.commandLine.appendSwitch('enable-hardware-overlays', 'single-fullscreen,single-on-top,underlay');
|
||||
app.commandLine.appendSwitch('ignore-gpu-blocklist');
|
||||
|
||||
// Video-specific optimizations
|
||||
app.commandLine.appendSwitch('enable-accelerated-video-decode');
|
||||
|
||||
// Enable advanced features
|
||||
app.commandLine.appendSwitch('enable-features', ['ThrottleDisplayNoneAndVisibilityHiddenCrossOriginIframes', 'PlatformEncryptedDolbyVision', 'CanvasOopRasterization', 'UseSkiaRenderer', 'WebAssemblyLazyCompilation', 'RawDraw', // "Vulkan",
|
||||
// 'MediaFoundationHEVC',
|
||||
'PlatformHEVCDecoderSupport',].join(','));
|
||||
|
||||
app.commandLine.appendSwitch('enable-unsafe-webgpu');
|
||||
app.commandLine.appendSwitch('enable-gpu-rasterization');
|
||||
app.commandLine.appendSwitch('enable-oop-rasterization');
|
||||
|
||||
// Background processing optimizations
|
||||
app.commandLine.appendSwitch('disable-background-timer-throttling');
|
||||
app.commandLine.appendSwitch('disable-backgrounding-occluded-windows');
|
||||
app.commandLine.appendSwitch('disable-renderer-backgrounding');
|
||||
app.commandLine.appendSwitch('disable-background-media-suspend');
|
||||
|
||||
app.commandLine.appendSwitch('double-buffer-compositing');
|
||||
app.commandLine.appendSwitch('disable-direct-composition-video-overlays');
|
||||
}
|
||||
|
||||
const _development = process.env.NODE_ENV === 'development';
|
||||
// const _development = false;
|
||||
|
||||
// Setup electron-serve for production
|
||||
const appServe = !_development ? serve({
|
||||
directory: path.join(__dirname, '../web-denshi')
|
||||
}) : null;
|
||||
|
||||
// Custom protocol handler for routing in production
|
||||
if (!_development) {
|
||||
app.whenReady().then(() => {
|
||||
const {protocol} = require('electron');
|
||||
const mime = require('mime-types');
|
||||
|
||||
// Register a custom protocol to handle routing
|
||||
protocol.handle('app', async (request) => {
|
||||
const url = new URL(request.url);
|
||||
let filePath = url.pathname;
|
||||
|
||||
// Remove leading slash
|
||||
if (filePath.startsWith('/')) {
|
||||
filePath = filePath.substring(1);
|
||||
}
|
||||
|
||||
// Handle root path
|
||||
if (filePath === '' || filePath === '-') {
|
||||
filePath = 'index.html';
|
||||
} else if (!filePath.endsWith('.html') && !filePath.includes('.') && !filePath.includes('/')) {
|
||||
// If it's a route without extension, try to find corresponding HTML file
|
||||
filePath = filePath + '.html';
|
||||
}
|
||||
|
||||
// Handle subdirectories (like splashscreen/crash)
|
||||
if (filePath.includes('/')) {
|
||||
const parts = filePath.split('/');
|
||||
if (parts.length > 1) {
|
||||
// For nested routes like splashscreen/crash, try the nested structure first
|
||||
const nestedPath = parts.join('/');
|
||||
const nestedFullPath = path.join(__dirname, '../web-denshi', nestedPath);
|
||||
if (fs.existsSync(nestedFullPath)) {
|
||||
filePath = nestedPath;
|
||||
} else {
|
||||
// Try with .html extension
|
||||
const nestedHtmlPath = nestedPath + '.html';
|
||||
const nestedHtmlFullPath = path.join(__dirname, '../web-denshi', nestedHtmlPath);
|
||||
if (fs.existsSync(nestedHtmlFullPath)) {
|
||||
filePath = nestedHtmlPath;
|
||||
} else {
|
||||
// Fall back to the last part with .html
|
||||
filePath = parts[parts.length - 1] + '.html';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const fullPath = path.join(__dirname, '../web-denshi', filePath);
|
||||
|
||||
// Check if file exists, otherwise fall back to index.html
|
||||
if (!fs.existsSync(fullPath)) {
|
||||
filePath = 'index.html';
|
||||
}
|
||||
|
||||
const finalPath = path.join(__dirname, '../web-denshi', filePath);
|
||||
|
||||
try {
|
||||
const fileContent = fs.readFileSync(finalPath);
|
||||
const mimeType = mime.lookup(finalPath) || 'application/octet-stream';
|
||||
return new Response(fileContent, {
|
||||
headers: {
|
||||
'Content-Type': mimeType,
|
||||
'Cache-Control': 'no-cache'
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Protocol] Error reading file:', finalPath, error);
|
||||
return new Response('File not found', {
|
||||
status: 404,
|
||||
headers: {'Content-Type': 'text/plain'}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Setup update events for logging
|
||||
autoUpdater.logger = log;
|
||||
log.transports.file.level = 'debug';
|
||||
|
||||
// Redirect console logging to electron-log
|
||||
console.log = log.info;
|
||||
console.error = log.error;
|
||||
|
||||
function logStartupEvent(stage, detail = '') {
|
||||
const message = `[STARTUP] ${stage}: ${detail}`;
|
||||
log.info(message);
|
||||
// console.info(message);
|
||||
}
|
||||
|
||||
// Global error handlers to catch unhandled exceptions
|
||||
process.on('uncaughtException', (error) => {
|
||||
log.error('Uncaught Exception:', error);
|
||||
|
||||
if (app.isReady()) {
|
||||
dialog.showErrorBox('An error occurred', `Uncaught Exception: ${error.message}\n\nCheck the logs for more details.`);
|
||||
}
|
||||
|
||||
logStartupEvent('UNCAUGHT EXCEPTION', error.stack || error.message);
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
log.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
||||
logStartupEvent('UNHANDLED REJECTION', reason?.stack || reason?.message || JSON.stringify(reason));
|
||||
});
|
||||
|
||||
// Dumps important environment information for debugging
|
||||
function logEnvironmentInfo() {
|
||||
logStartupEvent('NODE_ENV', process.env.NODE_ENV || 'not set');
|
||||
logStartupEvent('Platform', process.platform);
|
||||
logStartupEvent('Architecture', process.arch);
|
||||
logStartupEvent('Node version', process.version);
|
||||
logStartupEvent('Electron version', process.versions.electron);
|
||||
logStartupEvent('App path', app.getAppPath());
|
||||
logStartupEvent('Dir name', __dirname);
|
||||
logStartupEvent('User data path', app.getPath('userData'));
|
||||
logStartupEvent('Executable path', app.getPath('exe'));
|
||||
|
||||
if (process.resourcesPath) {
|
||||
logStartupEvent('Resources path', process.resourcesPath);
|
||||
try {
|
||||
// const resourceFiles = fs.readdirSync(process.resourcesPath);
|
||||
// logStartupEvent('Resources directory contents', JSON.stringify(resourceFiles));
|
||||
|
||||
// Check if binaries directory exists
|
||||
const binariesDir = path.join(process.resourcesPath, 'binaries');
|
||||
if (fs.existsSync(binariesDir)) {
|
||||
const binariesFiles = fs.readdirSync(binariesDir);
|
||||
logStartupEvent('Binaries directory contents', JSON.stringify(binariesFiles));
|
||||
} else {
|
||||
logStartupEvent('ERROR', 'Binaries directory not found');
|
||||
}
|
||||
} catch (err) {
|
||||
logStartupEvent('ERROR reading resources', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Check app directory structure
|
||||
try {
|
||||
const appPath = app.getAppPath();
|
||||
// logStartupEvent('App directory contents', JSON.stringify(fs.readdirSync(appPath)));
|
||||
|
||||
const webPath = path.join(appPath, 'web-denshi');
|
||||
if (fs.existsSync(webPath)) {
|
||||
// logStartupEvent('Web directory contents', JSON.stringify(fs.readdirSync(webPath)));
|
||||
} else {
|
||||
logStartupEvent('ERROR', 'web-denshi directory not found in app path');
|
||||
}
|
||||
} catch (err) {
|
||||
logStartupEvent('ERROR reading app directory', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
const updateConfig = {
|
||||
provider: 'generic',
|
||||
url: 'https://github.com/5rahim/seanime/releases/latest/download',
|
||||
channel: 'latest',
|
||||
allowPrerelease: false,
|
||||
verifyUpdateCodeSignature: false,
|
||||
};
|
||||
|
||||
// Override with environment variable if set
|
||||
if (process.env.UPDATES_URL) {
|
||||
updateConfig.url = process.env.UPDATES_URL;
|
||||
}
|
||||
|
||||
// Configure the updater
|
||||
autoUpdater.setFeedURL(updateConfig);
|
||||
|
||||
// Enable automatic download
|
||||
autoUpdater.autoDownload = true;
|
||||
autoUpdater.autoInstallOnAppQuit = true;
|
||||
|
||||
// App state
|
||||
let mainWindow = null;
|
||||
let splashScreen = null;
|
||||
let crashScreen = null;
|
||||
let tray = null;
|
||||
let serverProcess = null;
|
||||
let isShutdown = false;
|
||||
let serverStarted = false;
|
||||
let updateDownloaded = false;
|
||||
|
||||
// Setup autoUpdater events with improved error handling
|
||||
autoUpdater.on('checking-for-update', () => {
|
||||
autoUpdater.logger.info('Checking for update...');
|
||||
});
|
||||
|
||||
autoUpdater.on('update-available', (info) => {
|
||||
autoUpdater.logger.info('Update available:', info);
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send('update-available', {
|
||||
version: info.version, releaseDate: info.releaseDate, files: info.files
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
autoUpdater.on('update-not-available', (info) => {
|
||||
autoUpdater.logger.info('Update not available:', info);
|
||||
});
|
||||
|
||||
autoUpdater.on('download-progress', (progressObj) => {
|
||||
autoUpdater.logger.info(`Download progress: ${progressObj.percent}%`);
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send('download-progress', {
|
||||
percent: progressObj.percent, bytesPerSecond: progressObj.bytesPerSecond, transferred: progressObj.transferred, total: progressObj.total
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
autoUpdater.on('update-downloaded', (info) => {
|
||||
autoUpdater.logger.info('Update downloaded:', info);
|
||||
updateDownloaded = true;
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send('update-downloaded', {
|
||||
version: info.version, releaseDate: info.releaseDate, files: info.files
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
autoUpdater.on('error', (err) => {
|
||||
autoUpdater.logger.error('Error in auto-updater:', err);
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send('update-error', {
|
||||
code: err.code || 'unknown', message: err.message, stack: err.stack
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Create the tray icon and menu
|
||||
*/
|
||||
function createTray() {
|
||||
let iconPath = path.join(__dirname, '../assets/icon.png');
|
||||
if (process.platform === 'darwin') {
|
||||
iconPath = path.join(__dirname, '../assets/18x18.png');
|
||||
}
|
||||
tray = new Tray(iconPath);
|
||||
|
||||
const contextMenu = Menu.buildFromTemplate([{
|
||||
id: 'toggle_visibility', label: 'Toggle visibility', click: () => {
|
||||
if (mainWindow.isVisible()) {
|
||||
mainWindow.hide();
|
||||
if (process.platform === 'darwin') {
|
||||
app.dock.hide();
|
||||
}
|
||||
} else {
|
||||
mainWindow.show();
|
||||
mainWindow.focus();
|
||||
if (process.platform === 'darwin') {
|
||||
app.dock.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}, ...(process.platform === 'darwin' ? [{
|
||||
id: 'accessory_mode', label: 'Remove from dock', click: () => {
|
||||
app.dock.hide();
|
||||
}
|
||||
}] : []), {
|
||||
id: 'quit', label: 'Quit Seanime', click: () => {
|
||||
cleanupAndExit();
|
||||
}
|
||||
}]);
|
||||
|
||||
tray.setToolTip('Seanime');
|
||||
tray.setContextMenu(contextMenu);
|
||||
|
||||
tray.on('click', () => {
|
||||
mainWindow.show();
|
||||
mainWindow.focus();
|
||||
if (process.platform === 'darwin') {
|
||||
app.dock.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the Seanime server
|
||||
*/
|
||||
async function launchSeanimeServer(isRestart) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// TEST ONLY: Check for -no-binary flag
|
||||
if (process.argv.includes('-no-binary')) {
|
||||
logStartupEvent('SKIPPING SERVER LAUNCH', 'Detected -no-binary flag');
|
||||
console.log('[Main] Skipping server launch due to -no-binary flag');
|
||||
serverStarted = true; // Assume server is "started" for UI flow
|
||||
// Resolve immediately to bypass server spawning
|
||||
if (splashScreen && !splashScreen.isDestroyed()) {
|
||||
splashScreen.close();
|
||||
splashScreen = null;
|
||||
}
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.maximize();
|
||||
mainWindow.show();
|
||||
}
|
||||
return resolve();
|
||||
}
|
||||
|
||||
// Determine the correct binary to use based on platform and architecture
|
||||
let binaryName = '';
|
||||
if (process.platform === 'win32') {
|
||||
binaryName = 'seanime-server-windows.exe';
|
||||
} else if (process.platform === 'darwin') {
|
||||
const arch = process.arch === 'arm64' ? 'arm64' : 'amd64';
|
||||
binaryName = `seanime-server-darwin-${arch}`;
|
||||
} else if (process.platform === 'linux') {
|
||||
const arch = process.arch === 'arm64' ? 'arm64' : 'amd64';
|
||||
binaryName = `seanime-server-linux-${arch}`;
|
||||
}
|
||||
|
||||
let binaryPath;
|
||||
|
||||
if (_development) {
|
||||
// In development, look for binaries in the project directory
|
||||
binaryPath = path.join(__dirname, '../binaries', binaryName);
|
||||
} else {
|
||||
// In production, use the resources path
|
||||
binaryPath = path.join(process.resourcesPath, 'binaries', binaryName);
|
||||
}
|
||||
|
||||
logStartupEvent('Using binary', `${binaryPath} (${process.arch})`);
|
||||
logStartupEvent('Resources path', process.resourcesPath);
|
||||
|
||||
// Check if binary exists and is executable
|
||||
if (!fs.existsSync(binaryPath)) {
|
||||
const error = new Error(`Server binary not found at ${binaryPath}`);
|
||||
logStartupEvent('ERROR', error.message);
|
||||
return reject(error);
|
||||
}
|
||||
|
||||
// Make binary executable (for macOS/Linux)
|
||||
if (process.platform !== 'win32') {
|
||||
try {
|
||||
fs.chmodSync(binaryPath, '755');
|
||||
} catch (error) {
|
||||
console.error(`Failed to make binary executable: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Arguments
|
||||
const args = [];
|
||||
|
||||
// Development mode
|
||||
if (_development && process.env.TEST_DATADIR) {
|
||||
console.log('[Main] TEST_DATADIR', process.env.TEST_DATADIR);
|
||||
args.push('-datadir', process.env.TEST_DATADIR);
|
||||
}
|
||||
|
||||
args.push('-desktop-sidecar', 'true');
|
||||
|
||||
console.log('\x1b[32m[Main] Spawning server process\x1b[0m', {args, binaryPath});
|
||||
|
||||
// Spawn the process
|
||||
try {
|
||||
serverProcess = spawn(binaryPath, args);
|
||||
} catch (spawnError) {
|
||||
console.error('[Main] Failed to spawn server process synchronously:', spawnError);
|
||||
return reject(spawnError);
|
||||
}
|
||||
|
||||
serverProcess.stdout.on('data', (data) => {
|
||||
const dataStr = data.toString();
|
||||
const lineStr = stripAnsi ? stripAnsi(dataStr) : dataStr;
|
||||
|
||||
// // Check if mainWindow exists and is not destroyed
|
||||
// if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
// mainWindow.webContents.send('message', lineStr);
|
||||
// }
|
||||
|
||||
// Check if the frontend is connected
|
||||
if (!serverStarted && lineStr.includes('Client connected')) {
|
||||
console.log('[Main] Server started');
|
||||
serverStarted = true;
|
||||
setTimeout(() => {
|
||||
console.log('[Main] Server started timeout');
|
||||
if (splashScreen && !splashScreen.isDestroyed()) {
|
||||
splashScreen.close();
|
||||
splashScreen = null;
|
||||
}
|
||||
console.log('[Main] Server started close splash screen');
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.maximize();
|
||||
mainWindow.show();
|
||||
}
|
||||
resolve();
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
|
||||
serverProcess.stderr.on('data', (data) => {
|
||||
console.error(data.toString());
|
||||
});
|
||||
|
||||
serverProcess.on('close', (code) => {
|
||||
console.log(`[Main] Server process exited with code ${code}`);
|
||||
|
||||
// If the server didn't start properly and we're not in the process of shutting down
|
||||
if (!serverStarted && !isShutdown) {
|
||||
console.log('[Main] Server process exited before starting');
|
||||
reject(new Error(`Server process exited prematurely with code ${code} before starting.`));
|
||||
|
||||
// close splash screen and main window
|
||||
if (splashScreen && !splashScreen.isDestroyed()) {
|
||||
splashScreen.close();
|
||||
splashScreen = null;
|
||||
}
|
||||
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.close();
|
||||
}
|
||||
|
||||
// show crash screen
|
||||
if (crashScreen && !crashScreen.isDestroyed()) {
|
||||
crashScreen.show();
|
||||
crashScreen.webContents.send('crash', `Seanime server process terminated with status: ${code}. Closing in 10 seconds.`);
|
||||
|
||||
setTimeout(() => {
|
||||
app.exit(1);
|
||||
}, 10000);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Handle spawn errors
|
||||
serverProcess.on('error', (err) => {
|
||||
console.error('[Main] Server process spawn error event:', err);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create main application window
|
||||
*/
|
||||
function createMainWindow() {
|
||||
logStartupEvent('Creating main window');
|
||||
const windowOptions = {
|
||||
width: 800, height: 600, show: false, webPreferences: {
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
preload: path.join(__dirname, 'preload.js'),
|
||||
webSecurity: false,
|
||||
allowRunningInsecureContent: true,
|
||||
enableBlinkFeatures: 'FontAccess, AudioVideoTracks',
|
||||
backgroundThrottling: false
|
||||
}
|
||||
};
|
||||
|
||||
// contextMenu({
|
||||
// showInspectElement: true
|
||||
// });
|
||||
|
||||
// Set title bar style based on platform
|
||||
if (process.platform === 'darwin') {
|
||||
windowOptions.titleBarStyle = 'hiddenInset';
|
||||
}
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
windowOptions.titleBarStyle = 'hidden';
|
||||
}
|
||||
|
||||
mainWindow = new BrowserWindow(windowOptions);
|
||||
|
||||
// Hide the title bar on Windows
|
||||
if (process.platform === 'win32' || process.platform === 'linux') {
|
||||
mainWindow.setMenuBarVisibility(false);
|
||||
}
|
||||
|
||||
mainWindow.on('render-process-gone', (event, details) => {
|
||||
console.log('[Main] Render process gone', details);
|
||||
if (crashScreen && !crashScreen.isDestroyed()) {
|
||||
crashScreen.show();
|
||||
}
|
||||
});
|
||||
|
||||
mainWindow.webContents.setWindowOpenHandler(({url}) => {
|
||||
// Open external links in the default browser
|
||||
if (url.startsWith('http://') || url.startsWith('https://')) {
|
||||
shell.openExternal(url);
|
||||
return {action: 'deny'};
|
||||
}
|
||||
// Allow other URLs to open in the app
|
||||
return {action: 'allow'};
|
||||
})
|
||||
|
||||
// Load the web content
|
||||
if (_development) {
|
||||
// In development, load from the dev server
|
||||
logStartupEvent('Loading from dev server', 'http://127.0.0.1:43210');
|
||||
mainWindow.loadURL('http://127.0.0.1:43210');
|
||||
// mainWindow.loadURL('chrome://gpu');
|
||||
} else {
|
||||
// Load from custom protocol handler in production
|
||||
logStartupEvent('Loading production build with custom protocol');
|
||||
mainWindow.loadURL('app://-');
|
||||
}
|
||||
|
||||
// Development tools
|
||||
if (_development) {
|
||||
mainWindow.webContents.openDevTools();
|
||||
}
|
||||
|
||||
mainWindow.on('close', (event) => {
|
||||
if (!isShutdown) {
|
||||
event.preventDefault();
|
||||
mainWindow.hide();
|
||||
if (process.platform === 'darwin') {
|
||||
app.dock.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create splash screen window
|
||||
*/
|
||||
function createSplashScreen() {
|
||||
logStartupEvent('Creating splash screen');
|
||||
splashScreen = new BrowserWindow({
|
||||
width: 800, height: 600, frame: false, resizable: false, webPreferences: {
|
||||
nodeIntegration: false, contextIsolation: true, preload: path.join(__dirname, 'preload.js')
|
||||
}
|
||||
});
|
||||
|
||||
// Load the web content
|
||||
if (_development) {
|
||||
// In development, load from the dev server
|
||||
logStartupEvent('Loading splash from dev server', 'http://127.0.0.1:43210/splashscreen');
|
||||
splashScreen.loadURL('http://127.0.0.1:43210/splashscreen');
|
||||
} else {
|
||||
// Load from custom protocol handler
|
||||
logStartupEvent('Loading splash screen with custom protocol');
|
||||
splashScreen.loadURL('app://splashscreen');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create crash screen window
|
||||
*/
|
||||
function createCrashScreen() {
|
||||
crashScreen = new BrowserWindow({
|
||||
width: 800, height: 600, frame: false, resizable: false, show: false, webPreferences: {
|
||||
nodeIntegration: false, contextIsolation: true, preload: path.join(__dirname, 'preload.js')
|
||||
}
|
||||
});
|
||||
|
||||
// Load the web content
|
||||
if (_development) {
|
||||
// In development, load from the dev server
|
||||
crashScreen.loadURL('http://127.0.0.1:43210/splashscreen/crash');
|
||||
} else {
|
||||
// Load from custom protocol handler
|
||||
crashScreen.loadURL('app://splashscreen/crash');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup and exit the application gracefully
|
||||
*/
|
||||
function cleanupAndExit() {
|
||||
console.log('[Main] Cleaning up and exiting');
|
||||
isShutdown = true;
|
||||
|
||||
// Kill server process first
|
||||
if (serverProcess) {
|
||||
console.log('[Main] Killing server process');
|
||||
try {
|
||||
serverProcess.kill();
|
||||
serverProcess = null;
|
||||
} catch (err) {
|
||||
console.error('[Main] Error killing server process:', err);
|
||||
}
|
||||
}
|
||||
|
||||
// Exit the app after a short delay to allow cleanup
|
||||
setTimeout(() => {
|
||||
app.exit(0);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
// Initialize the app
|
||||
app.whenReady().then(async () => {
|
||||
logStartupEvent('App ready');
|
||||
|
||||
// Set up Chromium flags for better video playback
|
||||
setupChromiumFlags();
|
||||
|
||||
// Log environment information
|
||||
logEnvironmentInfo();
|
||||
|
||||
// Setup IPC handlers for update functions
|
||||
ipcMain.handle('check-for-updates', async () => {
|
||||
try {
|
||||
console.log('[Main] Checking for updates...');
|
||||
const result = await autoUpdater.checkForUpdates();
|
||||
return {
|
||||
updateAvailable: !!result?.updateInfo, updateInfo: result?.updateInfo
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[Main] Error checking for updates:', error);
|
||||
throw error; // Let the renderer handle the error
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('install-update', async () => {
|
||||
try {
|
||||
if (!updateDownloaded) {
|
||||
throw new Error('Update not downloaded yet');
|
||||
}
|
||||
console.log('[Main] Installing update...');
|
||||
autoUpdater.quitAndInstall(false, true);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('[Main] Error installing update:', error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('kill-server', async () => {
|
||||
if (serverProcess) {
|
||||
console.log('[Main] Killing server before update...');
|
||||
serverProcess.kill();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// Linux fix for compositing mode
|
||||
if (process.platform === 'linux') {
|
||||
process.env.WEBKIT_DISABLE_COMPOSITING_MODE = '1';
|
||||
}
|
||||
|
||||
// Create windows
|
||||
createMainWindow();
|
||||
createSplashScreen();
|
||||
createCrashScreen();
|
||||
|
||||
// Create tray
|
||||
createTray();
|
||||
|
||||
// Launch server
|
||||
try {
|
||||
logStartupEvent('Attempting to launch server');
|
||||
await launchSeanimeServer(false);
|
||||
logStartupEvent('Server launched successfully');
|
||||
// Check for updates only after server launch and main window setup is successful
|
||||
autoUpdater.checkForUpdatesAndNotify();
|
||||
} catch (error) {
|
||||
logStartupEvent('Server launch failed', error.message);
|
||||
console.error('[Main] Failed to start server:', error);
|
||||
if (splashScreen && !splashScreen.isDestroyed()) {
|
||||
splashScreen.close();
|
||||
splashScreen = null;
|
||||
}
|
||||
|
||||
if (crashScreen && !crashScreen.isDestroyed()) {
|
||||
crashScreen.show();
|
||||
crashScreen.webContents.send('crash', `The server failed to start: ${error}. Closing in 10 seconds.`);
|
||||
|
||||
setTimeout(() => {
|
||||
console.error('[Main] Exiting due to server start failure.');
|
||||
app.exit(1);
|
||||
}, 10000);
|
||||
}
|
||||
}
|
||||
|
||||
// Register Window Control IPC handlers
|
||||
ipcMain.on('window:minimize', () => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.minimize();
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('window:maximize', () => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.maximize();
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('window:close', () => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.close();
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('window:toggleMaximize', () => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
if (mainWindow.isMaximized()) {
|
||||
mainWindow.unmaximize();
|
||||
} else {
|
||||
mainWindow.maximize();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('window:setFullscreen', (_, fullscreen) => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.setFullScreen(fullscreen);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('window:hide', () => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.hide();
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('window:show', () => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.show();
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('window:getCurrentWindow', () => {
|
||||
const win = BrowserWindow.fromWebContents(mainWindow.webContents);
|
||||
return win?.id;
|
||||
});
|
||||
|
||||
// Window state query handlers
|
||||
ipcMain.handle('window:isMaximized', () => {
|
||||
return mainWindow && !mainWindow.isDestroyed() ? mainWindow.isMaximized() : false;
|
||||
});
|
||||
|
||||
ipcMain.handle('window:isMinimizable', () => {
|
||||
return mainWindow && !mainWindow.isDestroyed() ? mainWindow.minimizable : false;
|
||||
});
|
||||
|
||||
ipcMain.handle('window:isMaximizable', () => {
|
||||
return mainWindow && !mainWindow.isDestroyed() ? mainWindow.maximizable : false;
|
||||
});
|
||||
|
||||
ipcMain.handle('window:isClosable', () => {
|
||||
return mainWindow && !mainWindow.isDestroyed() ? mainWindow.closable : false;
|
||||
});
|
||||
|
||||
ipcMain.handle('window:isFullscreen', () => {
|
||||
return mainWindow && !mainWindow.isDestroyed() ? mainWindow.isFullScreen() : false;
|
||||
});
|
||||
|
||||
ipcMain.handle('window:isVisible', () => {
|
||||
return mainWindow && !mainWindow.isDestroyed() ? mainWindow.isVisible() : false;
|
||||
});
|
||||
|
||||
// Clipboard handler
|
||||
ipcMain.handle('clipboard:writeText', (_, text) => {
|
||||
if (text) {
|
||||
return require('electron').clipboard.writeText(text);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// Register server IPC handlers
|
||||
ipcMain.on('restart-server', () => {
|
||||
console.log('EVENT restart-server');
|
||||
if (serverProcess) {
|
||||
console.log('Killing existing server process');
|
||||
serverProcess.kill();
|
||||
}
|
||||
// devnote: don't set this to false or it will trigger the crashscreen
|
||||
// serverStarted = false;
|
||||
launchSeanimeServer(true).catch(console.error);
|
||||
});
|
||||
|
||||
ipcMain.on('kill-server', () => {
|
||||
console.log('EVENT kill-server');
|
||||
if (serverProcess) {
|
||||
console.log('Killing server process');
|
||||
serverProcess.kill();
|
||||
}
|
||||
});
|
||||
|
||||
// Watch for window events to notify renderer
|
||||
if (mainWindow) {
|
||||
mainWindow.on('maximize', () => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send('window:maximized');
|
||||
}
|
||||
});
|
||||
|
||||
mainWindow.on('unmaximize', () => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send('window:unmaximized');
|
||||
}
|
||||
});
|
||||
|
||||
mainWindow.on('enter-full-screen', () => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send('window:fullscreen', true);
|
||||
}
|
||||
});
|
||||
|
||||
mainWindow.on('leave-full-screen', () => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send('window:fullscreen', false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// macOS specific events
|
||||
ipcMain.on('macos-activation-policy-accessory', () => {
|
||||
console.log('EVENT macos-activation-policy-accessory');
|
||||
if (process.platform === 'darwin') {
|
||||
app.dock.hide();
|
||||
mainWindow.show();
|
||||
mainWindow.setFullScreen(true);
|
||||
|
||||
setTimeout(() => {
|
||||
mainWindow.focus();
|
||||
mainWindow.webContents.send('macos-activation-policy-accessory-done', '');
|
||||
}, 150);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('macos-activation-policy-regular', () => {
|
||||
console.log('EVENT macos-activation-policy-regular');
|
||||
if (process.platform === 'darwin') {
|
||||
app.dock.show();
|
||||
}
|
||||
});
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createMainWindow();
|
||||
}
|
||||
});
|
||||
|
||||
app.on('before-quit', () => {
|
||||
console.log('EVENT before-quit');
|
||||
cleanupAndExit();
|
||||
});
|
||||
});
|
||||
101
seanime-2.9.10/seanime-denshi/src/preload.js
Normal file
101
seanime-2.9.10/seanime-denshi/src/preload.js
Normal file
@@ -0,0 +1,101 @@
|
||||
const {contextBridge, ipcRenderer, ipcMain, shell, BrowserWindow} = require('electron');
|
||||
|
||||
// Expose protected methods that allow the renderer process to use
|
||||
// the ipcRenderer without exposing the entire object
|
||||
contextBridge.exposeInMainWorld(
|
||||
'electron', {
|
||||
// Window Controls
|
||||
window: {
|
||||
minimize: () => ipcRenderer.send('window:minimize'),
|
||||
maximize: () => ipcRenderer.send('window:maximize'),
|
||||
close: () => ipcRenderer.send('window:close'),
|
||||
isMaximized: () => ipcRenderer.invoke('window:isMaximized'),
|
||||
isMinimizable: () => ipcRenderer.invoke('window:isMinimizable'),
|
||||
isMaximizable: () => ipcRenderer.invoke('window:isMaximizable'),
|
||||
isClosable: () => ipcRenderer.invoke('window:isClosable'),
|
||||
isFullscreen: () => ipcRenderer.invoke('window:isFullscreen'),
|
||||
setFullscreen: (fullscreen) => ipcRenderer.send('window:setFullscreen', fullscreen),
|
||||
toggleMaximize: () => ipcRenderer.send('window:toggleMaximize'),
|
||||
hide: () => ipcRenderer.send('window:hide'),
|
||||
show: () => ipcRenderer.send('window:show'),
|
||||
isVisible: () => ipcRenderer.invoke('window:isVisible'),
|
||||
setTitleBarStyle: (style) => ipcRenderer.send('window:setTitleBarStyle', style),
|
||||
getCurrentWindow: () => ipcRenderer.invoke('window:getCurrentWindow')
|
||||
},
|
||||
|
||||
// Event listeners
|
||||
on: (channel, callback) => {
|
||||
// Whitelist channels
|
||||
const validChannels = [
|
||||
'message',
|
||||
'crash',
|
||||
'window:maximized',
|
||||
'window:unmaximized',
|
||||
'window:fullscreen',
|
||||
'update-downloaded',
|
||||
'update-error',
|
||||
'update-available',
|
||||
'download-progress',
|
||||
'window:currentWindow'
|
||||
];
|
||||
if (validChannels.includes(channel)) {
|
||||
// Remove the event listener to avoid memory leaks
|
||||
ipcRenderer.removeAllListeners(channel);
|
||||
// Add the event listener
|
||||
ipcRenderer.on(channel, (_, ...args) => callback(...args));
|
||||
|
||||
// Return a function to remove the listener
|
||||
return () => {
|
||||
ipcRenderer.removeAllListeners(channel);
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Send events
|
||||
emit: (channel, data) => {
|
||||
// Whitelist channels
|
||||
const validChannels = [
|
||||
'restart-server',
|
||||
'kill-server',
|
||||
'macos-activation-policy-accessory',
|
||||
'macos-activation-policy-regular'
|
||||
];
|
||||
if (validChannels.includes(channel)) {
|
||||
ipcRenderer.send(channel, data);
|
||||
}
|
||||
},
|
||||
|
||||
// General send method for any whitelisted channel
|
||||
send: (channel, ...args) => {
|
||||
// Whitelist channels
|
||||
const validChannels = [
|
||||
'restart-app',
|
||||
'quit-app'
|
||||
];
|
||||
if (validChannels.includes(channel)) {
|
||||
ipcRenderer.send(channel, ...args);
|
||||
}
|
||||
},
|
||||
|
||||
// Platform
|
||||
platform: process.platform,
|
||||
|
||||
// Shell functions
|
||||
shell: {
|
||||
open: (url) => shell.openExternal(url)
|
||||
},
|
||||
|
||||
// Clipboard
|
||||
clipboard: {
|
||||
writeText: (text) => ipcRenderer.invoke('clipboard:writeText', text)
|
||||
},
|
||||
|
||||
// Update functions
|
||||
checkForUpdates: () => ipcRenderer.invoke('check-for-updates'),
|
||||
installUpdate: () => ipcRenderer.invoke('install-update'),
|
||||
killServer: () => ipcRenderer.invoke('kill-server')
|
||||
}
|
||||
);
|
||||
|
||||
// Set __isElectronDesktop__ global variable
|
||||
contextBridge.exposeInMainWorld('__isElectronDesktop__', true);
|
||||
Reference in New Issue
Block a user