cncjs

Форк
0
/
main.js 
276 строк · 9.0 Кб
1
import fs from 'node:fs';
2
import path from 'node:path';
3
import {
4
  BrowserWindow,
5
  Menu,
6
  app,
7
  ipcMain,
8
  powerSaveBlocker,
9
  screen,
10
  shell,
11
} from 'electron';
12
import Store from 'electron-store';
13
import chalk from 'chalk';
14
import mkdirp from 'mkdirp';
15
import {
16
  createApplicationMenuTemplate,
17
  inputMenuTemplate,
18
  selectionMenuTemplate,
19
} from './electron-app/menu-template';
20
import launchServer from './server-cli';
21
import pkg from './package.json';
22

23
let mainWindow = null;
24
let powerId = 0;
25
const store = new Store();
26

27
// https://github.com/electron/electron/blob/master/docs/api/app.md#apprequestsingleinstancelock
28
const gotSingleInstanceLock = app.requestSingleInstanceLock();
29
const shouldQuitImmediately = !gotSingleInstanceLock;
30
if (shouldQuitImmediately) {
31
  app.quit();
32
  process.exit(0);
33
}
34

35
// Create the user data directory if it does not exist
36
const userDataPath = app.getPath('userData');
37
mkdirp.sync(userDataPath);
38

39
function getBrowserWindowOptions() {
40
  const defaultOptions = {
41
    width: 1440,
42
    height: 900,
43
    minHeight: 708,
44
    minWidth: 1024,
45
    show: false,
46
    title: `${pkg.name} ${pkg.version}`,
47

48
    // useContentSize boolean (optional) - The width and height would be used as web page's size, which means the actual window's size will include window frame's size and be slightly larger. Default is false.
49
    useContentSize: true,
50

51
    // webPreferences Object (optional) - Settings of web page's features.
52
    webPreferences: {
53
      // https://www.electronjs.org/docs/latest/breaking-changes#default-changed-contextisolation-defaults-to-true
54
      // require() cannot be used in the renderer process unless nodeIntegration is true and contextIsolation is false.
55
      contextIsolation: false,
56
      nodeIntegration: true,
57
    }
58
  };
59

60
  // { x, y, width, height }
61
  const lastOptions = store.get('bounds');
62

63
  // Get display that most closely intersects the provided bounds
64
  let windowOptions = {};
65
  if (lastOptions) {
66
    const display = screen.getDisplayMatching(lastOptions);
67

68
    if (display.id === lastOptions.id) {
69
      // Use last time options when using the same display
70
      windowOptions = {
71
        ...windowOptions,
72
        ...lastOptions,
73
      };
74
    } else {
75
      // Or center the window when using other display
76
      const workArea = display.workArea;
77

78
      // Calculate window size
79
      const width = Math.max(Math.min(lastOptions.width, workArea.width), 360);
80
      const height = Math.max(Math.min(lastOptions.height, workArea.height), 240);
81
      const x = workArea.x + (workArea.width - width) / 2;
82
      const y = workArea.y + (workArea.height - height) / 2;
83

84
      windowOptions = {
85
        id: display.id,
86
        x,
87
        y,
88
        width,
89
        height,
90
      };
91
    }
92
  } else {
93
    const display = screen.getPrimaryDisplay();
94
    const { x, y, width } = display.workArea;
95
    const nx = x + (width - 1440) / 2;
96
    windowOptions = {
97
      id: display.id,
98
      x: nx,
99
      y,
100
      center: true,
101
    };
102
  }
103

104
  return Object.assign({}, defaultOptions, windowOptions);
105
}
106

107
const showMainWindow = async () => {
108
  const browserWindowOptions = getBrowserWindowOptions();
109
  const browserWindow = new BrowserWindow(browserWindowOptions);
110
  mainWindow = browserWindow;
111
  powerId = powerSaveBlocker.start('prevent-display-sleep');
112

113
  const res = await launchServer();
114
  const { address, port, mountPoints } = { ...res };
115
  if (!(address && port)) {
116
    console.error('Unable to start the server at ' + chalk.cyan(`http://${address}:${port}`));
117
    return;
118
  }
119

120
  const applicationMenu = Menu.buildFromTemplate(createApplicationMenuTemplate({ address, port, mountPoints }));
121
  const inputMenu = Menu.buildFromTemplate(inputMenuTemplate);
122
  const selectionMenu = Menu.buildFromTemplate(selectionMenuTemplate);
123
  Menu.setApplicationMenu(applicationMenu);
124

125
  // https://www.electronjs.org/docs/latest/api/web-contents#contentssetwindowopenhandlerhandler
126
  // https://github.com/electron/electron/pull/24517
127
  mainWindow.webContents.setWindowOpenHandler(({ url }) => {
128
    shell.openExternal(url);
129
    return { action: 'deny' };
130
  });
131

132
  // https://github.com/electron/electron/blob/main/docs/api/web-contents.md#event-context-menu
133
  // https://github.com/electron/electron/issues/4068#issuecomment-274159726
134
  mainWindow.webContents.on('context-menu', (event, props) => {
135
    const { selectionText, isEditable } = props;
136
    if (isEditable) {
137
      inputMenu.popup(mainWindow);
138
    } else if (selectionText && String(selectionText).trim() !== '') {
139
      selectionMenu.popup(mainWindow);
140
    }
141
  });
142

143
  const webContentsSession = mainWindow.webContents.session;
144
  webContentsSession.setProxy({ proxyRules: 'direct://' })
145
    .then(() => {
146
      const url = `http://${address}:${port}`;
147
      mainWindow.loadURL(url);
148
    })
149
    .catch(err => {
150
      console.log('err', err.message);
151
    });
152

153
  if (process.platform === 'win32') {
154
    mainWindow.show();
155
  } else {
156
    mainWindow.on('ready-to-show', () => {
157
      mainWindow.show();
158
    });
159
  }
160

161
  // Save window size and position
162
  mainWindow.on('close', (event) => {
163
    const bounds = mainWindow.getBounds();
164
    const display = screen.getDisplayMatching(bounds);
165
    const options = {
166
      id: display.id,
167
      ...bounds,
168
    };
169

170
    store.set('bounds', options);
171
    mainWindow.webContents.send('save-and-close');
172

173
    mainWindow = null;
174
  });
175

176
  // @see 'src/app/store/index.js'
177
  ipcMain.handle('read-user-config', () => {
178
    let content = '{}';
179
    const configPath = path.join(userDataPath, 'cnc.json');
180
    if (fs.existsSync(configPath)) {
181
      content = fs.readFileSync(configPath, 'utf8');
182
    }
183
    return content;
184
  });
185

186
  // @see 'src/app/store/index.js'
187
  ipcMain.handle('write-user-config', (event, content) => {
188
    const configPath = path.join(userDataPath, 'cnc.json');
189
    fs.writeFileSync(configPath, content ?? '{}');
190
  });
191
};
192

