From db324b22951c14d2d3d74b670b6bec4e411c781a Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 13 Apr 2021 10:55:27 +0200 Subject: Ported ChromeOS support from master aka PrusaSlicer 2.4.0-alpha: 1) Detect platform 2) Disable OpenGL multi-sampling on ChromeOS 3) Disable eject on ChromeOS, different location of external devices mount point. --- src/PrusaSlicer.cpp | 4 + src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/Platform.cpp | 71 ++++++++++++++++ src/libslic3r/Platform.hpp | 41 +++++++++ src/libslic3r/utils.cpp | 141 +++++++++++++++++++++++++++++++ src/slic3r/GUI/OpenGLManager.cpp | 10 ++- src/slic3r/GUI/Plater.cpp | 5 +- src/slic3r/GUI/RemovableDriveManager.cpp | 51 +++++++---- 8 files changed, 305 insertions(+), 20 deletions(-) create mode 100644 src/libslic3r/Platform.cpp create mode 100644 src/libslic3r/Platform.hpp diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 60f3a1321..6a019271e 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -37,6 +37,7 @@ #include "libslic3r/GCode/PostProcessor.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/ModelArrange.hpp" +#include "libslic3r/Platform.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/SLAPrint.hpp" #include "libslic3r/TriangleMesh.hpp" @@ -594,6 +595,9 @@ bool CLI::setup(int argc, char **argv) } } + // Detect the operating system flavor after SLIC3R_LOGLEVEL is set. + detect_platform(); + boost::filesystem::path path_to_binary = boost::filesystem::system_complete(argv[0]); // Path from the Slic3r binary to its resources. diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 11e37afc6..5e0808190 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -137,6 +137,8 @@ add_library(libslic3r STATIC PerimeterGenerator.hpp PlaceholderParser.cpp PlaceholderParser.hpp + Platform.cpp + Platform.hpp Point.cpp Point.hpp Polygon.cpp diff --git a/src/libslic3r/Platform.cpp b/src/libslic3r/Platform.cpp new file mode 100644 index 000000000..ae02c42b3 --- /dev/null +++ b/src/libslic3r/Platform.cpp @@ -0,0 +1,71 @@ +#include "Platform.hpp" + +#include +#include + +namespace Slic3r { + +static auto s_platform = Platform::Uninitialized; +static auto s_platform_flavor = PlatformFlavor::Uninitialized; + +void detect_platform() +{ +#if defined(_WIN32) + BOOST_LOG_TRIVIAL(info) << "Platform: Windows"; + s_platform = Platform::Windows; + s_platform_flavor = PlatformFlavor::Generic; +#elif defined(__APPLE__) + BOOST_LOG_TRIVIAL(info) << "Platform: OSX"; + s_platform = Platform::OSX; + s_platform_flavor = PlatformFlavor::Generic; +#elif defined(__linux__) + BOOST_LOG_TRIVIAL(info) << "Platform: Linux"; + s_platform = Platform::Linux; + s_platform_flavor = PlatformFlavor::GenericLinux; + // Test for Chromium. + { + FILE *f = ::fopen("/proc/version", "rt"); + if (f) { + char buf[4096]; + // Read the 1st line. + if (::fgets(buf, 4096, f)) { + if (strstr(buf, "Chromium OS") != nullptr) { + s_platform_flavor = PlatformFlavor::LinuxOnChromium; + BOOST_LOG_TRIVIAL(info) << "Platform flavor: LinuxOnChromium"; + } else if (strstr(buf, "microsoft") != nullptr || strstr(buf, "Microsoft") != nullptr) { + if (boost::filesystem::exists("/run/WSL") && getenv("WSL_INTEROP") != nullptr) { + BOOST_LOG_TRIVIAL(info) << "Platform flavor: WSL2"; + s_platform_flavor = PlatformFlavor::WSL2; + } else { + BOOST_LOG_TRIVIAL(info) << "Platform flavor: WSL"; + s_platform_flavor = PlatformFlavor::WSL; + } + } + } + ::fclose(f); + } + } +#elif defined(__OpenBSD__) + BOOST_LOG_TRIVIAL(info) << "Platform: OpenBSD"; + s_platform = Platform::BSDUnix; + s_platform_flavor = PlatformFlavor::OpenBSD; +#else + // This should not happen. + BOOST_LOG_TRIVIAL(info) << "Platform: Unknown"; + static_assert(false, "Unknown platform detected"); + s_platform = Platform::Unknown; + s_platform_flavor = PlatformFlavor::Unknown; +#endif +} + +Platform platform() +{ + return s_platform; +} + +PlatformFlavor platform_flavor() +{ + return s_platform_flavor; +} + +} // namespace Slic3r diff --git a/src/libslic3r/Platform.hpp b/src/libslic3r/Platform.hpp new file mode 100644 index 000000000..735728e89 --- /dev/null +++ b/src/libslic3r/Platform.hpp @@ -0,0 +1,41 @@ +#ifndef SLIC3R_Platform_HPP +#define SLIC3R_Platform_HPP + +namespace Slic3r { + +enum class Platform +{ + Uninitialized, + Unknown, + Windows, + OSX, + Linux, + BSDUnix, +}; + +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, +}; + +// To be called on program start-up. +void detect_platform(); + +Platform platform(); +PlatformFlavor platform_flavor(); + +} // namespace Slic3r + +#endif // SLIC3R_Platform_HPP diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 0c26d42c8..9d0162980 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -6,6 +6,7 @@ #include #include +#include "Platform.hpp" #include "Time.hpp" #ifdef WIN32 @@ -417,6 +418,140 @@ std::error_code rename_file(const std::string &from, const std::string &to) #endif } +#ifdef __linux__ +// Copied from boost::filesystem, to support copying a file to a weird filesystem, which does not support changing file attributes, +// for example ChromeOS Linux integration or FlashAIR WebDAV. +// Copied and simplified from boost::filesystem::detail::copy_file() with option = overwrite_if_exists and with just the Linux path kept, +// and only features supported by Linux 3.10 (on our build server with CentOS 7) are kept, namely sendfile with ranges and statx() are not supported. +bool copy_file_linux(const boost::filesystem::path &from, const boost::filesystem::path &to, boost::system::error_code &ec) +{ + using namespace boost::filesystem; + + struct fd_wrapper + { + int fd { -1 }; + fd_wrapper() = default; + explicit fd_wrapper(int fd) throw() : fd(fd) {} + ~fd_wrapper() throw() { if (fd >= 0) ::close(fd); } + }; + + ec.clear(); + int err = 0; + + // Note: Declare fd_wrappers here so that errno is not clobbered by close() that may be called in fd_wrapper destructors + fd_wrapper infile, outfile; + + while (true) { + infile.fd = ::open(from.c_str(), O_RDONLY | O_CLOEXEC); + if (infile.fd < 0) { + err = errno; + if (err == EINTR) + continue; + fail: + ec.assign(err, boost::system::system_category()); + return false; + } + break; + } + + struct ::stat from_stat; + if (::fstat(infile.fd, &from_stat) != 0) { + fail_errno: + err = errno; + goto fail; + } + + const mode_t from_mode = from_stat.st_mode; + if (!S_ISREG(from_mode)) { + err = ENOSYS; + goto fail; + } + + // Enable writing for the newly created files. Having write permission set is important e.g. for NFS, + // which checks the file permission on the server, even if the client's file descriptor supports writing. + mode_t to_mode = from_mode | S_IWUSR; + int oflag = O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC; + + while (true) { + outfile.fd = ::open(to.c_str(), oflag, to_mode); + if (outfile.fd < 0) { + err = errno; + if (err == EINTR) + continue; + goto fail; + } + break; + } + + struct ::stat to_stat; + if (::fstat(outfile.fd, &to_stat) != 0) + goto fail_errno; + + to_mode = to_stat.st_mode; + if (!S_ISREG(to_mode)) { + err = ENOSYS; + goto fail; + } + + if (from_stat.st_dev == to_stat.st_dev && from_stat.st_ino == to_stat.st_ino) { + err = EEXIST; + goto fail; + } + + //! copy_file implementation that uses sendfile loop. Requires sendfile to support file descriptors. + //FIXME Vojtech: This is a copy loop valid for Linux 2.6.33 and newer. + // copy_file_data_copy_file_range() supports cross-filesystem copying since 5.3, but Vojtech did not want to polute this + // function with that, we don't think the performance gain is worth it for the types of files we are copying, + // and our build server based on CentOS 7 with Linux 3.10 does not support that anyways. + { + // sendfile will not send more than this amount of data in one call + constexpr std::size_t max_send_size = 0x7ffff000u; + uintmax_t offset = 0u; + while (off_t(offset) < from_stat.st_size) { + uintmax_t size_left = from_stat.st_size - offset; + std::size_t size_to_copy = max_send_size; + if (size_left < static_cast(max_send_size)) + size_to_copy = static_cast(size_left); + ssize_t sz = ::sendfile(outfile.fd, infile.fd, nullptr, size_to_copy); + if (sz < 0) { + err = errno; + if (err == EINTR) + continue; + if (err == 0) + break; + goto fail; // err already contains the error code + } + offset += sz; + } + } + + // If we created a new file with an explicitly added S_IWUSR permission, + // we may need to update its mode bits to match the source file. + if (to_mode != from_mode && ::fchmod(outfile.fd, from_mode) != 0) { + if (platform_flavor() == PlatformFlavor::LinuxOnChromium) { + // Ignore that. 9p filesystem does not allow fmod(). + BOOST_LOG_TRIVIAL(info) << "copy_file_linux() failed to fchmod() the output file \"" << to.string() << "\" to " << from_mode << ": " << ec.message() << + " This may be expected when writing to a 9p filesystem."; + } else { + // Generic linux. Write out an error to console. At least we may get some feedback. + BOOST_LOG_TRIVIAL(error) << "copy_file_linux() failed to fchmod() the output file \"" << to.string() << "\" to " << from_mode << ": " << ec.message(); + } + } + + // Note: Use fsync/fdatasync followed by close to avoid dealing with the possibility of close failing with EINTR. + // Even if close fails, including with EINTR, most operating systems (presumably, except HP-UX) will close the + // file descriptor upon its return. This means that if an error happens later, when the OS flushes data to the + // underlying media, this error will go unnoticed and we have no way to receive it from close. Calling fsync/fdatasync + // ensures that all data have been written, and even if close fails for some unfathomable reason, we don't really + // care at that point. + err = ::fdatasync(outfile.fd); + if (err != 0) + goto fail_errno; + + return true; +} +#endif // __linux__ + CopyFileResult copy_file_inner(const std::string& from, const std::string& to, std::string& error_message) { const boost::filesystem::path source(from); @@ -434,7 +569,13 @@ CopyFileResult copy_file_inner(const std::string& from, const std::string& to, s if (ec) BOOST_LOG_TRIVIAL(debug) << "boost::filesystem::permisions before copy error message (this could be irrelevant message based on file system): " << ec.message(); ec.clear(); +#ifdef __linux__ + // We want to allow copying files on Linux to succeed even if changing the file attributes fails. + // That may happen when copying on some exotic file system, for example Linux on Chrome. + copy_file_linux(source, target, ec); +#else // __linux__ boost::filesystem::copy_file(source, target, boost::filesystem::copy_option::overwrite_if_exists, ec); +#endif // __linux__ if (ec) { error_message = ec.message(); return FAIL_COPY_FILE; diff --git a/src/slic3r/GUI/OpenGLManager.cpp b/src/slic3r/GUI/OpenGLManager.cpp index 4f1e00793..1908c1d34 100644 --- a/src/slic3r/GUI/OpenGLManager.cpp +++ b/src/slic3r/GUI/OpenGLManager.cpp @@ -5,6 +5,8 @@ #include "I18N.hpp" #include "3DScene.hpp" +#include "libslic3r/Platform.hpp" + #include #include @@ -333,7 +335,13 @@ void OpenGLManager::detect_multisample(int* attribList) { int wxVersion = wxMAJOR_VERSION * 10000 + wxMINOR_VERSION * 100 + wxRELEASE_NUMBER; bool enable_multisample = wxVersion >= 30003; - s_multisample = (enable_multisample && wxGLCanvas::IsDisplaySupported(attribList)) ? EMultisampleState::Enabled : EMultisampleState::Disabled; + s_multisample = + enable_multisample && + // Disable multi-sampling on ChromeOS, as the OpenGL virtualization swaps Red/Blue channels with multi-sampling enabled, + // at least on some platforms. + platform_flavor() != PlatformFlavor::LinuxOnChromium && + wxGLCanvas::IsDisplaySupported(attribList) + ? EMultisampleState::Enabled : EMultisampleState::Disabled; // Alternative method: it was working on previous version of wxWidgets but not with the latest, at least on Windows // s_multisample = enable_multisample && wxGLCanvas::IsExtensionSupported("WGL_ARB_multisample"); } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index e154c0567..a9e66333e 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -88,6 +88,7 @@ #include // Needs to be last because reasons :-/ #include "WipeTowerDialog.hpp" #include "libslic3r/CustomGCode.hpp" +#include "libslic3r/Platform.hpp" using boost::optional; namespace fs = boost::filesystem; @@ -3769,7 +3770,9 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) // If writing to removable drive was scheduled, show notification with eject button if (exporting_status == ExportingStatus::EXPORTING_TO_REMOVABLE && !has_error) { show_action_buttons(false); - notification_manager->push_exporting_finished_notification(last_output_path, last_output_dir_path, true); + notification_manager->push_exporting_finished_notification(last_output_path, last_output_dir_path, + // Don't offer the "Eject" button on ChromeOS, the Linux side has no control over it. + platform_flavor() != PlatformFlavor::LinuxOnChromium); wxGetApp().removable_drive_manager()->set_exporting_finished(true); }else if (exporting_status == ExportingStatus::EXPORTING_TO_LOCAL && !has_error) notification_manager->push_exporting_finished_notification(last_output_path, last_output_dir_path, false); diff --git a/src/slic3r/GUI/RemovableDriveManager.cpp b/src/slic3r/GUI/RemovableDriveManager.cpp index b11cc8dd5..59a202beb 100644 --- a/src/slic3r/GUI/RemovableDriveManager.cpp +++ b/src/slic3r/GUI/RemovableDriveManager.cpp @@ -1,4 +1,5 @@ #include "RemovableDriveManager.hpp" +#include "libslic3r/Platform.hpp" #include #include @@ -185,8 +186,13 @@ namespace search_for_drives_internal { //confirms if the file is removable drive and adds it to vector - //if not same file system - could be removable drive - if (! compare_filesystem_id(path, parent_path)) { + if ( +#ifdef __linux__ + // Chromium mounts removable drives in a way that produces the same device ID. + platform_flavor() == PlatformFlavor::LinuxOnChromium || +#endif + // If not same file system - could be removable drive. + ! compare_filesystem_id(path, parent_path)) { //free space boost::system::error_code ec; boost::filesystem::space_info si = boost::filesystem::space(path, ec); @@ -229,22 +235,28 @@ std::vector RemovableDriveManager::search_for_removable_drives() cons #else - //search /media/* folder - search_for_drives_internal::search_path("/media/*", "/media", current_drives); - - //search_path("/Volumes/*", "/Volumes"); - std::string path(std::getenv("USER")); - std::string pp(path); - - //search /media/USERNAME/* folder - pp = "/media/"+pp; - path = "/media/" + path + "/*"; - search_for_drives_internal::search_path(path, pp, current_drives); + if (platform_flavor() == PlatformFlavor::LinuxOnChromium) { + // ChromeOS specific: search /mnt/chromeos/removable/* folder + search_for_drives_internal::search_path("/mnt/chromeos/removable/*", "/mnt/chromeos/removable", current_drives); + } else { + //search /media/* folder + search_for_drives_internal::search_path("/media/*", "/media", current_drives); + + //search_path("/Volumes/*", "/Volumes"); + std::string path(std::getenv("USER")); + std::string pp(path); + + //search /media/USERNAME/* folder + pp = "/media/"+pp; + path = "/media/" + path + "/*"; + search_for_drives_internal::search_path(path, pp, current_drives); + + //search /run/media/USERNAME/* folder + path = "/run" + path; + pp = "/run"+pp; + search_for_drives_internal::search_path(path, pp, current_drives); + } - //search /run/media/USERNAME/* folder - path = "/run" + path; - pp = "/run"+pp; - search_for_drives_internal::search_path(path, pp, current_drives); #endif return current_drives; @@ -441,7 +453,10 @@ RemovableDriveManager::RemovableDrivesStatus RemovableDriveManager::status() RemovableDriveManager::RemovableDrivesStatus out; { tbb::mutex::scoped_lock lock(m_drives_mutex); - out.has_eject = this->find_last_save_path_drive_data() != m_current_drives.end(); + out.has_eject = + // Cannot control eject on Chromium. + platform_flavor() != PlatformFlavor::LinuxOnChromium && + this->find_last_save_path_drive_data() != m_current_drives.end(); out.has_removable_drives = ! m_current_drives.empty(); } if (! out.has_eject) -- cgit v1.2.3