Files
atom/src/atom-application.coffee
Kevin Sawicki 438b8f6a14 Support launching the app directly with a URL
In this case there will be no paths to open and so editor windows
should be created.

This will allow sessions to be joined when Atom isn't currently running
but a session link is clicked from within another application.
2013-07-16 09:49:47 -07:00

280 lines
9.3 KiB
CoffeeScript

AtomWindow = require './atom-window'
BrowserWindow = require 'browser-window'
Menu = require 'menu'
autoUpdater = require 'auto-updater'
app = require 'app'
ipc = require 'ipc'
dialog = require 'dialog'
fs = require 'fs'
path = require 'path'
net = require 'net'
url = require 'url'
socketPath = '/tmp/atom.sock'
module.exports =
class AtomApplication
@open: (options) ->
createAtomApplication = -> new AtomApplication(options)
# FIXME: Sometimes when socketPath doesn't exist, net.connect would strangely
# take a few seconds to trigger 'error' event, it could be a bug of node
# or atom-shell, before it's fixed we check the existence of socketPath to
# speedup startup.
if not fs.existsSync socketPath
createAtomApplication()
return
client = net.connect {path: socketPath}, ->
client.write JSON.stringify(options), ->
client.end()
app.terminate()
client.on 'error', createAtomApplication
windows: null
configWindow: null
menu: null
resourcePath: null
installUpdate: null
version: null
constructor: ({@resourcePath, pathsToOpen, urlsToOpen, @version, test, pidToKillWhenClosed, @dev, newWindow}) ->
global.atomApplication = this
@pidsToOpenWindows = {}
@pathsToOpen ?= []
@windows = []
@listenForArgumentsFromNewProcess()
@setupJavaScriptArguments()
@buildApplicationMenu()
@handleEvents()
@checkForUpdates()
if test
@runSpecs({exitWhenDone: true, @resourcePath})
else if pathsToOpen.length > 0
@openPaths({pathsToOpen, pidToKillWhenClosed, newWindow})
else if urlsToOpen.length > 0
@openUrl(urlToOpen) for urlToOpen in urlsToOpen
else
# Always open a editor window if this is the first instance of Atom.
@openPath({pidToKillWhenClosed, newWindow})
removeWindow: (window) ->
@windows.splice @windows.indexOf(window), 1
addWindow: (window) ->
@windows.push window
listenForArgumentsFromNewProcess: ->
fs.unlinkSync socketPath if fs.existsSync(socketPath)
server = net.createServer (connection) =>
connection.on 'data', (data) =>
{pathsToOpen, pidToKillWhenClosed, newWindow} = JSON.parse(data)
@openPaths({pathsToOpen, pidToKillWhenClosed, newWindow})
server.listen socketPath
server.on 'error', (error) -> console.error 'Application server failed', error
setupJavaScriptArguments: ->
app.commandLine.appendSwitch 'js-flags', '--harmony_collections'
checkForUpdates: ->
return if /\w{7}/.test @version # Don't check for updates if version is a short sha
autoUpdater.setAutomaticallyChecksForUpdates true
autoUpdater.checkForUpdatesInBackground()
buildApplicationMenu: (version, continueUpdate) ->
menus = []
menus.push
label: 'Atom'
submenu: [
{ label: 'About Atom', selector: 'orderFrontStandardAboutPanel:' }
{ type: 'separator' }
{ label: 'Preferences...', accelerator: 'Command+,', click: => @openConfig() }
{ type: 'separator' }
{ label: 'Hide Atom', accelerator: 'Command+H', selector: 'hide:' }
{ label: 'Hide Others', accelerator: 'Command+Shift+H', selector: 'hideOtherApplications:' }
{ label: 'Show All', selector: 'unhideAllApplications:' }
{ type: 'separator' }
{
label: 'Run Specs'
accelerator: 'Command+MacCtrl+Alt+S'
click: =>
@runSpecs(exitWhenDone: false, resourcePath: global.devResourcePath)
}
{ type: 'separator' }
{ label: 'Quit', accelerator: 'Command+Q', click: -> app.quit() }
]
menus[0].submenu[1..0] =
if version
label: "Update to #{version}"
click: continueUpdate
else
label: "Version #{@version}"
enabled: false
if @dev
menus.push
label: '\uD83D\uDC80' # Skull emoji
submenu: [ { label: 'In Development Mode', enabled: false } ]
menus.push
label: 'File'
submenu: [
{ label: 'New Window', accelerator: 'Command+N', click: => @openPath() }
{ label: 'Open...', accelerator: 'Command+O', click: => @promptForPath() }
{ label: 'Open In Dev Mode...', accelerator: 'Command+Shift+O', click: => @promptForPath(devMode: true) }
]
menus.push
label: 'Edit'
submenu:[
{ label: 'Undo', accelerator: 'Command+Z', selector: 'undo:' }
{ label: 'Redo', accelerator: 'Command+Shift+Z', selector: 'redo:' }
{ type: 'separator' }
{ label: 'Cut', accelerator: 'Command+X', selector: 'cut:' }
{ label: 'Copy', accelerator: 'Command+C', selector: 'copy:' }
{ label: 'Paste', accelerator: 'Command+V', selector: 'paste:' }
{ label: 'Select All', accelerator: 'Command+A', selector: 'selectAll:' }
]
menus.push
label: 'View'
submenu:[
{ label: 'Reload', accelerator: 'Command+R', click: => BrowserWindow.getFocusedWindow()?.restart() }
{ label: 'Toggle Full Screen', accelerator: 'Command+MacCtrl+F', click: => BrowserWindow.getFocusedWindow()?.setFullScreen(!BrowserWindow.getFocusedWindow().isFullScreen()) }
{ label: 'Toggle Developer Tools', accelerator: 'Alt+Command+I', click: => BrowserWindow.getFocusedWindow()?.toggleDevTools() }
]
menus.push
label: 'Window'
submenu: [
{ label: 'Minimize', accelerator: 'Command+M', selector: 'performMiniaturize:' }
{ label: 'Zoom', accelerator: 'Alt+Command+MacCtrl+M', selector: 'zoom:' }
{ label: 'Close', accelerator: 'Command+W', selector: 'performClose:' }
{ type: 'separator' }
{ label: 'Bring All to Front', selector: 'arrangeInFront:' }
]
@menu = Menu.buildFromTemplate menus
Menu.setApplicationMenu @menu
handleEvents: ->
# Clean the socket file when quit normally.
app.on 'will-quit', =>
fs.unlinkSync socketPath if fs.existsSync(socketPath)
app.on 'open-file', (event, pathToOpen) =>
event.preventDefault()
@openPath({pathToOpen})
app.on 'open-url', (event, urlToOpen) =>
event.preventDefault()
@openUrl(urlToOpen)
autoUpdater.on 'ready-for-update-on-quit', (event, version, quitAndUpdate) =>
event.preventDefault()
@installUpdate = quitAndUpdate
@buildApplicationMenu version, quitAndUpdate
ipc.on 'open-config', =>
@openConfig()
ipc.on 'open', (processId, routingId, pathsToOpen) =>
if pathsToOpen?.length > 0
@openPaths({pathsToOpen})
else
@promptForPath()
ipc.on 'open-window', (processId, routingId, windowSettings) ->
new AtomWindow(windowSettings)
ipc.on 'open-dev', (processId, routingId, pathsToOpen) =>
if pathsToOpen?.length > 0
@openPaths({pathsToOpen, devMode: true})
else
@promptForPath(devMode: true)
ipc.on 'new-window', =>
@openPath()
ipc.on 'install-update', =>
@installUpdate?()
ipc.on 'get-version', (event) =>
event.result = @version
sendCommand: (command, args...) ->
for atomWindow in @windows when atomWindow.isFocused()
atomWindow.sendCommand(command, args...)
windowForPath: (pathToOpen) ->
for atomWindow in @windows
return atomWindow if atomWindow.containsPath(pathToOpen)
openPaths: ({pathsToOpen, pidToKillWhenClosed, newWindow, devMode}) ->
@openPath({pathToOpen, pidToKillWhenClosed, newWindow, devMode}) for pathToOpen in pathsToOpen ? []
openPath: ({pathToOpen, pidToKillWhenClosed, newWindow, devMode}={}) ->
unless devMode
existingWindow = @windowForPath(pathToOpen) unless pidToKillWhenClosed or newWindow
if existingWindow
openedWindow = existingWindow
openedWindow.openPath(pathToOpen)
else
bootstrapScript = 'window-bootstrap'
if devMode
resourcePath = global.devResourcePath
else
resourcePath = @resourcePath
openedWindow = new AtomWindow({pathToOpen, bootstrapScript, resourcePath})
if pidToKillWhenClosed?
@pidsToOpenWindows[pidToKillWhenClosed] = openedWindow
openedWindow.browserWindow.on 'destroyed', =>
for pid, trackedWindow of @pidsToOpenWindows when trackedWindow is openedWindow
try
process.kill(pid)
catch error
if error.code isnt 'ESRCH'
console.log("Killing process #{pid} failed: #{error.code}")
delete @pidsToOpenWindows[pid]
openUrl: (urlToOpen) ->
parsedUrl = url.parse(urlToOpen)
if parsedUrl.host is 'session'
sessionId = parsedUrl.path.split('/')[1]
if sessionId
bootstrapScript = 'collaboration/lib/bootstrap'
new AtomWindow({bootstrapScript, @resourcePath, sessionId})
openConfig: ->
if @configWindow
@configWindow.focus()
return
@configWindow = new AtomWindow
bootstrapScript: 'config-bootstrap'
resourcePath: @resourcePath
@configWindow.browserWindow.on 'destroyed', =>
@configWindow = null
runSpecs: ({exitWhenDone, resourcePath}) ->
if resourcePath isnt @resourcePath and not fs.existsSync(resourcePath)
resourcePath = @resourcePath
bootstrapScript = 'spec-bootstrap'
isSpec = true
new AtomWindow({bootstrapScript, resourcePath, exitWhenDone, isSpec})
promptForPath: ({devMode}={}) ->
pathsToOpen = dialog.showOpenDialog title: 'Open', properties: ['openFile', 'openDirectory', 'multiSelections', 'createDirectory']
@openPaths({pathsToOpen, devMode})