diff --git a/CHANGELOG.md b/CHANGELOG.md index e6e0ecd5..19ec22bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ - Dev: Added CMake Install Support on Windows. (#4300) - Dev: Changed conan generator to [`CMakeDeps`](https://docs.conan.io/en/latest/reference/conanfile/tools/cmake/cmakedeps.html) and [`CMakeToolchain`](https://docs.conan.io/en/latest/reference/conanfile/tools/cmake/cmaketoolchain.html). See PR for migration notes. (#4335) - Dev: Refactored 7TV EventAPI implementation. (#4342) +- Dev: Don't rely on undocumented registry keys to find the default browser on Windows. (#4362) ## 2.4.0 diff --git a/src/util/IncognitoBrowser.cpp b/src/util/IncognitoBrowser.cpp index 0092d328..074f07c1 100644 --- a/src/util/IncognitoBrowser.cpp +++ b/src/util/IncognitoBrowser.cpp @@ -1,15 +1,17 @@ #include "util/IncognitoBrowser.hpp" +#ifdef USEWINSDK +# include "util/WindowsHelper.hpp" +#endif #include #include -#include #include namespace { using namespace chatterino; -#ifdef Q_OS_WIN +#ifdef USEWINSDK QString injectPrivateSwitch(QString command) { // list of command line switches to turn on private browsing in browsers @@ -47,23 +49,27 @@ QString injectPrivateSwitch(QString command) QString getCommand() { - // get default browser prog id - auto browserId = QSettings("HKEY_CURRENT_" - "USER\\Software\\Microsoft\\Windows\\Shell\\" - "Associations\\UrlAssociatio" - "ns\\http\\UserChoice", - QSettings::NativeFormat) - .value("Progid") - .toString(); + // get default browser start command, by protocol if possible, falling back to extension if not + QString command = + getAssociatedCommand(AssociationQueryType::Protocol, L"http"); - // get default browser start command - auto command = - QSettings("HKEY_CLASSES_ROOT\\" + browserId + "\\shell\\open\\command", - QSettings::NativeFormat) - .value("Default") - .toString(); if (command.isNull()) { + // failed to fetch default browser by protocol, try by file extension instead + command = + getAssociatedCommand(AssociationQueryType::FileExtension, L".html"); + } + + if (command.isNull()) + { + // also try the equivalent .htm extension + command = + getAssociatedCommand(AssociationQueryType::FileExtension, L".htm"); + } + + if (command.isNull()) + { + // failed to find browser command return QString(); } @@ -84,7 +90,7 @@ namespace chatterino { bool supportsIncognitoLinks() { -#ifdef Q_OS_WIN +#ifdef USEWINSDK return !getCommand().isNull(); #else return false; @@ -93,7 +99,7 @@ bool supportsIncognitoLinks() bool openLinkIncognito(const QString &link) { -#ifdef Q_OS_WIN +#ifdef USEWINSDK auto command = getCommand(); // TODO: split command into program path and incognito argument diff --git a/src/util/WindowsHelper.cpp b/src/util/WindowsHelper.cpp index 06fc0dd7..d46d2915 100644 --- a/src/util/WindowsHelper.cpp +++ b/src/util/WindowsHelper.cpp @@ -6,6 +6,9 @@ #ifdef USEWINSDK +# include +# include + namespace chatterino { typedef enum MONITOR_DPI_TYPE { @@ -17,6 +20,8 @@ typedef enum MONITOR_DPI_TYPE { typedef HRESULT(CALLBACK *GetDpiForMonitor_)(HMONITOR, MONITOR_DPI_TYPE, UINT *, UINT *); +typedef HRESULT(CALLBACK *AssocQueryString_)(ASSOCF, ASSOCSTR, LPCWSTR, LPCWSTR, + LPWSTR, DWORD *); boost::optional getWindowDpi(HWND hwnd) { @@ -83,6 +88,67 @@ void setRegisteredForStartup(bool isRegistered) } } +QString getAssociatedCommand(AssociationQueryType queryType, LPCWSTR query) +{ + static HINSTANCE shlwapi = LoadLibrary(L"shlwapi"); + if (shlwapi == nullptr) + { + return QString(); + } + + static auto assocQueryString = + AssocQueryString_(GetProcAddress(shlwapi, "AssocQueryStringW")); + if (assocQueryString == nullptr) + { + return QString(); + } + + // always error out instead of returning a truncated string when the + // buffer is too small - avoids race condition when the user changes their + // default browser between calls to AssocQueryString + ASSOCF flags = ASSOCF_NOTRUNCATE; + + if (queryType == AssociationQueryType::Protocol) + { + // ASSOCF_IS_PROTOCOL was introduced in Windows 8 + if (IsWindows8OrGreater()) + { + flags |= ASSOCF_IS_PROTOCOL; + } + else + { + return QString(); + } + } + + DWORD resultSize = 0; + if (FAILED(assocQueryString(flags, ASSOCSTR_COMMAND, query, nullptr, + nullptr, &resultSize))) + { + return QString(); + } + + if (resultSize <= 1) + { + // resultSize includes the null terminator. if resultSize is 1, the + // returned value would be the empty string. + return QString(); + } + + QString result; + auto buf = new wchar_t[resultSize]; + if (SUCCEEDED(assocQueryString(flags, ASSOCSTR_COMMAND, query, nullptr, buf, + &resultSize))) + { + // QString::fromWCharArray expects the length in characters *not + // including* the null terminator, but AssocQueryStringW calculates + // length including the null terminator + result = QString::fromWCharArray(buf, resultSize - 1); + } + delete[] buf; + return result; +} + } // namespace chatterino #endif diff --git a/src/util/WindowsHelper.hpp b/src/util/WindowsHelper.hpp index f39e6651..478368b8 100644 --- a/src/util/WindowsHelper.hpp +++ b/src/util/WindowsHelper.hpp @@ -7,12 +7,16 @@ namespace chatterino { +enum class AssociationQueryType { Protocol, FileExtension }; + boost::optional getWindowDpi(HWND hwnd); void flushClipboard(); bool isRegisteredForStartup(); void setRegisteredForStartup(bool isRegistered); +QString getAssociatedCommand(AssociationQueryType queryType, LPCWSTR query); + } // namespace chatterino #endif