:zap: Add API: SetASDefaultProtocolHandler
authorFelix Rieseberg <felix@felixrieseberg.com>
Mon, 21 Mar 2016 18:24:25 +0000 (11:24 -0700)
committerFelix Rieseberg <felix@felixrieseberg.com>
Thu, 24 Mar 2016 05:12:17 +0000 (22:12 -0700)
This PR adds an API enabling Electron to set itself as the default
protocol handler for a custom porotocl on both oS X and Windows.

For details, please see `docs/app.md`.

Closes #4857

atom/browser/api/atom_api_app.cc
atom/browser/browser.h
atom/browser/browser_linux.cc
atom/browser/browser_mac.mm
atom/browser/browser_win.cc
docs/api/app.md

index d728d9d..00edcf7 100644 (file)
@@ -370,6 +370,8 @@ mate::ObjectTemplateBuilder App::GetObjectTemplateBuilder(
                  base::Bind(&Browser::ClearRecentDocuments, browser))
       .SetMethod("setAppUserModelId",
                  base::Bind(&Browser::SetAppUserModelID, browser))
+      .SetMethod("setAsDefaultProtocolClient",
+                 base::Bind(&Browser::SetAsDefaultProtocolClient, browser))
 #if defined(OS_MACOSX)
       .SetMethod("hide", base::Bind(&Browser::Hide, browser))
       .SetMethod("show", base::Bind(&Browser::Show, browser))
