Octoprint (#796)

* Octoprint: GUI for CA file, improvements

* Octoprint: Add GUI for Bonjour lookup, bugfixes

* Octoprint: Bonjour browser: Cleanup Perl interaction

* Octoprint: Bonjour: Perform several broadcast, UI fixes

* Octoprint: Add files to localization list

* Http: Disable CA File setting on SSL backends that don't support it
This commit is contained in:
Vojtech Kral 2018-03-15 18:06:26 +01:00 committed by bubnikv
parent 8d4b603572
commit c88d2780ce
21 changed files with 1587 additions and 1341 deletions

View File

@ -217,11 +217,7 @@ sub _init_tabpanel {
$self->{is_disabled_button_browse} = (!eval "use Net::Bonjour; 1") ? 1 : 0 ;
# A variable to inform C++ Tab implementation about user_agent
$self->{is_user_agent} = (eval "use LWP::UserAgent; 1") ? 1 : 0 ;
Slic3r::GUI::create_preset_tabs($self->{no_controller},
$self->{is_disabled_button_browse},
$self->{is_user_agent},
$VALUE_CHANGE_EVENT, $PRESETS_CHANGED_EVENT,
$BUTTON_BROWSE_EVENT, $BUTTON_TEST_EVENT);
Slic3r::GUI::create_preset_tabs($self->{no_controller}, $VALUE_CHANGE_EVENT, $PRESETS_CHANGED_EVENT);
$self->{options_tabs} = {};
for my $tab_name (qw(print filament printer)) {
$self->{options_tabs}{$tab_name} = Slic3r::GUI::get_preset_tab("$tab_name");

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,8 @@ xs/src/slic3r/GUI/2DBed.cpp
xs/src/slic3r/GUI/PresetHints.cpp
xs/src/slic3r/GUI/Preferences.hpp
xs/src/slic3r/GUI/Preferences.cpp
xs/src/slic3r/GUI/BonjourDialog.cpp
xs/src/slic3r/Utils/OctoPrint.cpp
xs/src/libslic3r/PrintConfig.cpp
xs/src/libslic3r/GCode/PreviewData.cpp
lib/Slic3r/GUI.pm

View File

@ -199,6 +199,8 @@ add_library(libslic3r_gui STATIC
${LIBDIR}/slic3r/GUI/2DBed.hpp
${LIBDIR}/slic3r/GUI/wxExtensions.cpp
${LIBDIR}/slic3r/GUI/wxExtensions.hpp
${LIBDIR}/slic3r/GUI/BonjourDialog.cpp
${LIBDIR}/slic3r/GUI/BonjourDialog.hpp
${LIBDIR}/slic3r/Utils/Http.cpp
${LIBDIR}/slic3r/Utils/Http.hpp
${LIBDIR}/slic3r/Utils/OctoPrint.cpp

View File

@ -0,0 +1,200 @@
#include "slic3r/Utils/Bonjour.hpp" // On Windows, boost needs to be included before wxWidgets headers
#include "BonjourDialog.hpp"
#include <set>
#include <mutex>
#include <wx/sizer.h>
#include <wx/button.h>
#include <wx/listctrl.h>
#include <wx/stattext.h>
#include <wx/timer.h>
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/Utils/Bonjour.hpp"
namespace Slic3r {
struct BonjourReplyEvent : public wxEvent
{
BonjourReply reply;
BonjourReplyEvent(wxEventType eventType, int winid, BonjourReply &&reply) :
wxEvent(winid, eventType),
reply(std::move(reply))
{}
virtual wxEvent *Clone() const
{
return new BonjourReplyEvent(*this);
}
};
wxDEFINE_EVENT(EVT_BONJOUR_REPLY, BonjourReplyEvent);
wxDECLARE_EVENT(EVT_BONJOUR_COMPLETE, wxCommandEvent);
wxDEFINE_EVENT(EVT_BONJOUR_COMPLETE, wxCommandEvent);
class ReplySet: public std::set<BonjourReply> {};
struct LifetimeGuard
{
std::mutex mutex;
BonjourDialog *dialog;
LifetimeGuard(BonjourDialog *dialog) : dialog(dialog) {}
};
BonjourDialog::BonjourDialog(wxWindow *parent) :
wxDialog(parent, wxID_ANY, _(L("Network lookup"))),
list(new wxListView(this, wxID_ANY, wxDefaultPosition, wxSize(800, 300))),
replies(new ReplySet),
label(new wxStaticText(this, wxID_ANY, "")),
timer(new wxTimer()),
timer_state(0)
{
wxBoxSizer *vsizer = new wxBoxSizer(wxVERTICAL);
vsizer->Add(label, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 10);
list->SetSingleStyle(wxLC_SINGLE_SEL);
list->SetSingleStyle(wxLC_SORT_DESCENDING);
list->AppendColumn(_(L("Address")), wxLIST_FORMAT_LEFT, 50);
list->AppendColumn(_(L("Hostname")), wxLIST_FORMAT_LEFT, 100);
list->AppendColumn(_(L("Service name")), wxLIST_FORMAT_LEFT, 200);
list->AppendColumn(_(L("OctoPrint version")), wxLIST_FORMAT_LEFT, 50);
vsizer->Add(list, 1, wxEXPAND | wxALL, 10);
wxBoxSizer *button_sizer = new wxBoxSizer(wxHORIZONTAL);
button_sizer->Add(new wxButton(this, wxID_OK, "OK"), 0, wxALL, 10);
button_sizer->Add(new wxButton(this, wxID_CANCEL, "Cancel"), 0, wxALL, 10);
// ^ Note: The Ok/Cancel labels are translated by wxWidgets
vsizer->Add(button_sizer, 0, wxALIGN_CENTER);
SetSizerAndFit(vsizer);
Bind(EVT_BONJOUR_REPLY, &BonjourDialog::on_reply, this);
Bind(EVT_BONJOUR_COMPLETE, [this](wxCommandEvent &) {
this->timer_state = 0;
});
Bind(wxEVT_TIMER, &BonjourDialog::on_timer, this);
}
BonjourDialog::~BonjourDialog()
{
// Needed bacuse of forward defs
}
bool BonjourDialog::show_and_lookup()
{
Show(); // Because we need GetId() to work before ShowModal()
timer->Stop();
timer->SetOwner(this);
timer_state = 1;
timer->Start(1000);
wxTimerEvent evt_dummy;
on_timer(evt_dummy);
// The background thread needs to queue messages for this dialog
// and for that it needs a valid pointer to it (mandated by the wxWidgets API).
// Here we put the pointer under a shared_ptr and protect it by a mutex,
// so that both threads can access it safely.
auto dguard = std::make_shared<LifetimeGuard>(this);
bonjour = std::move(Bonjour("octoprint")
.set_retries(3)
.set_timeout(4)
.on_reply([dguard](BonjourReply &&reply) {
std::lock_guard<std::mutex> lock_guard(dguard->mutex);
auto dialog = dguard->dialog;
if (dialog != nullptr) {
auto evt = new BonjourReplyEvent(EVT_BONJOUR_REPLY, dialog->GetId(), std::move(reply));
wxQueueEvent(dialog, evt);
}
})
.on_complete([dguard]() {
std::lock_guard<std::mutex> lock_guard(dguard->mutex);
auto dialog = dguard->dialog;
if (dialog != nullptr) {
auto evt = new wxCommandEvent(EVT_BONJOUR_COMPLETE, dialog->GetId());
wxQueueEvent(dialog, evt);
}
})
.lookup()
);
bool res = ShowModal() == wxID_OK && list->GetFirstSelected() >= 0;
{
// Tell the background thread the dialog is going away...
std::lock_guard<std::mutex> lock_guard(dguard->mutex);
dguard->dialog = nullptr;
}
return res;
}
wxString BonjourDialog::get_selected() const
{
auto sel = list->GetFirstSelected();
return sel >= 0 ? list->GetItemText(sel) : wxString();
}
// Private
void BonjourDialog::on_reply(BonjourReplyEvent &e)
{
if (replies->find(e.reply) != replies->end()) {
// We already have this reply
return;
}
replies->insert(std::move(e.reply));
auto selected = get_selected();
list->DeleteAllItems();
// The whole list is recreated so that we benefit from it already being sorted in the set.
// (And also because wxListView's sorting API is bananas.)
for (const auto &reply : *replies) {
auto item = list->InsertItem(0, reply.full_address);
list->SetItem(item, 1, reply.hostname);
list->SetItem(item, 2, reply.service_name);
list->SetItem(item, 3, reply.version);
}
for (int i = 0; i < 4; i++) {
this->list->SetColumnWidth(i, wxLIST_AUTOSIZE);
if (this->list->GetColumnWidth(i) < 100) { this->list->SetColumnWidth(i, 100); }
}
if (!selected.IsEmpty()) {
// Attempt to preserve selection
auto hit = list->FindItem(-1, selected);
if (hit >= 0) { list->SetItemState(hit, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); }
}
}
void BonjourDialog::on_timer(wxTimerEvent &)
{
const auto search_str = _(L("Searching for devices"));
if (timer_state > 0) {
const std::string dots(timer_state, '.');
label->SetLabel(wxString::Format("%s %s", search_str, dots));
timer_state = (timer_state) % 3 + 1;
} else {
label->SetLabel(wxString::Format("%s: %s", search_str, _(L("Finished."))));
timer->Stop();
}
}
}

View File

@ -0,0 +1,49 @@
#ifndef slic3r_BonjourDialog_hpp_
#define slic3r_BonjourDialog_hpp_
#include <memory>
#include <wx/dialog.h>
class wxListView;
class wxStaticText;
class wxTimer;
class wxTimerEvent;
namespace Slic3r {
class Bonjour;
class BonjourReplyEvent;
class ReplySet;
class BonjourDialog: public wxDialog
{
public:
BonjourDialog(wxWindow *parent);
BonjourDialog(BonjourDialog &&) = delete;
BonjourDialog(const BonjourDialog &) = delete;
BonjourDialog &operator=(BonjourDialog &&) = delete;
BonjourDialog &operator=(const BonjourDialog &) = delete;
~BonjourDialog();
bool show_and_lookup();
wxString get_selected() const;
private:
wxListView *list;
std::unique_ptr<ReplySet> replies;
wxStaticText *label;
std::shared_ptr<Bonjour> bonjour;
std::unique_ptr<wxTimer> timer;
unsigned timer_state;
void on_reply(BonjourReplyEvent &);
void on_timer(wxTimerEvent &);
};
}
#endif

View File

@ -261,7 +261,7 @@ void SpinCtrl::BUILD() {
// # when it was changed from the text control, so the on_change callback
// # gets the old one, and on_kill_focus resets the control to the old value.
// # As a workaround, we get the new value from $event->GetString and store
// # here temporarily so that we can return it from $self->get_value
// # here temporarily so that we can return it from $self->get_value
std::string value = e.GetString().utf8_str().data();
if (is_matched(value, "^\\d+$"))
tmp_value = std::stoi(value);
@ -365,9 +365,9 @@ void Choice::set_selection()
}
}
void Choice::set_value(const std::string value) //! Redundant?
void Choice::set_value(const std::string value, bool change_event) //! Redundant?
{
m_disable_change_event = true;
m_disable_change_event = !change_event;
size_t idx=0;
for (auto el : m_opt.enum_values)
@ -384,9 +384,9 @@ void Choice::set_value(const std::string value) //! Redundant?
m_disable_change_event = false;
}
void Choice::set_value(boost::any value)
void Choice::set_value(boost::any value, bool change_event)
{
m_disable_change_event = true;
m_disable_change_event = !change_event;
switch (m_opt.type){
case coInt:
@ -429,7 +429,7 @@ void Choice::set_values(const std::vector<std::string> values)
return;
m_disable_change_event = true;
// # it looks that Clear() also clears the text field in recent wxWidgets versions,
// # it looks that Clear() also clears the text field in recent wxWidgets versions,
// # but we want to preserve it
auto ww = dynamic_cast<wxComboBox*>(window);
auto value = ww->GetValue();
@ -541,9 +541,9 @@ void PointCtrl::BUILD()
y_textctrl->SetToolTip(get_tooltip_text(X+", "+Y));
}
void PointCtrl::set_value(const Pointf value)
void PointCtrl::set_value(const Pointf value, bool change_event)
{
m_disable_change_event = true;
m_disable_change_event = !change_event;
double val = value.x;
x_textctrl->SetValue(val - int(val) == 0 ? wxString::Format(_T("%i"), int(val)) : wxNumberFormatter::ToString(val, 2, wxNumberFormatter::Style_None));
@ -553,7 +553,7 @@ void PointCtrl::set_value(const Pointf value)
m_disable_change_event = false;
}
void PointCtrl::set_value(boost::any value)
void PointCtrl::set_value(boost::any value, bool change_event)
{
Pointf pt;
Pointf *ptf = boost::any_cast<Pointf>(&value);
@ -579,7 +579,7 @@ void PointCtrl::set_value(boost::any value)
// return;
// }
// }
set_value(pt);
set_value(pt, change_event);
}
boost::any PointCtrl::get_value()

View File

@ -78,7 +78,7 @@ public:
/// Sets a value for this control.
/// subclasses should overload with a specific version
/// Postcondition: Method does not fire the on_change event.
virtual void set_value(boost::any value) = 0;
virtual void set_value(boost::any value, bool change_event) = 0;
/// Gets a boost::any representing this control.
/// subclasses should overload with a specific version
@ -134,13 +134,13 @@ public:
void BUILD();
wxWindow* window {nullptr};
virtual void set_value(std::string value) {
m_disable_change_event = true;
virtual void set_value(std::string value, bool change_event = false) {
m_disable_change_event = !change_event;
dynamic_cast<wxTextCtrl*>(window)->SetValue(wxString(value));
m_disable_change_event = false;
}
virtual void set_value(boost::any value) {
m_disable_change_event = true;
virtual void set_value(boost::any value, bool change_event = false) {
m_disable_change_event = !change_event;
dynamic_cast<wxTextCtrl*>(window)->SetValue(boost::any_cast<wxString>(value));
m_disable_change_event = false;
}
@ -161,13 +161,13 @@ public:
wxWindow* window{ nullptr };
void BUILD() override;
void set_value(const bool value) {
m_disable_change_event = true;
void set_value(const bool value, bool change_event = false) {
m_disable_change_event = !change_event;
dynamic_cast<wxCheckBox*>(window)->SetValue(value);
m_disable_change_event = false;
}
void set_value(boost::any value) {
m_disable_change_event = true;
void set_value(boost::any value, bool change_event = false) {
m_disable_change_event = !change_event;
dynamic_cast<wxCheckBox*>(window)->SetValue(boost::any_cast<bool>(value));
m_disable_change_event = false;
}
@ -189,13 +189,13 @@ public:
wxWindow* window{ nullptr };
void BUILD() override;
void set_value(const std::string value) {
m_disable_change_event = true;
void set_value(const std::string value, bool change_event = false) {
m_disable_change_event = !change_event;
dynamic_cast<wxSpinCtrl*>(window)->SetValue(value);
m_disable_change_event = false;
}
void set_value(boost::any value) {
m_disable_change_event = true;
void set_value(boost::any value, bool change_event = false) {
m_disable_change_event = !change_event;
dynamic_cast<wxSpinCtrl*>(window)->SetValue(boost::any_cast<int>(value));
m_disable_change_event = false;
}
@ -218,8 +218,8 @@ public:
void BUILD() override;
void set_selection();
void set_value(const std::string value);
void set_value(boost::any value);
void set_value(const std::string value, bool change_event = false);
void set_value(boost::any value, bool change_event = false);
void set_values(const std::vector<std::string> values);
boost::any get_value() override;
@ -237,13 +237,13 @@ public:
wxWindow* window{ nullptr };
void BUILD() override;
void set_value(const std::string value) {
m_disable_change_event = true;
void set_value(const std::string value, bool change_event = false) {
m_disable_change_event = !change_event;
dynamic_cast<wxColourPickerCtrl*>(window)->SetColour(value);
m_disable_change_event = false;
}
void set_value(boost::any value) {
m_disable_change_event = true;
void set_value(boost::any value, bool change_event = false) {
m_disable_change_event = !change_event;
dynamic_cast<wxColourPickerCtrl*>(window)->SetColour(boost::any_cast<wxString>(value));
m_disable_change_event = false;
}
@ -267,8 +267,8 @@ public:
void BUILD() override;
void set_value(const Pointf value);
void set_value(boost::any value);
void set_value(const Pointf value, bool change_event = false);
void set_value(boost::any value, bool change_event = false);
boost::any get_value() override;
void enable() override {

View File

@ -358,24 +358,17 @@ void open_preferences_dialog(int event_preferences)
dlg->ShowModal();
}
void create_preset_tabs(bool no_controller, bool is_disabled_button_browse, bool is_user_agent,
int event_value_change, int event_presets_changed,
int event_button_browse, int event_button_test)
void create_preset_tabs(bool no_controller, int event_value_change, int event_presets_changed)
{
add_created_tab(new TabPrint (g_wxTabPanel, no_controller));
add_created_tab(new TabFilament (g_wxTabPanel, no_controller));
add_created_tab(new TabPrinter (g_wxTabPanel, no_controller, is_disabled_button_browse, is_user_agent));
add_created_tab(new TabPrinter (g_wxTabPanel, no_controller));
for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++ i) {
Tab *tab = dynamic_cast<Tab*>(g_wxTabPanel->GetPage(i));
if (! tab)
continue;
tab->set_event_value_change(wxEventType(event_value_change));
tab->set_event_presets_changed(wxEventType(event_presets_changed));
if (tab->name() == "printer"){
TabPrinter* tab_printer = static_cast<TabPrinter*>(tab);
tab_printer->set_event_button_browse(wxEventType(event_button_browse));
tab_printer->set_event_button_test(wxEventType(event_button_test));
}
}
}
@ -591,19 +584,6 @@ wxString from_u8(const std::string &str)
return wxString::FromUTF8(str.c_str());
}
wxWindow *get_widget_by_id(int id)
{
if (g_wxMainFrame == nullptr) {
throw std::runtime_error("Main frame not set");
}
wxWindow *window = g_wxMainFrame->FindWindow(id);
if (window == nullptr) {
throw std::runtime_error((boost::format("Could not find widget by ID: %1%") % id).str());
}
return window;
}
void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer)
{

View File

@ -86,9 +86,7 @@ void add_debug_menu(wxMenuBar *menu, int event_language_change);
void open_preferences_dialog(int event_preferences);
// Create a new preset tab (print, filament and printer),
void create_preset_tabs(bool no_controller, bool is_disabled_button_browse, bool is_user_agent,
int event_value_change, int event_presets_changed,
int event_button_browse, int event_button_test);
void create_preset_tabs(bool no_controller, int event_value_change, int event_presets_changed);
TabIface* get_preset_tab_iface(char *name);
// add it at the end of the tab panel.
@ -127,7 +125,6 @@ wxString L_str(const std::string &str);
// Return wxString from std::string in UTF8
wxString from_u8(const std::string &str);
wxWindow *get_widget_by_id(int id);
void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer);

View File

@ -97,9 +97,9 @@ public:
if (m_fields.find(id) == m_fields.end()) return nullptr;
return m_fields.at(id).get();
}
bool set_value(t_config_option_key id, boost::any value) {
bool set_value(t_config_option_key id, boost::any value, bool change_event = false) {
if (m_fields.find(id) == m_fields.end()) return false;
m_fields.at(id)->set_value(value);
m_fields.at(id)->set_value(value, change_event);
return true;
}
boost::any get_value(t_config_option_key id) {

View File

@ -3,6 +3,9 @@
#include "PresetBundle.hpp"
#include "PresetHints.hpp"
#include "../../libslic3r/Utils.hpp"
#include "slic3r/Utils/Http.hpp"
#include "slic3r/Utils/OctoPrint.hpp"
#include "BonjourDialog.hpp"
#include <wx/app.h>
#include <wx/button.h>
@ -14,6 +17,7 @@
#include <wx/treectrl.h>
#include <wx/imaglist.h>
#include <wx/settings.h>
#include <wx/filedlg.h>
#include <boost/algorithm/string/predicate.hpp>
@ -1102,39 +1106,18 @@ void TabPrinter::build()
}
optgroup = page->new_optgroup(_(L("OctoPrint upload")));
// # append two buttons to the Host line
auto octoprint_host_browse = [this] (wxWindow* parent) {
auto octoprint_host_browse = [this, optgroup] (wxWindow* parent) {
auto btn = new wxButton(parent, wxID_ANY, _(L(" Browse "))+"\u2026", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
// btn->SetFont($Slic3r::GUI::small_font);
btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("zoom.png")), wxBITMAP_TYPE_PNG));
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(btn);
if (m_is_disabled_button_browse)
btn->Disable();
btn->Bind(wxEVT_BUTTON, [this, parent](wxCommandEvent e){
if (m_event_button_browse > 0){
wxCommandEvent event(m_event_button_browse);
event.SetString("Button BROWSE was clicked!");
g_wxMainFrame->ProcessWindowEvent(event);
btn->Bind(wxEVT_BUTTON, [this, parent, optgroup](wxCommandEvent e) {
BonjourDialog dialog(parent);
if (dialog.show_and_lookup()) {
optgroup->set_value("octoprint_host", std::move(dialog.get_selected()), true);
}
// // # look for devices
// auto entries;
// {
// my $res = Net::Bonjour->new('http');
// $res->discover;
// $entries = [$res->entries];
// }
// if (@{$entries}) {
// my $dlg = Slic3r::GUI::BonjourBrowser->new($self, $entries);
// $self->_load_key_value('octoprint_host', $dlg->GetValue . ":".$dlg->GetPort)
// if $dlg->ShowModal == wxID_OK;
// }
// else {
// auto msg_window = new wxMessageDialog(parent, "No Bonjour device found", "Device Browser", wxOK | wxICON_INFORMATION);
// msg_window->ShowModal();
// }
});
return sizer;
@ -1143,33 +1126,23 @@ void TabPrinter::build()
auto octoprint_host_test = [this](wxWindow* parent) {
auto btn = m_octoprint_host_test_btn = new wxButton(parent, wxID_ANY, _(L("Test")),
wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT);
// btn->SetFont($Slic3r::GUI::small_font);
btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("wrench.png")), wxBITMAP_TYPE_PNG));
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(btn);
btn->Bind(wxEVT_BUTTON, [this, parent](wxCommandEvent e) {
if (m_event_button_test > 0){
wxCommandEvent event(m_event_button_test);
event.SetString("Button TEST was clicked!");
g_wxMainFrame->ProcessWindowEvent(event);
btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent e) {
OctoPrint octoprint(m_config);
wxString msg;
if (octoprint.test(msg)) {
show_info(this, _(L("Connection to OctoPrint works correctly.")), _(L("Success!")));
} else {
const auto text = wxString::Format("%s: %s\n\n%s",
_(L("Could not connect to OctoPrint")), msg, _(L("Note: OctoPrint version at least 1.1.0 is required."))
);
show_error(this, text);
}
// my $ua = LWP::UserAgent->new;
// $ua->timeout(10);
//
// my $res = $ua->get(
// "http://".$self->{config}->octoprint_host . "/api/version",
// 'X-Api-Key' = > $self->{config}->octoprint_apikey,
// );
// if ($res->is_success) {
// show_info(parent, "Connection to OctoPrint works correctly.", "Success!");
// }
// else {
// show_error(parent,
// "I wasn't able to connect to OctoPrint (".$res->status_line . "). "
// . "Check hostname and OctoPrint version (at least 1.1.0 is required).");
// }
});
return sizer;
};
@ -1179,6 +1152,45 @@ void TabPrinter::build()
optgroup->append_line(host_line);
optgroup->append_single_option_line("octoprint_apikey");
if (Http::ca_file_supported()) {
Line cafile_line = optgroup->create_single_option_line("octoprint_cafile");
auto octoprint_cafile_browse = [this, optgroup] (wxWindow* parent) {
auto btn = new wxButton(parent, wxID_ANY, _(L(" Browse "))+"\u2026", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("zoom.png")), wxBITMAP_TYPE_PNG));
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(btn);
btn->Bind(wxEVT_BUTTON, [this, optgroup] (wxCommandEvent e){
static const auto filemasks = _(L("Certificate files (*.crt, *.pem)|*.crt;*.pem|All files|*.*"));
wxFileDialog openFileDialog(this, _(L("Open CA certificate file")), "", "", filemasks, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (openFileDialog.ShowModal() != wxID_CANCEL) {
optgroup->set_value("octoprint_cafile", std::move(openFileDialog.GetPath()), true);
}
});
return sizer;
};
cafile_line.append_widget(octoprint_cafile_browse);
optgroup->append_line(cafile_line);
auto octoprint_cafile_hint = [this, optgroup] (wxWindow* parent) {
auto txt = new wxStaticText(parent, wxID_ANY,
_(L("HTTPS CA file is optional. It is only needed if you use HTTPS with a self-signed certificate.")));
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(txt);
return sizer;
};
Line cafile_hint { "", "" };
cafile_hint.full_width = 1;
cafile_hint.widget = std::move(octoprint_cafile_hint);
optgroup->append_line(cafile_hint);
}
optgroup = page->new_optgroup(_(L("Firmware")));
optgroup->append_single_option_line("gcode_flavor");
@ -1337,12 +1349,7 @@ void TabPrinter::update(){
m_serial_test_btn->Disable();
}
en = !m_config->opt_string("octoprint_host").empty();
if ( en && m_is_user_agent)
m_octoprint_host_test_btn->Enable();
else
m_octoprint_host_test_btn->Disable();
get_field("octoprint_apikey")->toggle(en);
m_octoprint_host_test_btn->Enable(!m_config->opt_string("octoprint_host").empty());
bool have_multiple_extruders = m_extruders_count > 1;
get_field("toolchange_gcode")->toggle(have_multiple_extruders);

View File

@ -214,11 +214,6 @@ public:
//Slic3r::GUI::Tab::Printer;
class TabPrinter : public Tab
{
bool m_is_disabled_button_browse;
bool m_is_user_agent;
// similar event by clicking Buttons "Browse" & "Test"
wxEventType m_event_button_browse = 0;
wxEventType m_event_button_test = 0;
public:
wxButton* m_serial_test_btn;
wxButton* m_octoprint_host_test_btn;
@ -228,10 +223,7 @@ public:
std::vector<PageShp> m_extruder_pages;
TabPrinter() {}
TabPrinter(wxNotebook* parent, bool no_controller, bool is_disabled_btn_browse, bool is_user_agent) :
Tab(parent, _(L("Printer Settings")), "printer", no_controller),
m_is_disabled_button_browse(is_disabled_btn_browse),
m_is_user_agent(is_user_agent) {}
TabPrinter(wxNotebook* parent, bool no_controller) : Tab(parent, _(L("Printer Settings")), "printer", no_controller) {}
~TabPrinter(){}
void build() override;
@ -240,10 +232,6 @@ public:
void extruders_count_changed(size_t extruders_count);
void build_extruder_pages();
void on_preset_loaded() override;
// Set the events to the callbacks posted to the main frame window (currently implemented in Perl).
void set_event_button_browse(wxEventType evt) { m_event_button_browse = evt; }
void set_event_button_test(wxEventType evt) { m_event_button_test = evt; }
};
class SavePresetWindow :public wxDialog

View File

@ -1,9 +1,7 @@
#include "Bonjour.hpp"
#include <iostream> // XXX
#include <cstdint>
#include <algorithm>
#include <unordered_map>
#include <array>
#include <vector>
#include <string>
@ -23,16 +21,18 @@ namespace asio = boost::asio;
using boost::asio::ip::udp;
// TODO: Fuzzing test (done without TXT)
// FIXME: check char retype to unsigned
namespace Slic3r {
// Minimal implementation of a MDNS/DNS-SD client
// This implementation is extremely simple, only the bits that are useful
// for very basic MDNS discovery are present.
// for basic MDNS discovery of OctoPi devices are present.
// However, the bits that are present are implemented with security in mind.
// Only fully correct DNS replies are allowed through.
// While decoding the decoder will bail the moment it encounters anything fishy.
// At least that's the idea. To help prove this is actually the case,
// the implementations has been tested with AFL.
struct DnsName: public std::string
{
@ -48,8 +48,7 @@ struct DnsName: public std::string
return boost::none;
}
// Check for recursion depth to prevent parsing names that are nested too deeply
// or end up cyclic:
// Check for recursion depth to prevent parsing names that are nested too deeply or end up cyclic:
if (depth >= MAX_RECURSION) {
return boost::none;
}
@ -443,6 +442,30 @@ private:
}
};
std::ostream& operator<<(std::ostream &os, const DnsMessage &msg)
{
os << "DnsMessage(ID: " << msg.header.id << ", "
<< "Q: " << (msg.question ? msg.question->name.c_str() : "none") << ", "
<< "A: " << (msg.rr_a ? msg.rr_a->ip.to_string() : "none") << ", "
<< "AAAA: " << (msg.rr_aaaa ? msg.rr_aaaa->ip.to_string() : "none") << ", "
<< "services: [";
enum { SRV_PRINT_MAX = 3 };
unsigned i = 0;
for (const auto &sdpair : msg.sdmap) {
os << sdpair.first << ", ";
if (++i >= SRV_PRINT_MAX) {
os << "...";
break;
}
}
os << "])";
return os;
}
struct BonjourRequest
{
@ -515,6 +538,7 @@ struct Bonjour::priv
const std::string protocol;
const std::string service_dn;
unsigned timeout;
unsigned retries;
uint16_t rq_id;
std::vector<char> buffer;
@ -524,6 +548,7 @@ struct Bonjour::priv
priv(std::string service, std::string protocol);
std::string strip_service_dn(const std::string &service_name) const;
void udp_receive(udp::endpoint from, size_t bytes);
void lookup_perform();
};
@ -533,11 +558,26 @@ Bonjour::priv::priv(std::string service, std::string protocol) :
protocol(std::move(protocol)),
service_dn((boost::format("_%1%._%2%.local") % this->service % this->protocol).str()),
timeout(10),
retries(1),
rq_id(0)
{
buffer.resize(DnsMessage::MAX_SIZE);
}
std::string Bonjour::priv::strip_service_dn(const std::string &service_name) const
{
if (service_name.size() <= service_dn.size()) {
return service_name;
}
auto needle = service_name.rfind(service_dn);
if (needle == service_name.size() - service_dn.size()) {
return service_name.substr(0, needle - 1);
} else {
return service_name;
}
}
void Bonjour::priv::udp_receive(udp::endpoint from, size_t bytes)
{
if (bytes == 0 || !replyfn) {
@ -557,7 +597,10 @@ void Bonjour::priv::udp_receive(udp::endpoint from, size_t bytes)
}
const auto &srv = *sdpair.second.srv;
BonjourReply reply(ip, sdpair.first, srv.hostname);
auto service_name = strip_service_dn(sdpair.first);
std::string path;
std::string version;
if (sdpair.second.txt) {
static const std::string tag_path = "path=";
@ -565,13 +608,14 @@ void Bonjour::priv::udp_receive(udp::endpoint from, size_t bytes)
for (const auto &value : sdpair.second.txt->values) {
if (value.size() > tag_path.size() && value.compare(0, tag_path.size(), tag_path) == 0) {
reply.path = value.substr(tag_path.size());
path = std::move(value.substr(tag_path.size()));
} else if (value.size() > tag_version.size() && value.compare(0, tag_version.size(), tag_version) == 0) {
reply.version = value.substr(tag_version.size());
version = std::move(value.substr(tag_version.size()));
}
}
}
BonjourReply reply(ip, srv.port, std::move(service_name), srv.hostname, std::move(path), std::move(version));
replyfn(std::move(reply));
}
}
@ -595,15 +639,26 @@ void Bonjour::priv::lookup_perform()
udp::endpoint mcast(BonjourRequest::MCAST_IP4, BonjourRequest::MCAST_PORT);
socket.send_to(asio::buffer(brq->data), mcast);
bool timeout = false;
bool expired = false;
bool retry = false;
asio::deadline_timer timer(io_service);
timer.expires_from_now(boost::posix_time::seconds(10));
timer.async_wait([=, &timeout](const error_code &error) {
timeout = true;
retries--;
std::function<void(const error_code &)> timer_handler = [&](const error_code &error) {
if (retries == 0 || error) {
expired = true;
if (self->completefn) {
self->completefn();
}
});
} else {
retry = true;
retries--;
timer.expires_from_now(boost::posix_time::seconds(timeout));
timer.async_wait(timer_handler);
}
};
timer.expires_from_now(boost::posix_time::seconds(timeout));
timer.async_wait(timer_handler);
udp::endpoint recv_from;
const auto recv_handler = [&](const error_code &error, size_t bytes) {
@ -612,8 +667,11 @@ void Bonjour::priv::lookup_perform()
socket.async_receive_from(asio::buffer(buffer, buffer.size()), recv_from, recv_handler);
while (io_service.run_one()) {
if (timeout) {
if (expired) {
socket.cancel();
} else if (retry) {
retry = false;
socket.send_to(asio::buffer(brq->data), mcast);
} else {
buffer.resize(DnsMessage::MAX_SIZE);
socket.async_receive_from(asio::buffer(buffer, buffer.size()), recv_from, recv_handler);
@ -626,13 +684,39 @@ void Bonjour::priv::lookup_perform()
// API - public part
BonjourReply::BonjourReply(boost::asio::ip::address ip, std::string service_name, std::string hostname) :
BonjourReply::BonjourReply(boost::asio::ip::address ip, uint16_t port, std::string service_name, std::string hostname, std::string path, std::string version) :
ip(std::move(ip)),
port(port),
service_name(std::move(service_name)),
hostname(std::move(hostname)),
path("/"),
version("Unknown")
{}
path(path.empty() ? std::move(std::string("/")) : std::move(path)),
version(version.empty() ? std::move(std::string("Unknown")) : std::move(version))
{
std::string proto;
std::string port_suffix;
if (port == 443) { proto = "https://"; }
if (port != 443 && port != 80) { port_suffix = std::to_string(port).insert(0, 1, ':'); }
if (this->path[0] != '/') { this->path.insert(0, 1, '/'); }
full_address = proto + ip.to_string() + port_suffix;
if (this->path != "/") { full_address += path; }
}
bool BonjourReply::operator==(const BonjourReply &other) const
{
return this->full_address == other.full_address
&& this->service_name == other.service_name;
}
bool BonjourReply::operator<(const BonjourReply &other) const
{
if (this->ip != other.ip) {
// So that the common case doesn't involve string comparison
return this->ip < other.ip;
} else {
auto cmp = this->full_address.compare(other.full_address);
return cmp != 0 ? cmp < 0 : this->service_name < other.service_name;
}
}
std::ostream& operator<<(std::ostream &os, const BonjourReply &reply)
{
@ -641,6 +725,7 @@ std::ostream& operator<<(std::ostream &os, const BonjourReply &reply)
return os;
}
Bonjour::Bonjour(std::string service, std::string protocol) :
p(new priv(std::move(service), std::move(protocol)))
{}
@ -660,6 +745,12 @@ Bonjour& Bonjour::set_timeout(unsigned timeout)
return *this;
}
Bonjour& Bonjour::set_retries(unsigned retries)
{
if (p && retries > 0) { p->retries = retries; }
return *this;
}
Bonjour& Bonjour::on_reply(ReplyFn fn)
{
if (p) { p->replyfn = std::move(fn); }
@ -677,7 +768,7 @@ Bonjour::Ptr Bonjour::lookup()
auto self = std::make_shared<Bonjour>(std::move(*this));
if (self->p) {
auto io_thread = std::thread([self](){
auto io_thread = std::thread([self]() {
self->p->lookup_perform();
});
self->p->io_thread = std::move(io_thread);
@ -687,18 +778,4 @@ Bonjour::Ptr Bonjour::lookup()
}
void Bonjour::pokus() // XXX
{
auto bonjour = Bonjour("octoprint")
.set_timeout(15)
.on_reply([](BonjourReply &&reply) {
std::cerr << "BonjourReply: " << reply << std::endl;
})
.on_complete([](){
std::cerr << "MDNS lookup complete" << std::endl;
})
.lookup();
}
}

View File

@ -1,26 +1,31 @@
#ifndef slic3r_Bonjour_hpp_
#define slic3r_Bonjour_hpp_
#include <cstdint>
#include <memory>
#include <string>
#include <functional>
// #include <ostream>
#include <boost/asio/ip/address.hpp>
namespace Slic3r {
// TODO: reply data structure
struct BonjourReply
{
boost::asio::ip::address ip;
uint16_t port;
std::string service_name;
std::string hostname;
std::string full_address;
std::string path;
std::string version;
BonjourReply(boost::asio::ip::address ip, std::string service_name, std::string hostname);
BonjourReply() = delete;
BonjourReply(boost::asio::ip::address ip, uint16_t port, std::string service_name, std::string hostname, std::string path, std::string version);
bool operator==(const BonjourReply &other) const;
bool operator<(const BonjourReply &other) const;
};
std::ostream& operator<<(std::ostream &, const BonjourReply &);
@ -32,7 +37,7 @@ private:
struct priv;
public:
typedef std::shared_ptr<Bonjour> Ptr;
typedef std::function<void(BonjourReply &&reply)> ReplyFn;
typedef std::function<void(BonjourReply &&)> ReplyFn;
typedef std::function<void()> CompleteFn;
Bonjour(std::string service, std::string protocol = "tcp");
@ -40,12 +45,15 @@ public:
~Bonjour();
Bonjour& set_timeout(unsigned timeout);
Bonjour& set_retries(unsigned retries);
// ^ Note: By default there is 1 retry (meaning 1 broadcast is sent).
// Timeout is per one retry, ie. total time spent listening = retries * timeout.
// If retries > 1, then care needs to be taken as more than one reply from the same service may be received.
Bonjour& on_reply(ReplyFn fn);
Bonjour& on_complete(CompleteFn fn);
Ptr lookup();
static void pokus(); // XXX: remove
private:
std::unique_ptr<priv> p;
};

View File

@ -3,7 +3,6 @@
#include <cstdlib>
#include <functional>
#include <thread>
#include <iostream>
#include <tuple>
#include <boost/format.hpp>
@ -45,7 +44,9 @@ struct Http::priv
priv(const std::string &url);
~priv();
static bool ca_file_supported(::CURL *curl);
static size_t writecb(void *data, size_t size, size_t nmemb, void *userp);
std::string curl_error(CURLcode curlcode);
std::string body_size_error();
void http_perform();
};
@ -71,6 +72,29 @@ Http::priv::~priv()
::curl_slist_free_all(headerlist);
}
bool Http::priv::ca_file_supported(::CURL *curl)
{
#ifdef _WIN32
bool res = false;
#else
bool res = true;
#endif
if (curl == nullptr) { return res; }
#if LIBCURL_VERSION_MAJOR >= 7 && LIBCURL_VERSION_MINOR >= 48
::curl_tlssessioninfo *tls;
if (::curl_easy_getinfo(curl, CURLINFO_TLS_SSL_PTR, &tls) == CURLE_OK) {
if (tls->backend == CURLSSLBACKEND_SCHANNEL || tls->backend == CURLSSLBACKEND_DARWINSSL) {
// With Windows and OS X native SSL support, cert files cannot be set
res = false;
}
}
#endif
return res;
}
size_t Http::priv::writecb(void *data, size_t size, size_t nmemb, void *userp)
{
auto self = static_cast<priv*>(userp);
@ -88,6 +112,14 @@ size_t Http::priv::writecb(void *data, size_t size, size_t nmemb, void *userp)
return realsize;
}
std::string Http::priv::curl_error(CURLcode curlcode)
{
return (boost::format("%1% (%2%)")
% ::curl_easy_strerror(curlcode)
% curlcode
).str();
}
std::string Http::priv::body_size_error()
{
return (boost::format("HTTP body data size exceeded limit (%1% bytes)") % limit).str();
@ -121,7 +153,7 @@ void Http::priv::http_perform()
if (res == CURLE_WRITE_ERROR) {
error = std::move(body_size_error());
} else {
error = ::curl_easy_strerror(res);
error = std::move(curl_error(res));
};
if (errorfn) {
@ -180,7 +212,7 @@ Http& Http::remove_header(std::string name)
Http& Http::ca_file(const std::string &name)
{
if (p) {
if (p && priv::ca_file_supported(p->curl)) {
::curl_easy_setopt(p->curl, CURLOPT_CAINFO, name.c_str());
}
@ -257,5 +289,13 @@ Http Http::post(std::string url)
return http;
}
bool Http::ca_file_supported()
{
::CURL *curl = ::curl_easy_init();
bool res = priv::ca_file_supported(curl);
if (curl != nullptr) { ::curl_easy_cleanup(curl); }
return res;
}
}

View File

@ -41,6 +41,7 @@ public:
Ptr perform();
void perform_sync();
static bool ca_file_supported();
private:
Http(const std::string &url);

View File

@ -20,16 +20,19 @@ OctoPrint::OctoPrint(DynamicPrintConfig *config) :
cafile(config->opt_string("octoprint_cafile"))
{}
std::string OctoPrint::test() const
bool OctoPrint::test(wxString &msg) const
{
// Since the request is performed synchronously here,
// it is ok to refer to `res` from within the closure
std::string res;
// it is ok to refer to `msg` from within the closure
auto http = Http::get(std::move(make_url("api/version")));
bool res = true;
auto url = std::move(make_url("api/version"));
auto http = Http::get(std::move(url));
set_auth(http);
http.on_error([&](std::string, std::string error, unsigned status) {
res = format_error(error, status);
res = false;
msg = format_error(error, status);
})
.perform_sync();
@ -43,21 +46,26 @@ void OctoPrint::send_gcode(int windowId, int completeEvt, int errorEvt, const st
http.form_add("print", print ? "true" : "false")
.form_add_file("file", filename)
.on_complete([=](std::string body, unsigned status) {
wxWindow *window = GUI::get_widget_by_id(windowId);
wxWindow *window = wxWindow::FindWindowById(windowId);
if (window == nullptr) { return; }
wxCommandEvent* evt = new wxCommandEvent(completeEvt);
evt->SetString("G-code file successfully uploaded to the OctoPrint server");
evt->SetString(_(L("G-code file successfully uploaded to the OctoPrint server")));
evt->SetInt(100);
wxQueueEvent(window, evt);
})
.on_error([=](std::string body, std::string error, unsigned status) {
wxWindow *window = GUI::get_widget_by_id(windowId);
wxWindow *window = wxWindow::FindWindowById(windowId);
if (window == nullptr) { return; }
wxCommandEvent* evt_complete = new wxCommandEvent(completeEvt);
evt_complete->SetInt(100);
wxQueueEvent(window, evt_complete);
wxCommandEvent* evt_error = new wxCommandEvent(errorEvt);
evt_error->SetString(wxString::Format("Error while uploading to the OctoPrint server: %s", format_error(error, status)));
evt_error->SetString(wxString::Format("%s: %s",
_(L("Error while uploading to the OctoPrint server")),
format_error(error, status)));
wxQueueEvent(window, evt_error);
})
.perform();
@ -85,19 +93,15 @@ std::string OctoPrint::make_url(const std::string &path) const
}
}
std::string OctoPrint::format_error(std::string error, unsigned status)
wxString OctoPrint::format_error(std::string error, unsigned status)
{
const wxString wxerror = error;
if (status != 0) {
std::string res{"HTTP "};
res.append(std::to_string(status));
if (status == 401) {
res.append(": Invalid API key");
}
return std::move(res);
return wxString::Format("HTTP %u: %s", status,
(status == 401 ? _(L("Invalid API key")) : wxerror));
} else {
return std::move(error);
return std::move(wxerror);
}
}

View File

@ -2,8 +2,8 @@
#define slic3r_OctoPrint_hpp_
#include <string>
#include <wx/string.h>
// #include "Http.hpp" // XXX: ?
namespace Slic3r {
@ -16,8 +16,7 @@ class OctoPrint
public:
OctoPrint(DynamicPrintConfig *config);
std::string test() const;
// XXX: style
bool test(wxString &curl_msg) const;
void send_gcode(int windowId, int completeEvt, int errorEvt, const std::string &filename, bool print = false) const;
private:
std::string host;
@ -26,7 +25,7 @@ private:
void set_auth(Http &http) const;
std::string make_url(const std::string &path) const;
static std::string format_error(std::string error, unsigned status);
static wxString format_error(std::string error, unsigned status);
};

View File

@ -35,12 +35,8 @@ void set_tab_panel(SV *ui)
void add_debug_menu(SV *ui, int event_language_change)
%code%{ Slic3r::GUI::add_debug_menu((wxMenuBar*)wxPli_sv_2_object(aTHX_ ui, "Wx::MenuBar"), event_language_change); %};
void create_preset_tabs(bool no_controller, bool is_disabled_button_browse, bool is_user_agent,
int event_value_change, int event_presets_changed,
int event_button_browse, int event_button_test)
%code%{ Slic3r::GUI::create_preset_tabs(no_controller, is_disabled_button_browse, is_user_agent,
event_value_change, event_presets_changed,
event_button_browse, event_button_test); %};
void create_preset_tabs(bool no_controller, int event_value_change, int event_presets_changed)
%code%{ Slic3r::GUI::create_preset_tabs(no_controller, event_value_change, event_presets_changed); %};
Ref<TabIface> get_preset_tab(char *name)
%code%{ RETVAL=Slic3r::GUI::get_preset_tab_iface(name); %};

View File

@ -9,6 +9,5 @@
OctoPrint(DynamicPrintConfig *config);
~OctoPrint();
std::string test() const;
void send_gcode(int windowId, int completeEvt, int errorEvt, std::string filename, bool print = false) const;
};