diff --git a/atom/common/api/atom_api_shell.cc b/atom/common/api/atom_api_shell.cc index 860787f17c8f..2d9d6e02697a 100644 --- a/atom/common/api/atom_api_shell.cc +++ b/atom/common/api/atom_api_shell.cc @@ -11,6 +11,34 @@ #include "atom/common/node_includes.h" #include "native_mate/dictionary.h" +#if defined(OS_WIN) +#include "base/win/scoped_com_initializer.h" +#include "base/win/shortcut.h" + +namespace mate { + +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, v8::Handle val, + base::win::ShortcutOperation* out) { + std::string operation; + if (!ConvertFromV8(isolate, val, & operation)) + return false; + if (operation.empty() || operation == "create") + *out = base::win::SHORTCUT_CREATE_ALWAYS; + else if (operation == "update") + *out = base::win::SHORTCUT_UPDATE_EXISTING; + else if (operation == "replace") + *out = base::win::SHORTCUT_REPLACE_EXISTING; + else + return false; + return true; + } +}; + +} // namespace mate +#endif + namespace { bool OpenExternal( @@ -30,6 +58,61 @@ bool OpenExternal( return platform_util::OpenExternal(url, activate); } +#if defined(OS_WIN) +bool WriteShortcutLink(const base::FilePath& shortcut_path, + mate::Arguments* args) { + base::win::ShortcutOperation operation = base::win::SHORTCUT_CREATE_ALWAYS; + args->GetNext(&operation); + mate::Dictionary options = mate::Dictionary::CreateEmpty(args->isolate()); + if (!args->GetNext(&options)) { + args->ThrowError(); + return false; + } + + base::win::ShortcutProperties properties; + base::FilePath path; + base::string16 str; + int index; + if (options.Get("target", &path)) + properties.set_target(path); + if (options.Get("cwd", &path)) + properties.set_working_dir(path); + if (options.Get("args", &str)) + properties.set_arguments(str); + if (options.Get("description", &str)) + properties.set_description(str); + if (options.Get("icon", &path) && options.Get("iconIndex", &index)) + properties.set_icon(path, index); + if (options.Get("appUserModelId", &str)) + properties.set_app_id(str); + + base::win::ScopedCOMInitializer com_initializer; + return base::win::CreateOrUpdateShortcutLink( + shortcut_path, properties, operation); +} + +v8::Local ReadShortcutLink(mate::Arguments* args, + const base::FilePath& path) { + using base::win::ShortcutProperties; + mate::Dictionary options = mate::Dictionary::CreateEmpty(args->isolate()); + base::win::ScopedCOMInitializer com_initializer; + base::win::ShortcutProperties properties; + if (!base::win::ResolveShortcutProperties( + path, ShortcutProperties::PROPERTIES_ALL, &properties)) { + args->ThrowError("Failed to read shortcut link"); + return v8::Null(args->isolate()); + } + options.Set("target", properties.target); + options.Set("cwd", properties.working_dir); + options.Set("args", properties.arguments); + options.Set("description", properties.description); + options.Set("icon", properties.icon); + options.Set("iconIndex", properties.icon_index); + options.Set("appUserModelId", properties.app_id); + return options.GetHandle(); +} +#endif + void Initialize(v8::Local exports, v8::Local unused, v8::Local context, void* priv) { mate::Dictionary dict(context->GetIsolate(), exports); @@ -38,6 +121,10 @@ void Initialize(v8::Local exports, v8::Local unused, dict.SetMethod("openExternal", &OpenExternal); dict.SetMethod("moveItemToTrash", &platform_util::MoveItemToTrash); dict.SetMethod("beep", &platform_util::Beep); +#if defined(OS_WIN) + dict.SetMethod("writeShortcutLink", &WriteShortcutLink); + dict.SetMethod("readShortcutLink", &ReadShortcutLink); +#endif } } // namespace diff --git a/docs/api/shell.md b/docs/api/shell.md index 5963246caf2c..bad864f28679 100644 --- a/docs/api/shell.md +++ b/docs/api/shell.md @@ -48,3 +48,37 @@ Move the given file to trash and returns a boolean status for the operation. ### `shell.beep()` Play the beep sound. + +### `shell.writeShortcutLink(shortcutPath[, operation], options)` _Windows_ + +* `shortcutPath` String +* `operation` String (optional) - Default is `create`, can be one of followings: + * `create` - Creates a new shortcut, overwriting if necessary. + * `update` - Updates specified properties only on an existing shortcut. + * `replace` - Overwrites an existing shortcut, fails if the shortcut doesn't + exist. +* `options` Object + * `target` String - The target to launch from this shortcut. + * `cwd` String (optional) - The target to launch from this shortcut. Default + is empty. + * `args` String (optional) - The arguments to be applied to `target` when + launching from this shortcut. Default is empty. + * `description` String (optional) - The description of the shortcut. Default + is empty. + * `icon` String (optional) - The path to the icon, can be a DLL or EXE. `icon` + and `iconIndex` have to be set together. Default is empty, which uses the + target's icon. + * `iconIndex` Integer (optional) - The resource ID of icon when `icon` is a + DLL or EXE. Default is 0. + * `appUserModelId` String (optional) - The Application User Model ID. Default + is empty. + +Creates or updates a shortcut link at `shortcutPath`. On success `true` is +returned, otherwise `false` is returned. + +### `shell.readShortcutLink(shortcutPath)` _Windows_ + +Resolves the shortcut link at `shortcutPath`, an object is returned with the +fields described in the `options` of `shell.writeShortcutLink`. + +An exception will be thrown when any error happens. diff --git a/spec/api-shell-spec.js b/spec/api-shell-spec.js new file mode 100644 index 000000000000..8c9f9833848a --- /dev/null +++ b/spec/api-shell-spec.js @@ -0,0 +1,77 @@ +const assert = require('assert') +const fs = require('fs') +const path = require('path') +const os = require('os') +const {shell} = require('electron') + +describe('shell module', function () { + if (process.platform !== 'win32') return + + const fixtures = path.resolve(__dirname, 'fixtures') + const shortcutOptions = { + target: 'C:\\target', + description: 'description', + cwd: 'cwd', + args: 'args', + appUserModelId: 'appUserModelId', + icon: 'icon', + iconIndex: 1 + } + + describe('shell.readShortcutLink(shortcutPath)', function () { + it('throws when failed', function () { + assert.throws(function () { + shell.readShortcutLink('not-exist') + }, /Failed to read shortcut link/) + }) + + it('reads all properties of a shortcut', function () { + const shortcut = shell.readShortcutLink(path.join(fixtures, 'assets', 'shortcut.lnk')) + assert.deepEqual(shortcut, shortcutOptions) + }) + }) + + describe('shell.writeShortcutLink(shortcutPath[, operation], options)', function () { + const tmpShortcut = path.join(os.tmpdir(), `${Date.now()}.lnk`) + + afterEach(function () { + fs.unlinkSync(tmpShortcut) + }) + + it('writes the shortcut', function () { + assert.equal(shell.writeShortcutLink(tmpShortcut, {target: 'C:\\'}), true) + assert.equal(fs.existsSync(tmpShortcut), true) + }) + + it('correctly sets the fields', function () { + assert.equal(shell.writeShortcutLink(tmpShortcut, shortcutOptions), true) + assert.deepEqual(shell.readShortcutLink(tmpShortcut), shortcutOptions) + }) + + it('updates the shortcut', function () { + assert.equal(shell.writeShortcutLink(tmpShortcut, 'update', shortcutOptions), false) + assert.equal(shell.writeShortcutLink(tmpShortcut, 'create', shortcutOptions), true) + assert.deepEqual(shell.readShortcutLink(tmpShortcut), shortcutOptions) + const change = {target: 'D:\\'} + assert.equal(shell.writeShortcutLink(tmpShortcut, 'update', change), true) + assert.deepEqual(shell.readShortcutLink(tmpShortcut), Object.assign(shortcutOptions, change)) + }) + + it('replaces the shortcut', function () { + assert.equal(shell.writeShortcutLink(tmpShortcut, 'replace', shortcutOptions), false) + assert.equal(shell.writeShortcutLink(tmpShortcut, 'create', shortcutOptions), true) + assert.deepEqual(shell.readShortcutLink(tmpShortcut), shortcutOptions) + const change = { + target: 'D:\\', + description: 'description2', + cwd: 'cwd2', + args: 'args2', + appUserModelId: 'appUserModelId2', + icon: 'icon2', + iconIndex: 2 + } + assert.equal(shell.writeShortcutLink(tmpShortcut, 'replace', change), true) + assert.deepEqual(shell.readShortcutLink(tmpShortcut), change) + }) + }) +}) diff --git a/spec/fixtures/assets/shortcut.lnk b/spec/fixtures/assets/shortcut.lnk new file mode 100755 index 000000000000..5f325ca733ea Binary files /dev/null and b/spec/fixtures/assets/shortcut.lnk differ