From ed482316ee22dd3888a73548696f3af3ea51cda3 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 25 May 2022 09:36:52 +0200 Subject: [PATCH 01/13] Revert of 39cefdad89f34741b4c6b17a4e42823a57a76c9c --- src/libslic3r/Technologies.hpp | 2 -- src/slic3r/GUI/ConfigWizard.cpp | 47 +++++++++--------------------- src/slic3r/GUI/GUI_App.cpp | 51 ++++++++++----------------------- src/slic3r/GUI/Preferences.cpp | 25 ++++++---------- 4 files changed, 38 insertions(+), 87 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 934d1b978..a89a7c8b0 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -81,8 +81,6 @@ #define ENABLE_USED_FILAMENT_POST_PROCESS (1 && ENABLE_2_5_0_ALPHA1) // Enable gizmo grabbers to share common models #define ENABLE_GIZMO_GRABBER_REFACTOR (1 && ENABLE_2_5_0_ALPHA1) -// Disable association to 3mf and stl files if the application is run on Windows 8 or later -#define ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER (1 && ENABLE_2_5_0_ALPHA1) #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index b9dcc1a4f..cbee62013 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -1934,10 +1934,7 @@ void ConfigWizard::priv::load_pages() index->add_page(page_update); index->add_page(page_reload_from_disk); #ifdef _WIN32 -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - if (page_files_association != nullptr) -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - index->add_page(page_files_association); + index->add_page(page_files_association); #endif // _WIN32 index->add_page(page_mode); @@ -2750,32 +2747,20 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese app_config->set("export_sources_full_pathnames", page_reload_from_disk->full_pathnames ? "1" : "0"); #ifdef _WIN32 -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - if (page_files_association != nullptr) { -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - app_config->set("associate_3mf", page_files_association->associate_3mf() ? "1" : "0"); - app_config->set("associate_stl", page_files_association->associate_stl() ? "1" : "0"); -// app_config->set("associate_gcode", page_files_association->associate_gcode() ? "1" : "0"); + app_config->set("associate_3mf", page_files_association->associate_3mf() ? "1" : "0"); + app_config->set("associate_stl", page_files_association->associate_stl() ? "1" : "0"); +// app_config->set("associate_gcode", page_files_association->associate_gcode() ? "1" : "0"); - if (wxGetApp().is_editor()) { - if (page_files_association->associate_3mf()) - wxGetApp().associate_3mf_files(); - if (page_files_association->associate_stl()) - wxGetApp().associate_stl_files(); - } -// else { -// if (page_files_association->associate_gcode()) -// wxGetApp().associate_gcode_files(); -// } -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER + if (wxGetApp().is_editor()) { + if (page_files_association->associate_3mf()) + wxGetApp().associate_3mf_files(); + if (page_files_association->associate_stl()) + wxGetApp().associate_stl_files(); } - else { - app_config->set("associate_3mf", "0"); - app_config->set("associate_stl", "0"); -// app_config->set("associate_gcode", "0"); - } -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - +// else { +// if (page_files_association->associate_gcode()) +// wxGetApp().associate_gcode_files(); +// } #endif // _WIN32 page_mode->serialize_mode(app_config); @@ -2935,11 +2920,7 @@ ConfigWizard::ConfigWizard(wxWindow *parent) p->add_page(p->page_update = new PageUpdate(this)); p->add_page(p->page_reload_from_disk = new PageReloadFromDisk(this)); #ifdef _WIN32 -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - // file association is not possible anymore starting with Win 8 - if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - p->add_page(p->page_files_association = new PageFilesAssociation(this)); + p->add_page(p->page_files_association = new PageFilesAssociation(this)); #endif // _WIN32 p->add_page(p->page_mode = new PageMode(this)); p->add_page(p->page_firmware = new PageFirmware(this)); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index af9b63acd..c871befbf 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -1203,17 +1203,10 @@ bool GUI_App::on_init_inner() if (is_editor()) { #ifdef __WXMSW__ -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - // file association is not possible anymore starting with Win 8 - if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) { -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - if (app_config->get("associate_3mf") == "1") - associate_3mf_files(); - if (app_config->get("associate_stl") == "1") - associate_stl_files(); -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - } -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER + if (app_config->get("associate_3mf") == "1") + associate_3mf_files(); + if (app_config->get("associate_stl") == "1") + associate_stl_files(); #endif // __WXMSW__ preset_updater = new PresetUpdater(); @@ -1251,15 +1244,8 @@ bool GUI_App::on_init_inner() } else { #ifdef __WXMSW__ -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - // file association is not possible anymore starting with Win 8 - if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) { -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - if (app_config->get("associate_gcode") == "1") - associate_gcode_files(); -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - } -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER + if (app_config->get("associate_gcode") == "1") + associate_gcode_files(); #endif // __WXMSW__ } @@ -2432,23 +2418,16 @@ void GUI_App::open_preferences(const std::string& highlight_option /*= std::stri this->plater_->refresh_print(); #ifdef _WIN32 -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - // file association is not possible anymore starting with Win 8 - if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) { -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - if (is_editor()) { - if (app_config->get("associate_3mf") == "1") - associate_3mf_files(); - if (app_config->get("associate_stl") == "1") - associate_stl_files(); - } - else { - if (app_config->get("associate_gcode") == "1") - associate_gcode_files(); - } -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER + if (is_editor()) { + if (app_config->get("associate_3mf") == "1") + associate_3mf_files(); + if (app_config->get("associate_stl") == "1") + associate_stl_files(); + } + else { + if (app_config->get("associate_gcode") == "1") + associate_gcode_files(); } -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER #endif // _WIN32 if (mainframe->preferences_dialog->settings_layout_changed()) { diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 3da843f89..1124c4552 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -230,23 +230,16 @@ void PreferencesDialog::build() app_config->get("export_sources_full_pathnames") == "1"); #ifdef _WIN32 -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - // file association is not possible anymore starting with Win 8 - if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) { -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - // Please keep in sync with ConfigWizard - append_bool_option(m_optgroup_general, "associate_3mf", - L("Associate .3mf files to PrusaSlicer"), - L("If enabled, sets PrusaSlicer as default application to open .3mf files."), - app_config->get("associate_3mf") == "1"); + // Please keep in sync with ConfigWizard + append_bool_option(m_optgroup_general, "associate_3mf", + L("Associate .3mf files to PrusaSlicer"), + L("If enabled, sets PrusaSlicer as default application to open .3mf files."), + app_config->get("associate_3mf") == "1"); - append_bool_option(m_optgroup_general, "associate_stl", - L("Associate .stl files to PrusaSlicer"), - L("If enabled, sets PrusaSlicer as default application to open .stl files."), - app_config->get("associate_stl") == "1"); -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - } -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER + append_bool_option(m_optgroup_general, "associate_stl", + L("Associate .stl files to PrusaSlicer"), + L("If enabled, sets PrusaSlicer as default application to open .stl files."), + app_config->get("associate_stl") == "1"); #endif // _WIN32 m_optgroup_general->append_separator(); From fcd3966a5b2f3b6c2d314946db995c300af2b7c0 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 25 May 2022 11:01:48 +0200 Subject: [PATCH 02/13] Fixed crash when opening the preference dialog in GCodeViewer --- src/slic3r/GUI/Preferences.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 1124c4552..a4317d8f4 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -79,9 +79,11 @@ void PreferencesDialog::show(const std::string& highlight_opt_key /*= std::strin m_custom_toolbar_size = atoi(get_app_config()->get("custom_toolbar_size").c_str()); m_use_custom_toolbar_size = get_app_config()->get("use_custom_toolbar_size") == "1"; - // update colors for color pickers - update_color(m_sys_colour, wxGetApp().get_label_clr_sys()); - update_color(m_mod_colour, wxGetApp().get_label_clr_modified()); + if (wxGetApp().is_editor()) { + // update colors for color pickers + update_color(m_sys_colour, wxGetApp().get_label_clr_sys()); + update_color(m_mod_colour, wxGetApp().get_label_clr_modified()); + } this->ShowModal(); } From 1b09628b0d224bacb94eb5ed3c9ebefbef9aa62b Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 25 May 2022 13:14:33 +0200 Subject: [PATCH 03/13] #8332 - File association on Windows: reimplemented to allow setting PrusaSlicer as default application for .3mf and .stl files and GCodeViewer as default application for .gcode files --- src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/GUI_App.cpp | 104 +------ src/slic3r/Utils/WinRegistry.cpp | 490 +++++++++++++++++++++++++++++++ src/slic3r/Utils/WinRegistry.hpp | 23 ++ 4 files changed, 519 insertions(+), 100 deletions(-) create mode 100644 src/slic3r/Utils/WinRegistry.cpp create mode 100644 src/slic3r/Utils/WinRegistry.hpp diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index ed994be18..09c2098d9 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -246,6 +246,8 @@ set(SLIC3R_GUI_SOURCES Utils/TCPConsole.hpp Utils/MKS.cpp Utils/MKS.hpp + Utils/WinRegistry.cpp + Utils/WinRegistry.hpp ) if (APPLE) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index c871befbf..b688e11f5 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -59,6 +59,7 @@ #include "../Utils/Process.hpp" #include "../Utils/MacDarkMode.hpp" #include "../Utils/AppUpdater.hpp" +#include "../Utils/WinRegistry.hpp" #include "slic3r/Config/Snapshot.hpp" #include "ConfigSnapshotDialog.hpp" #include "FirmwareDialog.hpp" @@ -3135,119 +3136,22 @@ bool GUI_App::open_browser_with_warning_dialog(const wxString& url, wxWindow* pa #ifdef __WXMSW__ -static bool set_into_win_registry(HKEY hkeyHive, const wchar_t* pszVar, const wchar_t* pszValue) -{ - // see as reference: https://stackoverflow.com/questions/20245262/c-program-needs-an-file-association - wchar_t szValueCurrent[1000]; - DWORD dwType; - DWORD dwSize = sizeof(szValueCurrent); - - int iRC = ::RegGetValueW(hkeyHive, pszVar, nullptr, RRF_RT_ANY, &dwType, szValueCurrent, &dwSize); - - bool bDidntExist = iRC == ERROR_FILE_NOT_FOUND; - - if ((iRC != ERROR_SUCCESS) && !bDidntExist) - // an error occurred - return false; - - if (!bDidntExist) { - if (dwType != REG_SZ) - // invalid type - return false; - - if (::wcscmp(szValueCurrent, pszValue) == 0) - // value already set - return false; - } - - DWORD dwDisposition; - HKEY hkey; - iRC = ::RegCreateKeyExW(hkeyHive, pszVar, 0, 0, 0, KEY_ALL_ACCESS, nullptr, &hkey, &dwDisposition); - bool ret = false; - if (iRC == ERROR_SUCCESS) { - iRC = ::RegSetValueExW(hkey, L"", 0, REG_SZ, (BYTE*)pszValue, (::wcslen(pszValue) + 1) * sizeof(wchar_t)); - if (iRC == ERROR_SUCCESS) - ret = true; - } - - RegCloseKey(hkey); - return ret; -} - void GUI_App::associate_3mf_files() { - wchar_t app_path[MAX_PATH]; - ::GetModuleFileNameW(nullptr, app_path, sizeof(app_path)); - - std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\""; - std::wstring prog_id = L"Prusa.Slicer.1"; - std::wstring prog_desc = L"PrusaSlicer"; - std::wstring prog_command = prog_path + L" \"%1\""; - std::wstring reg_base = L"Software\\Classes"; - std::wstring reg_extension = reg_base + L"\\.3mf"; - std::wstring reg_prog_id = reg_base + L"\\" + prog_id; - std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command"; - - bool is_new = false; - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str()); - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str()); - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str()); - - if (is_new) - // notify Windows only when any of the values gets changed - ::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr); + associate_file_type(L".3mf", L"Prusa.Slicer.1", L"PrusaSlicer", true); } void GUI_App::associate_stl_files() { - wchar_t app_path[MAX_PATH]; - ::GetModuleFileNameW(nullptr, app_path, sizeof(app_path)); - - std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\""; - std::wstring prog_id = L"Prusa.Slicer.1"; - std::wstring prog_desc = L"PrusaSlicer"; - std::wstring prog_command = prog_path + L" \"%1\""; - std::wstring reg_base = L"Software\\Classes"; - std::wstring reg_extension = reg_base + L"\\.stl"; - std::wstring reg_prog_id = reg_base + L"\\" + prog_id; - std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command"; - - bool is_new = false; - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str()); - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str()); - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str()); - - if (is_new) - // notify Windows only when any of the values gets changed - ::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr); + associate_file_type(L".stl", L"Prusa.Slicer.1", L"PrusaSlicer", true); } void GUI_App::associate_gcode_files() { - wchar_t app_path[MAX_PATH]; - ::GetModuleFileNameW(nullptr, app_path, sizeof(app_path)); - - std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\""; - std::wstring prog_id = L"PrusaSlicer.GCodeViewer.1"; - std::wstring prog_desc = L"PrusaSlicerGCodeViewer"; - std::wstring prog_command = prog_path + L" \"%1\""; - std::wstring reg_base = L"Software\\Classes"; - std::wstring reg_extension = reg_base + L"\\.gcode"; - std::wstring reg_prog_id = reg_base + L"\\" + prog_id; - std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command"; - - bool is_new = false; - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str()); - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str()); - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str()); - - if (is_new) - // notify Windows only when any of the values gets changed - ::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr); + associate_file_type(L".gcode", L"PrusaSlicer.GCodeViewer.1", L"PrusaSlicerGCodeViewer", true); } #endif // __WXMSW__ - void GUI_App::on_version_read(wxCommandEvent& evt) { app_config->set("version_online", into_u8(evt.GetString())); diff --git a/src/slic3r/Utils/WinRegistry.cpp b/src/slic3r/Utils/WinRegistry.cpp new file mode 100644 index 000000000..068a7fff1 --- /dev/null +++ b/src/slic3r/Utils/WinRegistry.cpp @@ -0,0 +1,490 @@ +#include "libslic3r/Technologies.hpp" +#include "WinRegistry.hpp" + +#ifdef _WIN32 +#include +#include +#include +#include + +namespace Slic3r { + +// Helper class which automatically closes the handle when +// going out of scope +class AutoHandle +{ + HANDLE m_handle{ nullptr }; + +public: + explicit AutoHandle(HANDLE handle) : m_handle(handle) {} + ~AutoHandle() { if (m_handle != nullptr) ::CloseHandle(m_handle); } + HANDLE get() { return m_handle; } +}; + +// Helper class which automatically closes the key when +// going out of scope +class AutoRegKey +{ + HKEY m_key{ nullptr }; + +public: + explicit AutoRegKey(HKEY key) : m_key(key) {} + ~AutoRegKey() { if (m_key != nullptr) ::RegCloseKey(m_key); } + HKEY get() { return m_key; } +}; + +// returns true if the given value is set/modified into Windows registry +static bool set_into_win_registry(HKEY hkeyHive, const wchar_t* pszVar, const wchar_t* pszValue) +{ + // see as reference: https://stackoverflow.com/questions/20245262/c-program-needs-an-file-association + wchar_t szValueCurrent[1000]; + DWORD dwType; + DWORD dwSize = sizeof(szValueCurrent); + + LSTATUS iRC = ::RegGetValueW(hkeyHive, pszVar, nullptr, RRF_RT_ANY, &dwType, szValueCurrent, &dwSize); + + const bool bDidntExist = iRC == ERROR_FILE_NOT_FOUND; + + if (iRC != ERROR_SUCCESS && !bDidntExist) + // an error occurred + return false; + + if (!bDidntExist) { + if (dwType != REG_SZ) + // invalid type + return false; + + if (::wcscmp(szValueCurrent, pszValue) == 0) + // value already set + return false; + } + + DWORD dwDisposition; + HKEY hkey; + iRC = ::RegCreateKeyExW(hkeyHive, pszVar, 0, 0, 0, KEY_ALL_ACCESS, nullptr, &hkey, &dwDisposition); + bool ret = false; + if (iRC == ERROR_SUCCESS) { + iRC = ::RegSetValueExW(hkey, L"", 0, REG_SZ, (BYTE*)pszValue, (::wcslen(pszValue) + 1) * sizeof(wchar_t)); + if (iRC == ERROR_SUCCESS) + ret = true; + } + + RegCloseKey(hkey); + return ret; +} + +static std::wstring get_current_user_string_sid() +{ + HANDLE rawProcessToken; + if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, + &rawProcessToken)) + return L""; + + AutoHandle processToken(rawProcessToken); + + DWORD userSize = 0; + if (!(!::GetTokenInformation(processToken.get(), TokenUser, nullptr, 0, &userSize) && + GetLastError() == ERROR_INSUFFICIENT_BUFFER)) + return L""; + + std::vector userBytes(userSize); + if (!::GetTokenInformation(processToken.get(), TokenUser, userBytes.data(), userSize, &userSize)) + return L""; + + wchar_t* rawSid = nullptr; + if (!::ConvertSidToStringSidW(reinterpret_cast(userBytes.data())->User.Sid, &rawSid)) + return L""; + + return rawSid; +} + +/* + * Create the string which becomes the input to the UserChoice hash. + * + * @see generate_user_choice_hash() for parameters. + * + * @return The formatted string, empty string on failure. + * + * NOTE: This uses the format as of Windows 10 20H2 (latest as of this writing), + * used at least since 1803. + * There was at least one older version, not currently supported: On Win10 RTM + * (build 10240, aka 1507) the hash function is the same, but the timestamp and + * User Experience string aren't included; instead (for protocols) the string + * ends with the exe path. The changelog of SetUserFTA suggests the algorithm + * changed in 1703, so there may be two versions: before 1703, and 1703 to now. + */ +static std::wstring format_user_choice_string(const wchar_t* aExt, const wchar_t* aUserSid, const wchar_t* aProgId, SYSTEMTIME aTimestamp) +{ + aTimestamp.wSecond = 0; + aTimestamp.wMilliseconds = 0; + + FILETIME fileTime = { 0 }; + if (!::SystemTimeToFileTime(&aTimestamp, &fileTime)) + return L""; + + // This string is built into Windows as part of the UserChoice hash algorithm. + // It might vary across Windows SKUs (e.g. Windows 10 vs. Windows Server), or + // across builds of the same SKU, but this is the only currently known + // version. There isn't any known way of deriving it, so we assume this + // constant value. If we are wrong, we will not be able to generate correct + // UserChoice hashes. + const wchar_t* userExperience = + L"User Choice set via Windows User Experience " + L"{D18B6DD5-6124-4341-9318-804003BAFA0B}"; + + const wchar_t* userChoiceFmt = + L"%s%s%s" + L"%08lx" + L"%08lx" + L"%s"; + + int userChoiceLen = _scwprintf(userChoiceFmt, aExt, aUserSid, aProgId, + fileTime.dwHighDateTime, fileTime.dwLowDateTime, userExperience); + userChoiceLen += 1; // _scwprintf does not include the terminator + + std::wstring userChoice(userChoiceLen, L'\0'); + _snwprintf_s(userChoice.data(), userChoiceLen, _TRUNCATE, userChoiceFmt, aExt, + aUserSid, aProgId, fileTime.dwHighDateTime, fileTime.dwLowDateTime, userExperience); + + ::CharLowerW(userChoice.data()); + return userChoice; +} + +// @return The MD5 hash of the input, nullptr on failure. +static std::vector cng_md5(const unsigned char* bytes, ULONG bytesLen) { + constexpr ULONG MD5_BYTES = 16; + constexpr ULONG MD5_DWORDS = MD5_BYTES / sizeof(DWORD); + std::vector hash; + + BCRYPT_ALG_HANDLE hAlg = nullptr; + if (NT_SUCCESS(::BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_MD5_ALGORITHM, nullptr, 0))) { + BCRYPT_HASH_HANDLE hHash = nullptr; + // As of Windows 7 the hash handle will manage its own object buffer when + // pbHashObject is nullptr and cbHashObject is 0. + if (NT_SUCCESS(::BCryptCreateHash(hAlg, &hHash, nullptr, 0, nullptr, 0, 0))) { + // BCryptHashData promises not to modify pbInput. + if (NT_SUCCESS(::BCryptHashData(hHash, const_cast(bytes), bytesLen, 0))) { + hash.resize(MD5_DWORDS); + if (!NT_SUCCESS(::BCryptFinishHash(hHash, reinterpret_cast(hash.data()), + MD5_DWORDS * sizeof(DWORD), 0))) + hash.clear(); + } + ::BCryptDestroyHash(hHash); + } + ::BCryptCloseAlgorithmProvider(hAlg, 0); + } + + return hash; +} + +static inline DWORD word_swap(DWORD v) +{ + return (v >> 16) | (v << 16); +} + +// @return The input bytes encoded as base64, nullptr on failure. +static std::wstring crypto_api_base64_encode(const unsigned char* bytes, DWORD bytesLen) { + DWORD base64Len = 0; + if (!::CryptBinaryToStringW(bytes, bytesLen, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, nullptr, &base64Len)) + return L""; + + std::wstring base64(base64Len, L'\0'); + if (!::CryptBinaryToStringW(bytes, bytesLen, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, base64.data(), &base64Len)) + return L""; + + return base64; +} + +/* + * Generate the UserChoice Hash. + * + * This implementation is based on the references listed above. + * It is organized to show the logic as clearly as possible, but at some + * point the reasoning is just "this is how it works". + * + * @param inputString A null-terminated string to hash. + * + * @return The base64-encoded hash, or empty string on failure. + */ +static std::wstring hash_string(const wchar_t* inputString) +{ + auto inputBytes = reinterpret_cast(inputString); + int inputByteCount = (::lstrlenW(inputString) + 1) * sizeof(wchar_t); + + constexpr size_t DWORDS_PER_BLOCK = 2; + constexpr size_t BLOCK_SIZE = sizeof(DWORD) * DWORDS_PER_BLOCK; + // Incomplete blocks are ignored. + int blockCount = inputByteCount / BLOCK_SIZE; + + if (blockCount == 0) + return L""; + + // Compute an MD5 hash. md5[0] and md5[1] will be used as constant multipliers + // in the scramble below. + auto md5 = cng_md5(inputBytes, inputByteCount); + if (md5.empty()) + return L""; + + // The following loop effectively computes two checksums, scrambled like a + // hash after every DWORD is added. + + // Constant multipliers for the scramble, one set for each DWORD in a block. + const DWORD C0s[DWORDS_PER_BLOCK][5] = { + {md5[0] | 1, 0xCF98B111uL, 0x87085B9FuL, 0x12CEB96DuL, 0x257E1D83uL}, + {md5[1] | 1, 0xA27416F5uL, 0xD38396FFuL, 0x7C932B89uL, 0xBFA49F69uL} }; + const DWORD C1s[DWORDS_PER_BLOCK][5] = { + {md5[0] | 1, 0xEF0569FBuL, 0x689B6B9FuL, 0x79F8A395uL, 0xC3EFEA97uL}, + {md5[1] | 1, 0xC31713DBuL, 0xDDCD1F0FuL, 0x59C3AF2DuL, 0x35BD1EC9uL} }; + + // The checksums. + DWORD h0 = 0; + DWORD h1 = 0; + // Accumulated total of the checksum after each DWORD. + DWORD h0Acc = 0; + DWORD h1Acc = 0; + + for (int i = 0; i < blockCount; ++i) { + for (int j = 0; j < DWORDS_PER_BLOCK; ++j) { + const DWORD* C0 = C0s[j]; + const DWORD* C1 = C1s[j]; + + DWORD input; + memcpy(&input, &inputBytes[(i * DWORDS_PER_BLOCK + j) * sizeof(DWORD)], sizeof(DWORD)); + + h0 += input; + // Scramble 0 + h0 *= C0[0]; + h0 = word_swap(h0) * C0[1]; + h0 = word_swap(h0) * C0[2]; + h0 = word_swap(h0) * C0[3]; + h0 = word_swap(h0) * C0[4]; + h0Acc += h0; + + h1 += input; + // Scramble 1 + h1 = word_swap(h1) * C1[1] + h1 * C1[0]; + h1 = (h1 >> 16) * C1[2] + h1 * C1[3]; + h1 = word_swap(h1) * C1[4] + h1; + h1Acc += h1; + } + } + + DWORD hash[2] = { h0 ^ h1, h0Acc ^ h1Acc }; + return crypto_api_base64_encode(reinterpret_cast(hash), sizeof(hash)); +} + +static std::wstring generate_user_choice_hash(const wchar_t* aExt, const wchar_t* aUserSid, const wchar_t* aProgId, SYSTEMTIME aTimestamp) +{ + const std::wstring userChoice = format_user_choice_string(aExt, aUserSid, aProgId, aTimestamp); + if (userChoice.empty()) + return L""; + + return hash_string(userChoice.c_str()); +} + +static bool add_milliseconds_to_system_time(SYSTEMTIME& aSystemTime, ULONGLONG aIncrementMS) +{ + FILETIME fileTime; + ULARGE_INTEGER fileTimeInt; + if (!::SystemTimeToFileTime(&aSystemTime, &fileTime)) + return false; + + fileTimeInt.LowPart = fileTime.dwLowDateTime; + fileTimeInt.HighPart = fileTime.dwHighDateTime; + + // FILETIME is in units of 100ns. + fileTimeInt.QuadPart += aIncrementMS * 1000 * 10; + + fileTime.dwLowDateTime = fileTimeInt.LowPart; + fileTime.dwHighDateTime = fileTimeInt.HighPart; + SYSTEMTIME tmpSystemTime; + if (!::FileTimeToSystemTime(&fileTime, &tmpSystemTime)) + return false; + + aSystemTime = tmpSystemTime; + return true; +} + +// Compare two SYSTEMTIMEs as FILETIME after clearing everything +// below minutes. +static bool check_equal_minutes(SYSTEMTIME aSystemTime1, SYSTEMTIME aSystemTime2) +{ + aSystemTime1.wSecond = 0; + aSystemTime1.wMilliseconds = 0; + + aSystemTime2.wSecond = 0; + aSystemTime2.wMilliseconds = 0; + + FILETIME fileTime1; + FILETIME fileTime2; + if (!::SystemTimeToFileTime(&aSystemTime1, &fileTime1) || !::SystemTimeToFileTime(&aSystemTime2, &fileTime2)) + return false; + + return (fileTime1.dwLowDateTime == fileTime2.dwLowDateTime) && (fileTime1.dwHighDateTime == fileTime2.dwHighDateTime); +} + +static std::wstring get_association_key_path(const wchar_t* aExt) +{ + const wchar_t* keyPathFmt; + if (aExt[0] == L'.') + keyPathFmt = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\%s"; + else + keyPathFmt = L"SOFTWARE\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\%s"; + + int keyPathLen = _scwprintf(keyPathFmt, aExt); + keyPathLen += 1; // _scwprintf does not include the terminator + + std::wstring keyPath(keyPathLen, '\0'); + _snwprintf_s(keyPath.data(), keyPathLen, _TRUNCATE, keyPathFmt, aExt); + return keyPath; +} + +/* + * Set an association with a UserChoice key + * + * Removes the old key, creates a new one with ProgID and Hash set to + * enable a new asociation. + * + * @param aExt File type or protocol to associate + * @param aProgID ProgID to use for the asociation + * + * @return true if successful, false on error. + */ +static bool set_user_choice(const wchar_t* aExt, const wchar_t* aProgID) { + + const std::wstring aSid = get_current_user_string_sid(); + if (aSid.empty()) + return false; + + SYSTEMTIME hashTimestamp; + ::GetSystemTime(&hashTimestamp); + std::wstring hash = generate_user_choice_hash(aExt, aSid.c_str(), aProgID, hashTimestamp); + if (hash.empty()) + return false; + + // The hash changes at the end of each minute, so check that the hash should + // be the same by the time we're done writing. + const ULONGLONG kWriteTimingThresholdMilliseconds = 100; + // Generating the hash could have taken some time, so start from now. + SYSTEMTIME writeEndTimestamp; + ::GetSystemTime(&writeEndTimestamp); + if (!add_milliseconds_to_system_time(writeEndTimestamp, +kWriteTimingThresholdMilliseconds)) + return false; + + if (!check_equal_minutes(hashTimestamp, writeEndTimestamp)) { + ::Sleep(kWriteTimingThresholdMilliseconds * 2); + + // For consistency, use the current time. + ::GetSystemTime(&hashTimestamp); + hash = generate_user_choice_hash(aExt, aSid.c_str(), aProgID, hashTimestamp); + if (hash.empty()) + return false; + } + + const std::wstring assocKeyPath = get_association_key_path(aExt); + if (assocKeyPath.empty()) + return false; + + LSTATUS ls; + HKEY rawAssocKey; + ls = ::RegOpenKeyExW(HKEY_CURRENT_USER, assocKeyPath.data(), 0, KEY_READ | KEY_WRITE, &rawAssocKey); + if (ls != ERROR_SUCCESS) + return false; + + AutoRegKey assocKey(rawAssocKey); + + HKEY currUserChoiceKey; + ls = ::RegOpenKeyExW(assocKey.get(), L"UserChoice", 0, KEY_READ, &currUserChoiceKey); + if (ls == ERROR_SUCCESS) { + ::RegCloseKey(currUserChoiceKey); + // When Windows creates this key, it is read-only (Deny Set Value), so we need + // to delete it first. + // We don't set any similar special permissions. + ls = ::RegDeleteKeyW(assocKey.get(), L"UserChoice"); + if (ls != ERROR_SUCCESS) + return false; + } + + HKEY rawUserChoiceKey; + ls = ::RegCreateKeyExW(assocKey.get(), L"UserChoice", 0, nullptr, + 0 /* options */, KEY_READ | KEY_WRITE, + 0 /* security attributes */, &rawUserChoiceKey, + nullptr); + if (ls != ERROR_SUCCESS) + return false; + + AutoRegKey userChoiceKey(rawUserChoiceKey); + DWORD progIdByteCount = (::lstrlenW(aProgID) + 1) * sizeof(wchar_t); + ls = ::RegSetValueExW(userChoiceKey.get(), L"ProgID", 0, REG_SZ, reinterpret_cast(aProgID), progIdByteCount); + if (ls != ERROR_SUCCESS) + return false; + + DWORD hashByteCount = (::lstrlenW(hash.data()) + 1) * sizeof(wchar_t); + ls = ::RegSetValueExW(userChoiceKey.get(), L"Hash", 0, REG_SZ, reinterpret_cast(hash.data()), hashByteCount); + if (ls != ERROR_SUCCESS) + return false; + + return true; +} + +static bool set_as_default_per_file_type(const std::wstring& extension, const std::wstring& prog_id) +{ + const std::wstring reg_extension = get_association_key_path(extension.c_str()); + if (reg_extension.empty()) + return false; + + bool needs_update = true; + bool modified = false; + HKEY rawAssocKey = nullptr; + LSTATUS res = ::RegOpenKeyExW(HKEY_CURRENT_USER, reg_extension.c_str(), 0, KEY_READ, &rawAssocKey); + AutoRegKey assoc_key(rawAssocKey); + if (res == ERROR_SUCCESS) { + DWORD data_size_bytes = 0; + res = ::RegGetValueW(assoc_key.get(), L"UserChoice", L"ProgId", RRF_RT_REG_SZ, nullptr, nullptr, &data_size_bytes); + if (res == ERROR_SUCCESS) { + // +1 in case dataSizeBytes was odd, +1 to ensure termination + DWORD data_size_chars = (data_size_bytes / sizeof(wchar_t)) + 2; + std::wstring curr_prog_id(data_size_chars, L'\0'); + res = ::RegGetValueW(assoc_key.get(), L"UserChoice", L"ProgId", RRF_RT_REG_SZ, nullptr, curr_prog_id.data(), &data_size_bytes); + if (res == ERROR_SUCCESS) { + const std::wstring::size_type pos = curr_prog_id.find_first_of(L'\0'); + if (pos != std::wstring::npos) + curr_prog_id = curr_prog_id.substr(0, pos); + needs_update = !boost::algorithm::iequals(curr_prog_id, prog_id); + } + } + } + + if (needs_update) + modified = set_user_choice(extension.c_str(), prog_id.c_str()); + + return modified; +} + +void associate_file_type(const std::wstring& extension, const std::wstring& prog_id, const std::wstring& prog_desc, bool set_as_default) +{ + assert(!extension.empty() && extension.front() == L'.'); + + const std::wstring reg_extension = L"SOFTWARE\\Classes\\" + extension; + const std::wstring reg_prog_id = L"SOFTWARE\\Classes\\" + prog_id; + const std::wstring reg_prog_id_command = L"SOFTWARE\\Classes\\" + prog_id + +L"\\Shell\\Open\\Command"; + + wchar_t app_path[1040]; + ::GetModuleFileNameW(nullptr, app_path, sizeof(app_path)); + const std::wstring prog_command = L"\"" + std::wstring(app_path) + L"\"" + L" \"%1\""; + + bool modified = false; + modified |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str()); + modified |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str()); + modified |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str()); + if (set_as_default) + modified |= set_as_default_per_file_type(extension, prog_id); + + // notify Windows only when any of the values gets changed + if (modified) + ::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr); +} + +} // namespace Slic3r + +#endif // _WIN32 diff --git a/src/slic3r/Utils/WinRegistry.hpp b/src/slic3r/Utils/WinRegistry.hpp new file mode 100644 index 000000000..bc0875d32 --- /dev/null +++ b/src/slic3r/Utils/WinRegistry.hpp @@ -0,0 +1,23 @@ +#ifndef slic3r_Utils_WinRegistry_hpp_ +#define slic3r_Utils_WinRegistry_hpp_ + +#ifdef _WIN32 + +#include + +namespace Slic3r { + +// Creates a Windows registry key for the files with the given 'extension' and associates them to the application 'prog_id'. +// If 'set_as_default' is true, the application 'prog_id' is set ad default application for the file type 'extension'. +// The file type registration implementation is based on code taken from: +// https://stackoverflow.com/questions/20245262/c-program-needs-an-file-association +// The set as default implementation is based on code taken from: +// https://hg.mozilla.org/mozilla-central/rev/e928b3e95a6c3b7257d0ba475fc2303bfbad1874 +// https://hg.mozilla.org/releases/mozilla-release/diff/7e775ce432b599c6daf7ac379aa42f1e9b3b33ed/browser/components/shell/WindowsUserChoice.cpp +void associate_file_type(const std::wstring& extension, const std::wstring& prog_id, const std::wstring& prog_desc, bool set_as_default); + +} // namespace Slic3r + +#endif // _WIN32 + +#endif // slic3r_Utils_WinRegistry_hpp_ From a115702289f399f14bd914dc98ac4795e3cce39c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 27 May 2022 13:22:08 +0200 Subject: [PATCH 04/13] Ignore CLion IDE files in all subdirectories. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c4df3f3f8..704289a22 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,4 @@ local-lib build-linux/* deps/build-linux/* **/.DS_Store -/.idea/ +**/.idea/ From 98928935877b3ec25a62c8fec1fc28642fff6b23 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 27 May 2022 15:33:03 +0200 Subject: [PATCH 05/13] Add UIThreadWorker for debugging and profiling purposes --- src/slic3r/CMakeLists.txt | 1 + src/slic3r/GUI/Jobs/PlaterWorker.hpp | 6 ++ src/slic3r/GUI/Jobs/UIThreadWorker.hpp | 114 ++++++++++++++++++++++++ src/slic3r/GUI/Plater.cpp | 3 + tests/slic3rutils/slic3r_jobs_tests.cpp | 40 ++++++--- 5 files changed, 150 insertions(+), 14 deletions(-) create mode 100644 src/slic3r/GUI/Jobs/UIThreadWorker.hpp diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 09c2098d9..78e73ba9a 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -172,6 +172,7 @@ set(SLIC3R_GUI_SOURCES GUI/Jobs/Worker.hpp GUI/Jobs/BoostThreadWorker.hpp GUI/Jobs/BoostThreadWorker.cpp + GUI/Jobs/UIThreadWorker.hpp GUI/Jobs/BusyCursorJob.hpp GUI/Jobs/PlaterWorker.hpp GUI/Jobs/ArrangeJob.hpp diff --git a/src/slic3r/GUI/Jobs/PlaterWorker.hpp b/src/slic3r/GUI/Jobs/PlaterWorker.hpp index 573590272..58bd1ec32 100644 --- a/src/slic3r/GUI/Jobs/PlaterWorker.hpp +++ b/src/slic3r/GUI/Jobs/PlaterWorker.hpp @@ -40,6 +40,12 @@ class PlaterWorker: public Worker { { wxWakeUpIdle(); ctl.update_status(st, msg); + + // If the worker is not using additional threads, the UI + // is refreshed with this call. If the worker is running + // in it's own thread, the yield should not have any + // visible effects. + wxYieldIfNeeded(); } bool was_canceled() const override { return ctl.was_canceled(); } diff --git a/src/slic3r/GUI/Jobs/UIThreadWorker.hpp b/src/slic3r/GUI/Jobs/UIThreadWorker.hpp new file mode 100644 index 000000000..aa946036e --- /dev/null +++ b/src/slic3r/GUI/Jobs/UIThreadWorker.hpp @@ -0,0 +1,114 @@ +#ifndef UITHREADWORKER_HPP +#define UITHREADWORKER_HPP + +#include +#include + +#include "Worker.hpp" +#include "ProgressIndicator.hpp" + +namespace Slic3r { namespace GUI { + +// Implementation of a worker which does not create any additional threads. +class UIThreadWorker : public Worker, private Job::Ctl { + std::queue, std::deque>> m_jobqueue; + std::shared_ptr m_progress; + bool m_running = false; + bool m_canceled = false; + + void process_front() + { + std::unique_ptr job; + + if (!m_jobqueue.empty()) { + job = std::move(m_jobqueue.front()); + m_jobqueue.pop(); + } + + if (job) { + std::exception_ptr eptr; + m_running = true; + + try { + job->process(*this); + } catch (...) { + eptr= std::current_exception(); + } + + m_running = false; + + job->finalize(m_canceled, eptr); + + m_canceled = false; + } + } + +protected: + // Implement Job::Ctl interface: + + void update_status(int st, const std::string &msg = "") override + { + if (m_progress) { + m_progress->set_progress(st); + m_progress->set_status_text(msg.c_str()); + } + } + + bool was_canceled() const override { return m_canceled; } + + std::future call_on_main_thread(std::function fn) override + { + return std::async(std::launch::deferred, [fn]{ fn(); }); + } + +public: + explicit UIThreadWorker(std::shared_ptr pri, + const std::string & /*name*/ = "") + : m_progress{pri} + {} + + UIThreadWorker() = default; + + bool push(std::unique_ptr job) override + { + m_canceled = false; + m_jobqueue.push(std::move(job)); + + return bool(job); + } + + bool is_idle() const override { return !m_running; } + + void cancel() override { m_canceled = true; } + + void cancel_all() override + { + m_canceled = true; + process_front(); + while (!m_jobqueue.empty()) m_jobqueue.pop(); + } + + void process_events() override { + while (!m_jobqueue.empty()) + process_front(); + } + + bool wait_for_current_job(unsigned /*timeout_ms*/ = 0) override { + process_front(); + + return true; + } + + bool wait_for_idle(unsigned /*timeout_ms*/ = 0) override { + process_events(); + + return true; + } + + ProgressIndicator * get_pri() { return m_progress.get(); } + const ProgressIndicator * get_pri() const { return m_progress.get(); } +}; + +}} // namespace Slic3r::GUI + +#endif // UITHREADWORKER_HPP diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 0456936e4..30c30d2ec 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1648,6 +1648,9 @@ struct Plater::priv // objects would be frozen for the user. In case of arrange, an animation // could be shown, or with the optimize orientations, partial results // could be displayed. + // + // UIThreadWorker can be used as a replacement for BoostThreadWorker if + // no additional worker threads are desired (useful for debugging or profiling) PlaterWorker m_worker; SLAImportDialog * m_sla_import_dlg; diff --git a/tests/slic3rutils/slic3r_jobs_tests.cpp b/tests/slic3rutils/slic3r_jobs_tests.cpp index d31b07349..d4b912b84 100644 --- a/tests/slic3rutils/slic3r_jobs_tests.cpp +++ b/tests/slic3rutils/slic3r_jobs_tests.cpp @@ -4,10 +4,9 @@ #include #include "slic3r/GUI/Jobs/BoostThreadWorker.hpp" +#include "slic3r/GUI/Jobs/UIThreadWorker.hpp" #include "slic3r/GUI/Jobs/ProgressIndicator.hpp" -//#include - struct Progress: Slic3r::ProgressIndicator { int range = 100; int pr = 0; @@ -19,17 +18,30 @@ struct Progress: Slic3r::ProgressIndicator { int get_range() const override { return range; } }; -TEST_CASE("nullptr job should be ignored", "[Jobs]") { - Slic3r::GUI::BoostThreadWorker worker{std::make_unique()}; +using TestClasses = std::tuple< Slic3r::GUI::UIThreadWorker, Slic3r::GUI::BoostThreadWorker >; + +TEMPLATE_LIST_TEST_CASE("Empty worker should not do anything", "[Jobs]", TestClasses) { + TestType worker{std::make_unique()}; + + REQUIRE(worker.is_idle()); + + worker.wait_for_current_job(); + worker.process_events(); + + REQUIRE(worker.is_idle()); +} + +TEMPLATE_LIST_TEST_CASE("nullptr job should be ignored", "[Jobs]", TestClasses) { + TestType worker{std::make_unique()}; worker.push(nullptr); REQUIRE(worker.is_idle()); } -TEST_CASE("State should not be idle while running a job", "[Jobs]") { +TEMPLATE_LIST_TEST_CASE("State should not be idle while running a job", "[Jobs]", TestClasses) { using namespace Slic3r; using namespace Slic3r::GUI; - BoostThreadWorker worker{std::make_unique(), "worker_thread"}; + TestType worker{std::make_unique(), "worker_thread"}; queue_job(worker, [&worker](Job::Ctl &ctl) { ctl.call_on_main_thread([&worker] { @@ -42,11 +54,11 @@ TEST_CASE("State should not be idle while running a job", "[Jobs]") { REQUIRE(worker.is_idle()); } -TEST_CASE("Status messages should be received by the main thread during job execution", "[Jobs]") { +TEMPLATE_LIST_TEST_CASE("Status messages should be received by the main thread during job execution", "[Jobs]", TestClasses) { using namespace Slic3r; using namespace Slic3r::GUI; auto pri = std::make_shared(); - BoostThreadWorker worker{pri}; + TestType worker{pri}; queue_job(worker, [](Job::Ctl &ctl){ for (int s = 0; s <= 100; ++s) { @@ -60,12 +72,12 @@ TEST_CASE("Status messages should be received by the main thread during job exec REQUIRE(pri->statustxt == "Running"); } -TEST_CASE("Cancellation should be recognized be the worker", "[Jobs]") { +TEMPLATE_LIST_TEST_CASE("Cancellation should be recognized be the worker", "[Jobs]", TestClasses) { using namespace Slic3r; using namespace Slic3r::GUI; auto pri = std::make_shared(); - BoostThreadWorker worker{pri}; + TestType worker{pri}; queue_job( worker, @@ -88,12 +100,12 @@ TEST_CASE("Cancellation should be recognized be the worker", "[Jobs]") { REQUIRE(pri->pr != 100); } -TEST_CASE("cancel_all should remove all pending jobs", "[Jobs]") { +TEMPLATE_LIST_TEST_CASE("cancel_all should remove all pending jobs", "[Jobs]", TestClasses) { using namespace Slic3r; using namespace Slic3r::GUI; auto pri = std::make_shared(); - BoostThreadWorker worker{pri}; + TestType worker{pri}; std::array jobres = {false, false, false, false}; @@ -125,12 +137,12 @@ TEST_CASE("cancel_all should remove all pending jobs", "[Jobs]") { REQUIRE(jobres[3] == false); } -TEST_CASE("Exception should be properly forwarded to finalize()", "[Jobs]") { +TEMPLATE_LIST_TEST_CASE("Exception should be properly forwarded to finalize()", "[Jobs]", TestClasses) { using namespace Slic3r; using namespace Slic3r::GUI; auto pri = std::make_shared(); - BoostThreadWorker worker{pri}; + TestType worker{pri}; queue_job( worker, [](Job::Ctl &) { throw std::runtime_error("test"); }, From 6c284882ba2e85bf397d4a5ab68a8e560621a9d3 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 27 May 2022 15:54:26 +0200 Subject: [PATCH 06/13] Fix cancellation from UI for UIThreadWorker --- src/slic3r/GUI/Jobs/UIThreadWorker.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Jobs/UIThreadWorker.hpp b/src/slic3r/GUI/Jobs/UIThreadWorker.hpp index aa946036e..2477489f7 100644 --- a/src/slic3r/GUI/Jobs/UIThreadWorker.hpp +++ b/src/slic3r/GUI/Jobs/UIThreadWorker.hpp @@ -65,7 +65,10 @@ public: explicit UIThreadWorker(std::shared_ptr pri, const std::string & /*name*/ = "") : m_progress{pri} - {} + { + if (m_progress) + m_progress->set_cancel_callback([this](){ cancel(); }); + } UIThreadWorker() = default; From b00c5504639bf8dae84f24e41438a3a964b6e466 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 27 May 2022 15:55:25 +0200 Subject: [PATCH 07/13] Do not show legend and bottom slider when loading an invalid gcode file into GCodeViewer --- src/slic3r/GUI/GCodeViewer.cpp | 12 +++++++----- src/slic3r/GUI/GUI_Preview.cpp | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 2134a795f..1e28d1287 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -991,11 +991,13 @@ void GCodeViewer::render() render_toolpaths(); render_shells(); float legend_height = 0.0f; - render_legend(legend_height); - if (m_sequential_view.current.last != m_sequential_view.endpoints.last) { - m_sequential_view.marker.set_world_position(m_sequential_view.current_position); - m_sequential_view.marker.set_world_offset(m_sequential_view.current_offset); - m_sequential_view.render(legend_height); + if (!m_layers.empty()) { + render_legend(legend_height); + if (m_sequential_view.current.last != m_sequential_view.endpoints.last) { + m_sequential_view.marker.set_world_position(m_sequential_view.current_position); + m_sequential_view.marker.set_world_offset(m_sequential_view.current_offset); + m_sequential_view.render(legend_height); + } } #if ENABLE_GCODE_VIEWER_STATISTICS render_statistics(); diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 9f62fa0b8..6fff561ce 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -954,7 +954,7 @@ void Preview::load_print_as_fff(bool keep_z_range) } GCodeViewer::EViewType gcode_view_type = m_canvas->get_gcode_view_preview_type(); - bool gcode_preview_data_valid = !m_gcode_result->moves.empty(); + bool gcode_preview_data_valid = !m_gcode_result->moves.empty() && !m_canvas->get_gcode_layers_zs().empty(); // Collect colors per extruder. std::vector colors; From 6ab8e3a1388cd5e17d7c0a20b6a148918071d772 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 27 May 2022 16:09:32 +0200 Subject: [PATCH 08/13] Fix id_idle() for UIThreadWorker --- src/slic3r/GUI/Jobs/UIThreadWorker.hpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/Jobs/UIThreadWorker.hpp b/src/slic3r/GUI/Jobs/UIThreadWorker.hpp index 2477489f7..b8ac914fd 100644 --- a/src/slic3r/GUI/Jobs/UIThreadWorker.hpp +++ b/src/slic3r/GUI/Jobs/UIThreadWorker.hpp @@ -67,7 +67,7 @@ public: : m_progress{pri} { if (m_progress) - m_progress->set_cancel_callback([this](){ cancel(); }); + m_progress->set_cancel_callback([this]() { cancel(); }); } UIThreadWorker() = default; @@ -75,12 +75,14 @@ public: bool push(std::unique_ptr job) override { m_canceled = false; - m_jobqueue.push(std::move(job)); + + if (job) + m_jobqueue.push(std::move(job)); return bool(job); } - bool is_idle() const override { return !m_running; } + bool is_idle() const override { return !m_running && m_jobqueue.empty(); } void cancel() override { m_canceled = true; } @@ -88,7 +90,9 @@ public: { m_canceled = true; process_front(); - while (!m_jobqueue.empty()) m_jobqueue.pop(); + + while (!m_jobqueue.empty()) + m_jobqueue.pop(); } void process_events() override { From c5e3a565117ef714485b132f990b15a251b01e99 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 30 May 2022 10:01:47 +0200 Subject: [PATCH 09/13] Further fix to is_idle() and rethrow unhandled exception after finalize In UIThreadWorker --- src/slic3r/GUI/Jobs/UIThreadWorker.hpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Jobs/UIThreadWorker.hpp b/src/slic3r/GUI/Jobs/UIThreadWorker.hpp index b8ac914fd..610d205cf 100644 --- a/src/slic3r/GUI/Jobs/UIThreadWorker.hpp +++ b/src/slic3r/GUI/Jobs/UIThreadWorker.hpp @@ -35,10 +35,14 @@ class UIThreadWorker : public Worker, private Job::Ctl { eptr= std::current_exception(); } - m_running = false; - job->finalize(m_canceled, eptr); + // Unhandled exceptions are rethrown without mercy. + if (eptr) + std::rethrow_exception(eptr); + + m_running = false; + m_canceled = false; } } From 4326e083eb127663016a6b8a4d65121dccda94db Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 30 May 2022 11:15:23 +0200 Subject: [PATCH 10/13] Fix sla rotation gizmo menu not being remembered --- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index b4b9e0777..640199019 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -758,7 +758,9 @@ GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_fil GLGizmoRotate(parent, GLGizmoRotate::X), GLGizmoRotate(parent, GLGizmoRotate::Y), GLGizmoRotate(parent, GLGizmoRotate::Z) }) -{} +{ + load_rotoptimize_state(); +} bool GLGizmoRotate3D::on_mouse(const wxMouseEvent &mouse_event) { From 78124689c5aa3f885f3905d9bfbc0bbae55c356e Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 30 May 2022 11:17:34 +0200 Subject: [PATCH 11/13] Fix excessive uptates to UI in sla rotation optimization --- src/libslic3r/SLA/Rotfinder.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/SLA/Rotfinder.cpp b/src/libslic3r/SLA/Rotfinder.cpp index 847e638e6..16cd28130 100644 --- a/src/libslic3r/SLA/Rotfinder.cpp +++ b/src/libslic3r/SLA/Rotfinder.cpp @@ -288,7 +288,7 @@ template struct RotfinderBoilerplate { static constexpr unsigned MAX_TRIES = MAX_ITER; - int status = 0; + int status = 0, prev_status = 0; TriangleMesh mesh; unsigned max_tries; const RotOptimizeParams ¶ms; @@ -314,13 +314,20 @@ struct RotfinderBoilerplate { RotfinderBoilerplate(const ModelObject &mo, const RotOptimizeParams &p) : mesh{get_mesh_to_rotate(mo)} - , params{p} , max_tries(p.accuracy() * MAX_TRIES) - { + , params{p} + {} + void statusfn() { + int s = status * 100 / max_tries; + if (s != prev_status) { + params.statuscb()(s); + prev_status = s; + } + + ++status; } - void statusfn() { params.statuscb()(++status * 100.0 / max_tries); } bool stopcond() { return ! params.statuscb()(-1); } }; From e8753ee8cd7bb4ffab9a0681bc904ca90e5ff60f Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 12 Jan 2022 11:29:38 +0100 Subject: [PATCH 12/13] Tech ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE - 1st installment - Copies custom bed texture and model files to 'data_dir()\printer' folder, if needed, and updates the printer config accordingly Fixed conflicts after rebase with master --- src/libslic3r/PresetBundle.cpp | 35 ++++++++++++++++++++++++++++++++- src/libslic3r/PresetBundle.hpp | 6 ++++++ src/libslic3r/Technologies.hpp | 3 ++- src/slic3r/GUI/ConfigWizard.cpp | 4 ++++ 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 85bcd69ba..e744d72ca 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -1,7 +1,7 @@ #include -#include "PresetBundle.hpp" #include "libslic3r.h" +#include "PresetBundle.hpp" #include "Utils.hpp" #include "Model.hpp" #include "format.hpp" @@ -453,6 +453,11 @@ void PresetBundle::save_changes_for_preset(const std::string& new_name, Preset:: presets.get_edited_preset().config.apply_only(presets.get_selected_preset().config, unselected_options); } +#if ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE + if (type == Preset::TYPE_PRINTER) + copy_bed_model_and_texture_if_needed(presets.get_edited_preset().config); +#endif // ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE + // Save the preset into Slic3r::data_dir / presets / section_name / preset_name.ini presets.save_current_preset(new_name); // Mark the print & filament enabled if they are compatible with the currently selected preset. @@ -1860,4 +1865,32 @@ void PresetBundle::set_default_suppressed(bool default_suppressed) printers.set_default_suppressed(default_suppressed); } +#if ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE +void copy_bed_model_and_texture_if_needed(DynamicPrintConfig& config) +{ + const boost::filesystem::path user_dir = boost::filesystem::absolute(boost::filesystem::path(data_dir()) / "printer").make_preferred(); + const boost::filesystem::path res_dir = boost::filesystem::absolute(boost::filesystem::path(resources_dir()) / "profiles").make_preferred(); + + auto do_copy = [&user_dir, &res_dir](ConfigOptionString* cfg, const std::string& type) { + if (cfg == nullptr || cfg->value.empty()) + return; + + const boost::filesystem::path src_dir = boost::filesystem::absolute(boost::filesystem::path(cfg->value)).make_preferred().parent_path(); + if (src_dir != user_dir && src_dir.parent_path() != res_dir) { + const std::string dst_value = (user_dir / boost::filesystem::path(cfg->value).filename()).string(); + std::string error; + if (copy_file_inner(cfg->value, dst_value, error) == SUCCESS) + cfg->value = dst_value; + else { + BOOST_LOG_TRIVIAL(error) << "Copying from " << cfg->value << " to " << dst_value << " failed. Unable to set custom bed " << type << ". [" << error << "]"; + cfg->value = ""; + } + } + }; + + do_copy(config.option("bed_custom_texture"), "texture"); + do_copy(config.option("bed_custom_model"), "model"); +} +#endif // ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE + } // namespace Slic3r diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index 2a5ce6839..b36f6ed6e 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -178,6 +178,12 @@ private: ENABLE_ENUM_BITMASK_OPERATORS(PresetBundle::LoadConfigBundleAttribute) +#if ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE +// Copies bed texture and model files to 'data_dir()\printer' folder, if needed +// and updates the config accordingly +extern void copy_bed_model_and_texture_if_needed(DynamicPrintConfig& config); +#endif // ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE + } // namespace Slic3r #endif /* slic3r_PresetBundle_hpp_ */ diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index a89a7c8b0..9f41196a1 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -81,6 +81,7 @@ #define ENABLE_USED_FILAMENT_POST_PROCESS (1 && ENABLE_2_5_0_ALPHA1) // Enable gizmo grabbers to share common models #define ENABLE_GIZMO_GRABBER_REFACTOR (1 && ENABLE_2_5_0_ALPHA1) - +// Enable copy of custom bed model and texture +#define ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE (1 && ENABLE_2_5_0_ALPHA1) #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index cbee62013..080de997e 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -2780,6 +2780,10 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese page_diams->apply_custom_config(*custom_config); page_temps->apply_custom_config(*custom_config); +#if ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE + copy_bed_model_and_texture_if_needed(*custom_config); +#endif // ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE + const std::string profile_name = page_custom->profile_name(); preset_bundle->load_config_from_wizard(profile_name, *custom_config); } From 4dee789e9e8aa55e3d9e9c03619767b500b74d18 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 31 May 2022 08:39:15 +0200 Subject: [PATCH 13/13] Follow-up of b00c5504639bf8dae84f24e41438a3a964b6e466 - More robust fix for: Do not show legend and bottom slider when loading an invalid gcode file into GCodeViewer --- src/slic3r/GUI/GUI_Preview.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 6fff561ce..88bd42a66 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -954,7 +954,7 @@ void Preview::load_print_as_fff(bool keep_z_range) } GCodeViewer::EViewType gcode_view_type = m_canvas->get_gcode_view_preview_type(); - bool gcode_preview_data_valid = !m_gcode_result->moves.empty() && !m_canvas->get_gcode_layers_zs().empty(); + bool gcode_preview_data_valid = !m_gcode_result->moves.empty(); // Collect colors per extruder. std::vector colors; @@ -983,10 +983,11 @@ void Preview::load_print_as_fff(bool keep_z_range) if (gcode_preview_data_valid) { // Load the real G-code preview. m_canvas->load_gcode_preview(*m_gcode_result, colors); - m_left_sizer->Show(m_bottom_toolbar_panel); m_left_sizer->Layout(); Refresh(); zs = m_canvas->get_gcode_layers_zs(); + if (!zs.empty()) + m_left_sizer->Show(m_bottom_toolbar_panel); m_loaded = true; } else if (wxGetApp().is_editor()) {