index d976fae..38899c1 100644 (file)
@@ -76,6 +76,9 @@ class Browser : public WindowListObserver {
   // Set the application user model ID.
   void SetAppUserModelID(const base::string16& name);
 
+  // Set as default handler for a protocol.
+  bool SetAsDefaultProtocolClient(const std::string& protocol);
+
 #if defined(OS_MACOSX)
   // Hide the application.
   void Hide();
index 25cb9a0..586ec71 100644 (file)
@@ -34,6 +34,10 @@ void Browser::ClearRecentDocuments() {
 void Browser::SetAppUserModelID(const base::string16& name) {
 }
 
+bool Browser::SetAsDefaultProtocolClient(const std::string& protocol) {
+  return false;
+}
+
 std::string Browser::GetExecutableFileVersion() const {
   return brightray::GetApplicationVersion();
 }
index 5988e66..c61a9bd 100644 (file)
@@ -8,6 +8,7 @@
 #include "atom/browser/mac/atom_application_delegate.h"
 #include "atom/browser/native_window.h"
 #include "atom/browser/window_list.h"
+#include "base/mac/bundle_locations.h"
 #include "base/mac/foundation_util.h"
 #include "base/strings/sys_string_conversions.h"
 #include "brightray/common/application_info.h"
@@ -45,6 +46,21 @@ void Browser::ClearRecentDocuments() {
   [[NSDocumentController sharedDocumentController] clearRecentDocuments:nil];
 }
 
+bool Browser::SetAsDefaultProtocolClient(const std::string& protocol) {
+  if (protocol.empty())
+    return false;
+
+  NSString* identifier = [base::mac::MainBundle() bundleIdentifier];
+  if (!identifier)
+    return false;
+
+  NSString* protocol_ns = [NSString stringWithUTF8String:protocol.c_str()];
+  OSStatus return_code =
+      LSSetDefaultHandlerForURLScheme(base::mac::NSToCFCast(protocol_ns),
+                                      base::mac::NSToCFCast(identifier));
+  return return_code == noErr;
+}
+
 void Browser::SetAppUserModelID(const base::string16& name) {
 }
 
index fdf4bd8..0b9fe54 100644 (file)
@@ -19,6 +19,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/win/win_util.h"
+#include "base/win/registry.h"
 #include "base/win/windows_version.h"
 #include "atom/common/atom_version.h"
 
@@ -125,6 +126,59 @@ void Browser::SetUserTasks(const std::vector<UserTask>& tasks) {
   destinations->CommitList();
 }
 
+bool Browser::SetAsDefaultProtocolClient(const std::string& protocol) {
+  // HKEY_CLASSES_ROOT
+  //    $PROTOCOL
+  //       (Default) = "URL:$NAME"
+  //       URL Protocol = ""
+  //       shell
+  //          open
+  //             command
+  //                (Default) = "$COMMAND" "%1"
+  //
+  // However, the "HKEY_CLASSES_ROOT" key can only be written by the
+  // Administrator user. So, we instead write to "HKEY_CURRENT_USER\
+  // Software\Classes", which is inherited by "HKEY_CLASSES_ROOT"
+  // anyway, and can be written by unprivileged users.
+
+  if (protocol.empty())
+    return false;
+
+  base::FilePath path;
+  if (!PathService::Get(base::FILE_EXE, &path)) {
+    LOG(ERROR) << "Error getting app exe path";
+    return false;
+  }
+
+  // Main Registry Key
+  HKEY root = HKEY_CURRENT_USER;
+  std::string keyPathStr = "Software\\Classes\\" + protocol;
+  std::wstring keyPath = std::wstring(keyPathStr.begin(), keyPathStr.end());
+  std::string urlDeclStr = "URL:" + protocol;
+  std::wstring urlDecl = std::wstring(urlDeclStr.begin(), urlDeclStr.end());
+
+  // Command Key
+  std::string cmdPathStr = keyPathStr + "\\shell\\open\\command";
+  std::wstring cmdPath = std::wstring(cmdPathStr.begin(), cmdPathStr.end());
+
+  // Executable Path
+  std::wstring exePath(path.value());
+  std::wstring exe = L"\"" + exePath + L"\" \"%1\"";
+
+  // Write information to registry
+  base::win::RegKey key(root, keyPath.c_str(), KEY_ALL_ACCESS);
+  if (FAILED(key.WriteValue(L"URL Protocol", L"")) ||
+      FAILED(key.WriteValue(L"", urlDecl.c_str())))
+    return false;
+
+  base::win::RegKey commandKey(root, cmdPath.c_str(), KEY_ALL_ACCESS);
+  if (FAILED(commandKey.WriteValue(L"", exe.c_str())))
+    return false;
+
+  VLOG(1) << "Chrome registered as default handler for " << protocol << ".";
+  return true;
+}
+
 PCWSTR Browser::GetAppUserModelID() {
   if (app_user_model_id_.empty()) {
     SetAppUserModelID(base::ReplaceStringPlaceholders(
index 8935ed8..e9bd106 100644 (file)
@@ -348,6 +348,25 @@ bar, and on OS X you can visit it from dock menu.
 
 Clears the recent documents list.
 
+### `app.setAsDefaultProtocolClient(protocol)` _OS X_ _Windows_
+
+ * `protocol` String - The name of your protocol, without `://`. If you want your
+   app to handle `electron://` links, call this method with `electron` as the
+   parameter.
+   
+This method sets the current executable as the default handler for a protocol
+(aka URI scheme). It allows you to integrate your app deeper into the operating
+system. Once registered, all links with `your-protocol://` will be openend with
+the current executable. The whole link, including protocol, will be passed to
+your application as a parameter.
+**Note:** On OS X, you can only register protocols that have been added to
+your app's `info.plist`, which can not be modified at runtime. You can however
+change the file with a simple text editor or script during build time.
+Please refer to [Apple's documentation][CFBundleURLTypes] for details.
+
+The API uses the Windows Registry and LSSetDefaultHandlerForURLScheme internally.
+
 ### `app.setUserTasks(tasks)` _Windows_
 
 * `tasks` Array - Array of `Task` objects
@@ -541,3 +560,4 @@ Sets the `image` associated with this dock icon.
 [dock-menu]:https://developer.apple.com/library/mac/documentation/Carbon/Conceptual/customizing_docktile/concepts/dockconcepts.html#//apple_ref/doc/uid/TP30000986-CH2-TPXREF103
 [tasks]:http://msdn.microsoft.com/en-us/library/windows/desktop/dd378460(v=vs.85).aspx#tasks
 [app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx
+[CFBundleURLTypes]: https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/TP40009249-102207-TPXREF115
\ No newline at end of file