diff --git a/client/SDL/common/aad/CMakeLists.txt b/client/SDL/common/aad/CMakeLists.txt index 1811a692e..9e45587c2 100644 --- a/client/SDL/common/aad/CMakeLists.txt +++ b/client/SDL/common/aad/CMakeLists.txt @@ -19,33 +19,17 @@ set(WITH_WEBVIEW_DEFAULT OFF) option(WITH_WEBVIEW "Build with WebView support for AAD login popup browser" ${WITH_WEBVIEW_DEFAULT}) if(WITH_WEBVIEW) - option(WITH_WEBVIEW_QT "Build with QtWebEngine support for AAD login broweser popup" OFF) - set(SRCS sdl_webview.hpp webview_impl.hpp sdl_webview.cpp) set(LIBS winpr) - if(WITH_WEBVIEW_QT) - find_package(Qt5 COMPONENTS WebEngineWidgets REQUIRED) + include(FetchContent) - list(APPEND SRCS qt/webview_impl.cpp) + FetchContent_Declare(webview GIT_REPOSITORY https://github.com/akallabeth/webview GIT_TAG navigation-listener SYSTEM) + FetchContent_MakeAvailable(webview) - list(APPEND LIBS Qt5::WebEngineWidgets) - else() - list(APPEND SRCS wrapper/webview.h wrapper/webview_impl.cpp) + list(APPEND SRCS wrapper/webview_impl.cpp) - if(APPLE) - find_library(WEBKIT Webkit REQUIRED) - list(APPEND LIBS ${WEBKIT}) - elseif(NOT WIN32) - find_package(PkgConfig REQUIRED) - pkg_check_modules(WEBVIEW_GTK webkit2gtk-4.1) - if(NOT WEBVIEW_GTK_FOUND) - pkg_check_modules(WEBVIEW_GTK webkit2gtk-4.0 REQUIRED) - endif() - include_directories(SYSTEM ${WEBVIEW_GTK_INCLUDE_DIRS}) - list(APPEND LIBS ${WEBVIEW_GTK_LIBRARIES}) - endif() - endif() + list(APPEND LIBS webview::core) else() set(SRCS dummy.cpp) endif() @@ -59,7 +43,3 @@ set_property(TARGET sdl-common-aad-view PROPERTY FOLDER "Client/Common") target_include_directories(sdl-common-aad-view PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) target_link_libraries(sdl-common-aad-view PRIVATE ${LIBS}) target_compile_definitions(sdl-common-aad-view PUBLIC ${DEFINITIONS}) -if(WITH_WEBVIEW AND NOT WITH_WEBVIEW_QT) - include(WebView2) - target_link_webview2("sdl-common-aad-view") -endif() diff --git a/client/SDL/common/aad/qt/webview_impl.cpp b/client/SDL/common/aad/qt/webview_impl.cpp deleted file mode 100644 index 6347dbc5e..000000000 --- a/client/SDL/common/aad/qt/webview_impl.cpp +++ /dev/null @@ -1,105 +0,0 @@ -/** - * FreeRDP: A Remote Desktop Protocol Implementation - * Popup browser for AAD authentication - * - * Copyright 2023 Isaac Klein - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "../webview_impl.hpp" - -#define TAG CLIENT_TAG("sdl.webview") - -class SchemeHandler : public QWebEngineUrlSchemeHandler -{ - public: - explicit SchemeHandler(QObject* parent = nullptr) : QWebEngineUrlSchemeHandler(parent) - { - } - - void requestStarted(QWebEngineUrlRequestJob* request) override - { - QUrl url = request->requestUrl(); - - int rc = -1; - for (auto& param : url.query().split('&')) - { - QStringList pair = param.split('='); - - if (pair.size() != 2 || pair[0] != QLatin1String("code")) - continue; - - auto qc = pair[1]; - m_code = qc.toStdString(); - rc = 0; - break; - } - qApp->exit(rc); - } - - [[nodiscard]] std::string code() const - { - return m_code; - } - - private: - std::string m_code{}; -}; - -bool webview_impl_run(const std::string& title, const std::string& url, std::string& code) -{ - int argc = 1; - const auto vendor = QLatin1String(FREERDP_VENDOR_STRING); - const auto product = QLatin1String(FREERDP_PRODUCT_STRING); - QWebEngineUrlScheme::registerScheme(QWebEngineUrlScheme("ms-appx-web")); - - std::string wtitle = title; - char* argv[] = { wtitle.data() }; - QCoreApplication::setOrganizationName(vendor); - QCoreApplication::setApplicationName(product); - QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); - QApplication app(argc, argv); - - SchemeHandler handler; - QWebEngineProfile::defaultProfile()->installUrlSchemeHandler("ms-appx-web", &handler); - - QWebEngineView webview; - webview.load(QUrl(QString::fromStdString(url))); - webview.show(); - - if (app.exec() != 0) - return false; - - auto val = handler.code(); - if (val.empty()) - return false; - code = val; - - return !code.empty(); -} diff --git a/client/SDL/common/aad/sdl_webview.cpp b/client/SDL/common/aad/sdl_webview.cpp index 4b2b7289b..a2de52ffa 100644 --- a/client/SDL/common/aad/sdl_webview.cpp +++ b/client/SDL/common/aad/sdl_webview.cpp @@ -61,14 +61,19 @@ static BOOL sdl_webview_get_rdsaad_access_token(freerdp* instance, const char* s WINPR_ASSERT(token); WINPR_UNUSED(instance); - WINPR_UNUSED(instance->context); - auto client_id = from_settings(instance->context->settings, FreeRDP_GatewayAvdClientID); + auto context = instance->context; + WINPR_UNUSED(context); + + auto settings = context->settings; + WINPR_ASSERT(settings); + + auto client_id = from_settings(settings, FreeRDP_GatewayAvdClientID); std::string redirect_uri = "ms-appx-web%3a%2f%2fMicrosoft.AAD.BrokerPlugin%2f" + client_id; *token = nullptr; - auto ep = from_aad_wellknown(instance->context, AAD_WELLKNOWN_authorization_endpoint); + auto ep = from_aad_wellknown(context, AAD_WELLKNOWN_authorization_endpoint); auto url = ep + "?client_id=" + client_id + "&response_type=code&scope=" + scope + "&redirect_uri=" + redirect_uri; diff --git a/client/SDL/common/aad/wrapper/webview.h b/client/SDL/common/aad/wrapper/webview.h deleted file mode 100644 index f348ef012..000000000 --- a/client/SDL/common/aad/wrapper/webview.h +++ /dev/null @@ -1,2811 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017 Serge Zaitsev - * Copyright (c) 2022 Steffen André Langnes - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#ifndef WEBVIEW_H -#define WEBVIEW_H - -#ifndef WEBVIEW_API -#define WEBVIEW_API extern -#endif - -#ifndef WEBVIEW_VERSION_MAJOR -// The current library major version. -#define WEBVIEW_VERSION_MAJOR 0 -#endif - -#ifndef WEBVIEW_VERSION_MINOR -// The current library minor version. -#define WEBVIEW_VERSION_MINOR 10 -#endif - -#ifndef WEBVIEW_VERSION_PATCH -// The current library patch version. -#define WEBVIEW_VERSION_PATCH 0 -#endif - -#ifndef WEBVIEW_VERSION_PRE_RELEASE -// SemVer 2.0.0 pre-release labels prefixed with "-". -#define WEBVIEW_VERSION_PRE_RELEASE "" -#endif - -#ifndef WEBVIEW_VERSION_BUILD_METADATA -// SemVer 2.0.0 build metadata prefixed with "+". -#define WEBVIEW_VERSION_BUILD_METADATA "" -#endif - -// Utility macro for stringifying a macro argument. -#define WEBVIEW_STRINGIFY(x) #x - -// Utility macro for stringifying the result of a macro argument expansion. -#define WEBVIEW_EXPAND_AND_STRINGIFY(x) WEBVIEW_STRINGIFY(x) - -// SemVer 2.0.0 version number in MAJOR.MINOR.PATCH format. -#define WEBVIEW_VERSION_NUMBER \ - WEBVIEW_EXPAND_AND_STRINGIFY(WEBVIEW_VERSION_MAJOR) \ - "." WEBVIEW_EXPAND_AND_STRINGIFY(WEBVIEW_VERSION_MINOR) "." WEBVIEW_EXPAND_AND_STRINGIFY( \ - WEBVIEW_VERSION_PATCH) - -// Holds the elements of a MAJOR.MINOR.PATCH version number. -typedef struct -{ - // Major version. - unsigned int major; - // Minor version. - unsigned int minor; - // Patch version. - unsigned int patch; -} webview_version_t; - -// Holds the library's version information. -typedef struct -{ - // The elements of the version number. - webview_version_t version; - // SemVer 2.0.0 version number in MAJOR.MINOR.PATCH format. - char version_number[32]; - // SemVer 2.0.0 pre-release labels prefixed with "-" if specified, otherwise - // an empty string. - char pre_release[48]; - // SemVer 2.0.0 build metadata prefixed with "+", otherwise an empty string. - char build_metadata[48]; -} webview_version_info_t; - -#ifdef __cplusplus -extern "C" -{ -#endif - - typedef void* webview_t; - - // Creates a new webview instance. If debug is non-zero - developer tools will - // be enabled (if the platform supports them). Window parameter can be a - // pointer to the native window handle. If it's non-null - then child WebView - // is embedded into the given parent window. Otherwise a new window is created. - // Depending on the platform, a GtkWindow, NSWindow or HWND pointer can be - // passed here. Returns null on failure. Creation can fail for various reasons - // such as when required runtime dependencies are missing or when window creation - // fails. - WEBVIEW_API webview_t webview_create(int debug, void* window); - - // Destroys a webview and closes the native window. - WEBVIEW_API void webview_destroy(webview_t w); - - // Runs the main loop until it's terminated. After this function exits - you - // must destroy the webview. - WEBVIEW_API void webview_run(webview_t w); - - // Stops the main loop. It is safe to call this function from another other - // background thread. - WEBVIEW_API void webview_terminate(webview_t w); - - // Posts a function to be executed on the main thread. You normally do not need - // to call this function, unless you want to tweak the native window. - WEBVIEW_API void webview_dispatch(webview_t w, void (*fn)(webview_t w, void* arg), void* arg); - - // Returns a native window handle pointer. When using GTK backend the pointer - // is GtkWindow pointer, when using Cocoa backend the pointer is NSWindow - // pointer, when using Win32 backend the pointer is HWND pointer. - WEBVIEW_API void* webview_get_window(webview_t w); - - // Updates the title of the native window. Must be called from the UI thread. - WEBVIEW_API void webview_set_title(webview_t w, const char* title); - -// Window size hints -#define WEBVIEW_HINT_NONE 0 // Width and height are default size -#define WEBVIEW_HINT_MIN 1 // Width and height are minimum bounds -#define WEBVIEW_HINT_MAX 2 // Width and height are maximum bounds -#define WEBVIEW_HINT_FIXED 3 // Window size can not be changed by a user - // Updates native window size. See WEBVIEW_HINT constants. - WEBVIEW_API void webview_set_size(webview_t w, int width, int height, int hints); - - // Navigates webview to the given URL. URL may be a properly encoded data URI. - // Examples: - // webview_navigate(w, "https://github.com/webview/webview"); - // webview_navigate(w, "data:text/html,%3Ch1%3EHello%3C%2Fh1%3E"); - // webview_navigate(w, "data:text/html;base64,PGgxPkhlbGxvPC9oMT4="); - WEBVIEW_API void webview_navigate(webview_t w, const char* url); - - // Set webview HTML directly. - // Example: webview_set_html(w, "

Hello

"); - WEBVIEW_API void webview_set_html(webview_t w, const char* html); - - // Injects JavaScript code at the initialization of the new page. Every time - // the webview will open a the new page - this initialization code will be - // executed. It is guaranteed that code is executed before window.onload. - WEBVIEW_API void webview_init(webview_t w, const char* js); - - // Evaluates arbitrary JavaScript code. Evaluation happens asynchronously, also - // the result of the expression is ignored. Use RPC bindings if you want to - // receive notifications about the results of the evaluation. - WEBVIEW_API void webview_eval(webview_t w, const char* js); - - // Binds a native C callback so that it will appear under the given name as a - // global JavaScript function. Internally it uses webview_init(). Callback - // receives a request string and a user-provided argument pointer. Request - // string is a JSON array of all the arguments passed to the JavaScript - // function. - WEBVIEW_API void webview_bind(webview_t w, const char* name, - void (*fn)(const char* seq, const char* req, void* arg), - void* arg); - - // Removes a native C callback that was previously set by webview_bind. - WEBVIEW_API void webview_unbind(webview_t w, const char* name); - - // Allows to return a value from the native binding. Original request pointer - // must be provided to help internal RPC engine match requests with responses. - // If status is zero - result is expected to be a valid JSON result value. - // If status is not zero - result is an error JSON object. - WEBVIEW_API void webview_return(webview_t w, const char* seq, int status, const char* result); - - // Get the library's version information. - // @since 0.10 - WEBVIEW_API const webview_version_info_t* webview_version(); - -#ifdef __cplusplus -} - -#ifndef WEBVIEW_HEADER - -#if !defined(WEBVIEW_GTK) && !defined(WEBVIEW_COCOA) && !defined(WEBVIEW_EDGE) -#if defined(__APPLE__) -#define WEBVIEW_COCOA -#elif defined(__unix__) -#define WEBVIEW_GTK -#elif defined(_WIN32) -#define WEBVIEW_EDGE -#else -#error "please, specify webview backend" -#endif -#endif - -#ifndef WEBVIEW_DEPRECATED -#if __cplusplus >= 201402L -#define WEBVIEW_DEPRECATED(reason) [[deprecated(reason)]] -#elif defined(_MSC_VER) -#define WEBVIEW_DEPRECATED(reason) __declspec(deprecated(reason)) -#else -#define WEBVIEW_DEPRECATED(reason) __attribute__((deprecated(reason))) -#endif -#endif - -#ifndef WEBVIEW_DEPRECATED_PRIVATE -#define WEBVIEW_DEPRECATED_PRIVATE WEBVIEW_DEPRECATED("Private API should not be used") -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace webview -{ - - using dispatch_fn_t = std::function; - - namespace detail - { - - // The library's version information. - constexpr const webview_version_info_t library_version_info{ - { WEBVIEW_VERSION_MAJOR, WEBVIEW_VERSION_MINOR, WEBVIEW_VERSION_PATCH }, - WEBVIEW_VERSION_NUMBER, - WEBVIEW_VERSION_PRE_RELEASE, - WEBVIEW_VERSION_BUILD_METADATA - }; - - inline int json_parse_c(const char* s, size_t sz, const char* key, size_t keysz, - const char** value, size_t* valuesz) - { - enum - { - JSON_STATE_VALUE, - JSON_STATE_LITERAL, - JSON_STATE_STRING, - JSON_STATE_ESCAPE, - JSON_STATE_UTF8 - } state = JSON_STATE_VALUE; - const char* k = nullptr; - int index = 1; - int depth = 0; - int utf8_bytes = 0; - - *value = nullptr; - *valuesz = 0; - - if (key == nullptr) - { - index = static_cast(keysz); - if (index < 0) - { - return -1; - } - keysz = 0; - } - - for (; sz > 0; s++, sz--) - { - enum - { - JSON_ACTION_NONE, - JSON_ACTION_START, - JSON_ACTION_END, - JSON_ACTION_START_STRUCT, - JSON_ACTION_END_STRUCT - } action = JSON_ACTION_NONE; - auto c = static_cast(*s); - switch (state) - { - case JSON_STATE_VALUE: - if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',' || c == ':') - { - continue; - } - else if (c == '"') - { - action = JSON_ACTION_START; - state = JSON_STATE_STRING; - } - else if (c == '{' || c == '[') - { - action = JSON_ACTION_START_STRUCT; - } - else if (c == '}' || c == ']') - { - action = JSON_ACTION_END_STRUCT; - } - else if (c == 't' || c == 'f' || c == 'n' || c == '-' || - (c >= '0' && c <= '9')) - { - action = JSON_ACTION_START; - state = JSON_STATE_LITERAL; - } - else - { - return -1; - } - break; - case JSON_STATE_LITERAL: - if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',' || - c == ']' || c == '}' || c == ':') - { - state = JSON_STATE_VALUE; - s--; - sz++; - action = JSON_ACTION_END; - } - else if (c < 32 || c > 126) - { - return -1; - } // fallthrough - case JSON_STATE_STRING: - if (c < 32 || (c > 126 && c < 192)) - { - return -1; - } - else if (c == '"') - { - action = JSON_ACTION_END; - state = JSON_STATE_VALUE; - } - else if (c == '\\') - { - state = JSON_STATE_ESCAPE; - } - else if (c >= 192 && c < 224) - { - utf8_bytes = 1; - state = JSON_STATE_UTF8; - } - else if (c >= 224 && c < 240) - { - utf8_bytes = 2; - state = JSON_STATE_UTF8; - } - else if (c >= 240 && c < 247) - { - utf8_bytes = 3; - state = JSON_STATE_UTF8; - } - else if (c >= 128 && c < 192) - { - return -1; - } - break; - case JSON_STATE_ESCAPE: - if (c == '"' || c == '\\' || c == '/' || c == 'b' || c == 'f' || c == 'n' || - c == 'r' || c == 't' || c == 'u') - { - state = JSON_STATE_STRING; - } - else - { - return -1; - } - break; - case JSON_STATE_UTF8: - if (c < 128 || c > 191) - { - return -1; - } - utf8_bytes--; - if (utf8_bytes == 0) - { - state = JSON_STATE_STRING; - } - break; - default: - return -1; - } - - if (action == JSON_ACTION_END_STRUCT) - { - depth--; - } - - if (depth == 1) - { - if (action == JSON_ACTION_START || action == JSON_ACTION_START_STRUCT) - { - if (index == 0) - { - *value = s; - } - else if (keysz > 0 && index == 1) - { - k = s; - } - else - { - index--; - } - } - else if (action == JSON_ACTION_END || action == JSON_ACTION_END_STRUCT) - { - if (*value != nullptr && index == 0) - { - *valuesz = (size_t)(s + 1 - *value); - return 0; - } - else if (keysz > 0 && k != nullptr) - { - if (keysz == (size_t)(s - k - 1) && memcmp(key, k + 1, keysz) == 0) - { - index = 0; - } - else - { - index = 2; - } - k = nullptr; - } - } - } - - if (action == JSON_ACTION_START_STRUCT) - { - depth++; - } - } - return -1; - } - - inline std::string json_escape(const std::string& s) - { - // TODO: implement - return '"' + s + '"'; - } - - inline int json_unescape(const char* s, size_t n, char* out) - { - int r = 0; - if (*s++ != '"') - { - return -1; - } - while (n > 2) - { - char c = *s; - if (c == '\\') - { - s++; - n--; - switch (*s) - { - case 'b': - c = '\b'; - break; - case 'f': - c = '\f'; - break; - case 'n': - c = '\n'; - break; - case 'r': - c = '\r'; - break; - case 't': - c = '\t'; - break; - case '\\': - c = '\\'; - break; - case '/': - c = '/'; - break; - case '\"': - c = '\"'; - break; - default: // TODO: support unicode decoding - return -1; - } - } - if (out != nullptr) - { - *out++ = c; - } - s++; - n--; - r++; - } - if (*s != '"') - { - return -1; - } - if (out != nullptr) - { - *out = '\0'; - } - return r; - } - - inline std::string json_parse(const std::string& s, const std::string& key, const int index) - { - const char* value = nullptr; - size_t value_sz = 0; - if (key.empty()) - { - json_parse_c(s.c_str(), s.length(), nullptr, index, &value, &value_sz); - } - else - { - json_parse_c(s.c_str(), s.length(), key.c_str(), key.length(), &value, &value_sz); - } - if (value != nullptr) - { - if (value[0] != '"') - { - return { value, value_sz }; - } - int n = json_unescape(value, value_sz, nullptr); - if (n > 0) - { - char* decoded = new char[1ull + n]; - json_unescape(value, value_sz, decoded); - std::string result(decoded, n); - delete[] decoded; - return result; - } - } - return ""; - } - - } // namespace detail - - WEBVIEW_DEPRECATED_PRIVATE - inline int json_parse_c(const char* s, size_t sz, const char* key, size_t keysz, - const char** value, size_t* valuesz) - { - return detail::json_parse_c(s, sz, key, keysz, value, valuesz); - } - - WEBVIEW_DEPRECATED_PRIVATE - inline std::string json_escape(const std::string& s) - { - return detail::json_escape(s); - } - - WEBVIEW_DEPRECATED_PRIVATE - inline int json_unescape(const char* s, size_t n, char* out) - { - return detail::json_unescape(s, n, out); - } - - WEBVIEW_DEPRECATED_PRIVATE - inline std::string json_parse(const std::string& s, const std::string& key, const int index) - { - return detail::json_parse(s, key, index); - } - -} // namespace webview - -#if defined(WEBVIEW_GTK) -// -// ==================================================================== -// -// This implementation uses webkit2gtk backend. It requires gtk+3.0 and -// webkit2gtk-4.0 libraries. Proper compiler flags can be retrieved via: -// -// pkg-config --cflags --libs gtk+-3.0 webkit2gtk-4.0 -// -// ==================================================================== -// -#include -#include -#include - -namespace webview -{ - namespace detail - { - - class gtk_webkit_engine - { - public: - gtk_webkit_engine(bool debug, void* window) : m_window(static_cast(window)) - { - if (gtk_init_check(nullptr, nullptr) == FALSE) - { - return; - } - m_window = static_cast(window); - if (m_window == nullptr) - { - m_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - } - g_signal_connect(G_OBJECT(m_window), "destroy", - G_CALLBACK(+[](GtkWidget*, gpointer arg) - { static_cast(arg)->terminate(); }), - this); - // Initialize webview widget - m_webview = webkit_web_view_new(); - WebKitUserContentManager* manager = - webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(m_webview)); - g_signal_connect(manager, "script-message-received::external", - G_CALLBACK(+[](WebKitUserContentManager*, - WebKitJavascriptResult* r, gpointer arg) - { - auto* w = static_cast(arg); - char* s = get_string_from_js_result(r); - w->on_message(s); - g_free(s); - }), - this); - webkit_user_content_manager_register_script_message_handler(manager, "external"); - init("window.external={invoke:function(s){window.webkit.messageHandlers." - "external.postMessage(s);}}"); - - gtk_container_add(GTK_CONTAINER(m_window), GTK_WIDGET(m_webview)); - gtk_widget_grab_focus(GTK_WIDGET(m_webview)); - - WebKitSettings* settings = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(m_webview)); - webkit_settings_set_javascript_can_access_clipboard(settings, true); - if (debug) - { - webkit_settings_set_enable_write_console_messages_to_stdout(settings, true); - webkit_settings_set_enable_developer_extras(settings, true); - } - - gtk_widget_show_all(m_window); - } - virtual ~gtk_webkit_engine() = default; - void* window() - { - return (void*)m_window; - } - void run() - { - gtk_main(); - } - void terminate() - { - gtk_main_quit(); - } - void dispatch(std::function f) - { - g_idle_add_full(G_PRIORITY_HIGH_IDLE, (GSourceFunc)([](void* f) -> int { - (*static_cast(f))(); - return G_SOURCE_REMOVE; - }), - new std::function(f), - [](void* f) { delete static_cast(f); }); - } - - void set_title(const std::string& title) - { - gtk_window_set_title(GTK_WINDOW(m_window), title.c_str()); - } - - void set_size(int width, int height, int hints) - { - gtk_window_set_resizable(GTK_WINDOW(m_window), hints != WEBVIEW_HINT_FIXED); - if (hints == WEBVIEW_HINT_NONE) - { - gtk_window_resize(GTK_WINDOW(m_window), width, height); - } - else if (hints == WEBVIEW_HINT_FIXED) - { - gtk_widget_set_size_request(m_window, width, height); - } - else - { - GdkGeometry g; - g.min_width = g.max_width = width; - g.min_height = g.max_height = height; - GdkWindowHints h = - (hints == WEBVIEW_HINT_MIN ? GDK_HINT_MIN_SIZE : GDK_HINT_MAX_SIZE); - // This defines either MIN_SIZE, or MAX_SIZE, but not both: - gtk_window_set_geometry_hints(GTK_WINDOW(m_window), nullptr, &g, h); - } - } - - void navigate(const std::string& url) - { - webkit_web_view_load_uri(WEBKIT_WEB_VIEW(m_webview), url.c_str()); - } - - void add_navigate_listener(std::function callback, - void* arg) - { - g_signal_connect(WEBKIT_WEB_VIEW(m_webview), "load-changed", - G_CALLBACK(on_load_changed), this); - navigateCallbackArg = arg; - navigateCallback = std::move(callback); - } - - void add_scheme_handler(const std::string& scheme, - std::function callback, - void* arg) - { - auto view = WEBKIT_WEB_VIEW(m_webview); - auto context = webkit_web_view_get_context(view); - - scheme_handlers.insert({ scheme, { .arg = arg, .fkt = callback } }); - webkit_web_context_register_uri_scheme(context, scheme.c_str(), scheme_handler, - static_cast(this), nullptr); - } - - void set_html(const std::string& html) - { - webkit_web_view_load_html(WEBKIT_WEB_VIEW(m_webview), html.c_str(), nullptr); - } - - void init(const std::string& js) - { - WebKitUserContentManager* manager = - webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(m_webview)); - webkit_user_content_manager_add_script( - manager, webkit_user_script_new( - js.c_str(), WEBKIT_USER_CONTENT_INJECT_TOP_FRAME, - WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START, nullptr, nullptr)); - } - - void eval(const std::string& js) - { - webkit_web_view_run_javascript(WEBKIT_WEB_VIEW(m_webview), js.c_str(), nullptr, - nullptr, nullptr); - } - - private: - virtual void on_message(const std::string& msg) = 0; - - struct handler_t - { - void* arg; - std::function fkt; - }; - - std::map scheme_handlers; - - void scheme_handler_call(const std::string& scheme, const std::string& url) - { - auto handler = scheme_handlers.find(scheme); - if (handler != scheme_handlers.end()) - { - const auto& arg = handler->second; - arg.fkt(url, arg.arg); - } - } - - static void scheme_handler(WebKitURISchemeRequest* request, gpointer user_data) - { - auto _this = static_cast(user_data); - - auto scheme = webkit_uri_scheme_request_get_scheme(request); - auto uri = webkit_uri_scheme_request_get_uri(request); - _this->scheme_handler_call(scheme, uri); - } - - static char* get_string_from_js_result(WebKitJavascriptResult* r) - { - char* s = nullptr; -#if WEBKIT_MAJOR_VERSION >= 2 && WEBKIT_MINOR_VERSION >= 22 - JSCValue* value = webkit_javascript_result_get_js_value(r); - s = jsc_value_to_string(value); -#else - JSGlobalContextRef ctx = webkit_javascript_result_get_global_context(r); - JSValueRef value = webkit_javascript_result_get_value(r); - JSStringRef js = JSValueToStringCopy(ctx, value, nullptr); - size_t n = JSStringGetMaximumUTF8CStringSize(js); - s = g_new(char, n); - JSStringGetUTF8CString(js, s, n); - JSStringRelease(js); -#endif - return s; - } - - GtkWidget* m_window; - GtkWidget* m_webview; - - void* navigateCallbackArg = nullptr; - std::function navigateCallback = nullptr; - - static void on_load_changed(WebKitWebView* web_view, WebKitLoadEvent load_event, - gpointer arg) - { - if (load_event == WEBKIT_LOAD_FINISHED) - { - auto inst = static_cast(arg); - inst->navigateCallback(webkit_web_view_get_uri(web_view), - inst->navigateCallbackArg); - } - } - }; - - } // namespace detail - - using browser_engine = detail::gtk_webkit_engine; - -} // namespace webview - -#elif defined(WEBVIEW_COCOA) - -// -// ==================================================================== -// -// This implementation uses Cocoa WKWebView backend on macOS. It is -// written using ObjC runtime and uses WKWebView class as a browser runtime. -// You should pass "-framework Webkit" flag to the compiler. -// -// ==================================================================== -// - -#include -#include -#include - -namespace webview -{ - namespace detail - { - namespace objc - { - - // A convenient template function for unconditionally casting the specified - // C-like function into a function that can be called with the given return - // type and arguments. Caller takes full responsibility for ensuring that - // the function call is valid. It is assumed that the function will not - // throw exceptions. - template - Result invoke(Callable callable, Args... args) noexcept - { - return reinterpret_cast(callable)(args...); - } - - // Calls objc_msgSend. - template Result msg_send(Args... args) noexcept - { - return invoke(objc_msgSend, args...); - } - - } // namespace objc - - enum NSBackingStoreType : NSUInteger - { - NSBackingStoreBuffered = 2 - }; - - enum NSWindowStyleMask : NSUInteger - { - NSWindowStyleMaskTitled = 1, - NSWindowStyleMaskClosable = 2, - NSWindowStyleMaskMiniaturizable = 4, - NSWindowStyleMaskResizable = 8 - }; - - enum NSApplicationActivationPolicy : NSInteger - { - NSApplicationActivationPolicyRegular = 0 - }; - - enum WKUserScriptInjectionTime : NSInteger - { - WKUserScriptInjectionTimeAtDocumentStart = 0 - }; - - enum NSModalResponse : NSInteger - { - NSModalResponseOK = 1 - }; - - // Convenient conversion of string literals. - inline id operator"" _cls(const char* s, std::size_t) - { - return (id)objc_getClass(s); - } - inline SEL operator"" _sel(const char* s, std::size_t) - { - return sel_registerName(s); - } - inline id operator"" _str(const char* s, std::size_t) - { - return objc::msg_send("NSString"_cls, "stringWithUTF8String:"_sel, s); - } - - class cocoa_wkwebview_engine - { - public: - cocoa_wkwebview_engine(bool debug, void* window) - : m_debug{ debug }, m_parent_window{ window } - { - auto app = get_shared_application(); - auto delegate = create_app_delegate(); - objc_setAssociatedObject(delegate, "webview", (id)this, OBJC_ASSOCIATION_ASSIGN); - objc::msg_send(app, "setDelegate:"_sel, delegate); - - // See comments related to application lifecycle in create_app_delegate(). - if (window) - { - on_application_did_finish_launching(delegate, app); - } - else - { - // Start the main run loop so that the app delegate gets the - // NSApplicationDidFinishLaunchingNotification notification after the run - // loop has started in order to perform further initialization. - // We need to return from this constructor so this run loop is only - // temporary. - objc::msg_send(app, "run"_sel); - } - } - virtual ~cocoa_wkwebview_engine() = default; - void* window() - { - return (void*)m_window; - } - void terminate() - { - auto app = get_shared_application(); - objc::msg_send(app, "terminate:"_sel, nullptr); - } - void run() - { - auto app = get_shared_application(); - objc::msg_send(app, "run"_sel); - } - void dispatch(std::function f) - { - dispatch_async_f(dispatch_get_main_queue(), new dispatch_fn_t(f), - (dispatch_function_t)([](void* arg) { - auto f = static_cast(arg); - (*f)(); - delete f; - })); - } - void set_title(const std::string& title) - { - objc::msg_send( - m_window, "setTitle:"_sel, - objc::msg_send("NSString"_cls, "stringWithUTF8String:"_sel, title.c_str())); - } - void set_size(int width, int height, int hints) - { - auto style = static_cast(NSWindowStyleMaskTitled | - NSWindowStyleMaskClosable | - NSWindowStyleMaskMiniaturizable); - if (hints != WEBVIEW_HINT_FIXED) - { - style = static_cast(style | NSWindowStyleMaskResizable); - } - objc::msg_send(m_window, "setStyleMask:"_sel, style); - - if (hints == WEBVIEW_HINT_MIN) - { - objc::msg_send(m_window, "setContentMinSize:"_sel, - CGSizeMake(width, height)); - } - else if (hints == WEBVIEW_HINT_MAX) - { - objc::msg_send(m_window, "setContentMaxSize:"_sel, - CGSizeMake(width, height)); - } - else - { - objc::msg_send(m_window, "setFrame:display:animate:"_sel, - CGRectMake(0, 0, width, height), YES, NO); - } - objc::msg_send(m_window, "center"_sel); - } - void navigate(const std::string& url) - { - auto nsurl = objc::msg_send( - "NSURL"_cls, "URLWithString:"_sel, - objc::msg_send("NSString"_cls, "stringWithUTF8String:"_sel, url.c_str())); - - objc::msg_send( - m_webview, "loadRequest:"_sel, - objc::msg_send("NSURLRequest"_cls, "requestWithURL:"_sel, nsurl)); - } - - void add_navigate_listener(std::function callback, - void* arg) - { - m_navigateCallback = callback; - m_navigateCallbackArg = arg; - } - - void add_scheme_handler(const std::string& scheme, - std::function callback, - void* arg) - { - // TODO: Implement - } - - void set_html(const std::string& html) - { - objc::msg_send( - m_webview, "loadHTMLString:baseURL:"_sel, - objc::msg_send("NSString"_cls, "stringWithUTF8String:"_sel, html.c_str()), - nullptr); - } - void init(const std::string& js) - { - // Equivalent Obj-C: - // [m_manager addUserScript:[[WKUserScript alloc] initWithSource:[NSString - // stringWithUTF8String:js.c_str()] - // injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES]] - objc::msg_send( - m_manager, "addUserScript:"_sel, - objc::msg_send( - objc::msg_send("WKUserScript"_cls, "alloc"_sel), - "initWithSource:injectionTime:forMainFrameOnly:"_sel, - objc::msg_send("NSString"_cls, "stringWithUTF8String:"_sel, js.c_str()), - WKUserScriptInjectionTimeAtDocumentStart, YES)); - } - void eval(const std::string& js) - { - objc::msg_send( - m_webview, "evaluateJavaScript:completionHandler:"_sel, - objc::msg_send("NSString"_cls, "stringWithUTF8String:"_sel, js.c_str()), - nullptr); - } - - private: - virtual void on_message(const std::string& msg) = 0; - id create_app_delegate() - { - // Note: Avoid registering the class name "AppDelegate" as it is the - // default name in projects created with Xcode, and using the same name - // causes objc_registerClassPair to crash. - auto cls = - objc_allocateClassPair((Class) "NSResponder"_cls, "WebviewAppDelegate", 0); - class_addProtocol(cls, objc_getProtocol("NSTouchBarProvider")); - class_addMethod(cls, "applicationShouldTerminateAfterLastWindowClosed:"_sel, - (IMP)(+[](id, SEL, id) -> BOOL { return 1; }), "c@:@"); - // If the library was not initialized with an existing window then the user - // is likely managing the application lifecycle and we would not get the - // "applicationDidFinishLaunching:" message and therefore do not need to - // add this method. - if (!m_parent_window) - { - class_addMethod(cls, "applicationDidFinishLaunching:"_sel, - (IMP)(+[](id self, SEL, id notification) - { - auto app = - objc::msg_send(notification, "object"_sel); - auto w = get_associated_webview(self); - w->on_application_did_finish_launching(self, app); - }), - "v@:@"); - } - objc_registerClassPair(cls); - return objc::msg_send((id)cls, "new"_sel); - } - id create_script_message_handler() - { - auto cls = objc_allocateClassPair((Class) "NSResponder"_cls, - "WebkitScriptMessageHandler", 0); - class_addProtocol(cls, objc_getProtocol("WKScriptMessageHandler")); - class_addMethod( - cls, "userContentController:didReceiveScriptMessage:"_sel, - (IMP)(+[](id self, SEL, id, id msg) - { - auto w = get_associated_webview(self); - w->on_message(objc::msg_send( - objc::msg_send(msg, "body"_sel), "UTF8String"_sel)); - }), - "v@:@@"); - objc_registerClassPair(cls); - auto instance = objc::msg_send((id)cls, "new"_sel); - objc_setAssociatedObject(instance, "webview", (id)this, OBJC_ASSOCIATION_ASSIGN); - return instance; - } - static id create_webkit_ui_delegate() - { - auto cls = objc_allocateClassPair((Class) "NSObject"_cls, "WebkitUIDelegate", 0); - class_addProtocol(cls, objc_getProtocol("WKUIDelegate")); - class_addMethod( - cls, - "webView:runOpenPanelWithParameters:initiatedByFrame:completionHandler:"_sel, - (IMP)(+[](id, SEL, id, id parameters, id, id completion_handler) - { - auto allows_multiple_selection = - objc::msg_send(parameters, "allowsMultipleSelection"_sel); - auto allows_directories = - objc::msg_send(parameters, "allowsDirectories"_sel); - - // Show a panel for selecting files. - auto panel = objc::msg_send("NSOpenPanel"_cls, "openPanel"_sel); - objc::msg_send(panel, "setCanChooseFiles:"_sel, YES); - objc::msg_send(panel, "setCanChooseDirectories:"_sel, - allows_directories); - objc::msg_send(panel, "setAllowsMultipleSelection:"_sel, - allows_multiple_selection); - auto modal_response = - objc::msg_send(panel, "runModal"_sel); - - // Get the URLs for the selected files. If the modal was canceled - // then we pass null to the completion handler to signify - // cancellation. - id urls = modal_response == NSModalResponseOK - ? objc::msg_send(panel, "URLs"_sel) - : nullptr; - - // Invoke the completion handler block. - auto sig = objc::msg_send("NSMethodSignature"_cls, - "signatureWithObjCTypes:"_sel, "v@?@"); - auto invocation = objc::msg_send( - "NSInvocation"_cls, "invocationWithMethodSignature:"_sel, sig); - objc::msg_send(invocation, "setTarget:"_sel, - completion_handler); - objc::msg_send(invocation, "setArgument:atIndex:"_sel, &urls, - 1); - objc::msg_send(invocation, "invoke"_sel); - }), - "v@:@@@@"); - objc_registerClassPair(cls); - return objc::msg_send((id)cls, "new"_sel); - } - id create_webkit_navigation_delegate() - { - auto cls = - objc_allocateClassPair((Class) "NSObject"_cls, "WebkitNavigationDelegate", 0); - class_addProtocol(cls, objc_getProtocol("WKNavigationDelegate")); - class_addMethod(cls, "webView:didFinishNavigation:"_sel, - (IMP)(+[](id delegate, SEL sel, id webview, id navigation) - { - auto w = get_associated_webview(delegate); - auto url = objc::msg_send(webview, "URL"_sel); - auto nstr = objc::msg_send(url, "absoluteString"_sel); - auto str = objc::msg_send(nstr, "UTF8String"_sel); - w->m_navigateCallback(str, w->m_navigateCallbackArg); - }), - "v@:@"); - objc_registerClassPair(cls); - auto instance = objc::msg_send((id)cls, "new"_sel); - objc_setAssociatedObject(instance, "webview", (id)this, OBJC_ASSOCIATION_ASSIGN); - return instance; - } - static id get_shared_application() - { - return objc::msg_send("NSApplication"_cls, "sharedApplication"_sel); - } - static cocoa_wkwebview_engine* get_associated_webview(id object) - { - auto w = (cocoa_wkwebview_engine*)objc_getAssociatedObject(object, "webview"); - assert(w); - return w; - } - static id get_main_bundle() noexcept - { - return objc::msg_send("NSBundle"_cls, "mainBundle"_sel); - } - static bool is_app_bundled() noexcept - { - auto bundle = get_main_bundle(); - if (!bundle) - { - return false; - } - auto bundle_path = objc::msg_send(bundle, "bundlePath"_sel); - auto bundled = objc::msg_send(bundle_path, "hasSuffix:"_sel, ".app"_str); - return !!bundled; - } - void on_application_did_finish_launching(id /*delegate*/, id app) - { - // See comments related to application lifecycle in create_app_delegate(). - if (!m_parent_window) - { - // Stop the main run loop so that we can return - // from the constructor. - objc::msg_send(app, "stop:"_sel, nullptr); - } - - // Activate the app if it is not bundled. - // Bundled apps launched from Finder are activated automatically but - // otherwise not. Activating the app even when it has been launched from - // Finder does not seem to be harmful but calling this function is rarely - // needed as proper activation is normally taken care of for us. - // Bundled apps have a default activation policy of - // NSApplicationActivationPolicyRegular while non-bundled apps have a - // default activation policy of NSApplicationActivationPolicyProhibited. - if (!is_app_bundled()) - { - // "setActivationPolicy:" must be invoked before - // "activateIgnoringOtherApps:" for activation to work. - objc::msg_send(app, "setActivationPolicy:"_sel, - NSApplicationActivationPolicyRegular); - // Activate the app regardless of other active apps. - // This can be obtrusive so we only do it when necessary. - objc::msg_send(app, "activateIgnoringOtherApps:"_sel, YES); - } - - // Main window - if (!m_parent_window) - { - m_window = objc::msg_send("NSWindow"_cls, "alloc"_sel); - auto style = NSWindowStyleMaskTitled; - m_window = objc::msg_send( - m_window, "initWithContentRect:styleMask:backing:defer:"_sel, - CGRectMake(0, 0, 0, 0), style, NSBackingStoreBuffered, NO); - } - else - { - m_window = (id)m_parent_window; - } - - // Webview - auto config = objc::msg_send("WKWebViewConfiguration"_cls, "new"_sel); - m_manager = objc::msg_send(config, "userContentController"_sel); - m_webview = objc::msg_send("WKWebView"_cls, "alloc"_sel); - - if (m_debug) - { - // Equivalent Obj-C: - // [[config preferences] setValue:@YES forKey:@"developerExtrasEnabled"]; - objc::msg_send( - objc::msg_send(config, "preferences"_sel), "setValue:forKey:"_sel, - objc::msg_send("NSNumber"_cls, "numberWithBool:"_sel, YES), - "developerExtrasEnabled"_str); - } - - // Equivalent Obj-C: - // [[config preferences] setValue:@YES forKey:@"fullScreenEnabled"]; - objc::msg_send(objc::msg_send(config, "preferences"_sel), - "setValue:forKey:"_sel, - objc::msg_send("NSNumber"_cls, "numberWithBool:"_sel, YES), - "fullScreenEnabled"_str); - - // Equivalent Obj-C: - // [[config preferences] setValue:@YES forKey:@"javaScriptCanAccessClipboard"]; - objc::msg_send(objc::msg_send(config, "preferences"_sel), - "setValue:forKey:"_sel, - objc::msg_send("NSNumber"_cls, "numberWithBool:"_sel, YES), - "javaScriptCanAccessClipboard"_str); - - // Equivalent Obj-C: - // [[config preferences] setValue:@YES forKey:@"DOMPasteAllowed"]; - objc::msg_send(objc::msg_send(config, "preferences"_sel), - "setValue:forKey:"_sel, - objc::msg_send("NSNumber"_cls, "numberWithBool:"_sel, YES), - "DOMPasteAllowed"_str); - - auto ui_delegate = create_webkit_ui_delegate(); - objc::msg_send(m_webview, "initWithFrame:configuration:"_sel, - CGRectMake(0, 0, 0, 0), config); - objc::msg_send(m_webview, "setUIDelegate:"_sel, ui_delegate); - - auto navigation_delegate = create_webkit_navigation_delegate(); - objc::msg_send(m_webview, "setNavigationDelegate:"_sel, navigation_delegate); - auto script_message_handler = create_script_message_handler(); - objc::msg_send(m_manager, "addScriptMessageHandler:name:"_sel, - script_message_handler, "external"_str); - - init(R""( - window.external = { - invoke: function(s) { - window.webkit.messageHandlers.external.postMessage(s); - }, - }; - )""); - objc::msg_send(m_window, "setContentView:"_sel, m_webview); - objc::msg_send(m_window, "makeKeyAndOrderFront:"_sel, nullptr); - } - bool m_debug; - void* m_parent_window; - id m_window; - id m_webview; - id m_manager; - void* m_navigateCallbackArg = nullptr; - std::function m_navigateCallback = 0; - }; - - } // namespace detail - - using browser_engine = detail::cocoa_wkwebview_engine; - -} // namespace webview - -#elif defined(WEBVIEW_EDGE) - -// -// ==================================================================== -// -// This implementation uses Win32 API to create a native window. It -// uses Edge/Chromium webview2 backend as a browser engine. -// -// ==================================================================== -// - -#define WIN32_LEAN_AND_MEAN -#include -#include -#include -#include - -#include "WebView2.h" - -#ifdef _MSC_VER -#pragma comment(lib, "advapi32.lib") -#pragma comment(lib, "ole32.lib") -#pragma comment(lib, "shell32.lib") -#pragma comment(lib, "shlwapi.lib") -#pragma comment(lib, "user32.lib") -#pragma comment(lib, "version.lib") -#endif - -namespace webview -{ - namespace detail - { - - using msg_cb_t = std::function; - - // Converts a narrow (UTF-8-encoded) string into a wide (UTF-16-encoded) string. - inline std::wstring widen_string(const std::string& input) - { - if (input.empty()) - { - return std::wstring(); - } - UINT cp = CP_UTF8; - DWORD flags = MB_ERR_INVALID_CHARS; - auto input_c = input.c_str(); - auto input_length = static_cast(input.size()); - auto required_length = - MultiByteToWideChar(cp, flags, input_c, input_length, nullptr, 0); - if (required_length > 0) - { - std::wstring output(static_cast(required_length), L'\0'); - if (MultiByteToWideChar(cp, flags, input_c, input_length, &output[0], - required_length) > 0) - { - return output; - } - } - // Failed to convert string from UTF-8 to UTF-16 - return std::wstring(); - } - - // Converts a wide (UTF-16-encoded) string into a narrow (UTF-8-encoded) string. - inline std::string narrow_string(const std::wstring& input) - { - if (input.empty()) - { - return std::string(); - } - UINT cp = CP_UTF8; - DWORD flags = WC_ERR_INVALID_CHARS; - auto input_c = input.c_str(); - auto input_length = static_cast(input.size()); - auto required_length = - WideCharToMultiByte(cp, flags, input_c, input_length, nullptr, 0, nullptr, nullptr); - if (required_length > 0) - { - std::string output(static_cast(required_length), '\0'); - if (WideCharToMultiByte(cp, flags, input_c, input_length, &output[0], - required_length, nullptr, nullptr) > 0) - { - return output; - } - } - // Failed to convert string from UTF-16 to UTF-8 - return std::string(); - } - - // Parses a version string with 1-4 integral components, e.g. "1.2.3.4". - // Missing or invalid components default to 0, and excess components are ignored. - template - std::array parse_version(const std::basic_string& version) noexcept - { - auto parse_component = [](auto sb, auto se) -> unsigned int - { - try - { - auto n = std::stol(std::basic_string(sb, se)); - return n < 0 ? 0 : n; - } - catch (std::exception&) - { - return 0; - } - }; - auto end = version.end(); - auto sb = version.begin(); // subrange begin - auto se = sb; // subrange end - unsigned int ci = 0; // component index - std::array components{}; - while (sb != end && se != end && ci < components.size()) - { - if (*se == static_cast('.')) - { - components[ci++] = parse_component(sb, se); - sb = ++se; - continue; - } - ++se; - } - if (sb < se && ci < components.size()) - { - components[ci] = parse_component(sb, se); - } - return components; - } - - template - auto parse_version(const T (&version)[Length]) noexcept - { - return parse_version(std::basic_string(version, Length)); - } - - std::wstring get_file_version_string(const std::wstring& file_path) noexcept - { - DWORD dummy_handle; // Unused - DWORD info_buffer_length = GetFileVersionInfoSizeW(file_path.c_str(), &dummy_handle); - if (info_buffer_length == 0) - { - return std::wstring(); - } - std::vector info_buffer; - info_buffer.reserve(info_buffer_length); - if (!GetFileVersionInfoW(file_path.c_str(), 0, info_buffer_length, info_buffer.data())) - { - return std::wstring(); - } - auto sub_block = L"\\StringFileInfo\\040904B0\\ProductVersion"; - LPWSTR version = nullptr; - unsigned int version_length = 0; - if (!VerQueryValueW(info_buffer.data(), sub_block, reinterpret_cast(&version), - &version_length)) - { - return std::wstring(); - } - if (!version || version_length == 0) - { - return std::wstring(); - } - return std::wstring(version, version_length); - } - - // A wrapper around COM library initialization. Calls CoInitializeEx in the - // constructor and CoUninitialize in the destructor. - class com_init_wrapper - { - public: - com_init_wrapper(DWORD dwCoInit) - { - // We can safely continue as long as COM was either successfully - // initialized or already initialized. - // RPC_E_CHANGED_MODE means that CoInitializeEx was already called with - // a different concurrency model. - switch (CoInitializeEx(nullptr, dwCoInit)) - { - case S_OK: - case S_FALSE: - m_initialized = true; - break; - } - } - - ~com_init_wrapper() - { - if (m_initialized) - { - CoUninitialize(); - m_initialized = false; - } - } - - com_init_wrapper(const com_init_wrapper& other) = delete; - com_init_wrapper& operator=(const com_init_wrapper& other) = delete; - com_init_wrapper(com_init_wrapper&& other) = delete; - com_init_wrapper& operator=(com_init_wrapper&& other) = delete; - - bool is_initialized() const - { - return m_initialized; - } - - private: - bool m_initialized = false; - }; - - // Holds a symbol name and associated type for code clarity. - template class library_symbol - { - public: - using type = T; - - constexpr explicit library_symbol(const char* name) : m_name(name) - { - } - constexpr const char* get_name() const - { - return m_name; - } - - private: - const char* m_name; - }; - - // Loads a native shared library and allows one to get addresses for those - // symbols. - class native_library - { - public: - explicit native_library(const wchar_t* name) : m_handle(LoadLibraryW(name)) - { - } - - ~native_library() - { - if (m_handle) - { - FreeLibrary(m_handle); - m_handle = nullptr; - } - } - - native_library(const native_library& other) = delete; - native_library& operator=(const native_library& other) = delete; - native_library(native_library&& other) = default; - native_library& operator=(native_library&& other) = default; - - // Returns true if the library is currently loaded; otherwise false. - operator bool() const - { - return is_loaded(); - } - - // Get the address for the specified symbol or nullptr if not found. - template typename Symbol::type get(const Symbol& symbol) const - { - if (is_loaded()) - { - return reinterpret_cast( - GetProcAddress(m_handle, symbol.get_name())); - } - return nullptr; - } - - // Returns true if the library is currently loaded; otherwise false. - bool is_loaded() const - { - return !!m_handle; - } - - void detach() - { - m_handle = nullptr; - } - - private: - HMODULE m_handle = nullptr; - }; - - struct user32_symbols - { - using DPI_AWARENESS_CONTEXT = HANDLE; - using SetProcessDpiAwarenessContext_t = BOOL(WINAPI*)(DPI_AWARENESS_CONTEXT); - using SetProcessDPIAware_t = BOOL(WINAPI*)(); - - static constexpr auto SetProcessDpiAwarenessContext = - library_symbol("SetProcessDpiAwarenessContext"); - static constexpr auto SetProcessDPIAware = - library_symbol("SetProcessDPIAware"); - }; - - struct shcore_symbols - { - typedef enum - { - PROCESS_PER_MONITOR_DPI_AWARE = 2 - } PROCESS_DPI_AWARENESS; - using SetProcessDpiAwareness_t = HRESULT(WINAPI*)(PROCESS_DPI_AWARENESS); - - static constexpr auto SetProcessDpiAwareness = - library_symbol("SetProcessDpiAwareness"); - }; - - class reg_key - { - public: - explicit reg_key(HKEY root_key, const wchar_t* sub_key, DWORD options, - REGSAM sam_desired) - { - HKEY handle; - auto status = RegOpenKeyExW(root_key, sub_key, options, sam_desired, &handle); - if (status == ERROR_SUCCESS) - { - m_handle = handle; - } - } - - explicit reg_key(HKEY root_key, const std::wstring& sub_key, DWORD options, - REGSAM sam_desired) - : reg_key(root_key, sub_key.c_str(), options, sam_desired) - { - } - - virtual ~reg_key() - { - if (m_handle) - { - RegCloseKey(m_handle); - m_handle = nullptr; - } - } - - reg_key(const reg_key& other) = delete; - reg_key& operator=(const reg_key& other) = delete; - reg_key(reg_key&& other) = delete; - reg_key& operator=(reg_key&& other) = delete; - - bool is_open() const - { - return !!m_handle; - } - bool get_handle() const - { - return m_handle; - } - - std::wstring query_string(const wchar_t* name) const - { - DWORD buf_length = 0; - // Get the size of the data in bytes. - auto status = - RegQueryValueExW(m_handle, name, nullptr, nullptr, nullptr, &buf_length); - if (status != ERROR_SUCCESS && status != ERROR_MORE_DATA) - { - return std::wstring(); - } - // Read the data. - std::wstring result(buf_length / sizeof(wchar_t), 0); - auto buf = reinterpret_cast(&result[0]); - status = RegQueryValueExW(m_handle, name, nullptr, nullptr, buf, &buf_length); - if (status != ERROR_SUCCESS) - { - return std::wstring(); - } - // Remove trailing null-characters. - for (std::size_t length = result.size(); length > 0; --length) - { - if (result[length - 1] != 0) - { - result.resize(length); - break; - } - } - return result; - } - - private: - HKEY m_handle = nullptr; - }; - - inline bool enable_dpi_awareness() - { - auto user32 = native_library(L"user32.dll"); - if (auto fn = user32.get(user32_symbols::SetProcessDpiAwarenessContext)) - { - if (fn(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE)) - { - return true; - } - return GetLastError() == ERROR_ACCESS_DENIED; - } - if (auto shcore = native_library(L"shcore.dll")) - { - if (auto fn = shcore.get(shcore_symbols::SetProcessDpiAwareness)) - { - auto result = fn(shcore_symbols::PROCESS_PER_MONITOR_DPI_AWARE); - return result == S_OK || result == E_ACCESSDENIED; - } - } - if (auto fn = user32.get(user32_symbols::SetProcessDPIAware)) - { - return !!fn(); - } - return true; - } - -// Enable built-in WebView2Loader implementation by default. -#ifndef WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL -#define WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL 1 -#endif - -// Link WebView2Loader.dll explicitly by default only if the built-in -// implementation is enabled. -#ifndef WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK -#define WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL -#endif - -// Explicit linking of WebView2Loader.dll should be used along with -// the built-in implementation. -#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1 && WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK != 1 -#undef WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK -#error Please set WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK=1. -#endif - -#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1 - // Gets the last component of a Windows native file path. - // For example, if the path is "C:\a\b" then the result is "b". - template - std::basic_string get_last_native_path_component(const std::basic_string& path) - { - if (auto pos = path.find_last_of(static_cast('\\')); - pos != std::basic_string::npos) - { - return path.substr(pos + 1); - } - return std::basic_string(); - } -#endif /* WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL */ - - template struct cast_info_t - { - using type = T; - IID iid; - }; - - namespace mswebview2 - { - static constexpr IID IID_ICoreWebView2CreateCoreWebView2ControllerCompletedHandler{ - 0x6C4819F3, 0xC9B7, 0x4260, 0x81, 0x27, 0xC9, 0xF5, 0xBD, 0xE7, 0xF6, 0x8C - }; - static constexpr IID IID_ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler{ - 0x4E8A3389, 0xC9D8, 0x4BD2, 0xB6, 0xB5, 0x12, 0x4F, 0xEE, 0x6C, 0xC1, 0x4D - }; - static constexpr IID IID_ICoreWebView2PermissionRequestedEventHandler{ - 0x15E1C6A3, 0xC72A, 0x4DF3, 0x91, 0xD7, 0xD0, 0x97, 0xFB, 0xEC, 0x6B, 0xFD - }; - static constexpr IID IID_ICoreWebView2WebMessageReceivedEventHandler{ - 0x57213F19, 0x00E6, 0x49FA, 0x8E, 0x07, 0x89, 0x8E, 0xA0, 0x1E, 0xCB, 0xD2 - }; - -#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1 - enum class webview2_runtime_type - { - installed = 0, - embedded = 1 - }; - - namespace webview2_symbols - { - using CreateWebViewEnvironmentWithOptionsInternal_t = HRESULT(STDMETHODCALLTYPE*)( - bool, webview2_runtime_type, PCWSTR, IUnknown*, - ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler*); - using DllCanUnloadNow_t = HRESULT(STDMETHODCALLTYPE*)(); - - static constexpr auto CreateWebViewEnvironmentWithOptionsInternal = - library_symbol( - "CreateWebViewEnvironmentWithOptionsInternal"); - static constexpr auto DllCanUnloadNow = - library_symbol("DllCanUnloadNow"); - } // namespace webview2_symbols -#endif /* WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL */ - -#if WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK == 1 - namespace webview2_symbols - { - using CreateCoreWebView2EnvironmentWithOptions_t = HRESULT(STDMETHODCALLTYPE*)( - PCWSTR, PCWSTR, ICoreWebView2EnvironmentOptions*, - ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler*); - using GetAvailableCoreWebView2BrowserVersionString_t = - HRESULT(STDMETHODCALLTYPE*)(PCWSTR, LPWSTR*); - - static constexpr auto CreateCoreWebView2EnvironmentWithOptions = - library_symbol( - "CreateCoreWebView2EnvironmentWithOptions"); - static constexpr auto GetAvailableCoreWebView2BrowserVersionString = - library_symbol( - "GetAvailableCoreWebView2BrowserVersionString"); - } // namespace webview2_symbols -#endif /* WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK */ - - class loader - { - public: - HRESULT create_environment_with_options( - PCWSTR browser_dir, PCWSTR user_data_dir, - ICoreWebView2EnvironmentOptions* env_options, - ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler* created_handler) - const - { -#if WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK == 1 - if (m_lib.is_loaded()) - { - if (auto fn = m_lib.get( - webview2_symbols::CreateCoreWebView2EnvironmentWithOptions)) - { - return fn(browser_dir, user_data_dir, env_options, created_handler); - } - } -#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1 - return create_environment_with_options_impl(browser_dir, user_data_dir, - env_options, created_handler); -#else - return S_FALSE; -#endif -#else - return ::CreateCoreWebView2EnvironmentWithOptions(browser_dir, user_data_dir, - env_options, created_handler); -#endif /* WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK */ - } - - HRESULT - get_available_browser_version_string(PCWSTR browser_dir, LPWSTR* version) const - { -#if WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK == 1 - if (m_lib.is_loaded()) - { - if (auto fn = m_lib.get( - webview2_symbols::GetAvailableCoreWebView2BrowserVersionString)) - { - return fn(browser_dir, version); - } - } -#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1 - return get_available_browser_version_string_impl(browser_dir, version); -#else - return S_FALSE; -#endif -#else - return ::GetAvailableCoreWebView2BrowserVersionString(browser_dir, version); -#endif /* WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK */ - } - - private: -#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1 - struct client_info_t - { - bool found = false; - std::wstring dll_path; - std::wstring version; - webview2_runtime_type runtime_type; - }; - - HRESULT create_environment_with_options_impl( - PCWSTR browser_dir, PCWSTR user_data_dir, - ICoreWebView2EnvironmentOptions* env_options, - ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler* created_handler) - const - { - auto found_client = find_available_client(browser_dir); - if (!found_client.found) - { - return -1; - } - auto client_dll = native_library(found_client.dll_path.c_str()); - if (auto fn = client_dll.get( - webview2_symbols::CreateWebViewEnvironmentWithOptionsInternal)) - { - return fn(true, found_client.runtime_type, user_data_dir, env_options, - created_handler); - } - if (auto fn = client_dll.get(webview2_symbols::DllCanUnloadNow)) - { - if (!fn()) - { - client_dll.detach(); - } - } - return ERROR_SUCCESS; - } - - HRESULT - get_available_browser_version_string_impl(PCWSTR browser_dir, LPWSTR* version) const - { - if (!version) - { - return -1; - } - auto found_client = find_available_client(browser_dir); - if (!found_client.found) - { - return -1; - } - auto info_length_bytes = - found_client.version.size() * sizeof(found_client.version[0]); - auto info = static_cast(CoTaskMemAlloc(info_length_bytes)); - if (!info) - { - return -1; - } - CopyMemory(info, found_client.version.c_str(), info_length_bytes); - *version = info; - return 0; - } - - client_info_t find_available_client(PCWSTR browser_dir) const - { - if (browser_dir) - { - return find_embedded_client(api_version, browser_dir); - } - auto found_client = - find_installed_client(api_version, true, default_release_channel_guid); - if (!found_client.found) - { - found_client = - find_installed_client(api_version, false, default_release_channel_guid); - } - return found_client; - } - - std::wstring make_client_dll_path(const std::wstring& dir) const - { - auto dll_path = dir; - if (!dll_path.empty()) - { - auto last_char = dir[dir.size() - 1]; - if (last_char != L'\\' && last_char != L'/') - { - dll_path += L'\\'; - } - } - dll_path += L"EBWebView\\"; -#if defined(_M_X64) || defined(__x86_64__) - dll_path += L"x64"; -#elif defined(_M_IX86) || defined(__i386__) - dll_path += L"x86"; -#elif defined(_M_ARM64) || defined(__aarch64__) - dll_path += L"arm64"; -#else -#error WebView2 integration for this platform is not yet supported. -#endif - dll_path += L"\\EmbeddedBrowserWebView.dll"; - return dll_path; - } - - client_info_t find_installed_client(unsigned int min_api_version, bool system, - const std::wstring& release_channel) const - { - std::wstring sub_key = client_state_reg_sub_key; - sub_key += release_channel; - auto root_key = system ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; - reg_key key(root_key, sub_key, 0, KEY_READ | KEY_WOW64_32KEY); - if (!key.is_open()) - { - return {}; - } - auto ebwebview_value = key.query_string(L"EBWebView"); - - auto client_version_string = get_last_native_path_component(ebwebview_value); - auto client_version = parse_version(client_version_string); - if (client_version[2] < min_api_version) - { - // Our API version is greater than the runtime API version. - return {}; - } - - auto client_dll_path = make_client_dll_path(ebwebview_value); - return { true, client_dll_path, client_version_string, - webview2_runtime_type::installed }; - } - - client_info_t find_embedded_client(unsigned int min_api_version, - const std::wstring& dir) const - { - auto client_dll_path = make_client_dll_path(dir); - - auto client_version_string = get_file_version_string(client_dll_path); - auto client_version = parse_version(client_version_string); - if (client_version[2] < min_api_version) - { - // Our API version is greater than the runtime API version. - return {}; - } - - return { true, client_dll_path, client_version_string, - webview2_runtime_type::embedded }; - } - - // The minimum WebView2 API version we need regardless of the SDK release - // actually used. The number comes from the SDK release version, - // e.g. 1.0.1150.38. To be safe the SDK should have a number that is greater - // than or equal to this number. The Edge browser webview client must - // have a number greater than or equal to this number. - static constexpr unsigned int api_version = 1150; - - static constexpr auto client_state_reg_sub_key = - L"SOFTWARE\\Microsoft\\EdgeUpdate\\ClientState\\"; - - // GUID for the stable release channel. - static constexpr auto stable_release_guid = - L"{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}"; - - static constexpr auto default_release_channel_guid = stable_release_guid; -#endif /* WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL */ - -#if WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK == 1 - native_library m_lib{ L"WebView2Loader.dll" }; -#endif - }; - - namespace cast_info - { - static constexpr auto controller_completed = - cast_info_t{ - IID_ICoreWebView2CreateCoreWebView2ControllerCompletedHandler - }; - - static constexpr auto environment_completed = - cast_info_t{ - IID_ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler - }; - - static constexpr auto message_received = - cast_info_t{ - IID_ICoreWebView2WebMessageReceivedEventHandler - }; - - static constexpr auto permission_requested = - cast_info_t{ - IID_ICoreWebView2PermissionRequestedEventHandler - }; - } // namespace cast_info - } // namespace mswebview2 - - class webview2_com_handler - : public ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler, - public ICoreWebView2CreateCoreWebView2ControllerCompletedHandler, - public ICoreWebView2WebMessageReceivedEventHandler, - public ICoreWebView2PermissionRequestedEventHandler, - public ICoreWebView2NavigationCompletedEventHandler - { - using webview2_com_handler_cb_t = - std::function; - - public: - webview2_com_handler(HWND hwnd, msg_cb_t msgCb, webview2_com_handler_cb_t cb) - : m_window(hwnd), m_msgCb(msgCb), m_cb(cb) - { - } - - virtual ~webview2_com_handler() = default; - webview2_com_handler(const webview2_com_handler& other) = delete; - webview2_com_handler& operator=(const webview2_com_handler& other) = delete; - webview2_com_handler(webview2_com_handler&& other) = delete; - webview2_com_handler& operator=(webview2_com_handler&& other) = delete; - - ULONG STDMETHODCALLTYPE AddRef() - { - return ++m_ref_count; - } - ULONG STDMETHODCALLTYPE Release() - { - if (m_ref_count > 1) - { - return --m_ref_count; - } - delete this; - return 0; - } - HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID* ppv) - { - using namespace mswebview2::cast_info; - - if (!ppv) - { - return E_POINTER; - } - - // All of the COM interfaces we implement should be added here regardless - // of whether they are required. - // This is just to be on the safe side in case the WebView2 Runtime ever - // requests a pointer to an interface we implement. - // The WebView2 Runtime must at the very least be able to get a pointer to - // ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler when we use - // our custom WebView2 loader implementation, and observations have shown - // that it is the only interface requested in this case. None have been - // observed to be requested when using the official WebView2 loader. - - if (cast_if_equal_iid(riid, controller_completed, ppv) || - cast_if_equal_iid(riid, environment_completed, ppv) || - cast_if_equal_iid(riid, message_received, ppv) || - cast_if_equal_iid(riid, permission_requested, ppv)) - { - return S_OK; - } - - return E_NOINTERFACE; - } - HRESULT STDMETHODCALLTYPE Invoke(HRESULT res, ICoreWebView2Environment* env) - { - if (SUCCEEDED(res)) - { - res = env->CreateCoreWebView2Controller(m_window, this); - if (SUCCEEDED(res)) - { - return S_OK; - } - } - try_create_environment(); - return S_OK; - } - HRESULT STDMETHODCALLTYPE Invoke(HRESULT res, ICoreWebView2Controller* controller) - { - if (FAILED(res)) - { - // See try_create_environment() regarding - // HRESULT_FROM_WIN32(ERROR_INVALID_STATE). - // The result is E_ABORT if the parent window has been destroyed already. - switch (res) - { - case HRESULT_FROM_WIN32(ERROR_INVALID_STATE): - case E_ABORT: - return S_OK; - } - try_create_environment(); - return S_OK; - } - - ICoreWebView2* webview; - ::EventRegistrationToken token; - controller->get_CoreWebView2(&webview); - webview->add_WebMessageReceived(this, &token); - webview->add_PermissionRequested(this, &token); - webview->add_NavigationCompleted(this, &token); - - m_cb(controller, webview); - return S_OK; - } - HRESULT STDMETHODCALLTYPE Invoke(ICoreWebView2* sender, - ICoreWebView2WebMessageReceivedEventArgs* args) - { - LPWSTR message; - args->TryGetWebMessageAsString(&message); - m_msgCb(narrow_string(message)); - sender->PostWebMessageAsString(message); - - CoTaskMemFree(message); - return S_OK; - } - HRESULT STDMETHODCALLTYPE Invoke(ICoreWebView2* sender, - ICoreWebView2PermissionRequestedEventArgs* args) - { - COREWEBVIEW2_PERMISSION_KIND kind; - args->get_PermissionKind(&kind); - if (kind == COREWEBVIEW2_PERMISSION_KIND_CLIPBOARD_READ) - { - args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW); - } - return S_OK; - } - HRESULT STDMETHODCALLTYPE Invoke(ICoreWebView2* sender, - ICoreWebView2NavigationCompletedEventArgs* args) - { - PWSTR uri = nullptr; - auto hr = sender->get_Source(&uri); - if (SUCCEEDED(hr)) - { - auto curi = std::wstring_convert >().to_bytes(uri); - if (navigateCallback) - navigateCallback(curi, navigateCallbackArg); - } - CoTaskMemFree(uri); - return hr; - } - - // Checks whether the specified IID equals the IID of the specified type and - // if so casts the "this" pointer to T and returns it. Returns nullptr on - // mismatching IIDs. - // If ppv is specified then the pointer will also be assigned to *ppv. - template - T* cast_if_equal_iid(REFIID riid, const cast_info_t& info, - LPVOID* ppv = nullptr) noexcept - { - T* ptr = nullptr; - if (IsEqualIID(riid, info.iid)) - { - ptr = static_cast(this); - ptr->AddRef(); - } - if (ppv) - { - *ppv = ptr; - } - return ptr; - } - - // Set the function that will perform the initiating logic for creating - // the WebView2 environment. - void set_attempt_handler(std::function attempt_handler) noexcept - { - m_attempt_handler = attempt_handler; - } - - // Retry creating a WebView2 environment. - // The initiating logic for creating the environment is defined by the - // caller of set_attempt_handler(). - void try_create_environment() noexcept - { - // WebView creation fails with HRESULT_FROM_WIN32(ERROR_INVALID_STATE) if - // a running instance using the same user data folder exists, and the - // Environment objects have different EnvironmentOptions. - // Source: - // https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environment?view=webview2-1.0.1150.38 - if (m_attempts < m_max_attempts) - { - ++m_attempts; - auto res = m_attempt_handler(); - if (SUCCEEDED(res)) - { - return; - } - // Not entirely sure if this error code only applies to - // CreateCoreWebView2Controller so we check here as well. - if (res == HRESULT_FROM_WIN32(ERROR_INVALID_STATE)) - { - return; - } - try_create_environment(); - return; - } - // Give up. - m_cb(nullptr, nullptr); - } - - void STDMETHODCALLTYPE add_navigate_listener( - std::function callback, void* arg) - { - navigateCallback = std::move(callback); - navigateCallbackArg = arg; - } - - private: - HWND m_window; - msg_cb_t m_msgCb; - webview2_com_handler_cb_t m_cb; - std::atomic m_ref_count{ 1 }; - std::function m_attempt_handler; - unsigned int m_max_attempts = 5; - unsigned int m_attempts = 0; - void* navigateCallbackArg = nullptr; - std::function navigateCallback = 0; - }; - - class win32_edge_engine - { - public: - win32_edge_engine(bool debug, void* window) - { - if (!is_webview2_available()) - { - return; - } - if (!m_com_init.is_initialized()) - { - return; - } - enable_dpi_awareness(); - if (window == nullptr) - { - HINSTANCE hInstance = GetModuleHandle(nullptr); - HICON icon = (HICON)LoadImage(hInstance, IDI_APPLICATION, IMAGE_ICON, - GetSystemMetrics(SM_CXICON), - GetSystemMetrics(SM_CYICON), LR_DEFAULTCOLOR); - - WNDCLASSEXW wc; - ZeroMemory(&wc, sizeof(WNDCLASSEX)); - wc.cbSize = sizeof(WNDCLASSEX); - wc.hInstance = hInstance; - wc.lpszClassName = L"webview"; - wc.hIcon = icon; - wc.lpfnWndProc = - (WNDPROC)(+[](HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) -> LRESULT - { - auto w = - (win32_edge_engine*)GetWindowLongPtr(hwnd, GWLP_USERDATA); - switch (msg) - { - case WM_SIZE: - w->resize(hwnd); - break; - case WM_CLOSE: - DestroyWindow(hwnd); - break; - case WM_DESTROY: - w->terminate(); - break; - case WM_GETMINMAXINFO: - { - auto lpmmi = (LPMINMAXINFO)lp; - if (w == nullptr) - { - return 0; - } - if (w->m_maxsz.x > 0 && w->m_maxsz.y > 0) - { - lpmmi->ptMaxSize = w->m_maxsz; - lpmmi->ptMaxTrackSize = w->m_maxsz; - } - if (w->m_minsz.x > 0 && w->m_minsz.y > 0) - { - lpmmi->ptMinTrackSize = w->m_minsz; - } - } - break; - default: - return DefWindowProcW(hwnd, msg, wp, lp); - } - return 0; - }); - RegisterClassExW(&wc); - m_window = CreateWindowW(L"webview", L"", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, - CW_USEDEFAULT, 640, 480, nullptr, nullptr, hInstance, - nullptr); - if (m_window == nullptr) - { - return; - } - SetWindowLongPtr(m_window, GWLP_USERDATA, (LONG_PTR)this); - } - else - { - m_window = *(static_cast(window)); - } - - ShowWindow(m_window, SW_SHOW); - UpdateWindow(m_window); - SetFocus(m_window); - - auto cb = std::bind(&win32_edge_engine::on_message, this, std::placeholders::_1); - - embed(m_window, debug, cb); - resize(m_window); - m_controller->MoveFocus(COREWEBVIEW2_MOVE_FOCUS_REASON_PROGRAMMATIC); - } - - virtual ~win32_edge_engine() - { - if (m_com_handler) - { - m_com_handler->Release(); - m_com_handler = nullptr; - } - if (m_webview) - { - m_webview->Release(); - m_webview = nullptr; - } - if (m_controller) - { - m_controller->Release(); - m_controller = nullptr; - } - } - - win32_edge_engine(const win32_edge_engine& other) = delete; - win32_edge_engine& operator=(const win32_edge_engine& other) = delete; - win32_edge_engine(win32_edge_engine&& other) = delete; - win32_edge_engine& operator=(win32_edge_engine&& other) = delete; - - void run() - { - MSG msg; - BOOL res; - while ((res = GetMessage(&msg, nullptr, 0, 0)) != -1) - { - if (msg.hwnd) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - continue; - } - if (msg.message == WM_APP) - { - auto f = (dispatch_fn_t*)(msg.lParam); - (*f)(); - delete f; - } - else if (msg.message == WM_QUIT) - { - return; - } - } - } - void* window() - { - return (void*)m_window; - } - void terminate() - { - PostQuitMessage(0); - } - void dispatch(dispatch_fn_t f) - { - PostThreadMessage(m_main_thread, WM_APP, 0, (LPARAM) new dispatch_fn_t(f)); - } - - void set_title(const std::string& title) - { - SetWindowTextW(m_window, widen_string(title).c_str()); - } - - void set_size(int width, int height, int hints) - { - auto style = GetWindowLong(m_window, GWL_STYLE); - if (hints == WEBVIEW_HINT_FIXED) - { - style &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX); - } - else - { - style |= (WS_THICKFRAME | WS_MAXIMIZEBOX); - } - SetWindowLong(m_window, GWL_STYLE, style); - - if (hints == WEBVIEW_HINT_MAX) - { - m_maxsz.x = width; - m_maxsz.y = height; - } - else if (hints == WEBVIEW_HINT_MIN) - { - m_minsz.x = width; - m_minsz.y = height; - } - else - { - RECT r; - r.left = r.top = 0; - r.right = width; - r.bottom = height; - AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, 0); - SetWindowPos(m_window, nullptr, r.left, r.top, r.right - r.left, - r.bottom - r.top, - SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE | SWP_FRAMECHANGED); - resize(m_window); - } - } - - void navigate(const std::string& url) - { - auto wurl = widen_string(url); - m_webview->Navigate(wurl.c_str()); - } - - void init(const std::string& js) - { - auto wjs = widen_string(js); - m_webview->AddScriptToExecuteOnDocumentCreated(wjs.c_str(), nullptr); - } - - void eval(const std::string& js) - { - auto wjs = widen_string(js); - m_webview->ExecuteScript(wjs.c_str(), nullptr); - } - - void add_navigate_listener(std::function callback, - void* arg) - { - m_com_handler->add_navigate_listener(callback, arg); - } - - void add_scheme_handler(const std::string& scheme, - std::function callback, - void* arg) - { - // TODO: Implement - } - - void set_html(const std::string& html) - { - m_webview->NavigateToString(widen_string(html).c_str()); - } - - private: - bool embed(HWND wnd, bool debug, msg_cb_t cb) - { - std::atomic_flag flag = ATOMIC_FLAG_INIT; - flag.test_and_set(); - - wchar_t currentExePath[MAX_PATH]; - GetModuleFileNameW(nullptr, currentExePath, MAX_PATH); - wchar_t* currentExeName = PathFindFileNameW(currentExePath); - - wchar_t dataPath[MAX_PATH]; - if (!SUCCEEDED(SHGetFolderPathW(nullptr, CSIDL_APPDATA, nullptr, 0, dataPath))) - { - return false; - } - wchar_t userDataFolder[MAX_PATH]; - PathCombineW(userDataFolder, dataPath, currentExeName); - - m_com_handler = new webview2_com_handler( - wnd, cb, - [&](ICoreWebView2Controller* controller, ICoreWebView2* webview) - { - if (!controller || !webview) - { - flag.clear(); - return; - } - controller->AddRef(); - webview->AddRef(); - m_controller = controller; - m_webview = webview; - flag.clear(); - }); - - m_com_handler->set_attempt_handler( - [&] - { - return m_webview2_loader.create_environment_with_options( - nullptr, userDataFolder, nullptr, m_com_handler); - }); - m_com_handler->try_create_environment(); - - MSG msg = {}; - while (flag.test_and_set() && GetMessage(&msg, nullptr, 0, 0)) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - if (!m_controller || !m_webview) - { - return false; - } - ICoreWebView2Settings* settings = nullptr; - auto res = m_webview->get_Settings(&settings); - if (res != S_OK) - { - return false; - } - res = settings->put_AreDevToolsEnabled(debug ? TRUE : FALSE); - if (res != S_OK) - { - return false; - } - init("window.external={invoke:s=>window.chrome.webview.postMessage(s)}"); - return true; - } - - void resize(HWND wnd) - { - if (m_controller == nullptr) - { - return; - } - RECT bounds; - GetClientRect(wnd, &bounds); - m_controller->put_Bounds(bounds); - } - - bool is_webview2_available() const noexcept - { - LPWSTR version_info = nullptr; - auto res = - m_webview2_loader.get_available_browser_version_string(nullptr, &version_info); - // The result will be equal to HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) - // if the WebView2 runtime is not installed. - auto ok = SUCCEEDED(res) && version_info; - if (version_info) - { - CoTaskMemFree(version_info); - } - return ok; - } - - virtual void on_message(const std::string& msg) = 0; - - // The app is expected to call CoInitializeEx before - // CreateCoreWebView2EnvironmentWithOptions. - // Source: - // https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl#createcorewebview2environmentwithoptions - com_init_wrapper m_com_init{ COINIT_APARTMENTTHREADED }; - HWND m_window = nullptr; - POINT m_minsz = POINT{ 0, 0 }; - POINT m_maxsz = POINT{ 0, 0 }; - DWORD m_main_thread = GetCurrentThreadId(); - ICoreWebView2* m_webview = nullptr; - ICoreWebView2Controller* m_controller = nullptr; - webview2_com_handler* m_com_handler = nullptr; - mswebview2::loader m_webview2_loader; - }; - - } // namespace detail - - using browser_engine = detail::win32_edge_engine; - -} // namespace webview - -#endif /* WEBVIEW_GTK, WEBVIEW_COCOA, WEBVIEW_EDGE */ - -namespace webview -{ - - class webview : public browser_engine - { - public: - webview(bool debug = false, void* wnd = nullptr) : browser_engine(debug, wnd) - { - } - - void navigate(const std::string& url) - { - if (url.empty()) - { - browser_engine::navigate("about:blank"); - return; - } - browser_engine::navigate(url); - } - - using binding_t = std::function; - class binding_ctx_t - { - public: - binding_ctx_t(binding_t callback, void* arg) : callback(callback), arg(arg) - { - } - // This function is called upon execution of the bound JS function - binding_t callback; - // This user-supplied argument is passed to the callback - void* arg; - }; - - using sync_binding_t = std::function; - - // Synchronous bind - void bind(const std::string& name, sync_binding_t fn) - { - auto wrapper = [this, fn](const std::string& seq, const std::string& req, void* /*arg*/) - { resolve(seq, 0, fn(req)); }; - bind(name, wrapper, nullptr); - } - - // Asynchronous bind - void bind(const std::string& name, binding_t fn, void* arg) - { - if (bindings.count(name) > 0) - { - return; - } - bindings.emplace(name, binding_ctx_t(fn, arg)); - auto js = "(function() { var name = '" + name + "';" + R""( - var RPC = window._rpc = (window._rpc || {nextSeq: 1}); - window[name] = function() { - var seq = RPC.nextSeq++; - var promise = new Promise(function(resolve, reject) { - RPC[seq] = { - resolve: resolve, - reject: reject, - }; - }); - window.external.invoke(JSON.stringify({ - id: seq, - method: name, - params: Array.prototype.slice.call(arguments), - })); - return promise; - } - })())""; - init(js); - eval(js); - } - - void unbind(const std::string& name) - { - auto found = bindings.find(name); - if (found != bindings.end()) - { - auto js = "delete window['" + name + "'];"; - init(js); - eval(js); - bindings.erase(found); - } - } - - void resolve(const std::string& seq, int status, const std::string& result) - { - dispatch( - [seq, status, result, this]() - { - if (status == 0) - { - eval("window._rpc[" + seq + "].resolve(" + result + - "); delete window._rpc[" + seq + "]"); - } - else - { - eval("window._rpc[" + seq + "].reject(" + result + - "); delete window._rpc[" + seq + "]"); - } - }); - } - - private: - void on_message(const std::string& msg) override - { - auto seq = detail::json_parse(msg, "id", 0); - auto name = detail::json_parse(msg, "method", 0); - auto args = detail::json_parse(msg, "params", 0); - auto found = bindings.find(name); - if (found == bindings.end()) - { - return; - } - const auto& context = found->second; - context.callback(seq, args, context.arg); - } - - std::map bindings; - }; -} // namespace webview - -WEBVIEW_API webview_t webview_create(int debug, void* wnd) -{ - auto w = new webview::webview(debug, wnd); - if (!w->window()) - { - delete w; - return nullptr; - } - return w; -} - -WEBVIEW_API void webview_destroy(webview_t w) -{ - delete static_cast(w); -} - -WEBVIEW_API void webview_run(webview_t w) -{ - static_cast(w)->run(); -} - -WEBVIEW_API void webview_terminate(webview_t w) -{ - static_cast(w)->terminate(); -} - -WEBVIEW_API void webview_dispatch(webview_t w, void (*fn)(webview_t, void*), void* arg) -{ - static_cast(w)->dispatch([=]() { fn(w, arg); }); -} - -WEBVIEW_API void* webview_get_window(webview_t w) -{ - return static_cast(w)->window(); -} - -WEBVIEW_API void webview_set_title(webview_t w, const char* title) -{ - static_cast(w)->set_title(title); -} - -WEBVIEW_API void webview_set_size(webview_t w, int width, int height, int hints) -{ - static_cast(w)->set_size(width, height, hints); -} - -WEBVIEW_API void webview_navigate(webview_t w, const char* url) -{ - static_cast(w)->navigate(url); -} - -WEBVIEW_API void webview_set_html(webview_t w, const char* html) -{ - static_cast(w)->set_html(html); -} - -WEBVIEW_API void webview_init(webview_t w, const char* js) -{ - static_cast(w)->init(js); -} - -WEBVIEW_API void webview_eval(webview_t w, const char* js) -{ - static_cast(w)->eval(js); -} - -WEBVIEW_API void webview_bind(webview_t w, const char* name, - void (*fn)(const char* seq, const char* req, void* arg), void* arg) -{ - static_cast(w)->bind( - name, - [=](const std::string& seq, const std::string& req, void* arg) - { fn(seq.c_str(), req.c_str(), arg); }, - arg); -} - -WEBVIEW_API void webview_unbind(webview_t w, const char* name) -{ - static_cast(w)->unbind(name); -} - -WEBVIEW_API void webview_return(webview_t w, const char* seq, int status, const char* result) -{ - static_cast(w)->resolve(seq, status, result); -} - -WEBVIEW_API const webview_version_info_t* webview_version() -{ - return &webview::detail::library_version_info; -} - -#endif /* WEBVIEW_HEADER */ -#endif /* __cplusplus */ -#endif /* WEBVIEW_H */ diff --git a/client/SDL/common/aad/wrapper/webview_impl.cpp b/client/SDL/common/aad/wrapper/webview_impl.cpp index f311523f8..89358e4b8 100644 --- a/client/SDL/common/aad/wrapper/webview_impl.cpp +++ b/client/SDL/common/aad/wrapper/webview_impl.cpp @@ -17,7 +17,7 @@ * limitations under the License. */ -#include "webview.h" +#include #include #include @@ -25,45 +25,137 @@ #include #include #include +#include +#include #include "../webview_impl.hpp" -static std::vector split(const std::string& input, const std::string& regex) -{ - // passing -1 as the submatch index parameter performs splitting - std::regex re(regex); - std::sregex_token_iterator first{ input.begin(), input.end(), re, -1 }; - std::sregex_token_iterator last; - return { first, last }; -} +#include +#include -static std::map urlsplit(const std::string& url) -{ - auto pos = url.find('?'); - if (pos == std::string::npos) - return {}; - auto surl = url.substr(pos); - auto args = split(surl, "&"); +#define TAG FREERDP_TAG("client.SDL.common.aad") - std::map argmap; - for (const auto& arg : args) +class fkt_arg +{ + public: + fkt_arg(const std::string& url) { - auto kv = split(arg, "="); - if (kv.size() == 2) - argmap.insert({ kv[0], kv[1] }); + auto args = urlsplit(url); + auto redir = args.find("redirect_uri"); + if (redir == args.end()) + { + WLog_ERR(TAG, "[Webview] url %s does not contain a redirect_uri parameter, aborting.", + url.c_str()); + } + else + { + _redirect_uri = from_url_encoded_str(redir->second); + } } - return argmap; -} -static void fkt(const std::string& url, void* arg) + bool valid() const + { + return !_redirect_uri.empty(); + } + + bool getCode(std::string& c) const + { + c = _code; + return !c.empty(); + } + + bool handle(const std::string& uri) const + { + std::string duri = from_url_encoded_str(uri); + if (duri.length() < _redirect_uri.length()) + return false; + auto rc = _strnicmp(duri.c_str(), _redirect_uri.c_str(), _redirect_uri.length()); + return rc == 0; + } + + bool parse(const std::string& uri) + { + _args = urlsplit(uri); + auto err = _args.find("error"); + if (err != _args.end()) + { + auto suberr = _args.find("error_subcode"); + WLog_ERR(TAG, "[Webview] %s: %s, %s: %s", err->first.c_str(), err->second.c_str(), + suberr->first.c_str(), suberr->second.c_str()); + return false; + } + auto val = _args.find("code"); + if (val == _args.end()) + { + WLog_ERR(TAG, "[Webview] no code parameter detected in redirect URI %s", uri.c_str()); + return false; + } + + _code = val->second; + return true; + } + + protected: + static std::string from_url_encoded_str(const std::string& str) + { + std::string cxxstr; + auto cstr = winpr_str_url_decode(str.c_str(), str.length()); + if (cstr) + { + cxxstr = std::string(cstr); + free(cstr); + } + return cxxstr; + } + + static std::vector split(const std::string& input, const std::string& regex) + { + // passing -1 as the submatch index parameter performs splitting + std::regex re(regex); + std::sregex_token_iterator first{ input.begin(), input.end(), re, -1 }; + std::sregex_token_iterator last; + return { first, last }; + } + + static std::map urlsplit(const std::string& url) + { + auto pos = url.find('?'); + if (pos == std::string::npos) + return {}; + + pos++; // skip '?' + auto surl = url.substr(pos); + auto args = split(surl, "&"); + + std::map argmap; + for (const auto& arg : args) + { + auto kv = split(arg, "="); + if (kv.size() == 2) + argmap.insert({ kv[0], kv[1] }); + } + + return argmap; + } + + private: + std::string _redirect_uri; + std::string _code; + std::map _args; +}; + +static void fkt(webview_t webview, const char* uri, webview_navigation_event_t type, void* arg) { - auto args = urlsplit(url); - auto val = args.find("code"); - if (val == args.end()) + assert(arg); + auto rcode = static_cast(arg); + + if (type != WEBVIEW_LOAD_FINISHED) return; - assert(arg); - auto rcode = static_cast(arg); - *rcode = val->second; + if (!rcode->handle(uri)) + return; + + (void)rcode->parse(uri); + webview_terminate(webview); } bool webview_impl_run(const std::string& title, const std::string& url, std::string& code) @@ -71,12 +163,15 @@ bool webview_impl_run(const std::string& title, const std::string& url, std::str webview::webview w(false, nullptr); w.set_title(title); - w.set_size(640, 480, WEBVIEW_HINT_NONE); + w.set_size(800, 600, WEBVIEW_HINT_NONE); - std::string scheme; - w.add_scheme_handler("ms-appx-web", fkt, &scheme); - w.add_navigate_listener(fkt, &code); + fkt_arg arg(url); + if (!arg.valid()) + { + return false; + } + w.add_navigation_listener(fkt, &arg); w.navigate(url); w.run(); - return !code.empty(); + return arg.getCode(code); } diff --git a/client/SDL/common/test/CMakeLists.txt b/client/SDL/common/test/CMakeLists.txt index 1c1b3bf1e..ffb510c13 100644 --- a/client/SDL/common/test/CMakeLists.txt +++ b/client/SDL/common/test/CMakeLists.txt @@ -3,15 +3,18 @@ set(MODULE_PREFIX "TEST_SDL") set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.cpp) -set(${MODULE_PREFIX}_TESTS TestSDLPrefs.cpp) +set(${MODULE_PREFIX}_TESTS TestSDLPrefs.cpp TestSDLWebview.cpp) disable_warnings_for_directory(${CMAKE_CURRENT_BINARY_DIR}) create_test_sourcelist(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_DRIVER} ${${MODULE_PREFIX}_TESTS}) +include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../aad") +include_directories("${CMAKE_CURRENT_BINARY_DIR}/../aad") + add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS}) -set(${MODULE_PREFIX}_LIBS freerdp winpr sdl-common-prefs) +set(${MODULE_PREFIX}_LIBS freerdp freerdp-client winpr sdl-common-prefs sdl-common-aad-view) target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) diff --git a/client/SDL/common/test/TestSDLWebview.cpp b/client/SDL/common/test/TestSDLWebview.cpp new file mode 100644 index 000000000..4e8076f7d --- /dev/null +++ b/client/SDL/common/test/TestSDLWebview.cpp @@ -0,0 +1,30 @@ +#include +#include +#include + +#include +#include + +#include + +int TestSDLWebview(WINPR_ATTR_UNUSED int argc, WINPR_ATTR_UNUSED char* argv[]) +{ +#if 0 + RDP_CLIENT_ENTRY_POINTS entry = {}; + entry.Version = RDP_CLIENT_INTERFACE_VERSION; + entry.Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1); + entry.ContextSize = sizeof(rdpContext); + + std::shared_ptr context(freerdp_client_context_new(&entry), + [](rdpContext* ptr) { freerdp_client_context_free(ptr); }); + + char* token = nullptr; + if (!sdl_webview_get_access_token(context->instance, ACCESS_TOKEN_TYPE_AAD, &token, 2, "scope", + "foobar")) + { + std::cerr << "test failed!" << std::endl; + return -1; + } +#endif + return 0; +} diff --git a/libfreerdp/core/aad.c b/libfreerdp/core/aad.c index 7a95ffd0e..bc7511360 100644 --- a/libfreerdp/core/aad.c +++ b/libfreerdp/core/aad.c @@ -52,7 +52,7 @@ struct rdp_aad #ifdef WITH_AAD -static BOOL aad_fetch_wellknown(rdpAad* aad); +static BOOL aad_fetch_wellknown(wLog* log, rdpContext* context); static BOOL get_encoded_rsa_params(wLog* wlog, rdpPrivateKey* key, char** e, char** n); static BOOL generate_pop_key(rdpAad* aad); @@ -180,6 +180,20 @@ static INLINE const char* aad_auth_result_to_string(DWORD code) return "Unknown error"; } +static BOOL ensure_wellknown(rdpContext* context) +{ + if (context->rdp->wellknown) + return TRUE; + + rdpAad* aad = context->rdp->aad; + if (!aad) + return FALSE; + + if (!aad_fetch_wellknown(aad->log, context)) + return FALSE; + return context->rdp->wellknown != NULL; +} + static BOOL aad_get_nonce(rdpAad* aad) { BOOL ret = FALSE; @@ -194,6 +208,9 @@ static BOOL aad_get_nonce(rdpAad* aad) rdpRdp* rdp = aad->rdpcontext->rdp; WINPR_ASSERT(rdp); + if (!ensure_wellknown(aad->rdpcontext)) + return FALSE; + WINPR_JSON* obj = WINPR_JSON_GetObjectItem(rdp->wellknown, "token_endpoint"); if (!obj) { @@ -292,7 +309,7 @@ int aad_client_begin(rdpAad* aad) return -1; } - if (!aad_fetch_wellknown(aad)) + if (!aad_fetch_wellknown(aad->log, aad->rdpcontext)) return -1; const BOOL arc = instance->GetAccessToken(instance, ACCESS_TOKEN_TYPE_AAD, &aad->access_token, @@ -756,12 +773,19 @@ int aad_client_begin(rdpAad* aad) WLog_Print(aad->log, WLOG_ERROR, "AAD security not compiled in, aborting!"); return -1; } + int aad_recv(rdpAad* aad, wStream* s) { WINPR_ASSERT(aad); WLog_Print(aad->log, WLOG_ERROR, "AAD security not compiled in, aborting!"); return -1; } + +static BOOL ensure_wellknown(WINPR_ATTR_UNUSED rdpContext* context) +{ + return FALSE; +} + #endif rdpAad* aad_new(rdpContext* context, rdpTransport* transport) @@ -855,26 +879,24 @@ cleanup: return token; } -BOOL aad_fetch_wellknown(rdpAad* aad) +BOOL aad_fetch_wellknown(wLog* log, rdpContext* context) { - WINPR_ASSERT(aad); - WINPR_ASSERT(aad->rdpcontext); + WINPR_ASSERT(context); - rdpRdp* rdp = aad->rdpcontext->rdp; + rdpRdp* rdp = context->rdp; WINPR_ASSERT(rdp); if (rdp->wellknown) return TRUE; const char* base = - freerdp_settings_get_string(aad->rdpcontext->settings, FreeRDP_GatewayAzureActiveDirectory); + freerdp_settings_get_string(context->settings, FreeRDP_GatewayAzureActiveDirectory); const BOOL useTenant = - freerdp_settings_get_bool(aad->rdpcontext->settings, FreeRDP_GatewayAvdUseTenantid); + freerdp_settings_get_bool(context->settings, FreeRDP_GatewayAvdUseTenantid); const char* tenantid = "common"; if (useTenant) - tenantid = - freerdp_settings_get_string(aad->rdpcontext->settings, FreeRDP_GatewayAvdAadtenantid); - rdp->wellknown = freerdp_utils_aad_get_wellknown(aad->log, base, tenantid); + tenantid = freerdp_settings_get_string(context->settings, FreeRDP_GatewayAvdAadtenantid); + rdp->wellknown = freerdp_utils_aad_get_wellknown(log, base, tenantid); return rdp->wellknown ? TRUE : FALSE; } @@ -889,7 +911,7 @@ const char* freerdp_utils_aad_get_wellknown_custom_string(rdpContext* context, c WINPR_ASSERT(context); WINPR_ASSERT(context->rdp); - if (!context->rdp->wellknown) + if (!ensure_wellknown(context)) return NULL; WINPR_JSON* obj = WINPR_JSON_GetObjectItem(context->rdp->wellknown, which); @@ -965,7 +987,7 @@ WINPR_JSON* freerdp_utils_aad_get_wellknown_custom_object(rdpContext* context, c WINPR_ASSERT(context); WINPR_ASSERT(context->rdp); - if (!context->rdp->wellknown) + if (!ensure_wellknown(context)) return NULL; return WINPR_JSON_GetObjectItem(context->rdp->wellknown, which);