Changes in PrusaConnect interface

Prepared handling methods. Inside is WIP.

Handling UPDATE_SELECTED_PRINTER Connect request

ConnectRequestHandler class

Error screen as default
This commit is contained in:
David Kocik 2024-02-20 09:47:02 +01:00
parent 565d89a2e2
commit b232894451
12 changed files with 278 additions and 67 deletions

View File

@ -301,6 +301,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Downloader.hpp
GUI/DownloaderFileGet.cpp
GUI/DownloaderFileGet.hpp
GUI/LoginDialog.cpp
GUI/LoginDialog.hpp
Utils/AppUpdater.cpp
Utils/AppUpdater.hpp
Utils/Http.cpp

View File

@ -588,7 +588,7 @@ PageWelcome::PageWelcome(ConfigWizard *parent)
{
welcome_text->Hide();
cbox_reset->Hide();
cbox_integrate->Hide();
cbox_integrate->Hide();
}
void PageWelcome::set_run_reason(ConfigWizard::RunReason run_reason)

View File

@ -101,6 +101,7 @@
#include "UserAccount.hpp"
#include "MediaControlPanel.hpp"
#include "WebViewDialog.hpp"
#include "LoginDialog.hpp"
#include "BitmapCache.hpp"
//#include "Notebook.hpp"
@ -3150,6 +3151,15 @@ bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage
{
wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null");
if (!plater()->get_user_account()->is_logged()) {
m_login_dialog = std::make_unique<LoginDialog>(mainframe, plater()->get_user_account());
m_login_dialog->ShowModal();
mainframe->RemoveChild(m_login_dialog.get());
m_login_dialog->Destroy();
// Destructor does not call Destroy
m_login_dialog.reset();
}
if (reason == ConfigWizard::RR_USER) {
// Cancel sync before starting wizard to prevent two downloads at same time
preset_updater->cancel_sync();
@ -3179,6 +3189,14 @@ bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage
return res;
}
void GUI_App::update_login_dialog()
{
if (!m_login_dialog) {
return;
}
m_login_dialog->update_account();
}
void GUI_App::show_desktop_integration_dialog()
{
#ifdef __linux__
@ -3665,14 +3683,14 @@ bool GUI_App::select_printer_from_connect(const Preset* preset)
return is_installed;
}
void GUI_App::handle_web_request(std::string cmd)
void GUI_App::handle_connect_request_printer_pick(std::string msg)
{
BOOST_LOG_TRIVIAL(error) << "Handling web request: " << cmd;
BOOST_LOG_TRIVIAL(error) << "Handling web request: " << msg;
// 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 model_name = plater()->get_user_account()->get_model_from_json(msg);
std::string nozzle = plater()->get_user_account()->get_nozzle_from_json(msg);
assert(!model_name.empty());
if (model_name.empty())
return;

View File

@ -59,7 +59,7 @@ class NotificationManager;
class Downloader;
struct GUI_InitParams;
class GalleryDialog;
class LoginDialog;
enum FileType
@ -373,6 +373,7 @@ public:
void open_web_page_localized(const std::string &http_address);
bool may_switch_to_SLA_preset(const wxString& caption);
bool run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page = ConfigWizard::SP_WELCOME);
void update_login_dialog();
void show_desktop_integration_dialog();
void show_downloader_registration_dialog();
@ -410,7 +411,7 @@ public:
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 handle_connect_request_printer_pick(std::string cmd);
void show_printer_webview_tab(bool show, const DynamicPrintConfig& dpc = {});
// return true if preset vas invisible and we have to installed it to make it selectable
bool select_printer_from_connect(const Preset* printer_preset);
@ -447,6 +448,8 @@ private:
// change to vector of items when adding more items that require update
//wxMenuItem* m_login_config_menu_item { nullptr };
std::map< ConfigMenuIDs, wxMenuItem*> m_config_menu_updatable_items;
std::unique_ptr<LoginDialog> m_login_dialog;
};
DECLARE_APP(GUI_App)

