From 016f5646b0fab8b7d68ca9aed7585f4b02e4d65d Mon Sep 17 00:00:00 2001 From: Anthony Tseng Date: Mon, 17 Oct 2016 12:09:00 -0400 Subject: [PATCH] Extend default protocol client 1. Set default protocol for win8 and win10 2. Get/Set default protocol for linux Auditors: @bridiver, @bbondy --- atom/browser/browser_linux.cc | 105 +++++++++++++++++++++++++++++++++- atom/browser/browser_win.cc | 51 +++++++++++++++++ docs/api/app.md | 4 +- 3 files changed, 156 insertions(+), 4 deletions(-) mode change 100644 => 100755 atom/browser/browser_win.cc diff --git a/atom/browser/browser_linux.cc b/atom/browser/browser_linux.cc index b5926f4515..339a1469c9 100644 --- a/atom/browser/browser_linux.cc +++ b/atom/browser/browser_linux.cc @@ -4,14 +4,55 @@ #include "atom/browser/browser.h" +#include +#include #include +#include +#include +#include #include "atom/browser/native_window.h" #include "atom/browser/window_list.h" #include "atom/common/atom_version.h" +#include "base/command_line.h" +#include "base/environment.h" +#include "base/process/launch.h" #include "brightray/common/application_info.h" #include "chrome/browser/ui/libgtk2ui/unity_service.h" +const char kXdgSettings[] = "xdg-settings"; +const char kXdgSettingsDefaultBrowser[] = "default-web-browser"; +const char kXdgSettingsDefaultSchemeHandler[] = "default-url-scheme-handler"; + +// Value returned by xdg-settings if it can't understand our request. +const int EXIT_XDG_SETTINGS_SYNTAX_ERROR = 1; + +// Helper to launch xdg scripts. We don't want them to ask any questions on the +// terminal etc. The function returns true if the utility launches and exits +// cleanly, in which case |exit_code| returns the utility's exit code. +bool LaunchXdgUtility(const std::vector& argv, int* exit_code) { + // xdg-settings internally runs xdg-mime, which uses mv to move newly-created + // files on top of originals after making changes to them. In the event that + // the original files are owned by another user (e.g. root, which can happen + // if they are updated within sudo), mv will prompt the user to confirm if + // standard input is a terminal (otherwise it just does it). So make sure it's + // not, to avoid locking everything up waiting for mv. + *exit_code = EXIT_FAILURE; + int devnull = open("/dev/null", O_RDONLY); + if (devnull < 0) + return false; + base::FileHandleMappingVector no_stdin; + no_stdin.push_back(std::make_pair(devnull, STDIN_FILENO)); + + base::LaunchOptions options; + options.fds_to_remap = &no_stdin; + base::Process process = base::LaunchProcess(argv, options); + close(devnull); + if (!process.IsValid()) + return false; + return process.WaitForExit(exit_code); +} + namespace atom { void Browser::Focus() { @@ -40,14 +81,74 @@ bool Browser::RemoveAsDefaultProtocolClient(const std::string& protocol, return false; } +// We delegate the difficulty of setting the default browser and default url +// scheme handler in Linux desktop environments to an xdg utility, xdg-settings. + +// When calling this script we first try to use the script on PATH. + +// If |protocol| is empty this function sets Brave as the default browser, +// otherwise it sets Brave as the default handler application for |protocol|. bool Browser::SetAsDefaultProtocolClient(const std::string& protocol, mate::Arguments* args) { - return false; + std::unique_ptr env(base::Environment::Create()); + + std::vector argv; + argv.push_back(kXdgSettings); + argv.push_back("set"); + if (protocol.empty()) { + argv.push_back(kXdgSettingsDefaultBrowser); + } else { + argv.push_back(kXdgSettingsDefaultSchemeHandler); + argv.push_back(protocol); + } + // TODO(Anthony): Make it configurable + argv.push_back("brave.desktop"); + + int exit_code; + bool ran_ok = LaunchXdgUtility(argv, &exit_code); + if (ran_ok && exit_code == EXIT_XDG_SETTINGS_SYNTAX_ERROR) { + return false; + } + + return ran_ok && exit_code == EXIT_SUCCESS; } +// If |protocol| is empty this function checks if Brave is the default browser, +// otherwise it checks if Brave is the default handler application for +// |protocol|. bool Browser::IsDefaultProtocolClient(const std::string& protocol, mate::Arguments* args) { - return false; + base::ThreadRestrictions::AssertIOAllowed(); + + std::unique_ptr env(base::Environment::Create()); + + std::vector argv; + argv.push_back(kXdgSettings); + argv.push_back("check"); + if (protocol.empty()) { + argv.push_back(kXdgSettingsDefaultBrowser); + } else { + argv.push_back(kXdgSettingsDefaultSchemeHandler); + argv.push_back(protocol); + } + // TODO(Anthony): Make it configurable + argv.push_back("brave.desktop"); + + std::string reply; + int success_code; + bool ran_ok = base::GetAppOutputWithExitCode(base::CommandLine(argv), &reply, + &success_code); + if (ran_ok && success_code == EXIT_XDG_SETTINGS_SYNTAX_ERROR) { + return false; + } + + if (!ran_ok || success_code != EXIT_SUCCESS) { + // xdg-settings failed: we can't determine or set the default browser. + return false; + } + + // Allow any reply that starts with "yes". + return (reply.find("yes") == 0) ? true : false; } bool Browser::SetBadgeCount(int count) { diff --git a/atom/browser/browser_win.cc b/atom/browser/browser_win.cc old mode 100644 new mode 100755 index c81b9ab3f5..e989895da0 --- a/atom/browser/browser_win.cc +++ b/atom/browser/browser_win.cc @@ -21,6 +21,7 @@ #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/win/registry.h" +#include "base/win/scoped_comptr.h" #include "base/win/win_util.h" #include "base/win/windows_version.h" @@ -171,6 +172,52 @@ bool Browser::RemoveAsDefaultProtocolClient(const std::string& protocol, } } +// Returns the target used as a activate parameter when opening the settings +// pointing to the page that is the most relevant to a user trying to change the +// default handler for |protocol|. +base::string16 GetTargetForDefaultAppsSettings(const wchar_t* protocol) { + static const wchar_t kSystemSettingsDefaultAppsFormat[] = + L"SystemSettings_DefaultApps_%ls"; + + if (base::EqualsCaseInsensitiveASCII(protocol, L"http")) + return base::StringPrintf(kSystemSettingsDefaultAppsFormat, L"Browser"); + if (base::EqualsCaseInsensitiveASCII(protocol, L"mailto")) + return base::StringPrintf(kSystemSettingsDefaultAppsFormat, L"Email"); + return L"SettingsPageAppsDefaultsProtocolView"; +} + +// Launches the Windows 'settings' modern app with the 'default apps' view +// focused. This only works for Windows 8 and Windows 10. The appModelId +// looks arbitrary but it is the same in Win8 and Win10. There is no easy way to +// retrieve the appModelId from the registry. +bool LaunchDefaultAppsSettingsModernDialog(const wchar_t* protocol) { + DCHECK(protocol); + static const wchar_t kControlPanelAppModelId[] = + L"windows.immersivecontrolpanel_cw5n1h2txyewy" + L"!microsoft.windows.immersivecontrolpanel"; + + base::win::ScopedComPtr activator; + HRESULT hr = activator.CreateInstance(CLSID_ApplicationActivationManager); + if (SUCCEEDED(hr)) { + DWORD pid = 0; + CoAllowSetForegroundWindow(activator.get(), nullptr); + hr = activator->ActivateApplication(kControlPanelAppModelId, + L"page=SettingsPageAppsDefaults", + AO_NONE, &pid); + if (SUCCEEDED(hr)) { + hr = activator->ActivateApplication( + kControlPanelAppModelId, + base::StringPrintf(L"page=SettingsPageAppsDefaults&target=%ls", + GetTargetForDefaultAppsSettings(protocol).c_str()) + .c_str(), + AO_NONE, &pid); + } + if (SUCCEEDED(hr)) + return true; + } + return false; +} + bool Browser::SetAsDefaultProtocolClient(const std::string& protocol, mate::Arguments* args) { // HKEY_CLASSES_ROOT @@ -190,6 +237,10 @@ bool Browser::SetAsDefaultProtocolClient(const std::string& protocol, if (protocol.empty()) return false; + if (base::win::GetVersion() >= base::win::VERSION_WIN8) + return LaunchDefaultAppsSettingsModernDialog( + base::UTF8ToUTF16(protocol).c_str()); + base::string16 exe; if (!GetProtocolLaunchPath(args, &exe)) return false; diff --git a/docs/api/app.md b/docs/api/app.md index 98450ee6f0..27c4d21bbb 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -459,7 +459,7 @@ bar, and on macOS you can visit it from dock menu. Clears the recent documents list. -### `app.setAsDefaultProtocolClient(protocol[, path, args])` _macOS_ _Windows_ +### `app.setAsDefaultProtocolClient(protocol[, path, args])` _macOS_ _Windows_ _Brave on Linux_ * `protocol` String - The name of your protocol, without `://`. If you want your app to handle `electron://` links, call this method with `electron` as the @@ -496,7 +496,7 @@ protocol (aka URI scheme). If so, it will remove the app as the default handler. Returns `true` when the call succeeded, otherwise returns `false`. -### `app.isDefaultProtocolClient(protocol[, path, args])` _macOS_ _Windows_ +### `app.isDefaultProtocolClient(protocol[, path, args])` _macOS_ _Windows_ _Brave on Linux_ * `protocol` String - The name of your protocol, without `://`. * `path` String (optional) _Windows_ - Defaults to `process.execPath`