WebView prototype

Web View has working panel with Connect webpage with Auth token login.
Webview2 is used on windows. It uses small dll to find its runtime.

WebViewDialog.cpp, WebViewDialog.hpp, WebView.cpp and WebView.hpp were taken from https://github.com/bambulab/BambuStudio and used as protype for future WebView Development. Thank you.

Co-authored-by: cmguo <chunmao.guo@bambulab.com>

Co-authored-by: lane.wei <lane.wei@bambulab.com>
This commit is contained in:
David Kocik 2024-03-25 11:08:24 +01:00
parent adc650ef4e
commit 2276cedc10
29 changed files with 1422 additions and 194 deletions

View File

@ -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()

60
deps/+WebView2/WebView2.cmake vendored Normal file
View File

@ -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 ()

View File

@ -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)

5
deps/CMakeLists.txt vendored
View File

@ -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)

View File

@ -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 ()

View File

@ -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})

View File

@ -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;

View File

@ -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

View File

@ -2,12 +2,9 @@
#include "GUI_App.hpp"
#include "format.hpp"
#include "../Utils/Http.hpp"
#include "I18N.hpp"
#include "slic3r/GUI/I18N.hpp"
#include <boost/log/trivial.hpp>
#include <boost/regex.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/beast/core/detail/base64.hpp>
#include <curl/curl.h>
#include <string>
@ -29,6 +26,10 @@
#include <wincrypt.h>
#endif // WIN32
#ifdef __APPLE__
#include <CommonCrypto/CommonDigest.h>
#endif
#ifdef __linux__
#include <openssl/evp.h>
#include <openssl/bio.h>
@ -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<AuthSession>(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<std::mutex> 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<std::mutex> lock(m_session_mutex);
@ -217,7 +224,7 @@ void PrusaAuthCommunication::login()
}
wakeup_session_thread();
}
void PrusaAuthCommunication::logout()
void PrusaAuthCommunication::do_logout()
{
{
std::lock_guard<std::mutex> 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<std::string, int> 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<CC_LONG>(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;

View File

@ -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,10 +50,12 @@ 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 get_access_token();
std::string proccess_prusaconnect_printers_message(const std::string& message);
private:
std::unique_ptr<AuthSession> m_session;
@ -73,6 +74,9 @@ private:
void init_session_thread();
void login_redirect();
std::string client_id() const { return "UfTRUm5QjWwaQEGpWQBHGHO3reAyuzgOdBaiqO52"; }
};
}
}

View File

@ -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 });
}

View File