View File

@ -0,0 +1,93 @@
#include "LoginDialog.hpp"
#include "GUI_App.hpp"
#include "I18N.hpp"
#include "format.hpp"
namespace Slic3r {
namespace GUI {
LoginDialog::LoginDialog(wxWindow* parent, UserAccount* user_account)
// TRN: This is the dialog title.
: DPIDialog(parent, wxID_ANY, _L("Prusa Account"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
, p_user_account(user_account)
{
const int em = wxGetApp().em_unit();
bool logged = p_user_account->is_logged();
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
// sizer with black border
wxStaticBoxSizer* static_box_sizer = new wxStaticBoxSizer(wxVERTICAL, this, _L("Log into your Prusa Account"));
static_box_sizer->SetMinSize(wxSize(em * 30, em * 15));
// avatar
boost::filesystem::path path = p_user_account->get_avatar_path(logged);
ScalableBitmap logo(this, path, wxSize(em * 10, em * 10));
m_avatar_bitmap = new wxStaticBitmap(this, wxID_ANY, logo.bmp(), wxDefaultPosition, wxDefaultSize);
static_box_sizer->Add(m_avatar_bitmap, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 10);
// username
const wxString username = GUI::format_wxstr("%1%", logged ? from_u8(p_user_account->get_username()) : _L("Anonymous"));
m_username_label = new wxStaticText(this, wxID_ANY, username, wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER);
m_username_label->SetFont(m_username_label->GetFont().Bold());
static_box_sizer->Add(m_username_label, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 5);
// login button
m_login_button_id = NewControlId();
m_login_button = new wxButton(this, m_login_button_id, logged ? _L("Log out") : _L("Log in"));
static_box_sizer->Add(m_login_button, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 10);
// TODO: why is m_login_button always hovered?
main_sizer->Add(static_box_sizer, 1, wxEXPAND | wxALL, 10);
// continue button
m_continue_button = new wxButton(this, wxID_OK, logged ? _L("Continue") : _L("Continue without Prusa Account"));
main_sizer->Add(m_continue_button, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 10);
SetSizerAndFit(main_sizer);
m_login_button->Bind(wxEVT_BUTTON, [user_account = p_user_account](wxCommandEvent& event) {
if (!user_account->is_logged())
user_account->do_login();
else
user_account->do_logout();
});
wxGetApp().UpdateDlgDarkUI(this);
SetFocus();
}
LoginDialog::~LoginDialog()
{
}
void LoginDialog::update_account()
{
bool logged = p_user_account->is_logged();
const wxString username = GUI::format_wxstr("%1%", logged ? from_u8(p_user_account->get_username()) : _L("Anonymous"));
m_username_label->SetLabel(username);
boost::filesystem::path path = p_user_account->get_avatar_path(logged);
if (boost::filesystem::exists(path)) {
const int em = wxGetApp().em_unit();
ScalableBitmap logo(this, path, wxSize(em * 10, em * 10));
m_avatar_bitmap->SetBitmap(logo.bmp());
}
m_login_button->SetLabel(logged ? _L("Log out") : _L("Log in"));
m_continue_button->SetLabel(logged ? _L("Continue") : _L("Continue without Prusa Account"));
// TODO: resize correctly m_continue_button
//m_continue_button->Fit();
Fit();
Refresh();
}
void LoginDialog::on_dpi_changed(const wxRect& suggested_rect)
{
SetFont(wxGetApp().normal_font());
const int em = em_unit();
msw_buttons_rescale(this, em, { wxID_OK, m_login_button_id});
Fit();
Refresh();
}
}}// Slicer::GUI

View File

@ -0,0 +1,36 @@
#ifndef slic3r_LoginDialog_hpp_
#define slic3r_LoginDialog_hpp_
#include "UserAccount.hpp"
#include "GUI_Utils.hpp"
#include <wx/button.h>
#include <wx/textctrl.h>
namespace Slic3r {
namespace GUI {
class RemovableDriveManager;
class LoginDialog : public DPIDialog
{
public:
LoginDialog(wxWindow* parent, UserAccount* user_account);
~LoginDialog();
void update_account();
private:
UserAccount* p_user_account;
wxStaticText* m_username_label;
wxStaticBitmap* m_avatar_bitmap;
wxButton* m_login_button;
int m_login_button_id{ wxID_ANY };
wxButton* m_continue_button;
protected:
void on_dpi_changed(const wxRect& suggested_rect) override;
void on_sys_color_changed() override {}
};
}} // Slicer::GUI
#endif

View File

@ -270,7 +270,7 @@ struct Plater::priv
std::unique_ptr<UserAccount> user_account;
ProjectDirtyStateManager dirty_state;
BackgroundSlicingProcess background_process;
bool suppressed_backround_processing_update { false };
@ -614,7 +614,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
}))
, sidebar(new Sidebar(q))
, notification_manager(std::make_unique<NotificationManager>(q))
, user_account(std::make_unique<UserAccount>(q, wxGetApp().app_config))
, user_account(std::make_unique<UserAccount>(q, wxGetApp().app_config, wxGetApp().get_instance_hash_string()))
, m_worker{q, std::make_unique<NotificationProgressIndicator>(notification_manager.get()), "ui_worker"}
, m_sla_import_dlg{new SLAImportDialog{q}}
, delayed_scene_refresh(false)
@ -892,6 +892,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
this->main_frame->refresh_account_menu(true);
// Update sidebar printer status
sidebar->update_printer_presets_combobox();
this->main_frame->refresh_account_menu();
wxGetApp().update_login_dialog();
});
this->q->Bind(EVT_UA_ID_USER_SUCCESS, [this](UserAccountSuccessEvent& evt) {
@ -905,6 +907,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
this->main_frame->add_connect_webview_tab();
// Update User name in TopBar
this->main_frame->refresh_account_menu();
wxGetApp().update_login_dialog();
} else {
// data were corrupt and username was not retrieved
// procced as if EVT_UA_RESET was recieved
@ -955,13 +958,13 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
}
});
this->q->Bind(EVT_UA_AVATAR_SUCCESS, [this](UserAccountSuccessEvent& evt) {
const std::string filename = "prusaslicer-avatar-" + wxGetApp().get_instance_hash_string() + ".png";
const boost::filesystem::path png_path = boost::filesystem::path(wxStandardPaths::Get().GetTempDir().utf8_str().data()) / filename;
boost::filesystem::path path = user_account->get_avatar_path(true);
FILE* file;
file = fopen(png_path.string().c_str(), "wb");
file = fopen(path.string().c_str(), "wb");
fwrite(evt.data.c_str(), 1, evt.data.size(), file);
fclose(file);
this->main_frame->refresh_account_menu(true);
wxGetApp().update_login_dialog();
});
wxGetApp().other_instance_message_handler()->init(this->q);

