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 };