diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 10d6bc2876..5504a10d38 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -6,6 +6,9 @@ #include "GUI_ObjectList.hpp" #include "slic3r/GUI/UserManager.hpp" #include "slic3r/GUI/TaskManager.hpp" +#include "slic3r/GUI/Automation/AutomationServer.hpp" +#include "slic3r/GUI/Automation/WxUiBackend.hpp" +#include "slic3r/GUI/Automation/JsonRpcDispatcher.hpp" #include "format.hpp" #include "libslic3r_version.h" #include "Downloader.hpp" @@ -730,6 +733,12 @@ void GUI_App::post_init() if (! this->initialized()) throw Slic3r::RuntimeError("Calling post_init() while not yet initialized"); + // UI automation: start the localhost server only when --automation-server set a port. + if (this->init_params != nullptr && this->init_params->automation_port > 0) { + m_automation_port = this->init_params->automation_port; + start_automation_server(); + } + m_open_method = "double_click"; bool switch_to_3d = false; @@ -2464,6 +2473,7 @@ bool GUI_App::OnInit() int GUI_App::OnExit() { stop_http_server(); + stop_automation_server(); stop_sync_user_preset(); if (m_device_manager) { @@ -7082,6 +7092,33 @@ void GUI_App::stop_http_server() m_http_server.stop(); } +void GUI_App::start_automation_server() +{ + if (m_automation_port <= 0) return; // disabled + if (m_automation_server) return; // already running + using namespace Slic3r::GUI::Automation; + m_automation_backend.reset(new WxUiBackend()); + m_automation_dispatcher.reset(new JsonRpcDispatcher(*m_automation_backend)); + m_automation_server.reset(new AutomationServer((unsigned short)m_automation_port)); + JsonRpcDispatcher* disp = m_automation_dispatcher.get(); + m_automation_server->set_handler( + [disp](const std::string& body) { return disp->handle_request(body); }); + m_automation_server->set_health_text( + std::string("OrcaSlicer automation server v") + kAutomationVersion); + m_automation_server->start(); + BOOST_LOG_TRIVIAL(warning) + << "UI automation server ENABLED on 127.0.0.1:" << m_automation_port + << " (input injection is active)"; +} + +void GUI_App::stop_automation_server() +{ + if (m_automation_server) m_automation_server->stop(); + m_automation_server.reset(); + m_automation_dispatcher.reset(); + m_automation_backend.reset(); +} + void GUI_App::switch_staff_pick(bool on) { mainframe->m_webview->SendDesignStaffpick(on); diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index ca2e93a8d7..8fc4d49249 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -86,6 +86,14 @@ class ModelMallDialog; class PingCodeBindDialog; class NetworkErrorDialog; +// UI automation (opt-in). Forward declarations so GUI_App can own the stack +// by unique_ptr without pulling the Automation headers into this header. +namespace Automation { + class AutomationServer; + class WxUiBackend; + class JsonRpcDispatcher; +} + enum FileType { @@ -335,6 +343,11 @@ private: HttpServer m_http_server; bool m_show_gcode_window{true}; boost::thread m_check_network_thread; + + // --- UI automation (opt-in; off unless --automation-server) --- + std::unique_ptr m_automation_server; + std::unique_ptr m_automation_backend; + std::unique_ptr m_automation_dispatcher; public: //try again when subscription fails void on_start_subscribe_again(std::string dev_id); @@ -371,6 +384,10 @@ public: // NOTE: Task 17 extends the automation lifecycle (server/backend) around this // accessor; here we only add the minimal flag/accessor the hooks depend on. bool is_automation_enabled() const { return m_automation_port > 0; } + // UI automation lifecycle: wires WxUiBackend -> JsonRpcDispatcher -> AutomationServer + // and starts/stops the localhost server. Both no-op when automation is off. + void start_automation_server(); + void stop_automation_server(); bool is_recreating_gui() const { return m_is_recreating_gui; } std::string logo_name() const { return is_editor() ? "OrcaSlicer" : "OrcaSlicer-gcodeviewer"; }