@ -103,8 +103,8 @@ public:
m_actions[UserActionID::CODE_FOR_TOKEN] = std::make_unique<UserActionPost>("EXCHANGE_TOKENS", "https://test-account.prusa3d.com/o/token/");
m_actions[UserActionID::TEST_CONNECTION] = std::make_unique<UserActionGetWithEvent>("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<UserActionGetWithEvent>("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<UserActionGetWithEvent>("CONNECT_DUMMY", "dev.connect.prusa:8000/slicer/dummy", evt_handler, EVT_PRUSAAUTH_SUCCESS, EVT_PRUSAAUTH_FAIL);
m_actions[UserActionID::CONNECT_PRINTERS] = std::make_unique<UserActionGetWithEvent>("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<UserActionGetWithEvent>("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<UserActionGetWithEvent>("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()
{

View File

@ -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) {

View File

@ -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__

View File

@ -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

View File

@ -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();

View File

@ -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 <dbt.h>
@ -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"),

View File

@ -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<wxTaskBarIcon> m_taskbar_icon;

View File

@ -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,
};

View File

@ -35,6 +35,8 @@
#include <boost/filesystem/operations.hpp>
#include <boost/log/trivial.hpp>
#include <boost/nowide/convert.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <wx/sizer.h>
#include <wx/stattext.h>
@ -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<NotificationManager> notification_manager;
std::unique_ptr<PrusaAuthCommunication> auth_communication;
std::unique_ptr<UserAccount> 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<NotificationManager>(q))
, auth_communication(std::make_unique<PrusaAuthCommunication>(q, wxGetApp().app_config))
, user_account(std::make_unique<UserAccount>(q, wxGetApp().app_config))
, m_worker{q, std::make_unique<NotificationProgressIndicator>(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<std::string>("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()

View File

@ -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();

View File

@ -0,0 +1,217 @@
#include "UserAccount.hpp"
#include <boost/regex.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
namespace pt = boost::property_tree;
namespace Slic3r {
namespace GUI {
UserAccount::UserAccount(wxEvtHandler* evt_handler, AppConfig* app_config)
: m_auth_communication(std::make_unique<PrusaAuthCommunication>(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<std::string>("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<size_t>(state)]++;
} else {
m_printer_map[name].reserve(static_cast<size_t>(ConnectPrinterState::CONNECT_PRINTER_STATE_COUNT));
for (size_t i = 0; i < static_cast<size_t>(ConnectPrinterState::CONNECT_PRINTER_STATE_COUNT); i++) {
m_printer_map[name].push_back(i == static_cast<size_t>(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<size_t>(ConnectPrinterState::CONNECT_PRINTER_OFFLINE)])
, std::to_string(it.second[static_cast<size_t>(ConnectPrinterState::CONNECT_PRINTER_IDLE)])
, std::to_string(it.second[static_cast<size_t>(ConnectPrinterState::CONNECT_PRINTER_PRINTING)])
, std::to_string(it.second[static_cast<size_t>(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

View File

@ -0,0 +1,78 @@
#ifndef slic3r_UserAccount_hpp_
#define slic3r_UserAccount_hpp_
#include "Auth.hpp"
#include "libslic3r/AppConfig.hpp"
#include <string>
#include <memory>
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<std::string, std::vector<size_t>> 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<Slic3r::GUI::PrusaAuthCommunication> m_auth_communication;
std::string m_username;
ConnectPrinterStateMap m_printer_map;
const std::map<std::string, std::string> 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<std::string, ConnectPrinterState> 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_

178
src/slic3r/GUI/WebView.cpp Normal file
View File

@ -0,0 +1,178 @@
#include "WebView.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/Utils/MacDarkMode.hpp"
#include <wx/webviewarchivehandler.h>
#include <wx/webviewfshandler.h>
#include <wx/msw/webview_edge.h>
#include <wx/uri.h>
#include "wx/private/jsscriptwrapper.h"
#include <boost/log/trivial.hpp>
#ifdef __WIN32__
#include <WebView2.h>
#elif defined __linux__
#include <gtk/gtk.h>
#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<wxWebViewHandler> 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<wxSharedPtr<wxWebViewHistoryItem>> GetBackwardHistory() override { return {}; }
virtual wxVector<wxSharedPtr<wxWebViewHistoryItem>> GetForwardHistory() override { return {}; }
virtual void LoadHistoryItem(wxSharedPtr<wxWebViewHistoryItem> 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<wxWebViewHandler>(new wxWebViewArchiveHandler("wxfs")));
//And the memory: file system
//webView->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new wxWebViewFSHandler("memory")));
#else
// With WKWebView handlers need to be registered before creation
//webView->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new wxWebViewArchiveHandler("wxfs")));
// And the memory: file system
//webView->RegisterHandler(wxSharedPtr<wxWebViewHandler>(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;
}
}

View File

@ -0,0 +1,16 @@
#ifndef slic3r_GUI_WebView_hpp_
#define slic3r_GUI_WebView_hpp_
#include <wx/webview.h>
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_

View File

@ -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 <wx/sizer.h>
#include <wx/toolbar.h>
#include <wx/textdlg.h>
#include <boost/log/trivial.hpp>
#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

View File

@ -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 <wx/panel.h>
#include <wx/tbarbase.h>
#include "wx/textctrl.h"
#include <wx/timer.h>
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_ */

View File

@ -5,12 +5,15 @@
#ifndef slic3r_MacDarkMode_hpp_
#define slic3r_MacDarkMode_hpp_
#include <wx/event.h>
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

View File

@ -1,5 +1,7 @@
#import "MacDarkMode.hpp"
#include "wx/osx/core/cfstring.h"
#import <algorithm>
#import <Cocoa/Cocoa.h>
@ -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);
}
}];
}
}
}