diff --git a/src/libslic3r/Platform.cpp b/src/libslic3r/Platform.cpp index 4aa72c95f..338752112 100644 --- a/src/libslic3r/Platform.cpp +++ b/src/libslic3r/Platform.cpp @@ -105,4 +105,42 @@ PlatformFlavor platform_flavor() return s_platform_flavor; } + + +std::string platform_to_string(Platform platform) +{ + switch (platform) { + case Platform::Uninitialized: return "Unitialized"; + case Platform::Unknown : return "Unknown"; + case Platform::Windows : return "Windows"; + case Platform::OSX : return "OSX"; + case Platform::Linux : return "Linux"; + case Platform::BSDUnix : return "BSDUnix"; + } + assert(false); + return ""; +} + + + +std::string platform_flavor_to_string(PlatformFlavor pf) +{ + switch (pf) { + case PlatformFlavor::Uninitialized : return "Unitialized"; + case PlatformFlavor::Unknown : return "Unknown"; + case PlatformFlavor::Generic : return "Generic"; + case PlatformFlavor::GenericLinux : return "GenericLinux"; + case PlatformFlavor::LinuxOnChromium : return "LinuxOnChromium"; + case PlatformFlavor::WSL : return "WSL"; + case PlatformFlavor::WSL2 : return "WSL2"; + case PlatformFlavor::OpenBSD : return "OpenBSD"; + case PlatformFlavor::GenericOSX : return "GenericOSX"; + case PlatformFlavor::OSXOnX86 : return "OSXOnX86"; + case PlatformFlavor::OSXOnArm : return "OSXOnArm"; + } + assert(false); + return ""; +} + + } // namespace Slic3r diff --git a/src/libslic3r/Platform.hpp b/src/libslic3r/Platform.hpp index 1b4d74c02..6db8ba880 100644 --- a/src/libslic3r/Platform.hpp +++ b/src/libslic3r/Platform.hpp @@ -1,6 +1,8 @@ #ifndef SLIC3R_Platform_HPP #define SLIC3R_Platform_HPP +#include + namespace Slic3r { enum class Platform @@ -16,24 +18,16 @@ enum class Platform enum class PlatformFlavor { Uninitialized, - Unknown, - // For Windows and OSX, until we need to be more specific. - Generic, - // For Platform::Linux - GenericLinux, - LinuxOnChromium, - // Microsoft's Windows on Linux (Linux kernel simulated on NTFS kernel) - WSL, - // Microsoft's Windows on Linux, version 2 (virtual machine) - WSL2, - // For Platform::BSDUnix - OpenBSD, - // For Platform::OSX - GenericOSX, - // For Apple's on Intel X86 CPU - OSXOnX86, - // For Apple's on Arm CPU - OSXOnArm, + Unknown, + Generic, // For Windows and OSX, until we need to be more specific. + GenericLinux, // For Platform::Linux + LinuxOnChromium, // For Platform::Linux + WSL, // Microsoft's Windows on Linux (Linux kernel simulated on NTFS kernel) + WSL2, // Microsoft's Windows on Linux, version 2 (virtual machine) + OpenBSD, // For Platform::BSDUnix + GenericOSX, // For Platform::OSX + OSXOnX86, // For Apple's on Intel X86 CPU + OSXOnArm, // For Apple's on Arm CPU }; // To be called on program start-up. @@ -42,6 +36,9 @@ void detect_platform(); Platform platform(); PlatformFlavor platform_flavor(); +std::string platform_to_string(Platform platform); +std::string platform_flavor_to_string(PlatformFlavor pf); + } // namespace Slic3r #endif // SLIC3R_Platform_HPP diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 29d1fae29..9a5252949 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -141,6 +141,8 @@ set(SLIC3R_GUI_SOURCES GUI/RammingChart.hpp GUI/RemovableDriveManager.cpp GUI/RemovableDriveManager.hpp + GUI/SendSystemInfoDialog.cpp + GUI/SendSystemInfoDialog.hpp GUI/BonjourDialog.cpp GUI/BonjourDialog.hpp GUI/ButtonsDescription.cpp diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 41f4c4b89..e1fcc029a 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -70,6 +70,7 @@ #include "SavePresetDialog.hpp" #include "PrintHostDialogs.hpp" #include "DesktopIntegrationDialog.hpp" +#include "SendSystemInfoDialog.hpp" #include "BitmapCache.hpp" #include "Notebook.hpp" @@ -681,6 +682,10 @@ void GUI_App::post_init() }); } + // 'Send system info' dialog. Again, a CallAfter is needed on mac. + // Without it, GL extensions did not show. + CallAfter([] { show_send_system_info_dialog_if_needed(); }); + #ifdef _WIN32 // Sets window property to mainframe so other instances can indentify it. OtherInstanceMessageHandler::init_windows_properties(mainframe, m_instance_hash_int); diff --git a/src/slic3r/GUI/OpenGLManager.cpp b/src/slic3r/GUI/OpenGLManager.cpp index 91f1f1f0b..3baa8a4da 100644 --- a/src/slic3r/GUI/OpenGLManager.cpp +++ b/src/slic3r/GUI/OpenGLManager.cpp @@ -27,7 +27,7 @@ namespace Slic3r { namespace GUI { // A safe wrapper around glGetString to report a "N/A" string in case glGetString returns nullptr. -inline std::string gl_get_string_safe(GLenum param, const std::string& default_value) +std::string gl_get_string_safe(GLenum param, const std::string& default_value) { const char* value = (const char*)::glGetString(param); return std::string((value != nullptr) ? value : default_value); diff --git a/src/slic3r/GUI/OpenGLManager.hpp b/src/slic3r/GUI/OpenGLManager.hpp index ca9452db6..716256e40 100644 --- a/src/slic3r/GUI/OpenGLManager.hpp +++ b/src/slic3r/GUI/OpenGLManager.hpp @@ -10,6 +10,7 @@ class wxGLContext; namespace Slic3r { namespace GUI { + class OpenGLManager { public: diff --git a/src/slic3r/GUI/SendSystemInfoDialog.cpp b/src/slic3r/GUI/SendSystemInfoDialog.cpp new file mode 100644 index 000000000..db6ebad57 --- /dev/null +++ b/src/slic3r/GUI/SendSystemInfoDialog.cpp @@ -0,0 +1,647 @@ +#include "SendSystemInfoDialog.hpp" + +#include "libslic3r/AppConfig.hpp" +#include "libslic3r/Platform.hpp" +#include "libslic3r/Utils.hpp" + +#include "slic3r/GUI/format.hpp" +#include "slic3r/Utils/Http.hpp" + +#include "GUI_App.hpp" +#include "GUI_Utils.hpp" +#include "I18N.hpp" +#include "MainFrame.hpp" +#include "MsgDialog.hpp" +#include "OpenGLManager.hpp" + +#include +#include +#include +#include +#include + +#include "GL/glew.h" + +#include +#include +#include +#include +#include + +#include +#include + +#ifdef _WIN32 + #include + #include + #pragma comment(lib, "iphlpapi.lib") +#elif __APPLE__ +#import +#endif + +namespace Slic3r { +namespace GUI { + + + +// Declaration of a free function defined in OpenGLManager.cpp: +std::string gl_get_string_safe(GLenum param, const std::string& default_value); + + +// A dialog with the information text and buttons send/dont send/ask later. +class SendSystemInfoDialog : public DPIDialog +{ + enum { + MIN_WIDTH = 80, + MIN_HEIGHT = 50 + }; + +public: + SendSystemInfoDialog(wxWindow* parent); + +private: + bool send_info(); + const std::string m_system_info_json; + wxButton* m_btn_send; + wxButton* m_btn_dont_send; + wxButton* m_btn_ask_later; + + void on_dpi_changed(const wxRect&) override; +}; + + + +// A dialog to show when the upload is in progress (with a Cancel button). +class SendSystemInfoProgressDialog : public wxDialog +{ +public: + SendSystemInfoProgressDialog(wxWindow* parent, const wxString& message) + : wxDialog(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxCAPTION) + { + auto* text = new wxStaticText(this, wxID_ANY, message, wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER_HORIZONTAL); + auto* btn = new wxButton(this, wxID_CANCEL, _L("Cancel")); + auto* vsizer = new wxBoxSizer(wxVERTICAL); + auto *top_sizer = new wxBoxSizer(wxVERTICAL); + vsizer->Add(text, 1, wxEXPAND|wxALIGN_CENTER_HORIZONTAL); + vsizer->AddSpacer(5); + vsizer->Add(btn, 0, wxALIGN_CENTER_HORIZONTAL); + top_sizer->Add(vsizer, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT | wxBOTTOM, 10); + SetSizer(top_sizer); + #ifdef _WIN32 + wxGetApp().UpdateDlgDarkUI(this); + #endif + } +}; + + + +// A dialog with multiline read-only text control to show the JSON. +class ShowJsonDialog : public wxDialog +{ +public: + ShowJsonDialog(wxWindow* parent, const wxString& json, const wxSize& size) + : wxDialog(parent, wxID_ANY, _L("Data to send"), wxDefaultPosition, size, wxCAPTION|wxRESIZE_BORDER) + { + auto* text = new wxTextCtrl(this, wxID_ANY, json, + wxDefaultPosition, wxDefaultSize, + wxTE_MULTILINE | wxTE_READONLY | wxTE_DONTWRAP); + text->SetFont(wxGetApp().code_font()); + text->ShowPosition(0); + + auto* btn = new wxButton(this, wxID_CANCEL, _L("Close")); + auto* vsizer = new wxBoxSizer(wxVERTICAL); + auto *top_sizer = new wxBoxSizer(wxVERTICAL); + vsizer->Add(text, 1, wxEXPAND); + vsizer->AddSpacer(5); + vsizer->Add(btn, 0, wxALIGN_CENTER_HORIZONTAL); + top_sizer->Add(vsizer, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT | wxBOTTOM, 10); + SetSizer(top_sizer); + #ifdef _WIN32 + wxGetApp().UpdateDlgDarkUI(this); + #endif + } +}; + + + +// Last version where the info was sent / dialog dismissed is saved in appconfig. +// Only show the dialog when this info is not found (e.g. fresh install) or when +// current version is newer. Only major and minor versions are compared. +static bool should_dialog_be_shown() +{ + return false; + + std::string last_sent_version = wxGetApp().app_config->get("version_system_info_sent"); + Semver semver_current(SLIC3R_VERSION); + Semver semver_last_sent; + if (! last_sent_version.empty()) + semver_last_sent = Semver(last_sent_version); + + if (semver_current.prerelease() && std::string(semver_current.prerelease()) == "alpha") + return false; // Don't show in alphas. + + // Show the dialog if current > last, but they differ in more than just patch. + return ((semver_current.maj() > semver_last_sent.maj()) + || (semver_current.maj() == semver_last_sent.maj() && semver_current.min() > semver_last_sent.min() )); +} + + + +// Following function saves current PrusaSlicer version into app config. +// It will be later used to decide whether to open the dialog or not. +static void save_version() +{ + wxGetApp().app_config->set("version_system_info_sent", std::string(SLIC3R_VERSION)); +} + + + +#ifdef _WIN32 +static std::map get_cpu_info_from_registry() +{ + std::map out; + + int idx = -1; + constexpr DWORD bufsize_ = 200; + DWORD bufsize = bufsize_; + char buf[bufsize_] = ""; + const std::string reg_dir = "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\"; + std::string reg_path = reg_dir; + + // Look into that reg dir and possibly into subdirs called 0, 1, 2, etc. + // If the latter, count them. + + while (true) { + if (RegGetValueA(HKEY_LOCAL_MACHINE, reg_path.c_str(), "ProcessorNameString", + RRF_RT_REG_SZ, NULL, &buf, &bufsize) == ERROR_SUCCESS) { + out["Model"] = buf; + out["Cores"] = std::to_string(std::max(1, idx + 1)); + if (RegGetValueA(HKEY_LOCAL_MACHINE, reg_path.c_str(), + "VendorIdentifier", RRF_RT_REG_SZ, NULL, &buf, &bufsize) == ERROR_SUCCESS) + out["Vendor"] = buf; + } + else { + if (idx >= 0) + break; + } + ++idx; + reg_path = reg_dir + std::to_string(idx) + "\\"; + bufsize = bufsize_; + } + return out; +} +#else // Apple, Linux, BSD +static std::map parse_lscpu_etc(const std::string& name, char delimiter) +{ + std::map out; + constexpr size_t max_len = 100; + char cline[max_len] = ""; + FILE* fp = popen(name.data(), "r"); + if (fp != NULL) { + while (fgets(cline, max_len, fp) != NULL) { + std::string line(cline); + line.erase(std::remove_if(line.begin(), line.end(), + [](char c) { return c == '\"' || c == '\r' || c == '\n'; }), + line.end()); + size_t pos = line.find(delimiter); + if (pos < line.size() - 1) { + std::string key = line.substr(0, pos); + std::string value = line.substr(pos + 1); + boost::trim_all(key); // remove leading and trailing spaces + boost::trim_all(value); + out[key] = value; + } + } + pclose(fp); + } + return out; +} +#endif + + + +static std::string get_unique_id() +{ + std::vector unique; + +#ifdef _WIN32 + // On Windows, get the MAC address of a network adaptor (preferably Ethernet + // or IEEE 802.11 wireless + + DWORD dwBufLen = sizeof(IP_ADAPTER_INFO); + PIP_ADAPTER_INFO AdapterInfo = (PIP_ADAPTER_INFO)malloc(dwBufLen); + + if (GetAdaptersInfo(AdapterInfo, &dwBufLen) == ERROR_BUFFER_OVERFLOW) { + free(AdapterInfo); + AdapterInfo = (IP_ADAPTER_INFO*)malloc(dwBufLen); + } + if (GetAdaptersInfo(AdapterInfo, &dwBufLen) == NO_ERROR) { + const IP_ADAPTER_INFO* pAdapterInfo = AdapterInfo; + std::vector> macs; + bool ethernet_seen = false; + while (pAdapterInfo) { + macs.emplace_back(); + for (unsigned char i = 0; i < pAdapterInfo->AddressLength; ++i) + macs.back().emplace_back(pAdapterInfo->Address[i]); + // Prefer Ethernet and IEEE 802.11 wireless + if (! ethernet_seen) { + if ((pAdapterInfo->Type == MIB_IF_TYPE_ETHERNET && (ethernet_seen = true)) + || pAdapterInfo->Type == IF_TYPE_IEEE80211) + std::swap(macs.front(), macs.back()); + } + pAdapterInfo = pAdapterInfo->Next; + } + if (! macs.empty()) + unique = macs.front(); + } + free(AdapterInfo); +#elif __APPLE__ + constexpr int buf_size = 100; + char buf[buf_size] = ""; + memset(&buf, 0, sizeof(buf)); + io_registry_entry_t ioRegistryRoot = IORegistryEntryFromPath(kIOMasterPortDefault, "IOService:/"); + CFStringRef uuidCf = (CFStringRef)IORegistryEntryCreateCFProperty(ioRegistryRoot, CFSTR(kIOPlatformUUIDKey), kCFAllocatorDefault, 0); + IOObjectRelease(ioRegistryRoot); + CFStringGetCString(uuidCf, buf, buf_size, kCFStringEncodingMacRoman); + CFRelease(uuidCf); + // Now convert the string to std::vector. + for (char* c = buf; *c != 0; ++c) + unique.emplace_back((unsigned char)(*c)); +#else // Linux/BSD + constexpr size_t max_len = 100; + char cline[max_len] = ""; + FILE* fp = popen("cat /etc/machine-id", "r"); + if (fp != NULL) { + // Maybe the only way to silence -Wunused-result on gcc... + // cline is simply not modified on failure, who cares. + [[maybe_unused]]auto dummy = fgets(cline, max_len, fp); + pclose(fp); + } + // Now convert the string to std::vector. + for (char* c = cline; *c != 0; ++c) + unique.emplace_back((unsigned char)(*c)); +#endif + + // In case that we did not manage to get the unique info, just return an empty + // string, so it is easily detectable and not masked by the hashing. + if (unique.empty()) + return ""; + + // We should have a unique vector. Append a long prime to be + // absolutely safe against unhashing. + uint64_t prime = 1171432692373; + size_t beg = unique.size(); + unique.resize(beg + 8); + memcpy(&unique[beg], &prime, 8); + + // Compute an MD5 hash and convert to std::string. + using boost::uuids::detail::md5; + md5 hash; + md5::digest_type digest; + hash.process_bytes(unique.data(), unique.size()); + hash.get_digest(digest); + const unsigned char* charDigest = reinterpret_cast(&digest); + std::string result; + boost::algorithm::hex(charDigest, charDigest + sizeof(md5::digest_type), std::back_inserter(result)); + return result; +} + + +// Following function generates one string that will be shown in the preview +// and later sent if confirmed by the user. +static std::string generate_system_info_json() +{ + // Calculate hash of username so it is possible to identify duplicates. + // The result is mod 10000 so most of the information is lost and it is + // not possible to unhash the username. It is more than enough to help + // identify duplicate entries. + std::string unique_id = get_unique_id(); + + // Get system language. + std::string sys_language = "Unknown"; + const wxLanguage lang_system = wxLanguage(wxLocale::GetSystemLanguage()); + if (lang_system != wxLANGUAGE_UNKNOWN) + sys_language = wxLocale::GetLanguageInfo(lang_system)->CanonicalName.ToUTF8().data(); + + // Build a property tree with all the information. + namespace pt = boost::property_tree; + + pt::ptree data_node; + data_node.put("PrusaSlicerVersion", SLIC3R_VERSION); + data_node.put("BuildID", SLIC3R_BUILD_ID); + data_node.put("UniqueID", unique_id); + data_node.put("Platform", platform_to_string(platform())); + data_node.put("PlatformFlavor", platform_flavor_to_string(platform_flavor())); + data_node.put("OSDescription", wxPlatformInfo::Get().GetOperatingSystemDescription().ToUTF8().data()); +#ifdef __linux__ + std::string distro_id = wxGetLinuxDistributionInfo().Id.ToUTF8().data(); // uses lsb-release + std::string distro_ver = wxGetLinuxDistributionInfo().Release.ToUTF8().data(); + if (distro_id.empty()) { // lsb-release probably not available + std::map dist_info = parse_lscpu_etc("cat /etc/*release", '='); + distro_id = dist_info["ID"]; + distro_ver = dist_info["VERSION_ID"]; + } + data_node.put("Linux_DistroID", distro_id); + data_node.put("Linux_DistroVer", distro_ver); + data_node.put("Linux_Wayland", wxGetEnv("WAYLAND_DISPLAY", nullptr)); +#endif + data_node.put("wxWidgets", wxVERSION_NUM_DOT_STRING); +#ifdef __WXGTK__ + data_node.put("GTK", + #if defined(__WXGTK2__) + 2 + #elif defined(__WXGTK3__) + 3 + #elif defined(__WXGTK4__) + 4 + #elif defined(__WXGTK5__) + 5 + #else + "Unknown" + #endif + ); +#endif // __WXGTK__ + data_node.put("SystemLanguage", sys_language); + data_node.put("TranslationLanguage: ", wxGetApp().app_config->get("translation_language")); + + pt::ptree hw_node; + hw_node.put("ArchName", wxPlatformInfo::Get().GetArchName()); + hw_node.put("RAM_MB", size_t(Slic3r::total_physical_memory()/1000000)); + + // Now get some CPU info: + pt::ptree cpu_node; +#ifdef _WIN32 + std::map cpu_info = get_cpu_info_from_registry(); + cpu_node.put("Cores", cpu_info["Cores"]); + cpu_node.put("Model", cpu_info["Model"]); + cpu_node.put("Vendor", cpu_info["Vendor"]); +#elif __APPLE__ + std::map sysctl = parse_lscpu_etc("sysctl -a", ':'); + cpu_node.put("Cores", sysctl["hw.ncpu"]); + cpu_node.put("Model", sysctl["machdep.cpu.brand_string"]); + cpu_node.put("Vendor", sysctl["machdep.cpu.vendor"]); +#else // linux/BSD + std::map lscpu = parse_lscpu_etc("lscpu", ':'); + cpu_node.put("Arch", lscpu["Architecture"]); + cpu_node.put("Cores", lscpu["CPU(s)"]); + cpu_node.put("Model", lscpu["Model name"]); + cpu_node.put("Vendor", lscpu["Vendor ID"]); +#endif + hw_node.add_child("CPU", cpu_node); + + pt::ptree monitors_node; + for (int i=0; i extensions_list; + boost::split(extensions_list, extensions_str, boost::is_any_of(" "), boost::token_compress_off); + std::sort(extensions_list.begin(), extensions_list.end()); + pt::ptree extensions_node; + for (const std::string& s : extensions_list) { + if (s.empty()) + continue; + pt::ptree ext_node; // Create an unnamed node containing the value + ext_node.put("", s); + extensions_node.push_back(std::make_pair("", ext_node)); // Add this node to the list. + } + opengl_node.add_child("Extensions", extensions_node); + data_node.add_child("OpenGL", opengl_node); + + pt::ptree root; + root.add_child("data", data_node); + + // Serialize the tree into JSON and return it. + std::stringstream ss; + pt::write_json(ss, root); + return ss.str(); + + // FURTHER THINGS TO CONSIDER: + //std::cout << wxPlatformInfo::Get().GetOperatingSystemFamilyName() << std::endl; // Unix + // ? CPU, GPU, UNKNOWN ? + // printers? will they be installed already? +} + + + +SendSystemInfoDialog::SendSystemInfoDialog(wxWindow* parent) + : m_system_info_json{generate_system_info_json()}, + GUI::DPIDialog(parent, wxID_ANY, _L("Send system info"), wxDefaultPosition, wxDefaultSize, + wxDEFAULT_DIALOG_STYLE) +{ + // Get current PrusaSliver version info. + std::string app_name; + { + Semver semver(SLIC3R_VERSION); + bool is_alpha = std::string{semver.prerelease()}.find("alpha") != std::string::npos; + bool is_beta = std::string{semver.prerelease()}.find("beta") != std::string::npos; + app_name = std::string(SLIC3R_APP_NAME) + " " + std::to_string(semver.maj()) + + "." + std::to_string(semver.min()) + " " + + (is_alpha ? "Alpha" : is_beta ? "Beta" : ""); + } + + // Get current source file name. + std::string filename(__FILE__); + size_t last_slash_idx = filename.find_last_of("/\\"); + if (last_slash_idx != std::string::npos) + filename = filename.substr(last_slash_idx+1); + + // Set dialog background color, fonts, etc. + SetFont(wxGetApp().normal_font()); + wxColour bgr_clr = wxGetApp().get_window_default_clr(); + SetBackgroundColour(bgr_clr); + const auto text_clr = wxGetApp().get_label_clr_default(); + auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue()); + auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()); + + + auto *topSizer = new wxBoxSizer(wxVERTICAL); + auto *vsizer = new wxBoxSizer(wxVERTICAL); + + wxString text0 = GUI::format_wxstr(_L("This is the first time you are running %1%. We would like to " + "ask you to send some of your system information to us. This will only " + "happen once and we will not ask you to do this again (only after you " + "upgrade to the next version)."), app_name ); + wxString text1 = _L("If we know your hardware, operating system, etc., it will greatly help us " + "in development and prioritization, because we will be able to focus our effort more efficiently " + "and spend time on features that are needed the most."); + wxString label2 = _L("Is it safe?"); + wxString text2 = GUI::format_wxstr( + _L("We do not send any personal information nor anything that would allow us " + "to identify you later. To detect duplicate entries, a unique number derived " + "from your system is sent, but the source information cannot be reconstructed. " + "Apart from that, only general data about your OS, hardware and OpenGL " + "installation are sent. PrusaSlicer is open source, if you want to " + "inspect the code actually performing the communication, see %1%."), + std::string("") + filename + ""); + wxString label3 = _L("Show verbatim data that will be sent"); + + auto* html_window = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_NEVER); + wxString html = GUI::format_wxstr( + "" + "
" + "" + "" + + text0 + "

