diff --git a/exports/atom.coffee b/exports/atom.coffee index 81d1726b8..bd8a1b62c 100644 --- a/exports/atom.coffee +++ b/exports/atom.coffee @@ -18,6 +18,10 @@ module.exports = Disposable: Disposable CompositeDisposable: CompositeDisposable +# Shell integration is required by both Squirrel and Settings-View +if process.platform is 'win32' + module.exports.WinShell = require '../src/main-process/win-shell' + # The following classes can't be used from a Task handler and should therefore # only be exported when not running as a child node process unless process.env.ATOM_SHELL_INTERNAL_RUN_AS_NODE diff --git a/package.json b/package.json index 4083f8a83..0c037cb2d 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "text-buffer": "9.2.2", "typescript-simple": "1.0.0", "underscore-plus": "^1.6.6", + "winreg": "^1.2.1", "yargs": "^3.23.0" }, "packageDependencies": { @@ -113,12 +114,12 @@ "status-bar": "1.4.0", "styleguide": "0.47.0", "symbols-view": "0.113.0", - "tabs": "0.99.0", - "timecop": "0.33.1", + "tabs": "0.100.0", + "timecop": "0.33.2", "tree-view": "0.208.1", "update-package-dependencies": "0.10.0", "welcome": "0.34.0", - "whitespace": "0.32.2", + "whitespace": "0.33.0", "wrap-guide": "0.38.1", "language-c": "0.52.1", "language-clojure": "0.21.0", diff --git a/spec/squirrel-update-spec.coffee b/spec/squirrel-update-spec.coffee index a8a39eb54..2fe944e41 100644 --- a/spec/squirrel-update-spec.coffee +++ b/spec/squirrel-update-spec.coffee @@ -5,7 +5,7 @@ temp = require 'temp' SquirrelUpdate = require '../src/main-process/squirrel-update' Spawner = require '../src/main-process/spawner' WinPowerShell = require '../src/main-process/win-powershell' -WinRegistry = require '../src/main-process/win-registry' +WinShell = require '../src/main-process/win-shell' # Run passed callback as Spawner.spawn() would do invokeCallback = (callback) -> @@ -26,12 +26,16 @@ describe "Windows Squirrel Update", -> # do nothing on command, just run passed callback invokeCallback callback - # Prevent any actual change to Windows registry - for own method of WinRegistry - # all WinRegistry APIs share the same signature - spyOn(WinRegistry, method).andCallFake (callback) -> - # do nothing on registry, just run passed callback - invokeCallback callback + # Prevent any actual change to Windows Shell + class FakeShellOption + isRegistered: (callback) -> callback true + register: (callback) -> callback null + deregister: (callback) -> callback null, true + update: (callback) -> callback null + WinShell.fileHandler = new FakeShellOption() + WinShell.fileContextMenu = new FakeShellOption() + WinShell.folderContextMenu = new FakeShellOption() + WinShell.folderBackgroundContextMenu = new FakeShellOption() it "quits the app on all squirrel events", -> app = quit: jasmine.createSpy('quit') diff --git a/src/crash-reporter-start.coffee b/src/crash-reporter-start.coffee index 37c381473..af371df79 100644 --- a/src/crash-reporter-start.coffee +++ b/src/crash-reporter-start.coffee @@ -1,8 +1,4 @@ module.exports = (extra) -> - # Breakpad on Mac OS X must be running on UI and non-UI processes - # Crashpad on Windows and Linux should only be running on non-UI process - return if process.type is 'renderer' and process.platform isnt 'darwin' - {crashReporter} = require 'electron' crashReporter.start({ diff --git a/src/main-process/squirrel-update.coffee b/src/main-process/squirrel-update.coffee index a1bfc5359..acac81457 100644 --- a/src/main-process/squirrel-update.coffee +++ b/src/main-process/squirrel-update.coffee @@ -1,7 +1,7 @@ fs = require 'fs-plus' path = require 'path' Spawner = require './spawner' -WinRegistry = require './win-registry' +WinShell = require './win-shell' WinPowerShell = require './win-powershell' appFolder = path.resolve(process.execPath, '..') @@ -125,26 +125,36 @@ exports.restartAtom = (app) -> app.once 'will-quit', -> Spawner.spawn(path.join(binFolder, 'atom.cmd'), args) app.quit() +updateContextMenus = (callback) -> + WinShell.fileContextMenu.update -> + WinShell.folderContextMenu.update -> + WinShell.folderBackgroundContextMenu.update -> + callback() + # Handle squirrel events denoted by --squirrel-* command line arguments. exports.handleStartupEvent = (app, squirrelCommand) -> switch squirrelCommand when '--squirrel-install' createShortcuts -> - WinRegistry.installContextMenu -> - addCommandsToPath -> - app.quit() + addCommandsToPath -> + WinShell.fileHandler.register -> + updateContextMenus -> + app.quit() true when '--squirrel-updated' updateShortcuts -> - WinRegistry.installContextMenu -> - addCommandsToPath -> + addCommandsToPath -> + updateContextMenus -> app.quit() true when '--squirrel-uninstall' removeShortcuts -> - WinRegistry.uninstallContextMenu -> - removeCommandsFromPath -> - app.quit() + removeCommandsFromPath -> + WinShell.fileHandler.deregister -> + WinShell.fileContextMenu.deregister -> + WinShell.folderContextMenu.deregister -> + WinShell.folderBackgroundContextMenu.deregister -> + app.quit() true when '--squirrel-obsolete' app.quit() diff --git a/src/main-process/win-registry.coffee b/src/main-process/win-registry.coffee deleted file mode 100644 index f4b81b377..000000000 --- a/src/main-process/win-registry.coffee +++ /dev/null @@ -1,62 +0,0 @@ -path = require 'path' -Spawner = require './spawner' - -if process.env.SystemRoot - system32Path = path.join(process.env.SystemRoot, 'System32') - regPath = path.join(system32Path, 'reg.exe') -else - regPath = 'reg.exe' - -# Registry keys used for context menu -fileKeyPath = 'HKCU\\Software\\Classes\\*\\shell\\Atom' -directoryKeyPath = 'HKCU\\Software\\Classes\\directory\\shell\\Atom' -backgroundKeyPath = 'HKCU\\Software\\Classes\\directory\\background\\shell\\Atom' -applicationsKeyPath = 'HKCU\\Software\\Classes\\Applications\\atom.exe' - -# Spawn reg.exe and callback when it completes -spawnReg = (args, callback) -> - Spawner.spawn(regPath, args, callback) - -# Install the Open with Atom explorer context menu items via the registry. -# -# * `callback` The {Function} to call after registry operation is done. -# It will be invoked with the same arguments provided by {Spawner.spawn}. -# -# Returns `undefined`. -exports.installContextMenu = (callback) -> - addToRegistry = (args, callback) -> - args.unshift('add') - args.push('/f') - spawnReg(args, callback) - - installFileHandler = (callback) -> - args = ["#{applicationsKeyPath}\\shell\\open\\command", '/ve', '/d', "\"#{process.execPath}\" \"%1\""] - addToRegistry(args, callback) - - installMenu = (keyPath, arg, callback) -> - args = [keyPath, '/ve', '/d', 'Open with Atom'] - addToRegistry args, -> - args = [keyPath, '/v', 'Icon', '/d', "\"#{process.execPath}\""] - addToRegistry args, -> - args = ["#{keyPath}\\command", '/ve', '/d', "\"#{process.execPath}\" \"#{arg}\""] - addToRegistry(args, callback) - - installMenu fileKeyPath, '%1', -> - installMenu directoryKeyPath, '%1', -> - installMenu backgroundKeyPath, '%V', -> - installFileHandler(callback) - -# Uninstall the Open with Atom explorer context menu items via the registry. -# -# * `callback` The {Function} to call after registry operation is done. -# It will be invoked with the same arguments provided by {Spawner.spawn}. -# -# Returns `undefined`. -exports.uninstallContextMenu = (callback) -> - deleteFromRegistry = (keyPath, callback) -> - spawnReg(['delete', keyPath, '/f'], callback) - - deleteFromRegistry fileKeyPath, -> - deleteFromRegistry directoryKeyPath, -> - deleteFromRegistry backgroundKeyPath, -> - deleteFromRegistry(applicationsKeyPath, callback) diff --git a/src/main-process/win-shell.coffee b/src/main-process/win-shell.coffee new file mode 100644 index 000000000..39e9f4ed0 --- /dev/null +++ b/src/main-process/win-shell.coffee @@ -0,0 +1,57 @@ +Registry = require 'winreg' +Path = require 'path' + +exeName = Path.basename(process.execPath) +appPath = "\"#{process.execPath}\"" +appName = exeName.replace('atom', 'Atom').replace('beta', 'Beta').replace('.exe', '') + +class ShellOption + constructor: (key, parts) -> + @key = key + @parts = parts + + isRegistered: (callback) => + new Registry({hive: 'HKCU', key: "#{@key}\\#{@parts[0].key}"}) + .get @parts[0].name, (err, val) => + callback(not err? and val.value is @parts[0].value) + + register: (callback) => + doneCount = @parts.length + for part in @parts + reg = new Registry({hive: 'HKCU', key: if part.key? then "#{@key}\\#{part.key}" else @key}) + reg.create( -> reg.set part.name, Registry.REG_SZ, part.value, -> callback() if --doneCount is 0) + + deregister: (callback) => + @isRegistered (isRegistered) => + if isRegistered + new Registry({hive: 'HKCU', key: @key}).destroy -> callback null, true + else + callback null, false + + update: (callback) => + new Registry({hive: 'HKCU', key: "#{@key}\\#{@parts[0].key}"}) + .get @parts[0].name, (err, val) => + if err? or not val.value.includes '\\' + exeName + callback(err) + else + @register callback + +exports.appName = appName + +exports.fileHandler = new ShellOption("\\Software\\Classes\\Applications\\#{exeName}", + [{key: 'shell\\open\\command', name: '', value: "#{appPath} \"%1\""}] +) + +contextParts = [ + {key: 'command', name: '', value: "#{appPath} \"%1\""}, + {name: '', value: "Open with #{appName}"}, + {name: 'Icon', value: "#{appPath}"} +] + +exports.fileContextMenu = new ShellOption("\\Software\\Classes\\*\\shell\\#{appName}", contextParts) + +exports.folderContextMenu = new ShellOption("\\Software\\Classes\\Directory\\shell\\#{appName}", contextParts) + +exports.folderBackgroundContextMenu = new ShellOption("\\Software\\Classes\\Directory\\background\\shell\\#{appName}", + JSON.parse(JSON.stringify(contextParts).replace('%1', '%V')) +)