/ src / application-delegate.js
application-delegate.js
  1  const { ipcRenderer, remote, shell } = require('electron');
  2  const ipcHelpers = require('./ipc-helpers');
  3  const { Emitter, Disposable } = require('event-kit');
  4  const getWindowLoadSettings = require('./get-window-load-settings');
  5  
  6  module.exports = class ApplicationDelegate {
  7    constructor() {
  8      this.pendingSettingsUpdateCount = 0;
  9      this._ipcMessageEmitter = null;
 10    }
 11  
 12    ipcMessageEmitter() {
 13      if (!this._ipcMessageEmitter) {
 14        this._ipcMessageEmitter = new Emitter();
 15        ipcRenderer.on('message', (event, message, detail) => {
 16          this._ipcMessageEmitter.emit(message, detail);
 17        });
 18      }
 19      return this._ipcMessageEmitter;
 20    }
 21  
 22    getWindowLoadSettings() {
 23      return getWindowLoadSettings();
 24    }
 25  
 26    open(params) {
 27      return ipcRenderer.send('open', params);
 28    }
 29  
 30    pickFolder(callback) {
 31      const responseChannel = 'atom-pick-folder-response';
 32      ipcRenderer.on(responseChannel, function(event, path) {
 33        ipcRenderer.removeAllListeners(responseChannel);
 34        return callback(path);
 35      });
 36      return ipcRenderer.send('pick-folder', responseChannel);
 37    }
 38  
 39    getCurrentWindow() {
 40      return remote.getCurrentWindow();
 41    }
 42  
 43    closeWindow() {
 44      return ipcHelpers.call('window-method', 'close');
 45    }
 46  
 47    async getTemporaryWindowState() {
 48      const stateJSON = await ipcHelpers.call('get-temporary-window-state');
 49      return JSON.parse(stateJSON);
 50    }
 51  
 52    setTemporaryWindowState(state) {
 53      return ipcHelpers.call('set-temporary-window-state', JSON.stringify(state));
 54    }
 55  
 56    getWindowSize() {
 57      const [width, height] = Array.from(remote.getCurrentWindow().getSize());
 58      return { width, height };
 59    }
 60  
 61    setWindowSize(width, height) {
 62      return ipcHelpers.call('set-window-size', width, height);
 63    }
 64  
 65    getWindowPosition() {
 66      const [x, y] = Array.from(remote.getCurrentWindow().getPosition());
 67      return { x, y };
 68    }
 69  
 70    setWindowPosition(x, y) {
 71      return ipcHelpers.call('set-window-position', x, y);
 72    }
 73  
 74    centerWindow() {
 75      return ipcHelpers.call('center-window');
 76    }
 77  
 78    focusWindow() {
 79      return ipcHelpers.call('focus-window');
 80    }
 81  
 82    showWindow() {
 83      return ipcHelpers.call('show-window');
 84    }
 85  
 86    hideWindow() {
 87      return ipcHelpers.call('hide-window');
 88    }
 89  
 90    reloadWindow() {
 91      return ipcHelpers.call('window-method', 'reload');
 92    }
 93  
 94    restartApplication() {
 95      return ipcRenderer.send('restart-application');
 96    }
 97  
 98    minimizeWindow() {
 99      return ipcHelpers.call('window-method', 'minimize');
100    }
101  
102    isWindowMaximized() {
103      return remote.getCurrentWindow().isMaximized();
104    }
105  
106    maximizeWindow() {
107      return ipcHelpers.call('window-method', 'maximize');
108    }
109  
110    unmaximizeWindow() {
111      return ipcHelpers.call('window-method', 'unmaximize');
112    }
113  
114    isWindowFullScreen() {
115      return remote.getCurrentWindow().isFullScreen();
116    }
117  
118    setWindowFullScreen(fullScreen = false) {
119      return ipcHelpers.call('window-method', 'setFullScreen', fullScreen);
120    }
121  
122    onDidEnterFullScreen(callback) {
123      return ipcHelpers.on(ipcRenderer, 'did-enter-full-screen', callback);
124    }
125  
126    onDidLeaveFullScreen(callback) {
127      return ipcHelpers.on(ipcRenderer, 'did-leave-full-screen', callback);
128    }
129  
130    async openWindowDevTools() {
131      // Defer DevTools interaction to the next tick, because using them during
132      // event handling causes some wrong input events to be triggered on
133      // `TextEditorComponent` (Ref.: https://github.com/atom/atom/issues/9697).
134      await new Promise(process.nextTick);
135      return ipcHelpers.call('window-method', 'openDevTools');
136    }
137  
138    async closeWindowDevTools() {
139      // Defer DevTools interaction to the next tick, because using them during
140      // event handling causes some wrong input events to be triggered on
141      // `TextEditorComponent` (Ref.: https://github.com/atom/atom/issues/9697).
142      await new Promise(process.nextTick);
143      return ipcHelpers.call('window-method', 'closeDevTools');
144    }
145  
146    async toggleWindowDevTools() {
147      // Defer DevTools interaction to the next tick, because using them during
148      // event handling causes some wrong input events to be triggered on
149      // `TextEditorComponent` (Ref.: https://github.com/atom/atom/issues/9697).
150      await new Promise(process.nextTick);
151      return ipcHelpers.call('window-method', 'toggleDevTools');
152    }
153  
154    executeJavaScriptInWindowDevTools(code) {
155      return ipcRenderer.send('execute-javascript-in-dev-tools', code);
156    }
157  
158    didClosePathWithWaitSession(path) {
159      return ipcHelpers.call(
160        'window-method',
161        'didClosePathWithWaitSession',
162        path
163      );
164    }
165  
166    setWindowDocumentEdited(edited) {
167      return ipcHelpers.call('window-method', 'setDocumentEdited', edited);
168    }
169  
170    setRepresentedFilename(filename) {
171      return ipcHelpers.call('window-method', 'setRepresentedFilename', filename);
172    }
173  
174    addRecentDocument(filename) {
175      return ipcRenderer.send('add-recent-document', filename);
176    }
177  
178    setProjectRoots(paths) {
179      return ipcHelpers.call('window-method', 'setProjectRoots', paths);
180    }
181  
182    setAutoHideWindowMenuBar(autoHide) {
183      return ipcHelpers.call('window-method', 'setAutoHideMenuBar', autoHide);
184    }
185  
186    setWindowMenuBarVisibility(visible) {
187      return remote.getCurrentWindow().setMenuBarVisibility(visible);
188    }
189  
190    getPrimaryDisplayWorkAreaSize() {
191      return remote.screen.getPrimaryDisplay().workAreaSize;
192    }
193  
194    getUserDefault(key, type) {
195      return remote.systemPreferences.getUserDefault(key, type);
196    }
197  
198    async setUserSettings(config, configFilePath) {
199      this.pendingSettingsUpdateCount++;
200      try {
201        await ipcHelpers.call(
202          'set-user-settings',
203          JSON.stringify(config),
204          configFilePath
205        );
206      } finally {
207        this.pendingSettingsUpdateCount--;
208      }
209    }
210  
211    onDidChangeUserSettings(callback) {
212      return this.ipcMessageEmitter().on('did-change-user-settings', detail => {
213        if (this.pendingSettingsUpdateCount === 0) callback(detail);
214      });
215    }
216  
217    onDidFailToReadUserSettings(callback) {
218      return this.ipcMessageEmitter().on(
219        'did-fail-to-read-user-setting',
220        callback
221      );
222    }
223  
224    confirm(options, callback) {
225      if (typeof callback === 'function') {
226        // Async version: pass options directly to Electron but set sane defaults
227        options = Object.assign(
228          { type: 'info', normalizeAccessKeys: true },
229          options
230        );
231        remote.dialog.showMessageBox(
232          remote.getCurrentWindow(),
233          options,
234          callback
235        );
236      } else {
237        // Legacy sync version: options can only have `message`,
238        // `detailedMessage` (optional), and buttons array or object (optional)
239        let { message, detailedMessage, buttons } = options;
240  
241        let buttonLabels;
242        if (!buttons) buttons = {};
243        if (Array.isArray(buttons)) {
244          buttonLabels = buttons;
245        } else {
246          buttonLabels = Object.keys(buttons);
247        }
248  
249        const chosen = remote.dialog.showMessageBox(remote.getCurrentWindow(), {
250          type: 'info',
251          message,
252          detail: detailedMessage,
253          buttons: buttonLabels,
254          normalizeAccessKeys: true
255        });
256  
257        if (Array.isArray(buttons)) {
258          return chosen;
259        } else {
260          const callback = buttons[buttonLabels[chosen]];
261          if (typeof callback === 'function') return callback();
262        }
263      }
264    }
265  
266    showMessageDialog(params) {}
267  
268    showSaveDialog(options, callback) {
269      if (typeof callback === 'function') {
270        // Async
271        this.getCurrentWindow().showSaveDialog(options, callback);
272      } else {
273        // Sync
274        if (typeof options === 'string') {
275          options = { defaultPath: options };
276        }
277        return this.getCurrentWindow().showSaveDialog(options);
278      }
279    }
280  
281    playBeepSound() {
282      return shell.beep();
283    }
284  
285    onDidOpenLocations(callback) {
286      return this.ipcMessageEmitter().on('open-locations', callback);
287    }
288  
289    onUpdateAvailable(callback) {
290      // TODO: Yes, this is strange that `onUpdateAvailable` is listening for
291      // `did-begin-downloading-update`. We currently have no mechanism to know
292      // if there is an update, so begin of downloading is a good proxy.
293      return this.ipcMessageEmitter().on(
294        'did-begin-downloading-update',
295        callback
296      );
297    }
298  
299    onDidBeginDownloadingUpdate(callback) {
300      return this.onUpdateAvailable(callback);
301    }
302  
303    onDidBeginCheckingForUpdate(callback) {
304      return this.ipcMessageEmitter().on('checking-for-update', callback);
305    }
306  
307    onDidCompleteDownloadingUpdate(callback) {
308      return this.ipcMessageEmitter().on('update-available', callback);
309    }
310  
311    onUpdateNotAvailable(callback) {
312      return this.ipcMessageEmitter().on('update-not-available', callback);
313    }
314  
315    onUpdateError(callback) {
316      return this.ipcMessageEmitter().on('update-error', callback);
317    }
318  
319    onApplicationMenuCommand(handler) {
320      const outerCallback = (event, ...args) => handler(...args);
321  
322      ipcRenderer.on('command', outerCallback);
323      return new Disposable(() =>
324        ipcRenderer.removeListener('command', outerCallback)
325      );
326    }
327  
328    onContextMenuCommand(handler) {
329      const outerCallback = (event, ...args) => handler(...args);
330  
331      ipcRenderer.on('context-command', outerCallback);
332      return new Disposable(() =>
333        ipcRenderer.removeListener('context-command', outerCallback)
334      );
335    }
336  
337    onURIMessage(handler) {
338      const outerCallback = (event, ...args) => handler(...args);
339  
340      ipcRenderer.on('uri-message', outerCallback);
341      return new Disposable(() =>
342        ipcRenderer.removeListener('uri-message', outerCallback)
343      );
344    }
345  
346    onDidRequestUnload(callback) {
347      const outerCallback = async (event, message) => {
348        const shouldUnload = await callback(event);
349        ipcRenderer.send('did-prepare-to-unload', shouldUnload);
350      };
351  
352      ipcRenderer.on('prepare-to-unload', outerCallback);
353      return new Disposable(() =>
354        ipcRenderer.removeListener('prepare-to-unload', outerCallback)
355      );
356    }
357  
358    onDidChangeHistoryManager(callback) {
359      const outerCallback = (event, message) => callback(event);
360  
361      ipcRenderer.on('did-change-history-manager', outerCallback);
362      return new Disposable(() =>
363        ipcRenderer.removeListener('did-change-history-manager', outerCallback)
364      );
365    }
366  
367    didChangeHistoryManager() {
368      return ipcRenderer.send('did-change-history-manager');
369    }
370  
371    openExternal(url) {
372      return shell.openExternal(url);
373    }
374  
375    checkForUpdate() {
376      return ipcRenderer.send('command', 'application:check-for-update');
377    }
378  
379    restartAndInstallUpdate() {
380      return ipcRenderer.send('command', 'application:install-update');
381    }
382  
383    getAutoUpdateManagerState() {
384      return ipcRenderer.sendSync('get-auto-update-manager-state');
385    }
386  
387    getAutoUpdateManagerErrorMessage() {
388      return ipcRenderer.sendSync('get-auto-update-manager-error');
389    }
390  
391    emitWillSavePath(path) {
392      return ipcHelpers.call('will-save-path', path);
393    }
394  
395    emitDidSavePath(path) {
396      return ipcHelpers.call('did-save-path', path);
397    }
398  
399    resolveProxy(requestId, url) {
400      return ipcRenderer.send('resolve-proxy', requestId, url);
401    }
402  
403    onDidResolveProxy(callback) {
404      const outerCallback = (event, requestId, proxy) =>
405        callback(requestId, proxy);
406  
407      ipcRenderer.on('did-resolve-proxy', outerCallback);
408      return new Disposable(() =>
409        ipcRenderer.removeListener('did-resolve-proxy', outerCallback)
410      );
411    }
412  };