193
// Increase V8 heap size of the main process in production
194
if (process.arch === 'x64') {
195
  const memoryLimit = 1024 * 4; // 4GB
196
  app.commandLine.appendSwitch('--js-flags', `--max-old-space-size=${memoryLimit}`);
197
}
198

199
// Ignore the GPU blacklist and use any available GPU
200
app.commandLine.appendSwitch('ignore-gpu-blacklist');
201

202
if (process.platform === 'linux') {
203
  // https://github.com/electron/electron/issues/18265
204
  // Run this at early startup, before app.on('ready')
205
  //
206
  // TODO: Maybe we can only disable --disable-setuid-sandbox
207
  // reference changes: https://github.com/microsoft/vscode/pull/122909/files
208
  app.commandLine.appendSwitch('--no-sandbox');
209
}
210

211
/**
212
 * https://www.electronjs.org/docs/latest/api/app#event-activate-macos
213
 *
214
 * Event: 'activate' [macOS]
215
 *
216
 * Returns:
217
 * - `event` Event
218
 * - `hasVisibleWindows` boolean
219
 *
220
 * Emitted when the application is activated. Various actions can trigger this event, such as launching the application for the first time, attempting to re-launch the application when it's already running, or clicking on the application's dock or taskbar icon.
221
 */
222
app.on('activate', async (event, hasVisibleWindows) => {
223
  if (!mainWindow) {
224
    await showMainWindow();
225
  }
226
});
227

228
/**
229
 * https://www.electronjs.org/docs/latest/api/app#event-window-all-closed
230
 *
231
 * Event: 'window-all-closed'
232
 *
233
 * Emitted when all windows have been closed.
234
 *
235
 * If you do not subscribe to this event and all windows are closed, the default behavior is to quit the app; however, if you subscribe, you control whether the app quits or not. If the user pressed `Cmd + Q`, or the developer called `app.quit()`, Electron will first try to close all the windows and then emit the `will-quit` event, and in this case the `window-all-closed` event would not be emitted.
236
 */
237
app.on('window-all-closed', () => {
238
  powerSaveBlocker.stop(powerId);
239

240
  app.quit();
241
});
242

243
/**
244
 * https://www.electronjs.org/docs/latest/api/app#event-second-instance
245
 *
246
 * Event: 'second-instance'
247
 *
248
 * Returns:
249
 * - `event` Event
250
 * - `argv` string[] - An array of the second instance's command line arguments
251
 * - `workingDirectory` string - The second instance's working directory
252
 * - `additionalData` unknown - A JSON object of additional data passed from the second instance
253
 *
254
 * This event will be emitted inside the primary instance of your application when a second instance has been executed and calls `app.requestSingleInstanceLock()`.
255
 *
256
 * `argv` is an Array of the second instance's command line arguments, and `workingDirectory` is its current working directory. Usually applications respond to this by making their primary window focused and non-minimized.
257
 *
258
 * Note: If the second instance is started by a different user than the first, the `argv` array will not include the arguments.
259
 *
260
 * This event is guaranteed to be emitted after the ready event of app gets emitted.
261
 */
262
app.on('second-instance', (event, argv, workingDirectory, additionalData) => {
263
  if (mainWindow) {
264
    if (mainWindow.isMinimized()) {
265
      mainWindow.restore();
266
    }
267
    mainWindow.focus();
268
  }
269
});
270

271
/**
272
 * Method: app.whenReady()
273
 *
274
 * Returns Promise<void> - fulfilled when Electron is initialized. May be used as a convenient alternative to checking `app.isReady()` and subscribing to the `ready` event if the app is not ready yet.
275
 */
276
app.whenReady().then(showMainWindow);
277

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.