From 52a3d0e556b7bec6e937036372760ee7910583a3 Mon Sep 17 00:00:00 2001 From: PJarczak Date: Mon, 20 Apr 2026 18:23:24 +0200 Subject: [PATCH] . --- .github/workflows/build_macos_bridge.yml | 239 ++++++++++++++++++ CMakeLists.txt | 8 + build_release_macos.sh | 26 +- deps/CMakeLists.txt | 7 + deps/FFMPEG/FFMPEG.cmake | 14 + src/slic3r/CMakeLists.txt | 17 +- src/slic3r/GUI/GUI_App.cpp | 120 ++++++++- src/slic3r/GUI/MediaPlayCtrl.h | 5 - src/slic3r/GUI/WebUserLoginDialog.cpp | 6 +- .../Utils/PJarczakLinuxBridge/CMakeLists.txt | 38 ++- .../PJarczakLinuxBridgeConfig.cpp | 22 +- .../PJarczakLinuxBridgeConfig.hpp | 3 + .../PJarczakLinuxSoBridgeLauncher_mac.mm | 182 ++++++++++++- .../pjarczak-bambu-linux-host-wrapper | 60 ++++- .../macos/pjarczak_install_macos_runtime.sh | 177 +++++++++++++ .../macos/pjarczak_lima_instance.txt | 1 + .../macos/pjarczak_verify_macos_runtime.sh | 116 +++++++++ 17 files changed, 1005 insertions(+), 36 deletions(-) create mode 100644 .github/workflows/build_macos_bridge.yml create mode 100644 deps/FFMPEG/FFMPEG.cmake create mode 100755 tools/pjarczak_bambu_runtime/macos/pjarczak_install_macos_runtime.sh create mode 100644 tools/pjarczak_bambu_runtime/macos/pjarczak_lima_instance.txt create mode 100755 tools/pjarczak_bambu_runtime/macos/pjarczak_verify_macos_runtime.sh diff --git a/.github/workflows/build_macos_bridge.yml b/.github/workflows/build_macos_bridge.yml new file mode 100644 index 0000000000..62e97ffdde --- /dev/null +++ b/.github/workflows/build_macos_bridge.yml @@ -0,0 +1,239 @@ +name: Build macOS with Linux bridge runtime + +on: + workflow_call: + workflow_dispatch: + +jobs: + prepare_linux_bridge_runtime: + name: Prepare Linux bridge runtime + runs-on: ubuntu-24.04 + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + lfs: 'false' + + - uses: lukka/get-cmake@latest + with: + cmakeVersion: "~4.3.0" + useLocalCache: true + useCloudCache: true + + - name: Apt-Install Dependencies + uses: ./.github/actions/apt-install-deps + + - name: Build Linux deps + shell: bash + run: | + set -euo pipefail + for attempt in 1 2 3; do + echo "Build Linux deps attempt $attempt" + if ./build_linux.sh -drlL; then + exit 0 + fi + if [ "$attempt" -lt 3 ]; then + echo "Transient dependency download/build failure, retrying after backoff" + sleep $((attempt * 20)) + fi + done + exit 1 + + - name: Configure linux bridge host build + shell: bash + run: | + cmake -S . -B build -G "Ninja Multi-Config" -DORCA_TOOLS=ON + + - name: Build linux host runtime + shell: bash + run: | + cmake --build build --config Release --target pjarczak_bambu_linux_host + bash tools/pjarczak_bambu_linux_host/package_linux_host_runtime.sh build + tar -czvf linux_host_runtime_${{ github.sha }}.tar.gz -C tools/pjarczak_bambu_linux_host/runtime linux-x86_64 + + - name: Upload linux host runtime artifact + uses: actions/upload-artifact@v7 + with: + name: linux_host_runtime_${{ github.sha }} + path: ./linux_host_runtime_${{ github.sha }}.tar.gz + if-no-files-found: error + + build_macos_arm64: + name: Build macOS arm64 + runs-on: macos-15 + needs: prepare_linux_bridge_runtime + env: + ORCA_UPDATER_SIG_KEY: ${{ secrets.ORCA_UPDATER_SIG_KEY }} + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + lfs: 'false' + + - uses: lukka/get-cmake@latest + with: + cmakeVersion: "~4.3.0" + useLocalCache: true + useCloudCache: true + + - name: Download linux host runtime artifact + uses: actions/download-artifact@v8 + with: + name: linux_host_runtime_${{ github.sha }} + path: ${{ github.workspace }}/linux_host_runtime_artifact + + - name: Extract linux host runtime artifact on macOS arm64 + shell: bash + run: | + mkdir -p "$GITHUB_WORKSPACE/tools/pjarczak_bambu_linux_host/runtime" + tar -xzf "$GITHUB_WORKSPACE/linux_host_runtime_artifact/linux_host_runtime_${{ github.sha }}.tar.gz" -C "$GITHUB_WORKSPACE/tools/pjarczak_bambu_linux_host/runtime" + + - name: Install mac tools + shell: bash + run: | + brew install libtool + mkdir -p "$GITHUB_WORKSPACE/deps/build/arm64" + + - name: Build macOS arm64 deps + shell: bash + run: | + ./build_release_macos.sh -dx -1 -a arm64 -t 11.3 + + - name: Build macOS arm64 app bundle + shell: bash + run: | + ./build_release_macos.sh -s -n -x -1 -a arm64 -t 11.3 + + - name: Pack macOS arm64 app bundle + shell: bash + run: | + tar -czvf OrcaSlicer_Mac_bundle_arm64_${{ github.sha }}.tar.gz -C build/arm64 OrcaSlicer + + - name: Upload macOS arm64 app bundle + uses: actions/upload-artifact@v7 + with: + name: OrcaSlicer_Mac_bundle_arm64_${{ github.sha }} + path: ${{ github.workspace }}/OrcaSlicer_Mac_bundle_arm64_${{ github.sha }}.tar.gz + if-no-files-found: error + + build_macos_x86_64: + name: Build macOS x86_64 + runs-on: macos-15-intel + needs: prepare_linux_bridge_runtime + env: + ORCA_UPDATER_SIG_KEY: ${{ secrets.ORCA_UPDATER_SIG_KEY }} + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + lfs: 'false' + + - uses: lukka/get-cmake@latest + with: + cmakeVersion: "~4.3.0" + useLocalCache: true + useCloudCache: true + + - name: Download linux host runtime artifact + uses: actions/download-artifact@v8 + with: + name: linux_host_runtime_${{ github.sha }} + path: ${{ github.workspace }}/linux_host_runtime_artifact + + - name: Extract linux host runtime artifact on macOS x86_64 + shell: bash + run: | + mkdir -p "$GITHUB_WORKSPACE/tools/pjarczak_bambu_linux_host/runtime" + tar -xzf "$GITHUB_WORKSPACE/linux_host_runtime_artifact/linux_host_runtime_${{ github.sha }}.tar.gz" -C "$GITHUB_WORKSPACE/tools/pjarczak_bambu_linux_host/runtime" + + - name: Install mac tools + shell: bash + run: | + brew install libtool + mkdir -p "$GITHUB_WORKSPACE/deps/build/x86_64" + + - name: Build macOS x86_64 deps + shell: bash + run: | + ./build_release_macos.sh -dx -1 -a x86_64 -t 11.3 + + - name: Build macOS x86_64 app bundle + shell: bash + run: | + ./build_release_macos.sh -s -n -x -1 -a x86_64 -t 11.3 + + - name: Pack macOS x86_64 app bundle + shell: bash + run: | + tar -czvf OrcaSlicer_Mac_bundle_x86_64_${{ github.sha }}.tar.gz -C build/x86_64 OrcaSlicer + + - name: Upload macOS x86_64 app bundle + uses: actions/upload-artifact@v7 + with: + name: OrcaSlicer_Mac_bundle_x86_64_${{ github.sha }} + path: ${{ github.workspace }}/OrcaSlicer_Mac_bundle_x86_64_${{ github.sha }}.tar.gz + if-no-files-found: error + + build_macos_universal: + name: Build macOS universal + runs-on: macos-15 + needs: + - build_macos_arm64 + - build_macos_x86_64 + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + lfs: 'false' + + - uses: lukka/get-cmake@latest + with: + cmakeVersion: "~4.3.0" + useLocalCache: true + useCloudCache: true + + - name: Download macOS app bundles + uses: actions/download-artifact@v8 + with: + pattern: OrcaSlicer_Mac_bundle_*_${{ github.sha }} + path: ${{ github.workspace }}/mac_bundles + + - name: Extract macOS app bundles + shell: bash + run: | + mkdir -p build/arm64 build/x86_64 + arm_bundle=$(find "$GITHUB_WORKSPACE/mac_bundles/OrcaSlicer_Mac_bundle_arm64_${{ github.sha }}" -name '*.tar.gz' -print -quit) + x86_bundle=$(find "$GITHUB_WORKSPACE/mac_bundles/OrcaSlicer_Mac_bundle_x86_64_${{ github.sha }}" -name '*.tar.gz' -print -quit) + tar -xzvf "$arm_bundle" -C "$GITHUB_WORKSPACE/build/arm64" + tar -xzvf "$x86_bundle" -C "$GITHUB_WORKSPACE/build/x86_64" + + - name: Build universal mac app bundle + shell: bash + run: | + ./build_release_macos.sh -u -x -1 -a universal -t 11.3 + + - name: Create DMG without runtime validation + shell: bash + run: | + mkdir -p "$GITHUB_WORKSPACE/build/universal/OrcaSlicer_dmg" + rm -rf "$GITHUB_WORKSPACE/build/universal/OrcaSlicer_dmg"/* + cp -R "$GITHUB_WORKSPACE/build/universal/OrcaSlicer/OrcaSlicer.app" "$GITHUB_WORKSPACE/build/universal/OrcaSlicer_dmg/" + ln -sfn /Applications "$GITHUB_WORKSPACE/build/universal/OrcaSlicer_dmg/Applications" + ver=$(grep 'set(SoftFever_VERSION' version.inc | cut -d '"' -f2) + hdiutil create -volname "OrcaSlicer" -srcfolder "$GITHUB_WORKSPACE/build/universal/OrcaSlicer_dmg" -ov -format UDZO "OrcaSlicer_Mac_universal_V${ver}.dmg" + + - name: Explain skipped hosted runtime validation + shell: bash + run: | + echo "Hosted macOS runners do not support nested virtualization, so bundled Lima runtime validation is intentionally skipped here." + + - name: Upload macOS universal DMG + uses: actions/upload-artifact@v7 + with: + name: OrcaSlicer_Mac_universal_${{ github.sha }} + path: ${{ github.workspace }}/OrcaSlicer_Mac_universal_V*.dmg + if-no-files-found: error diff --git a/CMakeLists.txt b/CMakeLists.txt index bc374f4251..a18c4daab6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -834,6 +834,10 @@ function(orcaslicer_copy_dlls target config postfix output_dlls) ${CMAKE_PREFIX_PATH}/bin/occt/TKXDESTEP.dll ${CMAKE_PREFIX_PATH}/bin/occt/TKXSBase.dll ${CMAKE_PREFIX_PATH}/bin/freetype.dll + ${CMAKE_PREFIX_PATH}/bin/avcodec-61.dll + ${CMAKE_PREFIX_PATH}/bin/swresample-5.dll + ${CMAKE_PREFIX_PATH}/bin/swscale-8.dll + ${CMAKE_PREFIX_PATH}/bin/avutil-59.dll DESTINATION ${_out_dir}) set(${output_dlls} @@ -869,6 +873,10 @@ function(orcaslicer_copy_dlls target config postfix output_dlls) ${_out_dir}/TKXSBase.dll ${_out_dir}/freetype.dll + ${_out_dir}/avcodec-61.dll + ${_out_dir}/swresample-5.dll + ${_out_dir}/swscale-8.dll + ${_out_dir}/avutil-59.dll PARENT_SCOPE ) diff --git a/build_release_macos.sh b/build_release_macos.sh index 698c8171e8..8a19d4e64b 100755 --- a/build_release_macos.sh +++ b/build_release_macos.sh @@ -100,7 +100,7 @@ if [ -z "$CMAKE_IGNORE_PREFIX_PATH" ]; then export CMAKE_IGNORE_PREFIX_PATH="/opt/local:/usr/local:/opt/homebrew" fi -CMAKE_VERSION=$(cmake --version | head -1 | sed 's/[^0-9]*\([0-9]*\).*//') +CMAKE_VERSION=$(cmake --version | head -1 | sed -E 's/[^0-9]*([0-9]+).*/\1/') if [ "$CMAKE_VERSION" -ge 4 ] 2>/dev/null; then export CMAKE_POLICY_VERSION_MINIMUM=3.5 export CMAKE_POLICY_COMPAT="-DCMAKE_POLICY_VERSION_MINIMUM=3.5" @@ -123,6 +123,7 @@ PROJECT_BUILD_DIR="$PROJECT_DIR/build/$ARCH" DEPS_DIR="$PROJECT_DIR/deps" HOST_RUNTIME_DIR="$PROJECT_DIR/tools/pjarczak_bambu_linux_host/runtime/linux-x86_64" HOST_WRAPPER="$PROJECT_DIR/tools/pjarczak_bambu_linux_host/pjarczak-bambu-linux-host-wrapper" +MAC_RUNTIME_HELPERS_DIR="$PROJECT_DIR/tools/pjarczak_bambu_runtime/macos" export BUILD_DIR_CONFIG_SUBDIR="/$BUILD_CONFIG" @@ -154,6 +155,21 @@ copy_linux_bridge_runtime_to_app() { exit 1 fi + if [ ! -f "$MAC_RUNTIME_HELPERS_DIR/pjarczak_install_macos_runtime.sh" ]; then + echo "Missing mac runtime installer: $MAC_RUNTIME_HELPERS_DIR/pjarczak_install_macos_runtime.sh" + exit 1 + fi + + if [ ! -f "$MAC_RUNTIME_HELPERS_DIR/pjarczak_verify_macos_runtime.sh" ]; then + echo "Missing mac runtime verifier: $MAC_RUNTIME_HELPERS_DIR/pjarczak_verify_macos_runtime.sh" + exit 1 + fi + + if [ ! -f "$MAC_RUNTIME_HELPERS_DIR/pjarczak_lima_instance.txt" ]; then + echo "Missing Lima instance file: $MAC_RUNTIME_HELPERS_DIR/pjarczak_lima_instance.txt" + exit 1 + fi + if [ ! -f "$bridge_dylib" ]; then echo "Missing installed bridge dylib: $bridge_dylib" exit 1 @@ -162,9 +178,17 @@ copy_linux_bridge_runtime_to_app() { cp -f "$bridge_dylib" "$macos_dir/" find "$HOST_RUNTIME_DIR" -maxdepth 1 -type f -exec cp -f {} "$macos_dir/" \; cp -f "$HOST_WRAPPER" "$macos_dir/" + cp -f "$MAC_RUNTIME_HELPERS_DIR/pjarczak_install_macos_runtime.sh" "$macos_dir/install_runtime_macos.sh" + cp -f "$MAC_RUNTIME_HELPERS_DIR/pjarczak_verify_macos_runtime.sh" "$macos_dir/verify_runtime_macos.sh" + cp -f "$MAC_RUNTIME_HELPERS_DIR/pjarczak_lima_instance.txt" "$macos_dir/" + if [ -f "$MAC_RUNTIME_HELPERS_DIR/README_runtime_bridge.txt" ]; then + cp -f "$MAC_RUNTIME_HELPERS_DIR/README_runtime_bridge.txt" "$macos_dir/" + fi chmod +x "$macos_dir/pjarczak_bambu_linux_host" chmod +x "$macos_dir/pjarczak-bambu-linux-host-wrapper" + chmod +x "$macos_dir/install_runtime_macos.sh" + chmod +x "$macos_dir/verify_runtime_macos.sh" } build_deps() { diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index a90de994db..c1724191e7 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -382,6 +382,12 @@ if(NOT FREETYPE_FOUND) set(FREETYPE_PKG "dep_FREETYPE") endif() +set(FFMPEG_PKG "") +if (MSVC) + include(FFMPEG/FFMPEG.cmake) + set(FFMPEG_PKG "dep_FFMPEG") +endif() + execute_process( COMMAND ${GIT_EXECUTABLE} rev-parse --is-inside-work-tree RESULT_VARIABLE REV_PARSE_RESULT @@ -415,6 +421,7 @@ set(_dep_list dep_GLFW dep_OCCT ${FREETYPE_PKG} + ${FFMPEG_PKG} ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} diff --git a/deps/FFMPEG/FFMPEG.cmake b/deps/FFMPEG/FFMPEG.cmake new file mode 100644 index 0000000000..9d553050e4 --- /dev/null +++ b/deps/FFMPEG/FFMPEG.cmake @@ -0,0 +1,14 @@ +set(_dstdir ${DESTDIR}/usr/local) +set(_source_dir "${CMAKE_BINARY_DIR}/dep_FFMPEG-prefix/src/dep_FFMPEG") + +ExternalProject_Add(dep_FFMPEG + URL https://github.com/bambulab/ffmpeg_prebuilts/releases/download/7.0.2/7.0.2_msvc.zip + URL_HASH SHA256=DF44AE6B97CE84C720695AE7F151B4A9654915D1841C68F10D62A1189E0E7181 + DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/FFMPEG + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND + COMMAND ${CMAKE_COMMAND} -E copy_directory "${_source_dir}/bin" "${_dstdir}/bin" + COMMAND ${CMAKE_COMMAND} -E copy_directory "${_source_dir}/lib" "${_dstdir}/lib" + COMMAND ${CMAKE_COMMAND} -E copy_directory "${_source_dir}/include" "${_dstdir}/include" +) diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 5dcb7cb3b0..0c2c0951b6 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -680,21 +680,22 @@ if (APPLE) GUI/GUI_UtilsMac.mm GUI/wxMediaCtrl2.mm GUI/wxMediaCtrl2.h + GUI/wxMediaCtrl3.h ) FIND_LIBRARY(DISKARBITRATION_LIBRARY DiskArbitration) else () list(APPEND SLIC3R_GUI_SOURCES + GUI/AVVideoDecoder.cpp + GUI/AVVideoDecoder.hpp GUI/wxMediaCtrl2.cpp GUI/wxMediaCtrl2.h + GUI/wxMediaCtrl3.cpp + GUI/wxMediaCtrl3.h ) endif () if (UNIX AND NOT APPLE) list(APPEND SLIC3R_GUI_SOURCES - GUI/AVVideoDecoder.cpp - GUI/AVVideoDecoder.hpp - GUI/wxMediaCtrl3.cpp - GUI/wxMediaCtrl3.h GUI/Printer/gstbambusrc.c ) endif () @@ -748,7 +749,13 @@ target_link_libraries(libslic3r_gui libslic3r cereal::cereal imgui imguizmo mini if (MSVC) - target_link_libraries(libslic3r_gui Setupapi.lib) + find_library(FFMPEG_AVCODEC_LIB NAMES avcodec avcodec-61 PATHS ${CMAKE_PREFIX_PATH} PATH_SUFFIXES lib NO_DEFAULT_PATH) + find_library(FFMPEG_AVUTIL_LIB NAMES avutil avutil-59 PATHS ${CMAKE_PREFIX_PATH} PATH_SUFFIXES lib NO_DEFAULT_PATH) + find_library(FFMPEG_SWSCALE_LIB NAMES swscale swscale-8 PATHS ${CMAKE_PREFIX_PATH} PATH_SUFFIXES lib NO_DEFAULT_PATH) + if (NOT FFMPEG_AVCODEC_LIB OR NOT FFMPEG_AVUTIL_LIB OR NOT FFMPEG_SWSCALE_LIB) + message(FATAL_ERROR "Missing FFmpeg import libs for Windows build. Expected avcodec/avutil/swscale under ${CMAKE_PREFIX_PATH}/lib") + endif () + target_link_libraries(libslic3r_gui Setupapi.lib ${FFMPEG_AVCODEC_LIB} ${FFMPEG_AVUTIL_LIB} ${FFMPEG_SWSCALE_LIB}) elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux") FIND_LIBRARY(WAYLAND_SERVER_LIBRARIES NAMES wayland-server) FIND_LIBRARY(WAYLAND_EGL_LIBRARIES NAMES wayland-egl) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 65abc09ac9..62434e0502 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -247,6 +247,8 @@ std::string VersionInfo::convert_short_version(std::string full_version) static void pjarczak_verify_or_install_windows_bridge_runtime(const boost::filesystem::path& plugin_folder, const boost::filesystem::path& plugin_cache_dir); +static void pjarczak_verify_or_install_macos_bridge_runtime(const boost::filesystem::path& plugin_folder, + const boost::filesystem::path& plugin_cache_dir); static long pjarczak_run_hidden_windows_command(const wxString& command, wxArrayString* stdout_lines, wxArrayString* stderr_lines); static void pjarczak_log_command_output(const char* tag, long exit_code, const wxArrayString& stdout_lines, const wxArrayString& stderr_lines); @@ -2976,13 +2978,15 @@ bool GUI_App::on_init_inner() } } */ copy_network_if_available(); -#ifdef WIN32 if (Slic3r::PJarczakLinuxBridge::enabled()) { const boost::filesystem::path plugin_folder = boost::filesystem::path(data_dir()) / "plugins"; const boost::filesystem::path plugin_cache_dir = boost::filesystem::path(data_dir()) / "ota" / "plugins"; +#ifdef WIN32 pjarczak_verify_or_install_windows_bridge_runtime(plugin_folder, plugin_cache_dir); - } +#elif defined(__APPLE__) || defined(__WXMAC__) + pjarczak_verify_or_install_macos_bridge_runtime(plugin_folder, plugin_cache_dir); #endif + } on_init_network(); if (m_agent && m_agent->is_user_login()) { @@ -3180,6 +3184,19 @@ static wxString pjarczak_quote_windows_arg(const wxString& value) return wxString::Format("\"%s\"", escaped); } +static wxString pjarczak_quote_posix_arg(const wxString& value) +{ + wxString out("'"); + for (wxUniChar ch : value) { + if (ch == '\'') + out += "'\\''"; + else + out += ch; + } + out += "'"; + return out; +} + static long pjarczak_run_hidden_windows_command(const wxString& command, wxArrayString* stdout_lines, wxArrayString* stderr_lines) { #ifdef WIN32 @@ -3199,6 +3216,25 @@ static long pjarczak_run_hidden_windows_command(const wxString& command, wxArray #endif } +static long pjarczak_run_hidden_posix_command(const wxString& command, wxArrayString* stdout_lines, wxArrayString* stderr_lines) +{ +#if defined(__WXMAC__) || defined(__APPLE__) || defined(__WXGTK__) + wxArrayString local_stdout; + wxArrayString local_stderr; + long exit_code = wxExecute(command, local_stdout, local_stderr, wxEXEC_SYNC); + if (stdout_lines) + *stdout_lines = local_stdout; + if (stderr_lines) + *stderr_lines = local_stderr; + return exit_code; +#else + (void)command; + (void)stdout_lines; + (void)stderr_lines; + return -1; +#endif +} + static void pjarczak_log_command_output(const char* tag, long exit_code, const wxArrayString& stdout_lines, const wxArrayString& stderr_lines) { BOOST_LOG_TRIVIAL(info) << tag << ": exit_code=" << exit_code; @@ -3259,6 +3295,66 @@ static void pjarczak_verify_or_install_windows_bridge_runtime(const boost::files #endif } +static void pjarczak_verify_or_install_macos_bridge_runtime(const boost::filesystem::path& plugin_folder, + const boost::filesystem::path& plugin_cache_dir) +{ +#if !(defined(__WXMAC__) || defined(__APPLE__)) + (void)plugin_folder; + (void)plugin_cache_dir; +#else + if (!Slic3r::PJarczakLinuxBridge::enabled()) + return; + + const auto verify_script = plugin_folder / Slic3r::PJarczakLinuxBridge::mac_runtime_verify_script_file_name(); + const auto install_script = plugin_folder / Slic3r::PJarczakLinuxBridge::mac_runtime_install_script_file_name(); + if (!boost::filesystem::exists(verify_script) || !boost::filesystem::exists(install_script)) { + BOOST_LOG_TRIVIAL(warning) << "[pjarczak_runtime_setup] missing macOS verify/install script in " << plugin_folder.string(); + return; + } + + const wxString plugin_dir_wx = from_u8(plugin_folder.string()); + const wxString cache_dir_wx = from_u8(plugin_cache_dir.string()); + const wxString verify_cmd = wxString::Format( + "/bin/bash %s -PackageDir %s -PluginDir %s -PluginCacheDir %s -AllowMissingLinuxPlugin", + pjarczak_quote_posix_arg(from_u8(verify_script.string())), + pjarczak_quote_posix_arg(plugin_dir_wx), + pjarczak_quote_posix_arg(plugin_dir_wx), + pjarczak_quote_posix_arg(cache_dir_wx)); + + wxArrayString verify_out; + wxArrayString verify_err; + long verify_code = pjarczak_run_hidden_posix_command(verify_cmd, &verify_out, &verify_err); + pjarczak_log_command_output("[pjarczak_runtime_verify_macos]", verify_code, verify_out, verify_err); + if (verify_code == 0) + return; + + wxMessageDialog prompt( + nullptr, + _L("The macOS Linux bridge runtime is not ready. OrcaSlicer can install or repair the bundled Lima runtime now."), + _L("OrcaSlicer Linux Bridge"), + wxYES_NO | wxICON_QUESTION | wxCENTRE); + if (prompt.ShowModal() != wxID_YES) + return; + + const wxString install_cmd = wxString::Format( + "/bin/bash %s -PackageDir %s -PluginDir %s -PluginCacheDir %s -ReplaceExisting", + pjarczak_quote_posix_arg(from_u8(install_script.string())), + pjarczak_quote_posix_arg(plugin_dir_wx), + pjarczak_quote_posix_arg(plugin_dir_wx), + pjarczak_quote_posix_arg(cache_dir_wx)); + + wxArrayString install_out; + wxArrayString install_err; + long install_code = pjarczak_run_hidden_posix_command(install_cmd, &install_out, &install_err); + pjarczak_log_command_output("[pjarczak_runtime_install_macos]", install_code, install_out, install_err); + + verify_out.clear(); + verify_err.clear(); + verify_code = pjarczak_run_hidden_posix_command(verify_cmd, &verify_out, &verify_err); + pjarczak_log_command_output("[pjarczak_runtime_verify_after_install_macos]", verify_code, verify_out, verify_err); +#endif +} + bool pjarczak_bridge_payload_ready(const boost::filesystem::path& plugin_folder, std::string* reason) { if (!Slic3r::PJarczakLinuxBridge::enabled()) { @@ -3271,6 +3367,22 @@ bool pjarczak_bridge_payload_ready(const boost::filesystem::path& plugin_folder, return boost::filesystem::exists(candidate) && !boost::filesystem::is_directory(candidate); }; +#if defined(__WXMAC__) || defined(__APPLE__) + const std::string required_files[] = { + Slic3r::PJarczakLinuxBridge::bridge_network_current_dir_name(), + Slic3r::PJarczakLinuxBridge::host_executable_file_name(), + std::string("pjarczak_bambu_linux_host_abi1"), + std::string("pjarczak_bambu_linux_host_abi0"), + Slic3r::PJarczakLinuxBridge::mac_host_wrapper_file_name(), + Slic3r::PJarczakLinuxBridge::mac_runtime_install_script_file_name(), + Slic3r::PJarczakLinuxBridge::mac_runtime_verify_script_file_name(), + Slic3r::PJarczakLinuxBridge::mac_lima_instance_file_name(), + Slic3r::PJarczakLinuxBridge::linux_network_library_name(), + Slic3r::PJarczakLinuxBridge::linux_source_library_name(), + std::string("ca-certificates.crt"), + std::string("slicer_base64.cer") + }; +#else const std::string required_files[] = { Slic3r::PJarczakLinuxBridge::bridge_network_current_dir_name(), Slic3r::PJarczakLinuxBridge::host_executable_file_name(), @@ -3285,6 +3397,7 @@ bool pjarczak_bridge_payload_ready(const boost::filesystem::path& plugin_folder, std::string("ca-certificates.crt"), std::string("slicer_base64.cer") }; +#endif for (const std::string& file_name : required_files) { if (!has_file(file_name)) { @@ -3292,7 +3405,7 @@ bool pjarczak_bridge_payload_ready(const boost::filesystem::path& plugin_folder, return false; } } - +#if !(defined(__WXMAC__) || defined(__APPLE__)) if (!has_file(Slic3r::PJarczakLinuxBridge::windows_wsl_import_script_file_name()) && !has_file("pjarczak-install-wsl-runtime.ps1")) { pjarczak_set_reason(reason, "missing required runtime file: " + Slic3r::PJarczakLinuxBridge::windows_wsl_import_script_file_name()); @@ -3304,6 +3417,7 @@ bool pjarczak_bridge_payload_ready(const boost::filesystem::path& plugin_folder, pjarczak_set_reason(reason, "missing required runtime file: " + Slic3r::PJarczakLinuxBridge::windows_wsl_bootstrap_script_file_name()); return false; } +#endif for (const std::string& file_name : { Slic3r::PJarczakLinuxBridge::linux_network_library_name(), diff --git a/src/slic3r/GUI/MediaPlayCtrl.h b/src/slic3r/GUI/MediaPlayCtrl.h index cf228cedf2..caa999c28f 100644 --- a/src/slic3r/GUI/MediaPlayCtrl.h +++ b/src/slic3r/GUI/MediaPlayCtrl.h @@ -8,13 +8,8 @@ #ifndef MediaPlayCtrl_h #define MediaPlayCtrl_h -#if defined(__linux__) && !defined(__APPLE__) #include "wxMediaCtrl3.h" using BBLMediaCtrl = wxMediaCtrl3; -#else -#include "wxMediaCtrl2.h" -using BBLMediaCtrl = wxMediaCtrl2; -#endif #include diff --git a/src/slic3r/GUI/WebUserLoginDialog.cpp b/src/slic3r/GUI/WebUserLoginDialog.cpp index 0d19a00796..da633cafa1 100644 --- a/src/slic3r/GUI/WebUserLoginDialog.cpp +++ b/src/slic3r/GUI/WebUserLoginDialog.cpp @@ -121,7 +121,7 @@ ZUserLogin::ZUserLogin() : wxDialog((wxWindow *) (wxGetApp().mainframe), wxID_AN CentreOnParent(); } else { -#ifdef WIN32 +#if defined(WIN32) || defined(__APPLE__) || defined(__WXMAC__) if (Slic3r::PJarczakLinuxBridge::enabled()) { m_external_browser_mode = true; SetTitle(_L("Login")); @@ -187,7 +187,7 @@ ZUserLogin::ZUserLogin() : wxDialog((wxWindow *) (wxGetApp().mainframe), wxID_AN int MaxY = (screenheight - pSize.y) > 0 ? (screenheight - pSize.y) / 2 : 0; wxPoint tmpPT((screenwidth - pSize.x) / 2, MaxY); Move(tmpPT); -#ifdef WIN32 +#if defined(WIN32) || defined(__APPLE__) || defined(__WXMAC__) } #endif } @@ -215,7 +215,7 @@ bool ZUserLogin::run() { m_timer = new wxTimer(this, NETWORK_OFFLINE_TIMER_ID); m_timer->Start(m_external_browser_mode ? 30000 : 8000); -#ifdef WIN32 +#if defined(WIN32) || defined(__APPLE__) || defined(__WXMAC__) if (m_external_browser_mode) { NetworkAgent* agent = wxGetApp().getAgent(); if (agent) { diff --git a/src/slic3r/Utils/PJarczakLinuxBridge/CMakeLists.txt b/src/slic3r/Utils/PJarczakLinuxBridge/CMakeLists.txt index cb4e3f37d5..b1b6cf7e7e 100644 --- a/src/slic3r/Utils/PJarczakLinuxBridge/CMakeLists.txt +++ b/src/slic3r/Utils/PJarczakLinuxBridge/CMakeLists.txt @@ -95,15 +95,47 @@ if (UNIX AND NOT APPLE AND EXISTS "${PJARCZAK_LINUX_HOST_DIR}/main.cpp" AND EXIS endif() if (APPLE) - set(PJARCZAK_MAC_WRAPPER_SCRIPT_SOURCE ${CMAKE_SOURCE_DIR}/tools/pjarczak_bambu_macos_linux_wrapper/pjarczak_bambu_macos_linux_wrapper.sh) + set(PJARCZAK_MAC_WRAPPER_SCRIPT_SOURCE ${CMAKE_SOURCE_DIR}/tools/pjarczak_bambu_linux_host/pjarczak-bambu-linux-host-wrapper) + set(PJARCZAK_MAC_INSTALLER_SOURCE ${CMAKE_SOURCE_DIR}/tools/pjarczak_bambu_runtime/macos/pjarczak_install_macos_runtime.sh) + set(PJARCZAK_MAC_VERIFY_SOURCE ${CMAKE_SOURCE_DIR}/tools/pjarczak_bambu_runtime/macos/pjarczak_verify_macos_runtime.sh) + set(PJARCZAK_MAC_INSTANCE_SOURCE ${CMAKE_SOURCE_DIR}/tools/pjarczak_bambu_runtime/macos/pjarczak_lima_instance.txt) + set(PJARCZAK_MAC_README_SOURCE ${CMAKE_SOURCE_DIR}/tools/pjarczak_bambu_runtime/macos/README_runtime_bridge.txt) add_custom_command(TARGET pjarczak_bambu_networking_bridge POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PJARCZAK_MAC_WRAPPER_SCRIPT_SOURCE} - $/pjarczak_bambu_macos_linux_wrapper.sh + $/pjarczak-bambu-linux-host-wrapper + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${PJARCZAK_MAC_INSTALLER_SOURCE} + $/install_runtime_macos.sh + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${PJARCZAK_MAC_VERIFY_SOURCE} + $/verify_runtime_macos.sh + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${PJARCZAK_MAC_INSTANCE_SOURCE} + $/pjarczak_lima_instance.txt + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${PJARCZAK_MAC_README_SOURCE} + $/README_runtime_bridge.txt ) install(FILES ${PJARCZAK_MAC_WRAPPER_SCRIPT_SOURCE} DESTINATION "." - RENAME pjarczak_bambu_macos_linux_wrapper.sh + RENAME pjarczak-bambu-linux-host-wrapper + ) + install(FILES ${PJARCZAK_MAC_INSTALLER_SOURCE} + DESTINATION "." + RENAME install_runtime_macos.sh + ) + install(FILES ${PJARCZAK_MAC_VERIFY_SOURCE} + DESTINATION "." + RENAME verify_runtime_macos.sh + ) + install(FILES ${PJARCZAK_MAC_INSTANCE_SOURCE} + DESTINATION "." + RENAME pjarczak_lima_instance.txt + ) + install(FILES ${PJARCZAK_MAC_README_SOURCE} + DESTINATION "." + RENAME README_runtime_bridge.txt ) endif() diff --git a/src/slic3r/Utils/PJarczakLinuxBridge/PJarczakLinuxBridgeConfig.cpp b/src/slic3r/Utils/PJarczakLinuxBridge/PJarczakLinuxBridgeConfig.cpp index 91d6a26850..df3194d5b5 100644 --- a/src/slic3r/Utils/PJarczakLinuxBridge/PJarczakLinuxBridgeConfig.cpp +++ b/src/slic3r/Utils/PJarczakLinuxBridge/PJarczakLinuxBridgeConfig.cpp @@ -39,7 +39,9 @@ std::uint16_t read_u16_le(const unsigned char* p) bool expected_machine_matches(std::uint16_t machine) { -#if defined(__x86_64__) || defined(_M_X64) +#if defined(__APPLE__) + return machine == EM_X86_64 || machine == EM_AARCH64; +#elif defined(__x86_64__) || defined(_M_X64) return machine == EM_X86_64; #elif defined(__aarch64__) return machine == EM_AARCH64; @@ -204,6 +206,21 @@ std::string mac_host_wrapper_file_name() return "pjarczak-bambu-linux-host-wrapper"; } +std::string mac_lima_instance_file_name() +{ + return "pjarczak_lima_instance.txt"; +} + +std::string mac_runtime_install_script_file_name() +{ + return "install_runtime_macos.sh"; +} + +std::string mac_runtime_verify_script_file_name() +{ + return "verify_runtime_macos.sh"; +} + std::string windows_wsl_distro_file_name() { return "pjarczak_wsl_distro.txt"; @@ -256,6 +273,9 @@ bool is_overlay_runtime_filename(const std::string& file_name) file_name == "pjarczak_bambu_linux_host_abi1" || file_name == "pjarczak_bambu_linux_host_abi0" || file_name == mac_host_wrapper_file_name() || + file_name == mac_lima_instance_file_name() || + file_name == mac_runtime_install_script_file_name() || + file_name == mac_runtime_verify_script_file_name() || file_name == windows_wsl_distro_file_name() || file_name == windows_wsl_import_script_file_name() || file_name == windows_wsl_validate_script_file_name() || diff --git a/src/slic3r/Utils/PJarczakLinuxBridge/PJarczakLinuxBridgeConfig.hpp b/src/slic3r/Utils/PJarczakLinuxBridge/PJarczakLinuxBridgeConfig.hpp index ebb167f0e5..647d69532f 100644 --- a/src/slic3r/Utils/PJarczakLinuxBridge/PJarczakLinuxBridgeConfig.hpp +++ b/src/slic3r/Utils/PJarczakLinuxBridge/PJarczakLinuxBridgeConfig.hpp @@ -23,6 +23,9 @@ std::string linux_network_library_name(); std::string linux_source_library_name(); std::string host_executable_file_name(); std::string mac_host_wrapper_file_name(); +std::string mac_lima_instance_file_name(); +std::string mac_runtime_install_script_file_name(); +std::string mac_runtime_verify_script_file_name(); std::string windows_wsl_distro_file_name(); std::string windows_wsl_import_script_file_name(); std::string windows_wsl_validate_script_file_name(); diff --git a/src/slic3r/Utils/PJarczakLinuxBridge/PJarczakLinuxSoBridgeLauncher_mac.mm b/src/slic3r/Utils/PJarczakLinuxBridge/PJarczakLinuxSoBridgeLauncher_mac.mm index e4b31eaf7e..844dcc7481 100644 --- a/src/slic3r/Utils/PJarczakLinuxBridge/PJarczakLinuxSoBridgeLauncher_mac.mm +++ b/src/slic3r/Utils/PJarczakLinuxBridge/PJarczakLinuxSoBridgeLauncher_mac.mm @@ -1,9 +1,12 @@ #include "PJarczakLinuxSoBridgeLauncher.hpp" #include "PJarczakLinuxBridgeConfig.hpp" +#include +#include #include #include #include +#include #include namespace Slic3r::PJarczakLinuxBridge { @@ -18,6 +21,144 @@ std::filesystem::path module_dir() return std::filesystem::path(info.dli_fname).parent_path(); } +std::string trim_ascii(std::string value) +{ + auto is_space = [](unsigned char ch) { return std::isspace(ch) != 0; }; + while (!value.empty() && is_space(static_cast(value.front()))) + value.erase(value.begin()); + while (!value.empty() && is_space(static_cast(value.back()))) + value.pop_back(); + return value; +} + +std::string read_text_file_trimmed(const std::filesystem::path& path) +{ + std::ifstream in(path, std::ios::binary); + if (!in) + return {}; + + std::string value((std::istreambuf_iterator(in)), std::istreambuf_iterator()); + if (value.size() >= 3 && + static_cast(value[0]) == 0xEFu && + static_cast(value[1]) == 0xBBu && + static_cast(value[2]) == 0xBFu) + value.erase(0, 3); + + return trim_ascii(value); +} + +std::string required_env(const char* name) +{ + const char* value = std::getenv(name); + return (value && *value) ? trim_ascii(std::string(value)) : std::string(); +} + +std::string shell_quote(const std::string& value) +{ + std::string out; + out.reserve(value.size() + 2); + out.push_back('\''); + for (char ch : value) { + if (ch == '\'') + out += "'\\''"; + else + out.push_back(ch); + } + out.push_back('\''); + return out; +} + +std::string run_and_capture(const std::string& command, int* exit_code = nullptr) +{ + std::string output; + FILE* pipe = popen(command.c_str(), "r"); + if (!pipe) { + if (exit_code) + *exit_code = -1; + return {}; + } + + char buffer[4096]; + while (fgets(buffer, sizeof(buffer), pipe) != nullptr) + output += buffer; + + const int rc = pclose(pipe); + if (exit_code) + *exit_code = rc; + return trim_ascii(output); +} + +std::string configured_instance_name(const std::filesystem::path& plugin_dir) +{ + const auto env_value = required_env("PJARCZAK_MAC_LIMA_INSTANCE"); + if (!env_value.empty()) + return env_value; + return read_text_file_trimmed(plugin_dir / mac_lima_instance_file_name()); +} + +std::string first_missing_runtime_file(const std::filesystem::path& plugin_dir) +{ + const std::string required_files[] = { + host_executable_file_name(), + std::string("pjarczak_bambu_linux_host_abi1"), + std::string("pjarczak_bambu_linux_host_abi0"), + mac_host_wrapper_file_name(), + mac_runtime_install_script_file_name(), + mac_runtime_verify_script_file_name(), + mac_lima_instance_file_name(), + linux_network_library_name(), + linux_source_library_name(), + std::string("ca-certificates.crt"), + std::string("slicer_base64.cer") + }; + + for (const std::string& name : required_files) { + if (!std::filesystem::exists(plugin_dir / std::filesystem::path(name))) + return name; + } + + return {}; +} + +bool probe_runtime_ready(const std::filesystem::path& plugin_dir, std::string* reason) +{ + const auto verify_script = plugin_dir / mac_runtime_verify_script_file_name(); + if (!std::filesystem::exists(verify_script)) { + if (reason) + *reason = "mac runtime verify script missing"; + return false; + } + + const std::string command = + std::string("/bin/bash ") + shell_quote(verify_script.string()) + + " -PackageDir " + shell_quote(plugin_dir.string()) + + " -PluginDir " + shell_quote(plugin_dir.string()); + + int rc = -1; + const std::string output = run_and_capture(command + " 2>&1", &rc); + if (rc == 0) { + if (reason) + reason->clear(); + return true; + } + + if (reason) + *reason = output.empty() ? std::string("macOS Lima runtime is not ready") : output; + return false; +} + +LaunchSpec error_launch_spec(const std::string& message) +{ + LaunchSpec spec; + spec.description = "mac bridge preflight error"; + spec.argv = { + "/bin/sh", + "-lc", + "printf '%s\\n' " + shell_quote(message) + " 1>&2; exit 127" + }; + return spec; +} + } std::string host_executable_name() @@ -35,33 +176,52 @@ std::string launch_preflight_error() const std::filesystem::path plugin_dir = module_dir(); if (plugin_dir.empty()) return "bridge launcher could not resolve plugin directory"; - if (!std::filesystem::exists(plugin_dir / host_executable_file_name())) - return "linux host executable missing in plugin directory"; - const char* runner = std::getenv("PJARCZAK_MAC_LINUX_GUEST_RUNNER"); - if (!runner || !*runner) - return "PJARCZAK_MAC_LINUX_GUEST_RUNNER is not set"; + + const auto missing_file = first_missing_runtime_file(plugin_dir); + if (!missing_file.empty()) + return "required macOS runtime file missing: " + missing_file; + + const auto instance = configured_instance_name(plugin_dir); + if (instance.empty()) + return "PJARCZAK_MAC_LIMA_INSTANCE is not set and pjarczak_lima_instance.txt is missing or empty"; + + std::string reason; + if (!probe_runtime_ready(plugin_dir, &reason)) + return reason.empty() ? std::string("macOS Lima runtime is not ready") : reason; + return {}; } LaunchSpec build_default_launch_spec() { const std::filesystem::path plugin_dir = module_dir(); + if (plugin_dir.empty()) + return error_launch_spec("bridge launcher could not resolve plugin directory"); - const char* wrapper_env = std::getenv("PJARCZAK_BAMBU_HOST_WRAPPER"); - const std::filesystem::path wrapper_path = (wrapper_env && *wrapper_env) - ? std::filesystem::path(wrapper_env) - : (plugin_dir / mac_host_wrapper_file_name()); + const auto missing_file = first_missing_runtime_file(plugin_dir); + if (!missing_file.empty()) + return error_launch_spec("required macOS runtime file missing: " + missing_file); + const auto instance = configured_instance_name(plugin_dir); + if (instance.empty()) + return error_launch_spec("PJARCZAK_MAC_LIMA_INSTANCE is not set and pjarczak_lima_instance.txt is missing or empty"); + + std::string reason; + if (!probe_runtime_ready(plugin_dir, &reason)) + return error_launch_spec(reason.empty() ? std::string("macOS Lima runtime is not ready") : reason); + + const std::filesystem::path wrapper_path = plugin_dir / mac_host_wrapper_file_name(); const std::filesystem::path host_path = plugin_dir / host_executable_file_name(); LaunchSpec spec; - spec.description = "mac via external linux guest wrapper"; + spec.description = "macOS via Lima linux guest"; spec.argv = {wrapper_path.string(), host_path.string()}; spec.env = { {"PJARCZAK_BAMBU_PLUGIN_DIR", plugin_dir.string()}, {"PJARCZAK_BAMBU_NETWORK_SO", (plugin_dir / linux_network_library_name()).string()}, {"PJARCZAK_BAMBU_SOURCE_SO", (plugin_dir / linux_source_library_name()).string()}, - {"PJARCZAK_BAMBU_REQUIRE_LINUX_GUEST", "1"} + {"PJARCZAK_BAMBU_REQUIRE_LINUX_GUEST", "1"}, + {"PJARCZAK_MAC_LIMA_INSTANCE", instance} }; return spec; } diff --git a/tools/pjarczak_bambu_linux_host/pjarczak-bambu-linux-host-wrapper b/tools/pjarczak_bambu_linux_host/pjarczak-bambu-linux-host-wrapper index 4606781736..29981a4762 100755 --- a/tools/pjarczak_bambu_linux_host/pjarczak-bambu-linux-host-wrapper +++ b/tools/pjarczak_bambu_linux_host/pjarczak-bambu-linux-host-wrapper @@ -7,10 +7,62 @@ if [ -z "$HOST_PATH" ]; then exit 127 fi -RUNNER="${PJARCZAK_MAC_LINUX_GUEST_RUNNER:-}" -if [ -z "$RUNNER" ]; then - echo "PJARCZAK_MAC_LINUX_GUEST_RUNNER not set" >&2 +PLUGIN_DIR="${PJARCZAK_BAMBU_PLUGIN_DIR:-}" +if [ -z "$PLUGIN_DIR" ]; then + PLUGIN_DIR=$(cd "$(dirname "$HOST_PATH")" && pwd) +fi + +trim_file() { + path="$1" + if [ ! -f "$path" ]; then + return 1 + fi + LC_ALL=C tr -d '\r' < "$path" | head -n 1 | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' +} + +INSTANCE="${PJARCZAK_MAC_LIMA_INSTANCE:-}" +if [ -z "$INSTANCE" ]; then + INSTANCE=$(trim_file "$PLUGIN_DIR/pjarczak_lima_instance.txt" || true) +fi +if [ -z "$INSTANCE" ]; then + INSTANCE="orcaslicer-bambu-network" +fi + +find_limactl() { + if [ -n "${PJARCZAK_LIMACTL:-}" ] && [ -x "${PJARCZAK_LIMACTL}" ]; then + printf '%s\n' "$PJARCZAK_LIMACTL" + return 0 + fi + + if command -v limactl >/dev/null 2>&1; then + command -v limactl + return 0 + fi + + for candidate in \ + "$HOME/Library/Application Support/OrcaSlicer/macos-bridge/lima/bin/limactl" \ + "/opt/homebrew/bin/limactl" \ + "/usr/local/bin/limactl" + do + if [ -x "$candidate" ]; then + printf '%s\n' "$candidate" + return 0 + fi + done + + return 1 +} + +LIMACTL=$(find_limactl || true) +if [ -z "$LIMACTL" ]; then + echo "limactl not found" >&2 exit 127 fi -exec "$RUNNER" "$HOST_PATH" +exec "$LIMACTL" shell "$INSTANCE" -- /usr/bin/env \ + PJARCZAK_BAMBU_PLUGIN_DIR="$PLUGIN_DIR" \ + PJARCZAK_BAMBU_NETWORK_SO="$PLUGIN_DIR/libbambu_networking.so" \ + PJARCZAK_BAMBU_SOURCE_SO="$PLUGIN_DIR/libBambuSource.so" \ + PJARCZAK_BAMBU_LIVE555_SO="$PLUGIN_DIR/liblive555.so" \ + PJARCZAK_EXPECTED_BAMBU_NETWORK_VERSION="${PJARCZAK_EXPECTED_BAMBU_NETWORK_VERSION:-}" \ + "$HOST_PATH" diff --git a/tools/pjarczak_bambu_runtime/macos/pjarczak_install_macos_runtime.sh b/tools/pjarczak_bambu_runtime/macos/pjarczak_install_macos_runtime.sh new file mode 100755 index 0000000000..90fdf85929 --- /dev/null +++ b/tools/pjarczak_bambu_runtime/macos/pjarczak_install_macos_runtime.sh @@ -0,0 +1,177 @@ +#!/bin/bash +set -euo pipefail + +PACKAGE_DIR="" +PLUGIN_DIR="" +PLUGIN_CACHE_DIR="" +REPLACE_EXISTING=0 + +while [[ $# -gt 0 ]]; do + case "$1" in + -PackageDir) + PACKAGE_DIR="${2:-}" + shift 2 + ;; + -PluginDir) + PLUGIN_DIR="${2:-}" + shift 2 + ;; + -PluginCacheDir) + PLUGIN_CACHE_DIR="${2:-}" + shift 2 + ;; + -ReplaceExisting) + REPLACE_EXISTING=1 + shift + ;; + *) + echo "unknown argument: $1" >&2 + exit 2 + ;; + esac +done + +if [[ -z "$PLUGIN_DIR" ]]; then + PLUGIN_DIR="$PACKAGE_DIR" +fi +if [[ -z "$PLUGIN_DIR" ]]; then + echo "PluginDir is required" >&2 + exit 2 +fi + +APP_SUPPORT_DIR="$HOME/Library/Application Support/OrcaSlicer/macos-bridge" +LOCAL_LIMA_ROOT="$APP_SUPPORT_DIR/lima" +LOCAL_LIMA_BIN="$LOCAL_LIMA_ROOT/bin" +mkdir -p "$APP_SUPPORT_DIR" "$LOCAL_LIMA_ROOT" + +trim_file() { + local path="$1" + if [[ ! -f "$path" ]]; then + return 1 + fi + LC_ALL=C tr -d '\r' < "$path" | head -n 1 | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' +} + +find_limactl() { + if [[ -n "${PJARCZAK_LIMACTL:-}" && -x "${PJARCZAK_LIMACTL}" ]]; then + printf '%s\n' "$PJARCZAK_LIMACTL" + return 0 + fi + if command -v limactl >/dev/null 2>&1; then + command -v limactl + return 0 + fi + if [[ -x "$LOCAL_LIMA_BIN/limactl" ]]; then + printf '%s\n' "$LOCAL_LIMA_BIN/limactl" + return 0 + fi + for candidate in /opt/homebrew/bin/limactl /usr/local/bin/limactl; do + if [[ -x "$candidate" ]]; then + printf '%s\n' "$candidate" + return 0 + fi + done + return 1 +} + +install_lima_binary_locally() { + local version + version=$(curl -fsSL https://api.github.com/repos/lima-vm/lima/releases/latest | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n 1) + if [[ -z "$version" ]]; then + echo "failed to resolve latest Lima version from GitHub API" >&2 + return 1 + fi + + local host_arch + host_arch=$(uname -m) + case "$host_arch" in + arm64|aarch64) + host_arch=arm64 + ;; + x86_64|amd64) + host_arch=x86_64 + ;; + *) + echo "unsupported macOS architecture for Lima: $host_arch" >&2 + return 1 + ;; + esac + + local version_no_v="${version#v}" + local base_url="https://github.com/lima-vm/lima/releases/download/${version}" + local main_archive="lima-${version_no_v}-Darwin-${host_arch}.tar.gz" + local guest_archive="lima-additional-guestagents-${version_no_v}-Darwin-${host_arch}.tar.gz" + local tmpdir + tmpdir=$(mktemp -d) + trap 'rm -rf "$tmpdir"' RETURN + + curl -fL "$base_url/$main_archive" -o "$tmpdir/$main_archive" + tar -xzf "$tmpdir/$main_archive" -C "$LOCAL_LIMA_ROOT" + + if curl -fL "$base_url/$guest_archive" -o "$tmpdir/$guest_archive"; then + tar -xzf "$tmpdir/$guest_archive" -C "$LOCAL_LIMA_ROOT" + fi + + [[ -x "$LOCAL_LIMA_BIN/limactl" ]] +} + +ensure_lima_installed() { + LIMACTL=$(find_limactl || true) + if [[ -n "$LIMACTL" ]]; then + return 0 + fi + + if command -v brew >/dev/null 2>&1; then + brew install lima + LIMACTL=$(find_limactl || true) + if [[ -n "$LIMACTL" ]]; then + return 0 + fi + fi + + install_lima_binary_locally + LIMACTL=$(find_limactl || true) + [[ -n "$LIMACTL" ]] +} + +maybe_install_rosetta() { + if [[ "$(uname -m)" != "arm64" ]]; then + return 0 + fi + if pgrep -q oahd >/dev/null 2>&1; then + return 0 + fi + /usr/sbin/softwareupdate --install-rosetta --agree-to-license >/dev/null 2>&1 || true +} + +INSTANCE="${PJARCZAK_MAC_LIMA_INSTANCE:-}" +if [[ -z "$INSTANCE" ]]; then + INSTANCE=$(trim_file "$PLUGIN_DIR/pjarczak_lima_instance.txt" || true) +fi +if [[ -z "$INSTANCE" ]]; then + INSTANCE="orcaslicer-bambu-network" +fi + +ensure_lima_installed +maybe_install_rosetta + +START_ARGS=(start "--name=${INSTANCE}" --mount-writable) +MACOS_MAJOR=$(sw_vers -productVersion | awk -F. '{print $1}') +if [[ "$MACOS_MAJOR" -ge 13 ]]; then + START_ARGS+=(--vm-type=vz --network=vzNAT) + if [[ "$(uname -m)" == "arm64" ]]; then + START_ARGS+=(--rosetta) + fi +fi + +if [[ "$REPLACE_EXISTING" -eq 1 ]]; then + "$LIMACTL" stop "$INSTANCE" >/dev/null 2>&1 || true +fi + +if ! "$LIMACTL" shell "$INSTANCE" -- /usr/bin/env true >/dev/null 2>&1; then + "$LIMACTL" "${START_ARGS[@]}" template:default +fi + +"$LIMACTL" start-at-login "$INSTANCE" --enabled >/dev/null 2>&1 || true +"$LIMACTL" shell "$INSTANCE" -- /usr/bin/env true >/dev/null +printf 'runtime installed\n' diff --git a/tools/pjarczak_bambu_runtime/macos/pjarczak_lima_instance.txt b/tools/pjarczak_bambu_runtime/macos/pjarczak_lima_instance.txt new file mode 100644 index 0000000000..bd203fd9c3 --- /dev/null +++ b/tools/pjarczak_bambu_runtime/macos/pjarczak_lima_instance.txt @@ -0,0 +1 @@ +orcaslicer-bambu-network diff --git a/tools/pjarczak_bambu_runtime/macos/pjarczak_verify_macos_runtime.sh b/tools/pjarczak_bambu_runtime/macos/pjarczak_verify_macos_runtime.sh new file mode 100755 index 0000000000..f6a5f88c8e --- /dev/null +++ b/tools/pjarczak_bambu_runtime/macos/pjarczak_verify_macos_runtime.sh @@ -0,0 +1,116 @@ +#!/bin/bash +set -euo pipefail + +PACKAGE_DIR="" +PLUGIN_DIR="" +PLUGIN_CACHE_DIR="" +ALLOW_MISSING_LINUX_PLUGIN=0 + +while [[ $# -gt 0 ]]; do + case "$1" in + -PackageDir) + PACKAGE_DIR="${2:-}" + shift 2 + ;; + -PluginDir) + PLUGIN_DIR="${2:-}" + shift 2 + ;; + -PluginCacheDir) + PLUGIN_CACHE_DIR="${2:-}" + shift 2 + ;; + -AllowMissingLinuxPlugin) + ALLOW_MISSING_LINUX_PLUGIN=1 + shift + ;; + *) + echo "unknown argument: $1" >&2 + exit 2 + ;; + esac +done + +if [[ -z "$PLUGIN_DIR" ]]; then + PLUGIN_DIR="$PACKAGE_DIR" +fi +if [[ -z "$PLUGIN_DIR" ]]; then + echo "PluginDir is required" >&2 + exit 2 +fi + +trim_file() { + local path="$1" + if [[ ! -f "$path" ]]; then + return 1 + fi + LC_ALL=C tr -d '\r' < "$path" | head -n 1 | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' +} + +find_limactl() { + if [[ -n "${PJARCZAK_LIMACTL:-}" && -x "${PJARCZAK_LIMACTL}" ]]; then + printf '%s\n' "$PJARCZAK_LIMACTL" + return 0 + fi + if command -v limactl >/dev/null 2>&1; then + command -v limactl + return 0 + fi + local local_bin="$HOME/Library/Application Support/OrcaSlicer/macos-bridge/lima/bin/limactl" + if [[ -x "$local_bin" ]]; then + printf '%s\n' "$local_bin" + return 0 + fi + for candidate in /opt/homebrew/bin/limactl /usr/local/bin/limactl; do + if [[ -x "$candidate" ]]; then + printf '%s\n' "$candidate" + return 0 + fi + done + return 1 +} + +require_file() { + local path="$1" + local label="$2" + if [[ ! -f "$path" ]]; then + echo "missing required file: $label" >&2 + exit 1 + fi +} + +if [[ "$ALLOW_MISSING_LINUX_PLUGIN" -eq 0 ]]; then + require_file "$PLUGIN_DIR/libbambu_networking.so" "libbambu_networking.so" + require_file "$PLUGIN_DIR/libBambuSource.so" "libBambuSource.so" +fi +require_file "$PLUGIN_DIR/pjarczak_bambu_linux_host" "pjarczak_bambu_linux_host" +require_file "$PLUGIN_DIR/pjarczak_bambu_linux_host_abi1" "pjarczak_bambu_linux_host_abi1" +require_file "$PLUGIN_DIR/pjarczak_bambu_linux_host_abi0" "pjarczak_bambu_linux_host_abi0" +require_file "$PLUGIN_DIR/pjarczak-bambu-linux-host-wrapper" "pjarczak-bambu-linux-host-wrapper" +require_file "$PLUGIN_DIR/install_runtime_macos.sh" "install_runtime_macos.sh" +require_file "$PLUGIN_DIR/verify_runtime_macos.sh" "verify_runtime_macos.sh" +require_file "$PLUGIN_DIR/pjarczak_lima_instance.txt" "pjarczak_lima_instance.txt" +require_file "$PLUGIN_DIR/ca-certificates.crt" "ca-certificates.crt" +require_file "$PLUGIN_DIR/slicer_base64.cer" "slicer_base64.cer" + +LIMACTL=$(find_limactl || true) +if [[ -z "$LIMACTL" ]]; then + echo "limactl not found" >&2 + exit 1 +fi + +INSTANCE="${PJARCZAK_MAC_LIMA_INSTANCE:-}" +if [[ -z "$INSTANCE" ]]; then + INSTANCE=$(trim_file "$PLUGIN_DIR/pjarczak_lima_instance.txt" || true) +fi +if [[ -z "$INSTANCE" ]]; then + echo "Lima instance name is not configured" >&2 + exit 1 +fi + +if ! "$LIMACTL" shell "$INSTANCE" -- /usr/bin/env true >/dev/null 2>&1; then + echo "Lima instance '$INSTANCE' is not ready" >&2 + exit 1 +fi + +printf 'runtime ok\n'