diff --git a/CMakeLists.txt b/CMakeLists.txt index b090ec3fd5..a6e6e2c8ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -606,6 +606,13 @@ function(prusaslicer_copy_dlls target) COMMENT "Copy mpfr runtime to build tree" VERBATIM) + if(DEFINED DESTDIR) + add_custom_command(TARGET ${target} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${DESTDIR}/usr/local/bin/WebView2Loader.dll ${_out_dir} + COMMENT "Copy WebView2Loader runtime to build tree" + VERBATIM) + endif () + endfunction() diff --git a/deps/+WebView2/WebView2.cmake b/deps/+WebView2/WebView2.cmake new file mode 100644 index 0000000000..b47a1ca30f --- /dev/null +++ b/deps/+WebView2/WebView2.cmake @@ -0,0 +1,60 @@ + +if (MSVC) + + # Update the following variables if updating WebView2 SDK + set(WEBVIEW2_VERSION "1.0.705.50") + set(WEBVIEW2_URL "https://www.nuget.org/api/v2/package/Microsoft.Web.WebView2/${WEBVIEW2_VERSION}") + set(WEBVIEW2_SHA256 "6a34bb553e18cfac7297b4031f3eac2558e439f8d16a45945c22945ac404105d") + + set(WEBVIEW2_DEFAULT_PACKAGE_DIR "${CMAKE_CURRENT_BINARY_DIR}/dep_WebView2-prefix/packages/Microsoft.Web.WebView2.${WEBVIEW2_VERSION}") + set(WEBVIEW2_DOWNLOAD_DIR "${CMAKE_CURRENT_BINARY_DIR}/dep_WebView2-prefix/download") + + #message(STATUS "WEBVIEW2_DEFAULT_PACKAGE_DIR = ${WEBVIEW2_DEFAULT_PACKAGE_DIR}") + + if(NOT EXISTS ${WEBVIEW2_PACKAGE_DIR}) + unset(WEBVIEW2_PACKAGE_DIR CACHE) + endif() + + set(WEBVIEW2_PACKAGE_DIR ${WEBVIEW2_DEFAULT_PACKAGE_DIR} CACHE PATH "WebView2 SDK PATH" FORCE) + + #file(MAKE_DIRECTORY ${DEP_DOWNLOAD_DIR}/WebView2) + + message(STATUS "WEBVIEW2_URL = ${WEBVIEW2_URL}") + message(STATUS "WEBVIEW2_DOWNLOAD_DIR = ${WEBVIEW2_DOWNLOAD_DIR}") + file(DOWNLOAD + ${WEBVIEW2_URL} + ${WEBVIEW2_DOWNLOAD_DIR}/WebView2.nuget + EXPECTED_HASH SHA256=${WEBVIEW2_SHA256}) + + file(MAKE_DIRECTORY ${WEBVIEW2_PACKAGE_DIR}) + + execute_process(COMMAND + ${CMAKE_COMMAND} -E tar x ${WEBVIEW2_DOWNLOAD_DIR}/WebView2.nuget + WORKING_DIRECTORY ${WEBVIEW2_PACKAGE_DIR} + ) + + set(_srcdir ${WEBVIEW2_PACKAGE_DIR}/build/native) + set(_dstdir ${DESTDIR}/usr/local) + + set(_output ${_dstdir}/include/WebView2.h + ${_dstdir}/bin/WebView2Loader.dll) + + if(NOT EXISTS ${_dstdir}/include) + file(MAKE_DIRECTORY ${_dstdir}/include) + endif() + + if(NOT EXISTS ${_dstdir}/bin) + file(MAKE_DIRECTORY ${_dstdir}/bin) + endif() + + add_custom_command( + OUTPUT ${_output} + COMMAND ${CMAKE_COMMAND} -E copy ${_srcdir}/include/WebView2.h ${_dstdir}/include/ + COMMAND ${CMAKE_COMMAND} -E copy ${_srcdir}/x${DEPS_BITS}/WebView2Loader.dll ${_dstdir}/bin/ + ) + + add_custom_target(dep_WebView2 SOURCES ${_output}) + + set(WEBVIEW2_PACKAGE_DIR ${WEBVIEW2_PACKAGE_DIR} CACHE INTERNAL "" FORCE) + +endif () \ No newline at end of file diff --git a/deps/+wxWidgets/wxWidgets.cmake b/deps/+wxWidgets/wxWidgets.cmake index ac15a48d0e..f0745d115e 100644 --- a/deps/+wxWidgets/wxWidgets.cmake +++ b/deps/+wxWidgets/wxWidgets.cmake @@ -14,6 +14,18 @@ if (UNIX AND NOT APPLE) # wxWidgets will not use char as the underlying type for set (_unicode_utf8 ON) endif() +if (MSVC) + set(_wx_webview "-DwxUSE_WEBVIEW_EDGE=ON") +else () + set(_wx_webview "-DwxUSE_WEBVIEW=ON") +endif () + +if (UNIX AND NOT APPLE) + set(_wx_secretstore "-DwxUSE_SECRETSTORE=OFF") +else () + set(_wx_secretstore "-DwxUSE_SECRETSTORE=ON") +endif () + add_cmake_project(wxWidgets URL https://github.com/prusa3d/wxWidgets/archive/78aa2dc0ea7ce99dc19adc1140f74c3e2e3f3a26.zip URL_HASH SHA256=94b7d972373503e380e5a8b0ca63b1ccb956da4006402298dd89a0c5c7041b1e @@ -21,7 +33,7 @@ add_cmake_project(wxWidgets "-DCMAKE_DEBUG_POSTFIX:STRING=" -DwxBUILD_PRECOMP=ON ${_wx_toolkit} - -DwxUSE_MEDIACTRL=OFF + -DwxUSE_MEDIACTRL=ON -DwxUSE_DETECT_SM=OFF -DwxUSE_UNICODE=ON -DwxUSE_UNICODE_UTF8=${_unicode_utf8} @@ -39,6 +51,8 @@ add_cmake_project(wxWidgets -DwxUSE_XTEST=OFF -DwxUSE_GLCANVAS_EGL=OFF -DwxUSE_WEBREQUEST=OFF + ${_wx_webview} + ${_wx_secretstore} ) set(DEP_wxWidgets_DEPENDS ZLIB PNG EXPAT TIFF JPEG NanoSVG) diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index 175b64a619..a50243766f 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -140,6 +140,11 @@ if (UNIX) endif () endif () +if (MSVC) + list(APPEND REQUIRED_PACKAGES WebView2) +endif() + + list(APPEND SYSTEM_PROVIDED_PACKAGES ${${PROJECT_NAME}_PLATFORM_PACKAGES}) list(REMOVE_DUPLICATES SYSTEM_PROVIDED_PACKAGES) diff --git a/deps/wxWidgets/wxWidgets.cmake b/deps/wxWidgets/wxWidgets.cmake deleted file mode 100644 index 05b7a88b79..0000000000 --- a/deps/wxWidgets/wxWidgets.cmake +++ /dev/null @@ -1,47 +0,0 @@ -set(_wx_toolkit "") -if(CMAKE_SYSTEM_NAME STREQUAL "Linux") - set(_gtk_ver 2) - if (DEP_WX_GTK3) - set(_gtk_ver 3) - endif () - set(_wx_toolkit "-DwxBUILD_TOOLKIT=gtk${_gtk_ver}") -endif() - -set(_unicode_utf8 OFF) -if (UNIX AND NOT APPLE) # wxWidgets will not use char as the underlying type for wxString unless its forced to. - set (_unicode_utf8 ON) -endif() - -prusaslicer_add_cmake_project(wxWidgets - URL https://github.com/prusa3d/wxWidgets/archive/78aa2dc0ea7ce99dc19adc1140f74c3e2e3f3a26.zip - URL_HASH SHA256=94b7d972373503e380e5a8b0ca63b1ccb956da4006402298dd89a0c5c7041b1e - DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} dep_TIFF dep_JPEG dep_NanoSVG - CMAKE_ARGS - -DwxBUILD_PRECOMP=ON - ${_wx_toolkit} - "-DCMAKE_DEBUG_POSTFIX:STRING=" - -DwxBUILD_DEBUG_LEVEL=0 - -DwxUSE_MEDIACTRL=OFF - -DwxUSE_DETECT_SM=OFF - -DwxUSE_UNICODE=ON - -DwxUSE_UNICODE_UTF8=${_unicode_utf8} - -DwxUSE_OPENGL=ON - -DwxUSE_LIBPNG=sys - -DwxUSE_ZLIB=sys - -DwxUSE_NANOSVG=sys - -DwxUSE_NANOSVG_EXTERNAL=ON - -DwxUSE_REGEX=OFF - -DwxUSE_LIBXPM=builtin - -DwxUSE_LIBJPEG=sys - -DwxUSE_LIBTIFF=sys - -DwxUSE_EXPAT=sys - -DwxUSE_LIBSDL=OFF - -DwxUSE_XTEST=OFF - -DwxUSE_GLCANVAS_EGL=OFF - -DwxUSE_WEBREQUEST=OFF - -DwxUSE_SECRETSTORE=ON -) - -if (MSVC) - add_debug_dep(dep_wxWidgets) -endif () \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9bbddbff90..31e14c3f0f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -48,7 +48,7 @@ if (SLIC3R_GUI) if (CMAKE_SYSTEM_NAME STREQUAL "Linux") set (wxWidgets_CONFIG_OPTIONS "--toolkit=gtk${SLIC3R_GTK}") endif () - find_package(wxWidgets 3.2 MODULE REQUIRED COMPONENTS base core adv html gl) + find_package(wxWidgets 3.2 MODULE REQUIRED COMPONENTS base core adv html gl webview media) include(${wxWidgets_USE_FILE}) diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 533c693812..0594bb672e 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -125,6 +125,7 @@ public: TYPE_PHYSICAL_PRINTER, // This type is here to support search through the Preferences TYPE_PREFERENCES, + TYPE_WEBVIEW, }; Type type = TYPE_INVALID; diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index b2f467b99f..4c297c93dd 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -23,6 +23,12 @@ set(SLIC3R_GUI_SOURCES GUI/Auth.hpp GUI/AuthSession.cpp GUI/AuthSession.hpp + GUI/UserAccount.cpp + GUI/UserAccount.hpp + GUI/WebViewDialog.cpp + GUI/WebViewDialog.hpp + GUI/WebView.cpp + GUI/WebView.hpp GUI/SysInfoDialog.cpp GUI/SysInfoDialog.hpp GUI/KBShortcutsDialog.cpp diff --git a/src/slic3r/GUI/Auth.cpp b/src/slic3r/GUI/Auth.cpp index 1a91aa4761..599b59a993 100644 --- a/src/slic3r/GUI/Auth.cpp +++ b/src/slic3r/GUI/Auth.cpp @@ -2,12 +2,9 @@ #include "GUI_App.hpp" #include "format.hpp" #include "../Utils/Http.hpp" -#include "I18N.hpp" +#include "slic3r/GUI/I18N.hpp" #include -#include -#include -#include #include #include #include @@ -29,6 +26,10 @@ #include #endif // WIN32 +#ifdef __APPLE__ +#include +#endif + #ifdef __linux__ #include #include @@ -38,7 +39,7 @@ namespace fs = boost::filesystem; -namespace pt = boost::property_tree; + namespace Slic3r { namespace GUI { @@ -144,14 +145,13 @@ PrusaAuthCommunication::PrusaAuthCommunication(wxEvtHandler* evt_handler, AppCon refresh_token = app_config->get("refresh_token"); shared_session_key = app_config->get("shared_session_key"); } - if (!access_token.empty() || !refresh_token.empty()) m_remember_session = true; m_session = std::make_unique(evt_handler, access_token, refresh_token, shared_session_key); init_session_thread(); // perform login at the start - do we want this if (m_remember_session) - login(); + do_login(); } PrusaAuthCommunication::~PrusaAuthCommunication() { @@ -182,7 +182,14 @@ void PrusaAuthCommunication::set_username(const std::string& username, AppConfig app_config->set("refresh_token", m_remember_session ? m_session->get_refresh_token() : std::string()); app_config->set("shared_session_key", m_remember_session ? m_session->get_shared_session_key() : std::string()); } - + } +} + +std::string PrusaAuthCommunication::get_access_token() +{ + { + std::lock_guard lock(m_session_mutex); + return m_session->get_access_token(); } } @@ -205,7 +212,7 @@ bool PrusaAuthCommunication::is_logged() { return !m_username.empty(); } -void PrusaAuthCommunication::login() +void PrusaAuthCommunication::do_login() { { std::lock_guard lock(m_session_mutex); @@ -217,7 +224,7 @@ void PrusaAuthCommunication::login() } wakeup_session_thread(); } -void PrusaAuthCommunication::logout() +void PrusaAuthCommunication::do_logout() { { std::lock_guard lock(m_session_mutex); @@ -301,78 +308,9 @@ void PrusaAuthCommunication::wakeup_session_thread() m_thread_stop_condition.notify_all(); } -namespace { -/* -void proccess_tree(const pt::ptree& tree, const std::string depth, std::string& out) -{ - - for (const auto& section : tree) { - printf("%s%s", depth.c_str(), section.first.c_str()); - if (!section.second.data().empty()) { - if (section.first == "printer_type_name") { - out += section.second.data(); - out += " : "; - } else if (section.first == "state") { - out += section.second.data(); - out += "\n"; - } - printf(" : %s\n", section.second.data().c_str()); - } else { - printf("\n"); - proccess_tree(section.second, depth + " ", out); - } - - } -} -*/ -typedef std::map ModelCounter; -void proccess_tree(const pt::ptree& tree, const std::string depth, ModelCounter& models) -{ - for (const auto& section : tree) { - //printf("%s%s", depth.c_str(), section.first.c_str()); - if (!section.second.data().empty()) { - //printf(" : %s\n", section.second.data().c_str()); - } - else { - if (section.first == "printer_type_compatible") { - for (const auto& sub : section.second) { - if (!sub.second.data().empty()) { - //printf(" : %s\n", section.second.data().c_str()); - if(models.find(sub.second.data()) == models.end()) - models.emplace(sub.second.data(), 1); - else - models[sub.second.data()]++; - } - } - } else { - //printf("\n"); - proccess_tree(section.second, depth + " ", models); - } - } - } -} -} -std::string PrusaAuthCommunication::proccess_prusaconnect_printers_message(const std::string& message) -{ - std::string out; - try { - std::stringstream ss(message); - pt::ptree ptree; - pt::read_json(ss, ptree); - - ModelCounter counter; - proccess_tree(ptree, "", counter); - for (const auto model : counter) - { - out += GUI::format("%1%x %2%\n", std::to_string(model.second), model.first); - } - } - catch (const std::exception& e) { - BOOST_LOG_TRIVIAL(error) << "Could not parse prusaconnect message. " << e.what(); - } - return out; -} + + std::string CodeChalengeGenerator::generate_chalenge(const std::string& verifier) { @@ -458,8 +396,28 @@ std::string CodeChalengeGenerator::sha256(const std::string& input) } return output; } -#endif // WIN32 -#ifdef __linux__ +#elif __APPLE__ +std::string CodeChalengeGenerator::sha256(const std::string& input) { + // Initialize the context + CC_SHA256_CTX sha256; + CC_SHA256_Init(&sha256); + + // Update the context with the input data + CC_SHA256_Update(&sha256, input.c_str(), static_cast(input.length())); + + // Finalize the hash and retrieve the result + unsigned char digest[CC_SHA256_DIGEST_LENGTH]; + CC_SHA256_Final(digest, &sha256); + + // Convert the result to a string + char hashString[CC_SHA256_DIGEST_LENGTH * 2 + 1]; + for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) { + sprintf(&hashString[i * 2], "%02x", digest[i]); + } + + return std::string(hashString); +} +#else std::string CodeChalengeGenerator::sha256(const std::string& input) { EVP_MD_CTX* mdctx; const EVP_MD* md; diff --git a/src/slic3r/GUI/Auth.hpp b/src/slic3r/GUI/Auth.hpp index 77274a2e32..50a8a9b568 100644 --- a/src/slic3r/GUI/Auth.hpp +++ b/src/slic3r/GUI/Auth.hpp @@ -34,14 +34,13 @@ public: // UI Session thread Interface // bool is_logged(); - void login(); - void logout(); + void do_login(); + void do_logout(); // Trigger function starts various remote operations - // Each user action is implemented in different UserAction class and stored in m_actions. void enqueue_user_id_action(); void enqueue_connect_dummy_action(); void enqueue_connect_printers_action(); - void set_remember_session(bool b) { m_remember_session = b; } + // Callbacks - called from UI after receiving Event from Session thread. Some might use Session thread. // @@ -51,13 +50,15 @@ public: void set_username(const std::string& username, AppConfig* app_config); + void set_remember_session(bool b) { m_remember_session = b; } std::string get_username() const { return m_username; } - - std::string proccess_prusaconnect_printers_message(const std::string& message); + std::string get_access_token(); + + private: - std::unique_ptr m_session; + std::unique_ptr m_session; std::thread m_thread; std::mutex m_session_mutex; std::mutex m_thread_stop_mutex; @@ -73,6 +74,9 @@ private: void init_session_thread(); void login_redirect(); std::string client_id() const { return "UfTRUm5QjWwaQEGpWQBHGHO3reAyuzgOdBaiqO52"; } + + + }; } } diff --git a/src/slic3r/GUI/AuthSession.cpp b/src/slic3r/GUI/AuthSession.cpp index d4ab7f3431..7d24d87eef 100644 --- a/src/slic3r/GUI/AuthSession.cpp +++ b/src/slic3r/GUI/AuthSession.cpp @@ -72,8 +72,11 @@ void UserActionGetWithEvent::perform(const std::string& access_token, UserAction void AuthSession::process_action_queue() { - if (m_priority_action_queue.empty() && m_action_queue.empty()) + BOOST_LOG_TRIVIAL(debug) << "process_action_queue start"; + if (m_priority_action_queue.empty() && m_action_queue.empty()) { + BOOST_LOG_TRIVIAL(debug) << "process_action_queue queues empty"; return; + } if (this->is_initialized()) { // if priority queue already has some action f.e. to exchange tokens, the test should not be neccessary but also shouldn't be problem @@ -86,18 +89,22 @@ void AuthSession::process_action_queue() m_priority_action_queue.pop(); } - if (!this->is_initialized()) + if (!this->is_initialized()) { + BOOST_LOG_TRIVIAL(debug) << "process_action_queue not initialized"; return; + } while (!m_action_queue.empty()) { m_actions[m_action_queue.front().action_id]->perform(m_access_token, m_action_queue.front().success_callback, m_action_queue.front().fail_callback, m_action_queue.front().input); if (!m_action_queue.empty()) m_action_queue.pop(); } + BOOST_LOG_TRIVIAL(debug) << "process_action_queue end"; } void AuthSession::enqueue_action(UserActionID id, UserActionSuccessFn success_callback, UserActionFailFn fail_callback, const std::string& input) { + BOOST_LOG_TRIVIAL(info) << "enqueue_action " << (int)id; m_action_queue.push({ id, success_callback, fail_callback, input }); } diff --git a/src/slic3r/GUI/AuthSession.hpp b/src/slic3r/GUI/AuthSession.hpp index afb9392e70..4b6b7316d9 100644 --- a/src/slic3r/GUI/AuthSession.hpp +++ b/src/slic3r/GUI/AuthSession.hpp @@ -103,8 +103,8 @@ public: m_actions[UserActionID::CODE_FOR_TOKEN] = std::make_unique("EXCHANGE_TOKENS", "https://test-account.prusa3d.com/o/token/"); m_actions[UserActionID::TEST_CONNECTION] = std::make_unique("TEST_CONNECTION", "https://test-account.prusa3d.com/api/v1/me/", evt_handler, EVT_PA_ID_USER_SUCCESS, EVT_PRUSAAUTH_FAIL); m_actions[UserActionID::USER_ID] = std::make_unique("USER_ID", "https://test-account.prusa3d.com/api/v1/me/", evt_handler, EVT_PA_ID_USER_SUCCESS, EVT_PRUSAAUTH_FAIL); - m_actions[UserActionID::CONNECT_DUMMY] = std::make_unique("CONNECT_DUMMY", "dev.connect.prusa:8000/slicer/dummy", evt_handler, EVT_PRUSAAUTH_SUCCESS, EVT_PRUSAAUTH_FAIL); - m_actions[UserActionID::CONNECT_PRINTERS] = std::make_unique("CONNECT_PRINTERS", "dev.connect.prusa:8000/slicer/printers", evt_handler, EVT_PRUSACONNECT_PRINTERS_SUCCESS, EVT_PRUSAAUTH_FAIL); + m_actions[UserActionID::CONNECT_DUMMY] = std::make_unique("CONNECT_DUMMY", "https://dev.connect.prusa3d.com/slicer/dummy"/*"dev.connect.prusa:8000/slicer/dummy"*/, evt_handler, EVT_PRUSAAUTH_SUCCESS, EVT_PRUSAAUTH_FAIL); + m_actions[UserActionID::CONNECT_PRINTERS] = std::make_unique("CONNECT_PRINTERS", "https://dev.connect.prusa3d.com/slicer/printers"/*"dev.connect.prusa:8000/slicer/printers"*/, evt_handler, EVT_PRUSACONNECT_PRINTERS_SUCCESS, EVT_PRUSAAUTH_FAIL); } ~AuthSession() { diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 596fa2b697..5971c4f97d 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -1536,11 +1536,9 @@ bool PageDownloader::on_finish_downloader() const return m_downloader->on_finish(); } -bool DownloaderUtils::Worker::perform_register(const std::string& path_override/* = {}*/) +bool DownloaderUtils::Worker::perform_register(const std::string& path) { - boost::filesystem::path aux_dest (GUI::into_u8(path_name())); - if (!path_override.empty()) - aux_dest = boost::filesystem::path(path_override); + boost::filesystem::path aux_dest (path); boost::system::error_code ec; boost::filesystem::path chosen_dest = boost::filesystem::absolute(aux_dest, ec); if(ec) @@ -1549,7 +1547,7 @@ bool DownloaderUtils::Worker::perform_register(const std::string& path_override/ if (chosen_dest.empty() || !boost::filesystem::is_directory(chosen_dest, ec) || ec) { std::string err_msg = GUI::format("%1%\n\n%2%",_L("Chosen directory for downloads does not exist.") ,chosen_dest.string()); BOOST_LOG_TRIVIAL(error) << err_msg; - show_error(m_parent, err_msg); + show_error(/*m_parent*/ nullptr, err_msg); return false; } BOOST_LOG_TRIVIAL(info) << "Downloader registration: Directory for downloads: " << chosen_dest.string(); @@ -1613,12 +1611,12 @@ bool DownloaderUtils::Worker::on_finish() { BOOST_LOG_TRIVIAL(debug) << "PageDownloader::on_finish_downloader ac_value " << ac_value << " downloader_checked " << downloader_checked; if (ac_value && downloader_checked) { // already registered but we need to do it again - if (!perform_register()) + if (!perform_register(GUI::into_u8(path_name()))) return false; app_config->set("downloader_url_registered", "1"); } else if (!ac_value && downloader_checked) { // register - if (!perform_register()) + if (!perform_register(GUI::into_u8(path_name()))) return false; app_config->set("downloader_url_registered", "1"); } else if (ac_value && !downloader_checked) { diff --git a/src/slic3r/GUI/ConfigWizard.hpp b/src/slic3r/GUI/ConfigWizard.hpp index 43e3b103af..59b98c8f6f 100644 --- a/src/slic3r/GUI/ConfigWizard.hpp +++ b/src/slic3r/GUI/ConfigWizard.hpp @@ -49,7 +49,7 @@ namespace DownloaderUtils { void set_path_name(const std::string& name); bool on_finish(); - bool perform_register(const std::string& path_override = {}); + static bool perform_register(const std::string& path); #ifdef __linux__ bool get_perform_registration_linux() { return perform_registration_linux; } #endif // __linux__ diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index c0115afd8a..db0eeb7bbd 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -98,7 +98,9 @@ #include "Downloader.hpp" #include "PhysicalPrinterDialog.hpp" #include "WifiConfigDialog.hpp" -#include "Auth.hpp" +#include "UserAccount.hpp" +#include "MediaControlPanel.hpp" +#include "WebViewDialog.hpp" #include "BitmapCache.hpp" #include "Notebook.hpp" @@ -2501,6 +2503,8 @@ void GUI_App::add_config_menu(wxMenuBar *menu) updatable_item = local_menu->Append(config_id_base + ConfigMenuConnectDummy, _L("PrusaConnect Printers"), _L("")); updatable_item->Enable(false); m_config_menu_updatable_items.emplace(ConfigMenuIDs::ConfigMenuConnectDummy, updatable_item); + local_menu->Append(config_id_base + ConfigMenuConnectDialog, _L("Connect Dialog"), _L("Connect Dialog")); + local_menu->Append(config_id_base + ConfigMenuMediaDialog, _L("Media Dialog"), _L("Media Dialog")); #if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) //if (DesktopIntegrationDialog::integration_possible()) local_menu->Append(config_id_base + ConfigMenuDesktopIntegration, _L("Desktop Integration"), _L("Desktop Integration")); @@ -2551,15 +2555,15 @@ void GUI_App::add_config_menu(wxMenuBar *menu) break; case ConfigMenuAuthLogin: { - if (this->plater()->get_auth_communication()->is_logged()) - this->plater()->get_auth_communication()->logout(); + if (this->plater()->get_user_account()->is_logged()) + this->plater()->get_user_account()->do_logout(); else - this->plater()->get_auth_communication()->login(); + this->plater()->get_user_account()->do_login(); } break; case ConfigMenuConnectDummy: { - this->plater()->get_auth_communication()->enqueue_connect_printers_action(); + this->plater()->get_user_account()->enqueue_connect_printers_action(); } break; #ifdef __linux__ @@ -2661,6 +2665,13 @@ void GUI_App::add_config_menu(wxMenuBar *menu) */ } break; + case ConfigMenuMediaDialog: + //MediaDialog(nullptr).ShowModal(); + wxMediaPlayerDialog("Media").ShowModal(); + break; + case ConfigMenuConnectDialog: + WebViewDialog(plater()).ShowModal(); + break; default: break; } @@ -2679,8 +2690,8 @@ void GUI_App::add_config_menu(wxMenuBar *menu) } void GUI_App::update_config_menu() { - m_config_menu_updatable_items[ConfigMenuIDs::ConfigMenuAuthLogin]->SetItemLabel(this->plater()->get_auth_communication()->is_logged() ? _L("PrusaAuth Log out") : _L("PrusaAuth Log in")); - m_config_menu_updatable_items[ConfigMenuIDs::ConfigMenuConnectDummy]->Enable(this->plater()->get_auth_communication()->is_logged()); + m_config_menu_updatable_items[ConfigMenuIDs::ConfigMenuAuthLogin]->SetItemLabel(this->plater()->get_user_account()->is_logged() ? _L("PrusaAuth Log out") : _L("PrusaAuth Log in")); + m_config_menu_updatable_items[ConfigMenuIDs::ConfigMenuConnectDummy]->Enable(this->plater()->get_user_account()->is_logged()); } void GUI_App::open_preferences(const std::string& highlight_option /*= std::string()*/, const std::string& tab_name/*= std::string()*/) { @@ -3465,7 +3476,7 @@ bool GUI_App::open_login_browser_with_dialog(const wxString& url, wxWindow* pare dialog.ShowCheckBox(_L("Remember me"), true); auto answer = dialog.ShowModal(); launch = answer == wxID_YES; - plater()->get_auth_communication()->set_remember_session(dialog.IsCheckBoxChecked()); + plater()->get_user_account()->set_remember_session(dialog.IsCheckBoxChecked()); return launch && wxLaunchDefaultBrowser(url, flags); } @@ -3665,5 +3676,72 @@ void GUI_App::open_wifi_config_dialog(bool forced, const wxString& drive_path/* m_wifi_config_dialog_shown = false; } +void GUI_App::select_printer_with_load(Preset* prst, const std::string& preset_name, const std::string& model_name, const std::string& nozzle_name, const std::string& nozzle) +{ + assert(prst); + if (prst->is_visible) + bool suc = get_tab(Preset::Type::TYPE_PRINTER)->select_preset(preset_name); + else { + AppConfig appconfig_new(AppConfig::EAppMode::Editor); + appconfig_new.set_vendors(*app_config); + prst->vendor->models; + if (auto it = std::find_if(prst->vendor->models.begin(), prst->vendor->models.end(), [model_name](const VendorProfile::PrinterModel& a) { + if (a.name == model_name) + return true; + else + return false; + }); it != prst->vendor->models.end()) + { + appconfig_new.set_variant("PrusaResearch", it->id, nozzle, true); + app_config->set_vendors(appconfig_new); + + preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem, + { it->id, nozzle, "", "" }); + load_current_presets(); + } + } +} + +void GUI_App::handle_web_request(std::string cmd) +{ + BOOST_LOG_TRIVIAL(error) << "Handling web request: " << cmd; + // return to plater + //this->mainframe->select_tab(size_t(0)); + // parse message + std::string model_name = plater()->get_user_account()->get_model_from_json(cmd); + std::string nozzle = plater()->get_user_account()->get_nozzle_from_json(cmd); + std::string nozzle_name = nozzle.empty() ? "" : (nozzle +" nozzle"); + assert(!model_name.empty()); + assert(!nozzle_name.empty()); + if (model_name.empty() && nozzle_name.empty()) + return; + // select printer + std::string preset_name = nozzle.empty() ? model_name : format("%1% %2%",model_name, nozzle_name); + Preset* prst = preset_bundle->printers.find_preset(preset_name, false); + if (!prst) { + model_name = std::string(*preset_bundle->printers.get_preset_name_renamed(model_name)); + preset_name = nozzle.empty() ? model_name : format("%1% %2%", model_name, nozzle_name); + prst = preset_bundle->printers.find_preset(preset_name, false); + } + if (!prst) { + preset_name = model_name; + prst = preset_bundle->printers.find_preset(preset_name, false); + } + if (prst) { + select_printer_with_load(prst, preset_name, model_name, nozzle_name, nozzle); + // notification + std::string out = GUI::format("Select Printer:\n%1%", preset_name); + this->plater()->get_notification_manager()->close_notification_of_type(NotificationType::PrusaAuthUserID); + this->plater()->get_notification_manager()->push_notification(NotificationType::PrusaAuthUserID, NotificationManager::NotificationLevel::ImportantNotificationLevel, out); + } else { + // notification + std::string out = GUI::format("Printer not found:\n%1%", preset_name); + this->plater()->get_notification_manager()->close_notification_of_type(NotificationType::PrusaAuthUserID); + this->plater()->get_notification_manager()->push_notification(NotificationType::PrusaAuthUserID, NotificationManager::NotificationLevel::ImportantNotificationLevel, out); + } + + +} + } // GUI } //Slic3r diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 6271b9a905..28963c9dea 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -98,8 +98,10 @@ enum ConfigMenuIDs { ConfigMenuTakeSnapshot, ConfigMenuUpdateConf, ConfigMenuUpdateApp, + ConfigMenuMediaDialog, ConfigMenuAuthLogin, ConfigMenuConnectDummy, + ConfigMenuConnectDialog, ConfigMenuDesktopIntegration, ConfigMenuPreferences, ConfigMenuModeSimple, @@ -401,6 +403,24 @@ public: void open_wifi_config_dialog(bool forced, const wxString& drive_path = {}); bool get_wifi_config_dialog_shown() const { return m_wifi_config_dialog_shown; } + + void request_login(bool show_user_info = false) {} + bool check_login() { return false; } + void get_login_info() {} + bool is_user_login() { return true; } + + void request_user_login(int online_login) {} + void request_user_logout() {} + int request_user_unbind(std::string dev_id) { return 0; } + void handle_web_request(std::string cmd); + void select_printer_with_load(Preset* prst, const std::string& preset_name, const std::string& printer_name, const std::string& nozzle_name, const std::string& nozzle ); + void handle_script_message(std::string msg) {} + void request_model_download(std::string import_json) {} + void download_project(std::string project_id) {} + void request_project_download(std::string project_id) {} + void request_open_project(std::string project_id) {} + void request_remove_project(std::string project_id) {} + private: bool on_init_inner(); void init_app_config(); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 7ca9dc045b..23b9b83a92 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -62,6 +62,9 @@ #include "GalleryDialog.hpp" #include "NotificationManager.hpp" #include "Preferences.hpp" +#include "WebViewDialog.hpp" +#include "MediaControlPanel.hpp" +#include "MediaControl.hpp" #ifdef _WIN32 #include @@ -724,6 +727,10 @@ void MainFrame::init_tabpanel() if (panel == nullptr || (tab != nullptr && !tab->supports_printer_technology(m_plater->printer_technology()))) return; + // temporary fix - WebViewPanel is not inheriting from Tab -> would jump to select Plater + if (panel && !tab) + return; + auto& tabs_list = wxGetApp().tabs_list; if (tab && std::find(tabs_list.begin(), tabs_list.end(), tab) != tabs_list.end()) { // On GTK, the wxEVT_NOTEBOOK_PAGE_CHANGED event is triggered @@ -739,6 +746,13 @@ void MainFrame::init_tabpanel() select_tab(size_t(0)); // select Plater }); + if (wxGetApp().is_editor()) { + + //m_webview = new WebViewPanel(m_tabpanel); + //m_tabpanel->AddPage(m_webview, "web", "cog"/*, "tab_home_active"*/); + //m_param_panel = new ParamsPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL); + } + m_plater = new Plater(this, this); m_plater->Hide(); @@ -823,6 +837,13 @@ void MainFrame::create_preset_tabs() add_created_tab(new TabSLAPrint(m_tabpanel), "cog"); add_created_tab(new TabSLAMaterial(m_tabpanel), "resin"); add_created_tab(new TabPrinter(m_tabpanel), wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF ? "printer" : "sla_printer"); + + m_webview = new WebViewPanel(m_tabpanel); + m_tabpanel->AddPage(m_webview, "Web View"); + /* + m_media = new MediaMainPanel(this); + m_tabpanel->AddPage(m_media, "Media"); + */ } void MainFrame::add_created_tab(Tab* panel, const std::string& bmp_name /*= ""*/) @@ -1510,6 +1531,9 @@ void MainFrame::init_menubar_as_editor() append_menu_check_item(viewMenu, wxID_ANY, _L("&Collapse Sidebar") + sep + "Shift+" + sep_space + "Tab", _L("Collapse sidebar"), [this](wxCommandEvent&) { m_plater->collapse_sidebar(!m_plater->is_sidebar_collapsed()); }, this, []() { return true; }, [this]() { return m_plater->is_sidebar_collapsed(); }, this); + append_menu_check_item(viewMenu, wxID_ANY, _L("&Load URL"), _L("Load URL"), + [this](wxCommandEvent&) { wxString url = "https://dev.connect.prusa3d.com/"/*"file:///C:/Projects/BambuStudio/resources/web/homepage/index.html"*/; m_webview->load_url(url); }, this, + []() { return true; }, []() { return true; }, this); #ifndef __APPLE__ // OSX adds its own menu item to toggle fullscreen. append_menu_check_item(viewMenu, wxID_ANY, _L("&Fullscreen") + "\t" + "F11", _L("Fullscreen"), diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 42d6afd129..6584df4a5c 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -43,6 +43,8 @@ class Plater; class MainFrame; class PreferencesDialog; class GalleryDialog; +class WebViewPanel; +class MediaMainPanel; enum QuickSlice { @@ -219,6 +221,8 @@ public: PreferencesDialog* preferences_dialog { nullptr }; PrintHostQueueDialog* m_printhost_queue_dlg; GalleryDialog* m_gallery_dialog{ nullptr }; + WebViewPanel* m_webview{ nullptr }; + MediaMainPanel* m_media{ nullptr}; #ifdef __APPLE__ std::unique_ptr m_taskbar_icon; diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 9759fd7d1f..2dfefffa35 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -129,7 +129,7 @@ enum class NotificationType // MacOS specific - PS comes forward even when downloader is not allowed URLNotRegistered, // Config file was detected during startup, open wifi config dialog via hypertext - WifiConfigFileDetected + WifiConfigFileDetected, // PrusaAuthUserID, }; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 859a55c8c7..1c2ff8e7de 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -35,6 +35,8 @@ #include #include #include +#include +#include #include #include @@ -119,7 +121,7 @@ #include "Gizmos/GLGizmoSVG.hpp" // Drop SVG file #include "Gizmos/GLGizmoCut.hpp" #include "FileArchiveDialog.hpp" -#include "Auth.hpp" +#include "UserAccount.hpp" #include "DesktopIntegrationDialog.hpp" #ifdef __APPLE__ @@ -264,7 +266,7 @@ struct Plater::priv GLToolbar collapse_toolbar; Preview *preview; std::unique_ptr notification_manager; - std::unique_ptr auth_communication; + std::unique_ptr user_account; ProjectDirtyStateManager dirty_state; @@ -611,7 +613,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) })) , sidebar(new Sidebar(q)) , notification_manager(std::make_unique(q)) - , auth_communication(std::make_unique(q, wxGetApp().app_config)) + , user_account(std::make_unique(q, wxGetApp().app_config)) , m_worker{q, std::make_unique(notification_manager.get()), "ui_worker"} , m_sla_import_dlg{new SLAImportDialog{q}} , delayed_scene_refresh(false) @@ -860,7 +862,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) }); this->q->Bind(EVT_LOGIN_OTHER_INSTANCE, [this](LoginOtherInstanceEvent& evt) { BOOST_LOG_TRIVIAL(trace) << "Received login from other instance event."; - auth_communication->on_login_code_recieved(evt.data); + user_account->on_login_code_recieved(evt.data); }); this->q->Bind(EVT_INSTANCE_GO_TO_FRONT, [this](InstanceGoToFrontEvent &) { @@ -870,8 +872,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) this->q->Bind(EVT_OPEN_PRUSAAUTH, [this](OpenPrusaAuthEvent& evt) { BOOST_LOG_TRIVIAL(info) << "open browser: " << evt.data; // first register url to be sure to get the code back - auto downloader_worker = new DownloaderUtils::Worker(nullptr); - downloader_worker->perform_register(wxGetApp().app_config->get("url_downloader_dest")); + //auto downloader_worker = new DownloaderUtils::Worker(nullptr); + DownloaderUtils::Worker::perform_register(wxGetApp().app_config->get("url_downloader_dest")); #ifdef __linux__ if (downloader_worker->get_perform_registration_linux()) DesktopIntegrationDialog::perform_downloader_desktop_integration(); @@ -881,7 +883,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) }); this->q->Bind(EVT_LOGGEDOUT_PRUSAAUTH, [this](PrusaAuthSuccessEvent& evt) { - auth_communication->set_username({}, wxGetApp().app_config); + user_account->on_logout(wxGetApp().app_config); std::string text = _u8L("Logged out."); this->notification_manager->close_notification_of_type(NotificationType::PrusaAuthUserID); this->notification_manager->push_notification(NotificationType::PrusaAuthUserID, NotificationManager::NotificationLevel::ImportantNotificationLevel, text); @@ -889,30 +891,15 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) }); this->q->Bind(EVT_PA_ID_USER_SUCCESS, [this](PrusaAuthSuccessEvent& evt) { - std::string text; - try { - std::stringstream ss(evt.data); - boost::property_tree::ptree ptree; - boost::property_tree::read_json(ss, ptree); - std::string public_username; - const auto public_username_optional = ptree.get_optional("public_username"); - - if (public_username_optional) - public_username = *public_username_optional; - text = format(_u8L("Logged as %1%."), public_username); - } - catch (const std::exception&) { - BOOST_LOG_TRIVIAL(error) << "UserIDUserAction Could not parse server response."; - } - assert(!text.empty()); - - auth_communication->set_username(evt.data, wxGetApp().app_config); + std::string username = user_account->on_user_id_success(evt.data, wxGetApp().app_config); + std::string text = format(_u8L("Logged as %1%."), username); this->notification_manager->close_notification_of_type(NotificationType::PrusaAuthUserID); this->notification_manager->push_notification(NotificationType::PrusaAuthUserID, NotificationManager::NotificationLevel::ImportantNotificationLevel, text); wxGetApp().update_config_menu(); }); this->q->Bind(EVT_PRUSAAUTH_FAIL, [this](PrusaAuthFailEvent& evt) { - auth_communication->set_username({}, wxGetApp().app_config); + BOOST_LOG_TRIVIAL(error) << "Network error message: " << evt.data; + user_account->on_communication_fail(evt.data, wxGetApp().app_config); this->notification_manager->close_notification_of_type(NotificationType::PrusaAuthUserID); this->notification_manager->push_notification(NotificationType::PrusaAuthUserID, NotificationManager::NotificationLevel::WarningNotificationLevel, evt.data); }); @@ -921,7 +908,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) this->notification_manager->push_notification(NotificationType::PrusaAuthUserID, NotificationManager::NotificationLevel::ImportantNotificationLevel, evt.data); }); this->q->Bind(EVT_PRUSACONNECT_PRINTERS_SUCCESS, [this](PrusaAuthSuccessEvent& evt) { - std::string out = GUI::format( "Printers in your PrusaConnect team:\n%1%", auth_communication->proccess_prusaconnect_printers_message(evt.data)); + BOOST_LOG_TRIVIAL(error) << "PrusaConnect printers message: " << evt.data; + std::string text = user_account->on_connect_printers_success(evt.data, wxGetApp().app_config); + std::string out = GUI::format( "Printers in your PrusaConnect team:\n%1%", text); this->notification_manager->close_notification_of_type(NotificationType::PrusaAuthUserID); this->notification_manager->push_notification(NotificationType::PrusaAuthUserID, NotificationManager::NotificationLevel::ImportantNotificationLevel, out); }); @@ -6648,14 +6637,14 @@ const NotificationManager * Plater::get_notification_manager() const return p->notification_manager.get(); } -PrusaAuthCommunication* Plater::get_auth_communication() +UserAccount* Plater::get_user_account() { - return p->auth_communication.get(); + return p->user_account.get(); } -const PrusaAuthCommunication* Plater::get_auth_communication() const +const UserAccount* Plater::get_user_account() const { - return p->auth_communication.get(); + return p->user_account.get(); } void Plater::init_notification_manager() diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index fd0cad7f9a..b29e43e154 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -62,7 +62,7 @@ class Mouse3DController; class NotificationManager; struct Camera; class GLToolbar; -class PrusaAuthCommunication; +class UserAccount; class Plater: public wxPanel { @@ -351,8 +351,8 @@ public: NotificationManager* get_notification_manager(); const NotificationManager* get_notification_manager() const; - PrusaAuthCommunication* get_auth_communication(); - const PrusaAuthCommunication* get_auth_communication() const; + UserAccount* get_user_account(); + const UserAccount* get_user_account() const; void init_notification_manager(); diff --git a/src/slic3r/GUI/UserAccount.cpp b/src/slic3r/GUI/UserAccount.cpp new file mode 100644 index 0000000000..7261f4e4c7 --- /dev/null +++ b/src/slic3r/GUI/UserAccount.cpp @@ -0,0 +1,217 @@ +#include "UserAccount.hpp" + +#include +#include +#include + +namespace pt = boost::property_tree; + +namespace Slic3r { +namespace GUI { + +UserAccount::UserAccount(wxEvtHandler* evt_handler, AppConfig* app_config) + : m_auth_communication(std::make_unique(evt_handler, app_config)) +{} + +UserAccount::~UserAccount() +{} + +void UserAccount::set_username(const std::string& username, AppConfig* app_config) +{ + m_username = username; + m_auth_communication->set_username(username, app_config); +} + +void UserAccount::set_remember_session(bool remember) +{ + m_auth_communication->set_remember_session(remember); +} + +bool UserAccount::is_logged() +{ + return m_auth_communication->is_logged(); +} +void UserAccount::do_login() +{ + m_auth_communication->do_login(); +} +void UserAccount::do_logout() +{ + m_auth_communication->do_logout(); +} + +std::string UserAccount::get_access_token() +{ + return m_auth_communication->get_access_token(); +} + +#if 0 +void UserAccount::enqueue_user_id_action() +{ + m_auth_communication->enqueue_user_id_action(); +} +void UserAccount::enqueue_connect_dummy_action() +{ + m_auth_communication->enqueue_connect_dummy_action(); +} +#endif + +void UserAccount::enqueue_connect_printers_action() +{ + m_auth_communication->enqueue_connect_printers_action(); +} + +void UserAccount::on_login_code_recieved(const std::string& url_message) +{ + m_auth_communication->on_login_code_recieved(url_message); +} + +std::string UserAccount::on_user_id_success(const std::string data, AppConfig* app_config) +{ + std::string public_username; + try { + std::stringstream ss(data); + boost::property_tree::ptree ptree; + boost::property_tree::read_json(ss, ptree); + const auto public_username_optional = ptree.get_optional("public_username"); + if (public_username_optional) + public_username = *public_username_optional; + } + catch (const std::exception&) { + BOOST_LOG_TRIVIAL(error) << "UserIDUserAction Could not parse server response."; + } + assert(!public_username.empty()); + set_username(public_username, app_config); + return public_username; +} + +void UserAccount::on_communication_fail(const std::string data, AppConfig* app_config) +{ + // TODO: should we just declare disconnect on every fail? + //set_username({}, app_config); +} + +void UserAccount::on_logout( AppConfig* app_config) +{ + set_username({}, app_config); +} + +namespace { + std::string parse_tree_for_param(const pt::ptree& tree, const std::string& param) + { + for (const auto& section : tree) { + if (section.first == param) { + return section.second.data(); + } else { + if (std::string res = parse_tree_for_param(section.second, param); !res.empty()) + return res; + } + + } + return {}; + } +} + +std::string UserAccount::on_connect_printers_success(const std::string data, AppConfig* app_config) +{ + + pt::ptree ptree; + try { + std::stringstream ss(data); + pt::read_json(ss, ptree); + } + catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "Could not parse prusaconnect message. " << e.what(); + return {}; + } + // fill m_printer_map with data from ptree + // tree string is in format {"printers": [{..}, {..}]} + m_printer_map.clear(); + assert(ptree.front().first == "printers"); + for (const auto& printer_tree : ptree.front().second) { + std::string name; + ConnectPrinterState state; + std::string type_string = parse_tree_for_param(printer_tree.second, "printer_type"); + std::string state_string = parse_tree_for_param(printer_tree.second, "connect_state"); + + assert(!type_string.empty()); + assert(!state_string.empty()); + if (type_string.empty() || state_string.empty()) + continue; + // name of printer needs to be taken from translate table, if missing + if (auto pair = printer_type_and_name_table.find(type_string); pair != printer_type_and_name_table.end()) { + name = pair->second; + } else { + assert(true); // On this assert, printer_type_and_name_table needs to be updated with type_string and correct printer name + continue; + } + // translate state string to enum value + if (auto pair = printer_state_table.find(state_string); pair != printer_state_table.end()) { + state = pair->second; + } else { + assert(true); // On this assert, printer_state_table and ConnectPrinterState needs to be updated + continue; + } + if (auto counter = m_printer_map.find(name); counter != m_printer_map.end()) { + m_printer_map[name][static_cast(state)]++; + } else { + m_printer_map[name].reserve(static_cast(ConnectPrinterState::CONNECT_PRINTER_STATE_COUNT)); + for (size_t i = 0; i < static_cast(ConnectPrinterState::CONNECT_PRINTER_STATE_COUNT); i++) { + m_printer_map[name].push_back(i == static_cast(state) ? 1 : 0); + } + } + } + std::string out; + for (const auto& it : m_printer_map) + { + out += GUI::format("%1%: O%2% I%3% P%4% F%5% \n" + , it.first + , std::to_string(it.second[static_cast(ConnectPrinterState::CONNECT_PRINTER_OFFLINE)]) + , std::to_string(it.second[static_cast(ConnectPrinterState::CONNECT_PRINTER_IDLE)]) + , std::to_string(it.second[static_cast(ConnectPrinterState::CONNECT_PRINTER_PRINTING)]) + , std::to_string(it.second[static_cast(ConnectPrinterState::CONNECT_PRINTER_FINISHED)])); + } + return out; +} + +std::string UserAccount::get_model_from_json(const std::string& message) const +{ + std::string out; + try { + std::stringstream ss(message); + pt::ptree ptree; + pt::read_json(ss, ptree); + + std::string printer_type = parse_tree_for_param(ptree, "printer_type"); + if (auto pair = printer_type_and_name_table.find(printer_type); pair != printer_type_and_name_table.end()) { + out = pair->second; + } + else { + out = parse_tree_for_param(ptree, "printer_type_name"); + } + //assert(!out.empty()); + } + catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "Could not parse prusaconnect message. " << e.what(); + } + return out; +} + +std::string UserAccount::get_nozzle_from_json(const std::string& message) const +{ + std::string out; + try { + std::stringstream ss(message); + pt::ptree ptree; + pt::read_json(ss, ptree); + + out = parse_tree_for_param(ptree, "nozzle_diameter"); + //assert(!out.empty()); + } + catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "Could not parse prusaconnect message. " << e.what(); + } + return out; +} + +}} // namespace slic3r::GUI \ No newline at end of file diff --git a/src/slic3r/GUI/UserAccount.hpp b/src/slic3r/GUI/UserAccount.hpp new file mode 100644 index 0000000000..a598f8257f --- /dev/null +++ b/src/slic3r/GUI/UserAccount.hpp @@ -0,0 +1,78 @@ +#ifndef slic3r_UserAccount_hpp_ +#define slic3r_UserAccount_hpp_ + +#include "Auth.hpp" +#include "libslic3r/AppConfig.hpp" + +#include +#include + +namespace Slic3r{ +namespace GUI{ + +enum class ConnectPrinterState { + CONNECT_PRINTER_OFFLINE, + CONNECT_PRINTER_IDLE, + CONNECT_PRINTER_PRINTING, + CONNECT_PRINTER_FINISHED, + CONNECT_PRINTER_STATE_COUNT +}; + +typedef std::map> ConnectPrinterStateMap; +// Class UserAccount should handle every request for entities outside PrusaSlicer like PrusaAuth or PrusaConnect. +// Outside communication is implemented in class PrusaAuthCommunication in Auth.hpp. +// All incoming data shoud be stored in UserAccount. +class UserAccount { +public: + UserAccount(wxEvtHandler* evt_handler, Slic3r::AppConfig* app_config); + ~UserAccount(); + + bool is_logged(); + void do_login(); + void do_logout(); + + void set_remember_session(bool remember); + +#if 0 + void enqueue_user_id_action(); + void enqueue_connect_dummy_action(); +#endif + void enqueue_connect_printers_action(); + + void on_login_code_recieved(const std::string& url_message); + std::string on_user_id_success(const std::string data, AppConfig* app_config); + void on_communication_fail(const std::string data, AppConfig* app_config); + void on_logout(AppConfig* app_config); + std::string on_connect_printers_success(const std::string data, AppConfig* app_config); + + std::string get_username() const { return m_username; } + std::string get_access_token(); + const ConnectPrinterStateMap& get_printer_state_map() const { return m_printer_map; } + + // standalone utility methods + std::string get_model_from_json(const std::string& message) const; + std::string get_nozzle_from_json(const std::string& message) const; +private: + void set_username(const std::string& username, AppConfig* app_config); + + std::unique_ptr m_auth_communication; + + std::string m_username; + ConnectPrinterStateMap m_printer_map; + + const std::map printer_type_and_name_table = { + {"1.3.0", "Original Prusa i3 MK3"}, + {"1.3.1", "Original Prusa i3 MK3S & MK3S+"}, + {"1.4.0", "Original Prusa MK4"}, + {"2.1.0", "Original Prusa MINI & MINI+"}, + }; + + const std::map printer_state_table = { + {"OFFLINE" , ConnectPrinterState::CONNECT_PRINTER_OFFLINE}, + {"IDLE" , ConnectPrinterState::CONNECT_PRINTER_IDLE}, + {"PRINTING" , ConnectPrinterState::CONNECT_PRINTER_PRINTING}, + {"FINISHED" , ConnectPrinterState::CONNECT_PRINTER_FINISHED}, + }; +}; +}} // namespace slic3r::GUI +#endif // slic3r_UserAccount_hpp_ diff --git a/src/slic3r/GUI/WebView.cpp b/src/slic3r/GUI/WebView.cpp new file mode 100644 index 0000000000..96c279517c --- /dev/null +++ b/src/slic3r/GUI/WebView.cpp @@ -0,0 +1,178 @@ +#include "WebView.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/Utils/MacDarkMode.hpp" + +#include +#include +#include +#include +#include "wx/private/jsscriptwrapper.h" + +#include + +#ifdef __WIN32__ +#include +#elif defined __linux__ +#include +#define WEBKIT_API +struct WebKitWebView; +struct WebKitJavascriptResult; +extern "C" { +WEBKIT_API void +webkit_web_view_run_javascript (WebKitWebView *web_view, + const gchar *script, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +WEBKIT_API WebKitJavascriptResult * +webkit_web_view_run_javascript_finish (WebKitWebView *web_view, + GAsyncResult *result, + GError **error); +WEBKIT_API void +webkit_javascript_result_unref (WebKitJavascriptResult *js_result); +} +#endif + +class FakeWebView : public wxWebView +{ + virtual bool Create(wxWindow* parent, wxWindowID id, const wxString& url, const wxPoint& pos, const wxSize& size, long style, const wxString& name) override { return false; } + virtual wxString GetCurrentTitle() const override { return wxString(); } + virtual wxString GetCurrentURL() const override { return wxString(); } + virtual bool IsBusy() const override { return false; } + virtual bool IsEditable() const override { return false; } + virtual void LoadURL(const wxString& url) override { } + virtual void Print() override { } + virtual void RegisterHandler(wxSharedPtr handler) override { } + virtual void Reload(wxWebViewReloadFlags flags = wxWEBVIEW_RELOAD_DEFAULT) override { } + virtual bool RunScript(const wxString& javascript, wxString* output = NULL) const override { return false; } + virtual void SetEditable(bool enable = true) override { } + virtual void Stop() override { } + virtual bool CanGoBack() const override { return false; } + virtual bool CanGoForward() const override { return false; } + virtual void GoBack() override { } + virtual void GoForward() override { } + virtual void ClearHistory() override { } + virtual void EnableHistory(bool enable = true) override { } + virtual wxVector> GetBackwardHistory() override { return {}; } + virtual wxVector> GetForwardHistory() override { return {}; } + virtual void LoadHistoryItem(wxSharedPtr item) override { } + virtual bool CanSetZoomType(wxWebViewZoomType type) const override { return false; } + virtual float GetZoomFactor() const override { return 0.0f; } + virtual wxWebViewZoomType GetZoomType() const override { return wxWebViewZoomType(); } + virtual void SetZoomFactor(float zoom) override { } + virtual void SetZoomType(wxWebViewZoomType zoomType) override { } + virtual bool CanUndo() const override { return false; } + virtual bool CanRedo() const override { return false; } + virtual void Undo() override { } + virtual void Redo() override { } + virtual void* GetNativeBackend() const override { return nullptr; } + virtual void DoSetPage(const wxString& html, const wxString& baseUrl) override { } +}; + +wxWebView* WebView::CreateWebView(wxWindow * parent, wxString const & url) +{ +#if wxUSE_WEBVIEW_EDGE + // WebView2Loader.dll in exe folder is enough? + /* + // Check if a fixed version of edge is present in + // $executable_path/edge_fixed and use it + wxFileName edgeFixedDir(wxStandardPaths::Get().GetExecutablePath()); + edgeFixedDir.SetFullName(""); + edgeFixedDir.AppendDir("edge_fixed"); + if (edgeFixedDir.DirExists()) { + wxWebViewEdge::MSWSetBrowserExecutableDir(edgeFixedDir.GetFullPath()); + wxLogMessage("Using fixed edge version"); + } + */ +#endif + wxString correct_url = url; +#ifdef __WIN32__ + correct_url.Replace("\\", "/"); +#endif + if (!correct_url.empty()) + correct_url = wxURI(correct_url).BuildURI(); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << correct_url.ToUTF8(); + + auto webView = wxWebView::New(); + if (webView) { +#ifdef __WIN32__ + webView->SetUserAgent(wxString::Format("PrusaSlicer/v%s", SLIC3R_VERSION)); + webView->Create(parent, wxID_ANY, correct_url, wxDefaultPosition, wxDefaultSize); + //We register the wxfs:// protocol for testing purposes + //webView->RegisterHandler(wxSharedPtr(new wxWebViewArchiveHandler("wxfs"))); + //And the memory: file system + //webView->RegisterHandler(wxSharedPtr(new wxWebViewFSHandler("memory"))); +#else + // With WKWebView handlers need to be registered before creation + //webView->RegisterHandler(wxSharedPtr(new wxWebViewArchiveHandler("wxfs"))); + // And the memory: file system + //webView->RegisterHandler(wxSharedPtr(new wxWebViewFSHandler("memory"))); + webView->Create(parent, wxID_ANY, url2, wxDefaultPosition, wxDefaultSize); + webView->SetUserAgent(wxString::Format("PrusaSlicer/v%s", SLIC3R_VERSION)); +#endif +#ifndef __WIN32__ + Slic3r::GUI::wxGetApp().CallAfter([webView] { +#endif + if (!webView->AddScriptMessageHandler("wx")) { + // TODO: dialog to user + //wxLogError("Could not add script message handler"); + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "Could not add script message handler"; + } +#ifndef __WIN32__ + }); +#endif + webView->EnableContextMenu(false); + } else { + // TODO: dialog to user + BOOST_LOG_TRIVIAL(error) << "Failed to create wxWebView object. Using Dummy object instead. Webview won't be working."; + webView = new FakeWebView; + } + return webView; +} + +void WebView::LoadUrl(wxWebView * webView, wxString const &url) +{ + auto url2 = url; +#ifdef __WIN32__ + url2.Replace("\\", "/"); +#endif + if (!url2.empty()) { url2 = wxURI(url2).BuildURI(); } + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << url2.ToUTF8(); + webView->LoadURL(url2); +} + +bool WebView::run_script(wxWebView *webView, wxString const &javascript) +{ + try { +#ifdef __WIN32__ + ICoreWebView2 * webView2 = (ICoreWebView2 *) webView->GetNativeBackend(); + if (webView2 == nullptr) + return false; + int count = 0; + wxJSScriptWrapper wrapJS(javascript, wxJSScriptWrapper::OutputType::JS_OUTPUT_STRING); + wxString wrapped_code = wrapJS.GetWrappedCode(); + return webView2->ExecuteScript(wrapJS.GetWrappedCode(), NULL) == 0; +#elif defined __WXMAC__ + WKWebView * wkWebView = (WKWebView *) webView->GetNativeBackend(); + int count = 0; + wxJSScriptWrapper wrapJS(javascript, wxJSScriptWrapper::OutputType::JS_OUTPUT_STRING); + Slic3r::GUI::WKWebView_evaluateJavaScript(wkWebView, wrapJS.GetWrappedCode(), nullptr); + return true; +#else + WebKitWebView *wkWebView = (WebKitWebView *) webView->GetNativeBackend(); + webkit_web_view_run_javascript( + wkWebView, javascript.utf8_str(), NULL, + [](GObject *wkWebView, GAsyncResult *res, void *) { + GError * error = NULL; + auto result = webkit_web_view_run_javascript_finish((WebKitWebView*)wkWebView, res, &error); + if (!result) + g_error_free (error); + else + webkit_javascript_result_unref (result); + }, NULL); + return true; +#endif + } catch (std::exception &e) { + return false; + } +} diff --git a/src/slic3r/GUI/WebView.hpp b/src/slic3r/GUI/WebView.hpp new file mode 100644 index 0000000000..21ace5c15f --- /dev/null +++ b/src/slic3r/GUI/WebView.hpp @@ -0,0 +1,16 @@ +#ifndef slic3r_GUI_WebView_hpp_ +#define slic3r_GUI_WebView_hpp_ + +#include + +class WebView +{ +public: + static wxWebView *CreateWebView(wxWindow *parent, wxString const &url); + + static void LoadUrl(wxWebView * webView, wxString const &url); + + static bool run_script(wxWebView * webView, wxString const & msg); +}; + +#endif // !slic3r_GUI_WebView_hpp_ diff --git a/src/slic3r/GUI/WebViewDialog.cpp b/src/slic3r/GUI/WebViewDialog.cpp new file mode 100644 index 0000000000..5769d30b83 --- /dev/null +++ b/src/slic3r/GUI/WebViewDialog.cpp @@ -0,0 +1,472 @@ +#include "WebViewDialog.hpp" + +#include "slic3r/GUI/I18N.hpp" +#include "slic3r/GUI/wxExtensions.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/MainFrame.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "libslic3r_version.h" +#include "libslic3r/Utils.hpp" +#include "slic3r/GUI/UserAccount.hpp" +#include "slic3r/GUI/format.hpp" + +#include +#include +#include + +#include + +#include "slic3r/GUI/WebView.hpp" + + +namespace Slic3r { +namespace GUI { + + +WebViewPanel::WebViewPanel(wxWindow *parent) + : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize) + { + wxString url = "https://dev.connect.prusa3d.com/prusa-slicer/printers"; + //std::string strlang = wxGetApp().app_config->get("language"); + //if (strlang != "") + // url = wxString::Format("file://%s/web/homepage/index.html?lang=%s", from_u8(resources_dir()), strlang); + //m_bbl_user_agent = wxString::Format("BBL-Slicer/v%s", SLIC3R_VERSION); + + wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL); +#ifdef DEBUG_URL_PANEL + // Create the button + bSizer_toolbar = new wxBoxSizer(wxHORIZONTAL); + + m_button_back = new wxButton(this, wxID_ANY, wxT("Back"), wxDefaultPosition, wxDefaultSize, 0); + m_button_back->Enable(false); + bSizer_toolbar->Add(m_button_back, 0, wxALL, 5); + + m_button_forward = new wxButton(this, wxID_ANY, wxT("Forward"), wxDefaultPosition, wxDefaultSize, 0); + m_button_forward->Enable(false); + bSizer_toolbar->Add(m_button_forward, 0, wxALL, 5); + + m_button_stop = new wxButton(this, wxID_ANY, wxT("Stop"), wxDefaultPosition, wxDefaultSize, 0); + + bSizer_toolbar->Add(m_button_stop, 0, wxALL, 5); + + m_button_reload = new wxButton(this, wxID_ANY, wxT("Reload"), wxDefaultPosition, wxDefaultSize, 0); + bSizer_toolbar->Add(m_button_reload, 0, wxALL, 5); + + m_url = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER); + bSizer_toolbar->Add(m_url, 1, wxALL | wxEXPAND, 5); + + m_button_tools = new wxButton(this, wxID_ANY, wxT("Tools"), wxDefaultPosition, wxDefaultSize, 0); + bSizer_toolbar->Add(m_button_tools, 0, wxALL, 5); + + // Create panel for find toolbar. + wxPanel* panel = new wxPanel(this); + topsizer->Add(bSizer_toolbar, 0, wxEXPAND, 0); + topsizer->Add(panel, wxSizerFlags().Expand()); + + // Create sizer for panel. + wxBoxSizer* panel_sizer = new wxBoxSizer(wxVERTICAL); + panel->SetSizer(panel_sizer); + + // Create the info panel + m_info = new wxInfoBar(this); + topsizer->Add(m_info, wxSizerFlags().Expand()); +#endif + + // Create the webview + m_browser = WebView::CreateWebView(this, url); + if (m_browser == nullptr) { + wxLogError("Could not init m_browser"); + return; + } + + SetSizer(topsizer); + + topsizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1)); + +#ifdef DEBUG_URL_PANEL + // Create the Tools menu + m_tools_menu = new wxMenu(); + wxMenuItem* viewSource = m_tools_menu->Append(wxID_ANY, _L("View Source")); + wxMenuItem* viewText = m_tools_menu->Append(wxID_ANY, _L("View Text")); + m_tools_menu->AppendSeparator(); + + wxMenu* script_menu = new wxMenu; + + m_script_custom = script_menu->Append(wxID_ANY, "Custom script"); + m_tools_menu->AppendSubMenu(script_menu, _L("Run Script")); + wxMenuItem* addUserScript = m_tools_menu->Append(wxID_ANY, _L("Add user script")); + wxMenuItem* setCustomUserAgent = m_tools_menu->Append(wxID_ANY, _L("Set custom user agent")); + +#endif + //Zoom + m_zoomFactor = 100; + + + Bind(wxEVT_SHOW, &WebViewPanel::on_show, this); + + // Connect the webview events + Bind(wxEVT_WEBVIEW_ERROR, &WebViewPanel::on_error, this, m_browser->GetId()); + Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &WebViewPanel::on_script_message, this, m_browser->GetId()); + +#ifdef DEBUG_URL_PANEL + // Connect the button events + Bind(wxEVT_BUTTON, &WebViewPanel::on_back, this, m_button_back->GetId()); + Bind(wxEVT_BUTTON, &WebViewPanel::on_forward, this, m_button_forward->GetId()); + Bind(wxEVT_BUTTON, &WebViewPanel::on_stop, this, m_button_stop->GetId()); + Bind(wxEVT_BUTTON, &WebViewPanel::on_reload, this, m_button_reload->GetId()); + Bind(wxEVT_BUTTON, &WebViewPanel::on_tools_clicked, this, m_button_tools->GetId()); + Bind(wxEVT_TEXT_ENTER, &WebViewPanel::on_url, this, m_url->GetId()); + + // Connect the menu events + Bind(wxEVT_MENU, &WebViewPanel::on_view_source_request, this, viewSource->GetId()); + Bind(wxEVT_MENU, &WebViewPanel::on_view_text_request, this, viewText->GetId()); + + Bind(wxEVT_MENU, &WebViewPanel::on_run_script_custom, this, m_script_custom->GetId()); + Bind(wxEVT_MENU, &WebViewPanel::on_add_user_script, this, addUserScript->GetId()); +#endif + //Connect the idle events + Bind(wxEVT_IDLE, &WebViewPanel::on_idle, this); + Bind(wxEVT_CLOSE_WINDOW, &WebViewPanel::on_close, this); + + m_LoginUpdateTimer = nullptr; + } + +WebViewPanel::~WebViewPanel() +{ + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " Start"; + SetEvtHandlerEnabled(false); +#ifdef DEBUG_URL_PANEL + delete m_tools_menu; + + if (m_LoginUpdateTimer != nullptr) { + m_LoginUpdateTimer->Stop(); + delete m_LoginUpdateTimer; + m_LoginUpdateTimer = NULL; + } +#endif + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " End"; +} + + +void WebViewPanel::load_url(wxString& url) +{ + this->Show(); + this->Raise(); +#ifdef DEBUG_URL_PANEL + m_url->SetLabelText(url); +#endif + m_browser->LoadURL(url); + m_browser->SetFocus(); +} + +void WebViewPanel::on_show(wxShowEvent& evt) +{ + // run script with access token to login + if (evt.IsShown()) { + std::string token = wxGetApp().plater()->get_user_account()->get_access_token(); + wxString script = GUI::format_wxstr("window.setAccessToken(\'%1%\')", token); + // TODO: should this be happening every OnShow? + run_script(script); + } + +} + +void WebViewPanel::on_idle(wxIdleEvent& WXUNUSED(evt)) +{ + if (m_browser->IsBusy()) + wxSetCursor(wxCURSOR_ARROWWAIT); + else + wxSetCursor(wxNullCursor); + +#ifdef DEBUG_URL_PANEL + m_button_stop->Enable(m_browser->IsBusy()); +#endif +} + +/** + * Callback invoked when user entered an URL and pressed enter + */ +void WebViewPanel::on_url(wxCommandEvent& WXUNUSED(evt)) +{ +#ifdef DEBUG_URL_PANEL + m_browser->LoadURL(m_url->GetValue()); + m_browser->SetFocus(); +#endif +} + +/** + * Callback invoked when user pressed the "back" button + */ +void WebViewPanel::on_back(wxCommandEvent& WXUNUSED(evt)) +{ + m_browser->GoBack(); +} + +/** + * Callback invoked when user pressed the "forward" button + */ +void WebViewPanel::on_forward(wxCommandEvent& WXUNUSED(evt)) +{ + m_browser->GoForward(); +} + +/** + * Callback invoked when user pressed the "stop" button + */ +void WebViewPanel::on_stop(wxCommandEvent& WXUNUSED(evt)) +{ + m_browser->Stop(); +} + +/** + * Callback invoked when user pressed the "reload" button + */ +void WebViewPanel::on_reload(wxCommandEvent& WXUNUSED(evt)) +{ + m_browser->Reload(); +} + + + +void WebViewPanel::on_close(wxCloseEvent& evt) +{ + this->Hide(); +} + + +void WebViewPanel::on_script_message(wxWebViewEvent& evt) +{ + wxGetApp().handle_web_request(evt.GetString().ToUTF8().data()); +} + + + +/** + * Invoked when user selects the "View Source" menu item + */ +void WebViewPanel::on_view_source_request(wxCommandEvent& WXUNUSED(evt)) +{ + SourceViewDialog dlg(this, m_browser->GetPageSource()); + dlg.ShowModal(); +} + +/** + * Invoked when user selects the "View Text" menu item + */ +void WebViewPanel::on_view_text_request(wxCommandEvent& WXUNUSED(evt)) +{ + wxDialog textViewDialog(this, wxID_ANY, "Page Text", + wxDefaultPosition, wxSize(700, 500), + wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER); + + wxTextCtrl* text = new wxTextCtrl(this, wxID_ANY, m_browser->GetPageText(), + wxDefaultPosition, wxDefaultSize, + wxTE_MULTILINE | + wxTE_RICH | + wxTE_READONLY); + + wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(text, 1, wxEXPAND); + SetSizer(sizer); + textViewDialog.ShowModal(); +} + +/** + * Invoked when user selects the "Menu" item + */ +void WebViewPanel::on_tools_clicked(wxCommandEvent& WXUNUSED(evt)) +{ +#ifdef DEBUG_URL_PANEL + wxPoint position = ScreenToClient(wxGetMousePosition()); + PopupMenu(m_tools_menu, position.x, position.y); +#endif +} + +void WebViewPanel::run_script(const wxString& javascript) +{ + // Remember the script we run in any case, so the next time the user opens + // the "Run Script" dialog box, it is shown there for convenient updating. + m_javascript = javascript; + + if (!m_browser) return; + + bool res = WebView::run_script(m_browser, javascript); + BOOST_LOG_TRIVIAL(debug) << "RunScript " << javascript << " " << res; +} + + +void WebViewPanel::on_run_script_custom(wxCommandEvent& WXUNUSED(evt)) +{ + wxTextEntryDialog dialog + ( + this, + "Please enter JavaScript code to execute", + wxGetTextFromUserPromptStr, + m_javascript, + wxOK | wxCANCEL | wxCENTRE | wxTE_MULTILINE + ); + if (dialog.ShowModal() != wxID_OK) + return; + + run_script(dialog.GetValue()); +} + +void WebViewPanel::on_add_user_script(wxCommandEvent& WXUNUSED(evt)) +{ + wxString userScript = "window.wx_test_var = 'wxWidgets webview sample';"; + wxTextEntryDialog dialog + ( + this, + "Enter the JavaScript code to run as the initialization script that runs before any script in the HTML document.", + wxGetTextFromUserPromptStr, + userScript, + wxOK | wxCANCEL | wxCENTRE | wxTE_MULTILINE + ); + if (dialog.ShowModal() != wxID_OK) + return; + + if (!m_browser->AddUserScript(dialog.GetValue())) + wxLogError("Could not add user script"); +} + +void WebViewPanel::on_set_custom_user_agent(wxCommandEvent& WXUNUSED(evt)) +{ + wxString customUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 13_1_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.1 Mobile/15E148 Safari/604.1"; + wxTextEntryDialog dialog + ( + this, + "Enter the custom user agent string you would like to use.", + wxGetTextFromUserPromptStr, + customUserAgent, + wxOK | wxCANCEL | wxCENTRE + ); + if (dialog.ShowModal() != wxID_OK) + return; + + if (!m_browser->SetUserAgent(customUserAgent)) + wxLogError("Could not set custom user agent"); +} + +void WebViewPanel::on_clear_selection(wxCommandEvent& WXUNUSED(evt)) +{ + m_browser->ClearSelection(); +} + +void WebViewPanel::on_delete_selection(wxCommandEvent& WXUNUSED(evt)) +{ + m_browser->DeleteSelection(); +} + +void WebViewPanel::on_select_all(wxCommandEvent& WXUNUSED(evt)) +{ + m_browser->SelectAll(); +} + +/** + * Callback invoked when a loading error occurs + */ +void WebViewPanel::on_error(wxWebViewEvent& evt) +{ +#define WX_ERROR_CASE(type) \ +case type: \ + category = #type; \ + break; + + wxString category; + switch (evt.GetInt()) + { + WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_CONNECTION); + WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_CERTIFICATE); + WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_AUTH); + WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_SECURITY); + WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_NOT_FOUND); + WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_REQUEST); + WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_USER_CANCELLED); + WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_OTHER); + } + + //Show the info bar with an error +#ifdef DEBUG_URL_PANEL + + m_info->ShowMessage(_L("An error occurred loading ") + evt.GetURL() + "\n" + + "'" + category + "'", wxICON_ERROR); +#endif +} + + +SourceViewDialog::SourceViewDialog(wxWindow* parent, wxString source) : + wxDialog(parent, wxID_ANY, "Source Code", + wxDefaultPosition, wxSize(700,500), + wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) +{ + wxTextCtrl* text = new wxTextCtrl(this, wxID_ANY, source, + wxDefaultPosition, wxDefaultSize, + wxTE_MULTILINE | + wxTE_RICH | + wxTE_READONLY); + + wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(text, 1, wxEXPAND); + SetSizer(sizer); +} + + +WebViewDialog::WebViewDialog(wxWindow* parent) + : wxDialog(parent, wxID_ANY, "Webview Dialog", wxDefaultPosition, wxSize(1366, 768)/* wxSize(100 * wxGetApp().em_unit(), 100 * wxGetApp().em_unit())*/) +{ + wxString url = "https://dev.connect.prusa3d.com/prusa-slicer/printers"; + ////std::string strlang = wxGetApp().app_config->get("language"); + ////if (strlang != "") + //// url = wxString::Format("file://%s/web/homepage/index.html?lang=%s", from_u8(resources_dir()), strlang); + ////m_bbl_user_agent = wxString::Format("BBL-Slicer/v%s", SLIC3R_VERSION); + + wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL); + + + + // Create the webview + m_browser = WebView::CreateWebView(this, url); + if (m_browser == nullptr) { + wxLogError("Could not init m_browser"); + return; + } + + SetSizer(topsizer); + + topsizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1)); + + Bind(wxEVT_SHOW, &WebViewDialog::on_show, this); + Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &WebViewDialog::on_script_message, this, m_browser->GetId()); +} + +WebViewDialog::~WebViewDialog() +{ + +} + +void WebViewDialog::on_show(wxShowEvent& evt) +{ + if (evt.IsShown()) { + std::string token = wxGetApp().plater()->get_user_account()->get_access_token(); + wxString script = GUI::format_wxstr("window.setAccessToken(\'%1%\')", token); + // TODO: should this be happening every OnShow? + run_script(script); + } + +} + +void WebViewDialog::run_script(const wxString& javascript) +{ + if (!m_browser) + return; + bool res = WebView::run_script(m_browser, javascript); +} + +void WebViewDialog::on_script_message(wxWebViewEvent& evt) +{ + this->EndModal(wxID_OK); +} + +} // GUI +} // Slic3r diff --git a/src/slic3r/GUI/WebViewDialog.hpp b/src/slic3r/GUI/WebViewDialog.hpp new file mode 100644 index 0000000000..f7b6908e52 --- /dev/null +++ b/src/slic3r/GUI/WebViewDialog.hpp @@ -0,0 +1,124 @@ +#ifndef slic3r_WebViewDialog_hpp_ +#define slic3r_WebViewDialog_hpp_ + + +#include "wx/artprov.h" +#include "wx/cmdline.h" +#include "wx/notifmsg.h" +#include "wx/settings.h" +#include "wx/webview.h" + +#if wxUSE_WEBVIEW_EDGE +#include "wx/msw/webview_edge.h" +#endif + +#include "wx/webviewarchivehandler.h" +#include "wx/webviewfshandler.h" +#include "wx/numdlg.h" +#include "wx/infobar.h" +#include "wx/filesys.h" +#include "wx/fs_arc.h" +#include "wx/fs_mem.h" +#include "wx/stdpaths.h" +#include +#include +#include "wx/textctrl.h" +#include + + +namespace Slic3r { +namespace GUI { + +#define DEBUG_URL_PANEL +class WebViewPanel : public wxPanel +{ +public: + WebViewPanel(wxWindow *parent); + virtual ~WebViewPanel(); + + void load_url(wxString& url); + + void on_show(wxShowEvent& evt); + + void on_idle(wxIdleEvent& evt); + void on_url(wxCommandEvent& evt); + void on_back(wxCommandEvent& evt); + void on_forward(wxCommandEvent& evt); + void on_stop(wxCommandEvent& evt); + void on_reload(wxCommandEvent& evt); + + void on_script_message(wxWebViewEvent& evt); + void on_view_source_request(wxCommandEvent& evt); + void on_view_text_request(wxCommandEvent& evt); + void on_tools_clicked(wxCommandEvent& evt); + void on_error(wxWebViewEvent& evt); + + void run_script(const wxString& javascript); + void on_run_script_custom(wxCommandEvent& evt); + void on_add_user_script(wxCommandEvent& evt); + void on_set_custom_user_agent(wxCommandEvent& evt); + void on_clear_selection(wxCommandEvent& evt); + void on_delete_selection(wxCommandEvent& evt); + void on_select_all(wxCommandEvent& evt); + + void on_close(wxCloseEvent& evt); + + wxTimer * m_LoginUpdateTimer{nullptr}; + +private: + + wxWebView* m_browser; +#ifdef DEBUG_URL_PANEL + + wxBoxSizer *bSizer_toolbar; + wxButton * m_button_back; + wxButton * m_button_forward; + wxButton * m_button_stop; + wxButton * m_button_reload; + wxTextCtrl *m_url; + wxButton * m_button_tools; + + wxMenu* m_tools_menu; + wxMenuItem* m_script_custom; + + + wxInfoBar *m_info; + wxStaticText* m_info_text; +#endif + long m_zoomFactor; + + // Last executed JavaScript snippet, for convenience. + wxString m_javascript; + wxString m_response_js; + + wxString m_bbl_user_agent; + + //DECLARE_EVENT_TABLE() +}; + + +class WebViewDialog : public wxDialog +{ +public: + WebViewDialog(wxWindow* parent); + virtual ~WebViewDialog(); + + void on_show(wxShowEvent& evt); + void run_script(const wxString& javascript); + void on_script_message(wxWebViewEvent& evt); + +private: + wxWebView* m_browser; +}; + + +class SourceViewDialog : public wxDialog +{ +public: + SourceViewDialog(wxWindow* parent, wxString source); +}; + +} // GUI +} // Slic3r + +#endif /* slic3r_Tab_hpp_ */ diff --git a/src/slic3r/Utils/MacDarkMode.hpp b/src/slic3r/Utils/MacDarkMode.hpp index 0a233049f3..9e3701ea91 100644 --- a/src/slic3r/Utils/MacDarkMode.hpp +++ b/src/slic3r/Utils/MacDarkMode.hpp @@ -5,12 +5,15 @@ #ifndef slic3r_MacDarkMode_hpp_ #define slic3r_MacDarkMode_hpp_ +#include + namespace Slic3r { namespace GUI { #if __APPLE__ extern bool mac_dark_mode(); extern double mac_max_scaling_factor(); +void WKWebView_evaluateJavaScript(void * web, wxString const & script, void (*callback)(wxString const &)); #endif diff --git a/src/slic3r/Utils/MacDarkMode.mm b/src/slic3r/Utils/MacDarkMode.mm index 512e96b94d..96f421d035 100644 --- a/src/slic3r/Utils/MacDarkMode.mm +++ b/src/slic3r/Utils/MacDarkMode.mm @@ -1,5 +1,7 @@ #import "MacDarkMode.hpp" +#include "wx/osx/core/cfstring.h" + #import #import @@ -33,6 +35,16 @@ double mac_max_scaling_factor() return scaling; } +void WKWebView_evaluateJavaScript(void * web, wxString const & script, void (*callback)(wxString const &)) +{ + [(WKWebView*)web evaluateJavaScript:wxCFStringRef(script).AsNSString() completionHandler: ^(id result, NSError *error) { + if (callback && error != nil) { + wxString err = wxCFStringRef(error.localizedFailureReason).AsString(); + callback(err); + } + }]; +} + } }