" + + text1 + "

" + "
" + + "" + label2 + "
" + + text2 + "

" + + "" + label3 + "
" + + "
", bgr_clr_str, text_clr_str); + html_window->SetPage(html); + html_window->Bind(wxEVT_HTML_LINK_CLICKED, [this](wxHtmlLinkEvent &evt) { + ShowJsonDialog dlg(this, m_system_info_json, GetSize().Scale(0.9, 0.7)); + dlg.ShowModal(); + }); + + vsizer->Add(html_window, 1, wxEXPAND); + + m_btn_ask_later = new wxButton(this, wxID_ANY, _L("Ask me next time")); + m_btn_dont_send = new wxButton(this, wxID_ANY, _L("Do not send anything")); + m_btn_send = new wxButton(this, wxID_ANY, _L("Send system info")); + + auto* hsizer = new wxBoxSizer(wxHORIZONTAL); + const int em = GUI::wxGetApp().em_unit(); + hsizer->Add(m_btn_ask_later); + hsizer->AddSpacer(em); + hsizer->Add(m_btn_dont_send); + hsizer->AddSpacer(em); + hsizer->Add(m_btn_send); + + vsizer->Add(hsizer, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 10); + topSizer->Add(vsizer, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, 10); + + SetSizer(topSizer); + topSizer->SetSizeHints(this); + + +#ifdef _WIN32 + wxGetApp().UpdateDlgDarkUI(this); +#endif + + const auto size = GetSize(); + SetSize(std::max(size.GetWidth(), MIN_WIDTH * em), + std::max(size.GetHeight(), MIN_HEIGHT * em)); + + m_btn_send->Bind(wxEVT_BUTTON, [this](const wxEvent&) + { + if (send_info()) { + save_version(); + EndModal(0); + } + }); + m_btn_dont_send->Bind(wxEVT_BUTTON, [this](const wxEvent&) + { + save_version(); + EndModal(0); + }); + m_btn_ask_later->Bind(wxEVT_BUTTON, [this](const wxEvent&) { EndModal(0); }); +} + + + +void SendSystemInfoDialog::on_dpi_changed(const wxRect&) +{ + const int& em = em_unit(); + msw_buttons_rescale(this, em, { m_btn_send->GetId(), + m_btn_dont_send->GetId(), + m_btn_ask_later->GetId() }); + SetMinSize(wxSize(MIN_WIDTH * em, MIN_HEIGHT * em)); + Fit(); + Refresh(); +} + + + +// This actually sends the info. +bool SendSystemInfoDialog::send_info() +{ + std::atomic job_done = false; // Flag to communicate between threads. + struct Result { + enum { + Success, + Cancelled, + Error + } value; + wxString str; + } result; // No synchronization needed, UI thread reads only after worker is joined. + + auto send = [&job_done, &result](const std::string& data) { + const std::string url = "https://files.prusa3d.com/wp-json/v1/ps"; + Http http = Http::post(url); + http.header("Content-Type", "application/json") + .set_post_body(data) + .on_complete([&result](std::string body, unsigned status) { + result = { Result::Success, _L("System info sent successfully. Thank you.") }; + }) + .on_error([&result](std::string body, std::string error, unsigned status) { + result = { Result::Error, GUI::format_wxstr(_L("Sending system info failed! Status: %1%"), status) }; + }) + .on_progress([&job_done, &result](Http::Progress, bool &cancel) { + if (job_done) // UI thread wants us to cancel. + cancel = true; + if (cancel) + result = { Result::Cancelled, _L("Sending system info was cancelled.") }; + }) + .perform_sync(); + job_done = true; // So that the dialog knows we are done. + }; + + std::thread sending_thread(send, m_system_info_json); + SendSystemInfoProgressDialog dlg(this, _L("Sending system info...")); + wxTimer timer(&dlg); // Periodically check the status of the other thread, close dialog when done. + dlg.Bind(wxEVT_TIMER, [&dlg, &job_done](wxTimerEvent&){ if (job_done) dlg.EndModal(0); }); + timer.Start(50); + dlg.ShowModal(); + // The dialog is closed, either by user, or by the now terminated worker thread. + job_done = true; // In case the user closed the dialog, let the other thread know + sending_thread.join(); // and wait until it terminates. + + InfoDialog info_dlg(wxGetApp().mainframe, wxEmptyString, result.str); + info_dlg.ShowModal(); + return result.value == Result::Success; +} + + + +// The only function callable from outside this unit. +void show_send_system_info_dialog_if_needed() +{ + if (wxGetApp().is_gcode_viewer() || ! should_dialog_be_shown()) + return; + + SendSystemInfoDialog dlg(wxGetApp().mainframe); + dlg.ShowModal(); +} + + + + + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/SendSystemInfoDialog.hpp b/src/slic3r/GUI/SendSystemInfoDialog.hpp new file mode 100644 index 000000000..dbc3ca978 --- /dev/null +++ b/src/slic3r/GUI/SendSystemInfoDialog.hpp @@ -0,0 +1,14 @@ +#ifndef slic3r_SendSystemInfoDialog_hpp_ +#define slic3r_SendSystemInfoDialog_hpp_ + +namespace Slic3r { + +namespace GUI { + +void show_send_system_info_dialog_if_needed(); + + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_SendSystemInfoDialog_hpp_ diff --git a/src/slic3r/GUI/format.hpp b/src/slic3r/GUI/format.hpp index 894803917..928171cd5 100644 --- a/src/slic3r/GUI/format.hpp +++ b/src/slic3r/GUI/format.hpp @@ -9,6 +9,8 @@ #include +#include + namespace Slic3r { namespace GUI {