Skip to content
This repository has been archived by the owner on Mar 3, 2023. It is now read-only.

Windows shell integration system settings #12123

Merged
merged 7 commits into from
Jul 20, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions exports/atom.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,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
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,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": {
Expand Down
18 changes: 11 additions & 7 deletions spec/squirrel-update-spec.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -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) ->
Expand All @@ -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')
Expand Down
28 changes: 19 additions & 9 deletions src/main-process/squirrel-update.coffee
Original file line number Diff line number Diff line change
@@ -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, '..')
Expand Down Expand Up @@ -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()
Expand Down
62 changes: 0 additions & 62 deletions src/main-process/win-registry.coffee

This file was deleted.

57 changes: 57 additions & 0 deletions src/main-process/win-shell.coffee
Original file line number Diff line number Diff line change
@@ -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) =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to use fat arrows for these functions?

nit It is typical to have a blank line between functions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fat arrows are required so that the @ operator points to this object not to the window when the callback functions are executed.

I can add spacing.

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'))
)