View File

@ -195,15 +195,14 @@ void TopBarItemsCtrl::UpdateAccountMenu(bool avatar/* = false*/)
m_login_menu_item->SetBitmap(user_account->is_logged() ? *get_bmp_bundle("logout", 16) : *get_bmp_bundle("login", 16));
}
const wxString user_name = user_account->is_logged() ? from_u8(user_account->get_username()) : _L("Anonymus");
const wxString user_name = user_account->is_logged() ? from_u8(user_account->get_username()) : _L("Anonymous");
if (m_user_menu_item)
m_user_menu_item->SetItemLabel(user_name);
m_account_btn->SetLabel(user_name);
if (avatar) {
if (user_account->is_logged()) {
const std::string filename = "prusaslicer-avatar-" + wxGetApp().get_instance_hash_string() + ".png";
boost::filesystem::path path = boost::filesystem::path(wxStandardPaths::Get().GetTempDir().utf8_str().data()) / filename;
boost::filesystem::path path = user_account->get_avatar_path(true);
ScalableBitmap new_logo(this, path, m_account_btn->GetBitmapSize());
if (new_logo.IsOk())
m_account_btn->SetBitmap_(new_logo);
@ -292,8 +291,7 @@ TopBarItemsCtrl::TopBarItemsCtrl(wxWindow *parent) :
// create Account menu
CreateAccountMenu();
// m_account_btn = new ButtonWithPopup(this, "user", 35);
m_account_btn = new ButtonWithPopup(this, _L("Anonymus"), "user");
m_account_btn = new ButtonWithPopup(this, _L("Anonymous"), "user");
right_sizer->Add(m_account_btn, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxRIGHT | wxLEFT, m_btn_margin);
m_account_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) {

View File

@ -2,6 +2,8 @@
#include "format.hpp"
#include "libslic3r/Utils.hpp"
#include <boost/regex.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
@ -12,8 +14,9 @@ namespace pt = boost::property_tree;
namespace Slic3r {
namespace GUI {
UserAccount::UserAccount(wxEvtHandler* evt_handler, AppConfig* app_config)
UserAccount::UserAccount(wxEvtHandler* evt_handler, AppConfig* app_config, const std::string& instance_hash)
: m_communication(std::make_unique<UserAccountCommunication>(evt_handler, app_config))
, m_instance_hash(instance_hash)
{}
UserAccount::~UserAccount()
@ -64,6 +67,16 @@ std::string UserAccount::get_access_token()
return m_communication->get_access_token();
}
boost::filesystem::path UserAccount::get_avatar_path(bool logged) const
{
if (logged) {
const std::string filename = "prusaslicer-avatar-" + m_instance_hash + ".png";
return boost::filesystem::path(wxStandardPaths::Get().GetTempDir().utf8_str().data()) / filename;
} else {
return boost::filesystem::path(resources_dir()) / "icons" / "user.svg";
}
}
#if 0
void UserAccount::enqueue_user_id_action()
{

View File

@ -27,7 +27,7 @@ typedef std::map<std::string, std::vector<size_t>> ConnectPrinterStateMap;
// All incoming data shoud be stored in UserAccount.
class UserAccount {
public:
UserAccount(wxEvtHandler* evt_handler, Slic3r::AppConfig* app_config);
UserAccount(wxEvtHandler* evt_handler, Slic3r::AppConfig* app_config, const std::string& instance_hash);
~UserAccount();
bool is_logged();
@ -59,6 +59,7 @@ public:
const ConnectPrinterStateMap& get_printer_state_map() const { return m_printer_map; }
const std::map<std::string, std::string> get_user_data() const { return m_user_data; }
std::string get_connect_address() const { return "https://dev.connect.prusa3d.com"; }
boost::filesystem::path get_avatar_path(bool logged) const;
// standalone utility methods
std::string get_model_from_json(const std::string& message) const;
@ -67,6 +68,7 @@ public:
private:
void set_username(const std::string& username);
std::string m_instance_hash; // used in avatar path
std::unique_ptr<Slic3r::GUI::UserAccountCommunication> m_communication;

View File

@ -79,7 +79,7 @@ WebViewPanel::WebViewPanel(wxWindow *parent, const wxString& default_url)
#endif
// Create the webview
m_browser = WebView::CreateWebView(this, m_default_url);
m_browser = WebView::CreateWebView(this, /*m_default_url*/ wxString::Format("file://%s/web/connection_failed.html", from_u8(resources_dir())));
if (m_browser == nullptr) {
wxLogError("Could not init m_browser");
return;
@ -439,38 +439,34 @@ SourceViewDialog::SourceViewDialog(wxWindow* parent, wxString source) :
SetSizer(sizer);
}
ConnectWebViewPanel::ConnectWebViewPanel(wxWindow* parent)
: WebViewPanel(parent, L"https://dev.connect.prusa3d.com/connect-slicer-app/printer-list")
ConnectRequestHandler::ConnectRequestHandler()
{
m_actions["requestAccessToken"] = std::bind(&ConnectWebViewPanel::connect_set_access_token, this);
m_actions["requestLanguage"] = std::bind(&ConnectWebViewPanel::connect_set_language, this);
m_actions["REQUEST_ACCESS_TOKEN"] = std::bind(&ConnectRequestHandler::on_request_access_token, this);
m_actions["REQUEST_LANGUAGE"] = std::bind(&ConnectRequestHandler::on_request_language_action, this);
m_actions["REQUEST_SESSION_ID"] = std::bind(&ConnectRequestHandler::on_request_session_id_action, this);
m_actions["UPDATE_SELECTED_PRINTER"] = std::bind(&ConnectRequestHandler::on_request_update_selected_printer_action, this);
}
void ConnectWebViewPanel::on_script_message(wxWebViewEvent& evt)
ConnectRequestHandler::~ConnectRequestHandler()
{
}
void ConnectRequestHandler::handle_message(const std::string& message)
{
BOOST_LOG_TRIVIAL(info) << "recieved message from _prusaConnect" << evt.GetString();
// read msg and choose action
/*
{"type":"request","detail":{"action":"requestAccessToken"}}
*/
/*
v0:
{"type":"request","detail":{"action":"requestAccessToken"}}
v1:
{"action":"REQUEST_ACCESS_TOKEN"}
*/
std::string action_string;
m_message_data = message;
try {
std::stringstream ss(into_u8(evt.GetString()));
std::stringstream ss(m_message_data);
pt::ptree ptree;
pt::read_json(ss, ptree);
std::string type_string;
if (const auto type = ptree.get_optional<std::string>("type"); type) {
type_string = *type;
}
assert(!type_string.empty());
if (type_string == "request") {
for (const auto& section : ptree) {
if (section.first == "detail") {
if (const auto action = section.second.get_optional<std::string>("action"); action) {
action_string = *action;
}
}
}
// v1:
if (const auto action = ptree.get_optional<std::string>("action"); action) {
action_string = *action;
}
}
catch (const std::exception& e) {
@ -479,31 +475,53 @@ void ConnectWebViewPanel::on_script_message(wxWebViewEvent& evt)
}
if (action_string.empty()) {
BOOST_LOG_TRIVIAL(error) << "Recieved invalid message from _prusaConnect (missing action). Message: " << evt.GetString();
BOOST_LOG_TRIVIAL(error) << "Recieved invalid message from _prusaConnect (missing action). Message: " << message;
return;
}
assert(m_actions.find(action_string) != m_actions.end()); // this assert means there is a action that has no handling.
if (m_actions.find(action_string) != m_actions.end()) {
m_actions[action_string]();
}
//wxGetApp().handle_web_request(evt.GetString().ToUTF8().data());
}
void ConnectWebViewPanel::connect_set_access_token()
void ConnectRequestHandler::on_request_access_token()
{
std::string token = wxGetApp().plater()->get_user_account()->get_access_token();
wxString script = GUI::format_wxstr("window._prusaConnect.setAccessToken(\'%1%\')", token);
run_script(script);
wxString script = GUI::format_wxstr("window._prusaConnect_v1.setAccessToken(\'%1%\')", token);
run_script_bridge(script);
}
void ConnectWebViewPanel::connect_set_language()
void ConnectRequestHandler::on_request_language_action()
{
// TODO:
std::string lang = "en";
wxString script = GUI::format_wxstr("window._prusaConnect.setAccessToken(\'en\')", lang);
run_script(script);
//std::string lang = "en";
//wxString script = GUI::format_wxstr("window._prusaConnect_v1.setAccessToken(\'en\')", lang);
//run_script(script);
}
void ConnectRequestHandler::on_request_session_id_action()
{
/*
std::string token = wxGetApp().plater()->get_user_account()->get_access_token();
wxString script = GUI::format_wxstr("window._prusaConnect_v1.setAccessToken(\'%1%\')", token);
run_script(script);
*/
}
ConnectWebViewPanel::ConnectWebViewPanel(wxWindow* parent)
: WebViewPanel(parent, L"https://dev.connect.prusa3d.com/connect-slicer-app/printer-list")
{
}
void ConnectWebViewPanel::on_script_message(wxWebViewEvent& evt)
{
BOOST_LOG_TRIVIAL(error) << "recieved message from PrusaConnect FE: " << evt.GetString();
handle_message(into_u8(evt.GetString()));
}
void ConnectWebViewPanel::on_request_update_selected_printer_action()
{
assert(!m_message_data.empty());
wxGetApp().handle_connect_request_printer_pick(m_message_data);
}
PrinterWebViewPanel::PrinterWebViewPanel(wxWindow* parent, const wxString& default_url)
: WebViewPanel(parent, default_url)
@ -605,13 +623,14 @@ void WebViewDialog::run_script(const wxString& javascript)
{
if (!m_browser)
return;
//bool res = WebView::run_script(m_browser, javascript);
bool res = WebView::run_script(m_browser, javascript);
}
PrinterPickWebViewDialog::PrinterPickWebViewDialog(wxWindow* parent, std::string& ret_val)
: WebViewDialog(parent, L"https://dev.connect.prusa3d.com/prusa-slicer/printers")
// : WebViewDialog(parent, L"https://dev.connect.prusa3d.com/prusa-slicer/printers")
: WebViewDialog(parent, L"https://dev.connect.prusa3d.com/connect-slicer-app/printer-list")
, m_ret_val(ret_val)
{
}
@ -626,7 +645,12 @@ void PrinterPickWebViewDialog::on_show(wxShowEvent& evt)
}
void PrinterPickWebViewDialog::on_script_message(wxWebViewEvent& evt)
{
m_ret_val = evt.GetString().ToUTF8().data();
handle_message(into_u8(evt.GetString()));
}
void PrinterPickWebViewDialog::on_request_update_selected_printer_action()
{
m_ret_val = m_message_data;
this->EndModal(wxID_OK);
}

View File

@ -43,7 +43,6 @@ public:
void on_show(wxShowEvent& evt);
virtual void on_script_message(wxWebViewEvent& evt);
void on_loaded(wxWebViewEvent& evt);
void on_idle(wxIdleEvent& evt);
void on_url(wxCommandEvent& evt);
@ -106,16 +105,33 @@ protected:
//DECLARE_EVENT_TABLE()
};
class ConnectWebViewPanel : public WebViewPanel
class ConnectRequestHandler
{
public:
ConnectRequestHandler();
~ConnectRequestHandler();
void handle_message(const std::string& message);
protected:
// action callbacs stored in m_actions
virtual void on_request_access_token();
virtual void on_request_language_action();
virtual void on_request_session_id_action();
virtual void on_request_update_selected_printer_action() = 0;
virtual void run_script_bridge(const wxString& script) = 0;
std::map<std::string, std::function<void(void)>> m_actions;
std::string m_message_data;
};
class ConnectWebViewPanel : public WebViewPanel, public ConnectRequestHandler
{
public:
ConnectWebViewPanel(wxWindow* parent);
void on_script_message(wxWebViewEvent& evt) override;
void connect_set_access_token();
void connect_set_language();
private:
std::map<std::string, std::function<void(void)>> m_actions;
protected:
void on_request_update_selected_printer_action() override;
void run_script_bridge(const wxString& script) override {run_script(script); }
};
class PrinterWebViewPanel : public WebViewPanel
@ -153,12 +169,15 @@ protected:
wxWebView* m_browser;
};
class PrinterPickWebViewDialog : public WebViewDialog
class PrinterPickWebViewDialog : public WebViewDialog, public ConnectRequestHandler
{
public:
PrinterPickWebViewDialog(wxWindow* parent, std::string& ret_val);
void on_show(wxShowEvent& evt) override;
void on_script_message(wxWebViewEvent& evt) override;
protected:
void on_request_update_selected_printer_action() override;
void run_script_bridge(const wxString& script) override { run_script(script); }
private:
std::string& m_ret_val;
};