diff --git a/cmake/modules/FindGTK3.cmake b/cmake/modules/FindGTK3.cmake new file mode 100644 index 000000000..9f62658d0 --- /dev/null +++ b/cmake/modules/FindGTK3.cmake @@ -0,0 +1,46 @@ +# - Try to find GTK+ 3 +# Once done, this will define +# +# GTK3_FOUND - system has GTK+ 3. +# GTK3_INCLUDE_DIRS - the GTK+ 3. include directories +# GTK3_LIBRARIES - link these to use GTK+ 3. +# +# Copyright (C) 2012 Raphael Kubo da Costa +# Copyright (C) 2013 Igalia S.L. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND ITS CONTRIBUTORS ``AS +# IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR ITS +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +find_package(PkgConfig) +pkg_check_modules(GTK3 QUIET gtk+-3.0) +set(VERSION_OK TRUE) +if (GTK3_VERSION) +if (GTK3_FIND_VERSION_EXACT) +if (NOT("${GTK3_FIND_VERSION}" VERSION_EQUAL "${GTK3_VERSION}")) +set(VERSION_OK FALSE) +endif () +else () +if ("${GTK3_VERSION}" VERSION_LESS "${GTK3_FIND_VERSION}") +set(VERSION_OK FALSE) +endif () +endif () +endif () +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(GTK3 DEFAULT_MSG GTK3_INCLUDE_DIRS GTK3_LIBRARIES VERSION_OK) diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 699135d27..019850a98 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -255,3 +255,12 @@ endif () if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY) add_precompiled_header(libslic3r_gui pchheader.hpp FORCEINCLUDE) endif () + +# We need to implement some hacks for wxWidgets and touch the underlying GTK +# layer and sub-libraries. This forces us to use the include locations of these +# libraries. No need to link to them, wxWidgets does that already. +# See PresetComboBox.cpp for the includes and subsequent workarounds. +if (UNIX AND NOT APPLE) + find_package(GTK${SLIC3R_GTK} REQUIRED) + target_include_directories(libslic3r_gui PRIVATE ${GTK${SLIC3R_GTK}_INCLUDE_DIRS}) +endif () diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 71517880a..03d6b94ce 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2996,6 +2996,7 @@ void GLCanvas3D::on_render_timer(wxTimerEvent& evt) } //render(); m_dirty = true; + wxWakeUpIdle(); } void GLCanvas3D::request_extra_frame_delayed(int miliseconds) diff --git a/src/slic3r/GUI/GUI_Init.cpp b/src/slic3r/GUI/GUI_Init.cpp index 70536c6ba..839782741 100644 --- a/src/slic3r/GUI/GUI_Init.cpp +++ b/src/slic3r/GUI/GUI_Init.cpp @@ -16,11 +16,26 @@ #include #include +#if __APPLE__ + #include +#endif // __APPLE__ + namespace Slic3r { namespace GUI { int GUI_Run(GUI_InitParams ¶ms) { +#if __APPLE__ + // On OSX, we use boost::process::spawn() to launch new instances of PrusaSlicer from another PrusaSlicer. + // boost::process::spawn() sets SIGCHLD to SIGIGN for the child process, thus if a child PrusaSlicer spawns another + // subprocess and the subrocess dies, the child PrusaSlicer will not receive information on end of subprocess + // (posix waitpid() call will always fail). + // https://jmmv.dev/2008/10/boostprocess-and-sigchld.html + // The child instance of PrusaSlicer has to reset SIGCHLD to its default, so that posix waitpid() and similar continue to work. + // See GH issue #5507 + signal(SIGCHLD, SIG_DFL); +#endif // __APPLE__ + try { GUI::GUI_App* gui = new GUI::GUI_App(params.start_as_gcodeviewer ? GUI::GUI_App::EAppMode::GCodeViewer : GUI::GUI_App::EAppMode::Editor); if (gui->get_app_mode() != GUI::GUI_App::EAppMode::GCodeViewer) { diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 410990ef7..98cbf1e55 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -1233,11 +1233,11 @@ bool NotificationManager::push_notification_data(std::unique_ptractivate_existing(notification.get())) { m_pop_notifications.back()->update(notification->get_data()); - canvas.request_extra_frame(); + canvas.request_extra_frame_delayed(33); return false; } else { m_pop_notifications.emplace_back(std::move(notification)); - canvas.request_extra_frame(); + canvas.request_extra_frame_delayed(33); return true; } } @@ -1387,15 +1387,19 @@ void NotificationManager::update_notifications() if (!top_level_wnd->IsActive()) return; - static size_t last_size = m_pop_notifications.size(); + //static size_t last_size = m_pop_notifications.size(); + //request frames + int64_t next_render = std::numeric_limits::max(); for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end();) { std::unique_ptr& notification = *it; + notification->set_paused(m_hovered); + notification->update_state(); + next_render = std::min(next_render, notification->next_render()); if (notification->get_state() == PopNotification::EState::Finished) it = m_pop_notifications.erase(it); else { - notification->set_paused(m_hovered); - notification->update_state(); + ++it; } } @@ -1436,16 +1440,11 @@ void NotificationManager::update_notifications() if (m_requires_render) m_requires_update = true; */ - //request frames - int64_t next_render = std::numeric_limits::max(); - const int64_t max = std::numeric_limits::max(); - for (const std::unique_ptr& notification : m_pop_notifications) { - next_render = std::min(next_render, notification->next_render()); - } + if (next_render == 0) - wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); - else if (next_render < max) + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame_delayed(33); //few milliseconds to get from GLCanvas::render + else if (next_render < std::numeric_limits::max()) wxGetApp().plater()->get_current_canvas3D()->request_extra_frame_delayed(int(next_render)); /* diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 6031edf78..8dd35a591 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -32,6 +32,14 @@ #include "PhysicalPrinterDialog.hpp" #include "SavePresetDialog.hpp" +// A workaround for a set of issues related to text fitting into gtk widgets: +// See e.g.: https://github.com/prusa3d/PrusaSlicer/issues/4584 +#if defined(__WXGTK20__) || defined(__WXGTK3__) + #include + #include + #include +#endif + using Slic3r::GUI::format_wxstr; namespace Slic3r { @@ -179,6 +187,25 @@ void PresetComboBox::update_selection() SetSelection(m_last_selected); SetToolTip(GetString(m_last_selected)); + +// A workaround for a set of issues related to text fitting into gtk widgets: +// See e.g.: https://github.com/prusa3d/PrusaSlicer/issues/4584 +#if defined(__WXGTK20__) || defined(__WXGTK3__) + GList* cells = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(m_widget)); + + // 'cells' contains the GtkCellRendererPixBuf for the icon, + // 'cells->next' contains GtkCellRendererText for the text we need to ellipsize + if (!cells || !cells->next) return; + + auto cell = static_cast(cells->next->data); + + if (!cell) return; + + g_object_set(G_OBJECT(cell), "ellipsize", PANGO_ELLIPSIZE_END, NULL); + + // Only the list of cells must be freed, the renderer isn't ours to free + g_list_free(cells); +#endif } void PresetComboBox::update(std::string select_preset_name) diff --git a/src/slic3r/GUI/RemovableDriveManager.cpp b/src/slic3r/GUI/RemovableDriveManager.cpp index ec69bb198..b11cc8dd5 100644 --- a/src/slic3r/GUI/RemovableDriveManager.cpp +++ b/src/slic3r/GUI/RemovableDriveManager.cpp @@ -261,19 +261,31 @@ void RemovableDriveManager::eject_drive() #ifndef REMOVABLE_DRIVE_MANAGER_OS_CALLBACKS this->update(); #endif // REMOVABLE_DRIVE_MANAGER_OS_CALLBACKS +#if __APPLE__ + // If eject is still pending on the eject thread, wait until it finishes. + //FIXME while waiting for the eject thread to finish, the main thread is not pumping Cocoa messages, which may lead + // to blocking by the diskutil tool for a couple (up to 10) seconds. This is likely not critical, as the eject normally + // finishes quickly. + this->eject_thread_finish(); +#endif + BOOST_LOG_TRIVIAL(info) << "Ejecting started"; - tbb::mutex::scoped_lock lock(m_drives_mutex); - auto it_drive_data = this->find_last_save_path_drive_data(); - if (it_drive_data != m_current_drives.end()) { - std::string correct_path(m_last_save_path); -#ifndef __APPLE__ - for (size_t i = 0; i < correct_path.size(); ++i) - if (correct_path[i]==' ') { - correct_path = correct_path.insert(i,1,'\\'); - ++ i; - } + DriveData drive_data; + { + tbb::mutex::scoped_lock lock(m_drives_mutex); + auto it_drive_data = this->find_last_save_path_drive_data(); + if (it_drive_data == m_current_drives.end()) + return; + drive_data = *it_drive_data; + } + + std::string correct_path(m_last_save_path); +#if __APPLE__ + // On Apple, run the eject asynchronously on a worker thread, see the discussion at GH issue #4844. + m_eject_thread = new boost::thread([this, correct_path, drive_data]() #endif + { //std::cout<<"Ejecting "<<(*it).name<<" from "<< correct_path<<"\n"; // there is no usable command in c++ so terminal command is used instead // but neither triggers "succesful safe removal messege" @@ -296,31 +308,36 @@ void RemovableDriveManager::eject_drive() // wait for command to finnish (blocks ui thread) std::error_code ec; child.wait(ec); + bool success = false; if (ec) { // The wait call can fail, as it did in https://github.com/prusa3d/PrusaSlicer/issues/5507 // It can happen even in cases where the eject is sucessful, but better report it as failed. // We did not find a way to reliably retrieve the exit code of the process. BOOST_LOG_TRIVIAL(error) << "boost::process::child::wait() failed during Ejection. State of Ejection is unknown. Error code: " << ec.value(); - assert(m_callback_evt_handler); - if (m_callback_evt_handler) - wxPostEvent(m_callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair(*it_drive_data, false))); - return; + } else { + int err = child.exit_code(); + if (err) { + BOOST_LOG_TRIVIAL(error) << "Ejecting failed. Exit code: " << err; + } else { + BOOST_LOG_TRIVIAL(info) << "Ejecting finished"; + success = true; + } } - int err = child.exit_code(); - if (err) { - BOOST_LOG_TRIVIAL(error) << "Ejecting failed. Exit code: " << err; - assert(m_callback_evt_handler); - if (m_callback_evt_handler) - wxPostEvent(m_callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair(*it_drive_data, false))); - return; - } - BOOST_LOG_TRIVIAL(info) << "Ejecting finished"; - assert(m_callback_evt_handler); if (m_callback_evt_handler) - wxPostEvent(m_callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair(std::move(*it_drive_data), true))); - m_current_drives.erase(it_drive_data); + wxPostEvent(m_callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair(drive_data, success))); + if (success) { + // Remove the drive_data from m_current drives, searching by value, not by pointer, as m_current_drives may get modified during + // asynchronous execution on m_eject_thread. + tbb::mutex::scoped_lock lock(m_drives_mutex); + auto it = std::find(m_current_drives.begin(), m_current_drives.end(), drive_data); + if (it != m_current_drives.end()) + m_current_drives.erase(it); + } } +#if __APPLE__ + ); +#endif // __APPLE__ } std::string RemovableDriveManager::get_removable_drive_path(const std::string &path) @@ -382,7 +399,11 @@ void RemovableDriveManager::init(wxEvtHandler *callback_evt_handler) void RemovableDriveManager::shutdown() { #if __APPLE__ - this->unregister_window_osx(); + // If eject is still pending on the eject thread, wait until it finishes. + //FIXME while waiting for the eject thread to finish, the main thread is not pumping Cocoa messages, which may lead + // to blocking by the diskutil tool for a couple (up to 10) seconds. This is likely not critical, as the eject normally + // finishes quickly. + this->eject_thread_finish(); #endif #ifndef REMOVABLE_DRIVE_MANAGER_OS_CALLBACKS @@ -493,4 +514,15 @@ std::vector::const_iterator RemovableDriveManager::find_last_save_pat [this](const DriveData &data){ return data.path == m_last_save_path; }); } +#if __APPLE__ +void RemovableDriveManager::eject_thread_finish() +{ + if (m_eject_thread) { + m_eject_thread->join(); + delete m_eject_thread; + m_eject_thread = nullptr; + } +} +#endif // __APPLE__ + }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/RemovableDriveManager.hpp b/src/slic3r/GUI/RemovableDriveManager.hpp index 26ee12e40..f707b40c3 100644 --- a/src/slic3r/GUI/RemovableDriveManager.hpp +++ b/src/slic3r/GUI/RemovableDriveManager.hpp @@ -132,6 +132,8 @@ private: void eject_device(const std::string &path); // Opaque pointer to RemovableDriveManagerMM void *m_impl_osx; + boost::thread *m_eject_thread { nullptr }; + void eject_thread_finish(); #endif }; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 0656c7e0d..a0d7d17f2 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -501,7 +501,7 @@ void Tab::update_labels_colour() if (opt.first == "bed_shape" || opt.first == "filament_ramming_parameters" || opt.first == "compatible_prints" || opt.first == "compatible_printers" ) { if (m_colored_Label_colors.find(opt.first) != m_colored_Label_colors.end()) - *m_colored_Label_colors.at(opt.first) = *color; + m_colored_Label_colors.at(opt.first) = *color; continue; } @@ -540,7 +540,7 @@ void Tab::decorate() if (opt.first == "bed_shape" || opt.first == "filament_ramming_parameters" || opt.first == "compatible_prints" || opt.first == "compatible_printers") - colored_label_clr = (m_colored_Label_colors.find(opt.first) == m_colored_Label_colors.end()) ? nullptr : m_colored_Label_colors.at(opt.first); + colored_label_clr = (m_colored_Label_colors.find(opt.first) == m_colored_Label_colors.end()) ? nullptr : &m_colored_Label_colors.at(opt.first); if (!colored_label_clr) { field = get_field(opt.first); @@ -3553,8 +3553,8 @@ void Tab::create_line_with_widget(ConfigOptionsGroup* optgroup, const std::strin line.widget = widget; line.label_path = path; - m_colored_Label_colors[opt_key] = &m_default_text_clr; - line.full_Label_color = m_colored_Label_colors[opt_key]; + m_colored_Label_colors[opt_key] = m_default_text_clr; + line.full_Label_color = &m_colored_Label_colors[opt_key]; optgroup->append_line(line); } diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 04aa9a0ef..927787933 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -246,7 +246,7 @@ public: // map of option name -> wxColour (color of the colored label, associated with option) // Used for options which don't have corresponded field - std::map m_colored_Label_colors; + std::map m_colored_Label_colors; // Counter for the updating (because of an update() function can have a recursive behavior): // 1. increase value from the very beginning of an update() function diff --git a/src/slic3r/Utils/Process.cpp b/src/slic3r/Utils/Process.cpp index bc6961f9a..a8fc59f52 100644 --- a/src/slic3r/Utils/Process.cpp +++ b/src/slic3r/Utils/Process.cpp @@ -78,6 +78,12 @@ static void start_new_slicer_or_gcodeviewer(const NewSlicerInstanceType instance if (instance_type == NewSlicerInstanceType::Slicer && single_instance) args.emplace_back("--single-instance"); boost::process::spawn(bin_path, args); + // boost::process::spawn() sets SIGCHLD to SIGIGN for the child process, thus if a child PrusaSlicer spawns another + // subprocess and the subrocess dies, the child PrusaSlicer will not receive information on end of subprocess + // (posix waitpid() call will always fail). + // https://jmmv.dev/2008/10/boostprocess-and-sigchld.html + // The child instance of PrusaSlicer has to reset SIGCHLD to its default, so that posix waitpid() and similar continue to work. + // See GH issue #5507 } catch (const std::exception& ex) { BOOST_LOG_TRIVIAL(error) << "Failed to spawn a new slicer \"" << bin_path.string() << "\": " << ex.what();