{BrowserWindow, app, dialog, ipcMain} = require 'electron' path = require 'path' fs = require 'fs' url = require 'url' {EventEmitter} = require 'events' module.exports = class AtomWindow Object.assign @prototype, EventEmitter.prototype @iconPath: path.resolve(__dirname, '..', '..', 'resources', 'atom.png') @includeShellLoadTime: true browserWindow: null loaded: null isSpec: null constructor: (@atomApplication, @fileRecoveryService, settings={}) -> {@resourcePath, pathToOpen, locationsToOpen, @isSpec, @headless, @safeMode, @devMode} = settings locationsToOpen ?= [{pathToOpen}] if pathToOpen locationsToOpen ?= [] @loadedPromise = new Promise((@resolveLoadedPromise) =>) @closedPromise = new Promise((@resolveClosedPromise) =>) options = show: false title: 'Atom' tabbingIdentifier: 'atom' webPreferences: # Prevent specs from throttling when the window is in the background: # this should result in faster CI builds, and an improvement in the # local development experience when running specs through the UI (which # now won't pause when e.g. minimizing the window). backgroundThrottling: not @isSpec # Disable the `auxclick` feature so that `click` events are triggered in # response to a middle-click. # (Ref: https://github.com/atom/atom/pull/12696#issuecomment-290496960) disableBlinkFeatures: 'Auxclick' # Don't set icon on Windows so the exe's ico will be used as window and # taskbar's icon. See https://github.com/atom/atom/issues/4811 for more. if process.platform is 'linux' options.icon = @constructor.iconPath if @shouldAddCustomTitleBar() options.titleBarStyle = 'hidden' if @shouldAddCustomInsetTitleBar() options.titleBarStyle = 'hidden-inset' if @shouldHideTitleBar() options.frame = false @browserWindow = new BrowserWindow(options) @handleEvents() @loadSettings = Object.assign({}, settings) @loadSettings.appVersion = app.getVersion() @loadSettings.resourcePath = @resourcePath @loadSettings.devMode ?= false @loadSettings.safeMode ?= false @loadSettings.atomHome = process.env.ATOM_HOME @loadSettings.clearWindowState ?= false @loadSettings.initialPaths ?= for {pathToOpen} in locationsToOpen when pathToOpen stat = fs.statSyncNoException(pathToOpen) or null if stat?.isDirectory() pathToOpen else parentDirectory = path.dirname(pathToOpen) if stat?.isFile() or fs.existsSync(parentDirectory) parentDirectory else pathToOpen @loadSettings.initialPaths.sort() # Only send to the first non-spec window created if @constructor.includeShellLoadTime and not @isSpec @constructor.includeShellLoadTime = false @loadSettings.shellLoadTime ?= Date.now() - global.shellStartTime @representedDirectoryPaths = @loadSettings.initialPaths @env = @loadSettings.env if @loadSettings.env? @browserWindow.loadSettingsJSON = JSON.stringify(@loadSettings) @browserWindow.on 'window:loaded', => @disableZoom() @emit 'window:loaded' @resolveLoadedPromise() @browserWindow.on 'window:locations-opened', => @emit 'window:locations-opened' @browserWindow.on 'enter-full-screen', => @browserWindow.webContents.send('did-enter-full-screen') @browserWindow.on 'leave-full-screen', => @browserWindow.webContents.send('did-leave-full-screen') @browserWindow.loadURL url.format protocol: 'file' pathname: "#{@resourcePath}/static/index.html" slashes: true @browserWindow.showSaveDialog = @showSaveDialog.bind(this) @browserWindow.focusOnWebView() if @isSpec @browserWindow.temporaryState = {windowDimensions} if windowDimensions? hasPathToOpen = not (locationsToOpen.length is 1 and not locationsToOpen[0].pathToOpen?) @openLocations(locationsToOpen) if hasPathToOpen and not @isSpecWindow() @atomApplication.addWindow(this) hasProjectPath: -> @representedDirectoryPaths.length > 0 setupContextMenu: -> ContextMenu = require './context-menu' @browserWindow.on 'context-menu', (menuTemplate) => new ContextMenu(menuTemplate, this) containsPaths: (paths) -> for pathToCheck in paths return false unless @containsPath(pathToCheck) true containsPath: (pathToCheck) -> @representedDirectoryPaths.some (projectPath) -> if not projectPath false else if not pathToCheck false else if pathToCheck is projectPath true else if fs.statSyncNoException(pathToCheck).isDirectory?() false else if pathToCheck.indexOf(path.join(projectPath, path.sep)) is 0 true else false handleEvents: -> @browserWindow.on 'close', (event) => unless @atomApplication.quitting or @unloading event.preventDefault() @unloading = true @atomApplication.saveState(false) @prepareToUnload().then (result) => @close() if result @browserWindow.on 'closed', => @fileRecoveryService.didCloseWindow(this) @atomApplication.removeWindow(this) @resolveClosedPromise() @browserWindow.on 'unresponsive', => return if @isSpec chosen = dialog.showMessageBox @browserWindow, type: 'warning' buttons: ['Force Close', 'Keep Waiting'] message: 'Editor is not responding' detail: 'The editor is not responding. Would you like to force close it or just keep waiting?' @browserWindow.destroy() if chosen is 0 @browserWindow.webContents.on 'crashed', => if @headless console.log "Renderer process crashed, exiting" @atomApplication.exit(100) return @fileRecoveryService.didCrashWindow(this) chosen = dialog.showMessageBox @browserWindow, type: 'warning' buttons: ['Close Window', 'Reload', 'Keep It Open'] message: 'The editor has crashed' detail: 'Please report this issue to https://github.com/atom/atom' switch chosen when 0 then @browserWindow.destroy() when 1 then @browserWindow.reload() @browserWindow.webContents.on 'will-navigate', (event, url) => unless url is @browserWindow.webContents.getURL() event.preventDefault() @setupContextMenu() if @isSpec # Spec window's web view should always have focus @browserWindow.on 'blur', => @browserWindow.focusOnWebView() prepareToUnload: -> if @isSpecWindow() return Promise.resolve(true) @lastPrepareToUnloadPromise = new Promise (resolve) => callback = (event, result) => if BrowserWindow.fromWebContents(event.sender) is @browserWindow ipcMain.removeListener('did-prepare-to-unload', callback) unless result @unloading = false @atomApplication.quitting = false resolve(result) ipcMain.on('did-prepare-to-unload', callback) @browserWindow.webContents.send('prepare-to-unload') openPath: (pathToOpen, initialLine, initialColumn) -> @openLocations([{pathToOpen, initialLine, initialColumn}]) openLocations: (locationsToOpen) -> @loadedPromise.then => @sendMessage 'open-locations', locationsToOpen replaceEnvironment: (env) -> @browserWindow.webContents.send 'environment', env sendMessage: (message, detail) -> @browserWindow.webContents.send 'message', message, detail sendCommand: (command, args...) -> if @isSpecWindow() unless @atomApplication.sendCommandToFirstResponder(command) switch command when 'window:reload' then @reload() when 'window:toggle-dev-tools' then @toggleDevTools() when 'window:close' then @close() else if @isWebViewFocused() @sendCommandToBrowserWindow(command, args...) else unless @atomApplication.sendCommandToFirstResponder(command) @sendCommandToBrowserWindow(command, args...) sendURIMessage: (uri) -> @browserWindow.webContents.send 'uri-message', uri sendCommandToBrowserWindow: (command, args...) -> action = if args[0]?.contextCommand then 'context-command' else 'command' @browserWindow.webContents.send action, command, args... getDimensions: -> [x, y] = @browserWindow.getPosition() [width, height] = @browserWindow.getSize() {x, y, width, height} shouldAddCustomTitleBar: -> not @isSpec and process.platform is 'darwin' and @atomApplication.config.get('core.titleBar') is 'custom' shouldAddCustomInsetTitleBar: -> not @isSpec and process.platform is 'darwin' and @atomApplication.config.get('core.titleBar') is 'custom-inset' shouldHideTitleBar: -> not @isSpec and process.platform is 'darwin' and @atomApplication.config.get('core.titleBar') is 'hidden' close: -> @browserWindow.close() focus: -> @browserWindow.focus() minimize: -> @browserWindow.minimize() maximize: -> @browserWindow.maximize() unmaximize: -> @browserWindow.unmaximize() restore: -> @browserWindow.restore() setFullScreen: (fullScreen) -> @browserWindow.setFullScreen(fullScreen) setAutoHideMenuBar: (autoHideMenuBar) -> @browserWindow.setAutoHideMenuBar(autoHideMenuBar) handlesAtomCommands: -> not @isSpecWindow() and @isWebViewFocused() isFocused: -> @browserWindow.isFocused() isMaximized: -> @browserWindow.isMaximized() isMinimized: -> @browserWindow.isMinimized() isWebViewFocused: -> @browserWindow.isWebViewFocused() isSpecWindow: -> @isSpec reload: -> @loadedPromise = new Promise((@resolveLoadedPromise) =>) @prepareToUnload().then (result) => @browserWindow.reload() if result @loadedPromise showSaveDialog: (params) -> params = Object.assign({ title: 'Save File', defaultPath: @representedDirectoryPaths[0] }, params) dialog.showSaveDialog(@browserWindow, params) toggleDevTools: -> @browserWindow.toggleDevTools() openDevTools: -> @browserWindow.openDevTools() closeDevTools: -> @browserWindow.closeDevTools() setDocumentEdited: (documentEdited) -> @browserWindow.setDocumentEdited(documentEdited) setRepresentedFilename: (representedFilename) -> @browserWindow.setRepresentedFilename(representedFilename) setRepresentedDirectoryPaths: (@representedDirectoryPaths) -> @representedDirectoryPaths.sort() @loadSettings.initialPaths = @representedDirectoryPaths @browserWindow.loadSettingsJSON = JSON.stringify(@loadSettings) @atomApplication.saveState() copy: -> @browserWindow.copy() disableZoom: -> @browserWindow.webContents.setVisualZoomLevelLimits(1, 1)