Reset device selection on agent swap or unload (#124)

set_live_printer_agent centralizes
the swap: deselect the machine,
clear stale sidebar state and the
previous agent's Other Devices, then
install the new agent (or null when
its provider vanished). Plugin
load/unload callbacks refresh the
dropdown and re-run agent selection.
load_last_machine no longer falls
back to the first available machine.
This commit is contained in:
Andrew
2026-07-16 16:27:53 +08:00
committed by Ian Chua
parent 501af81ba9
commit 1534268183
6 changed files with 150 additions and 47 deletions

View File

@@ -496,6 +496,26 @@ namespace Slic3r
OnSelectedMachineChanged(previous_selected_machine, selected_machine);
}
void DeviceManager::clear_other_devices()
{
// why: on agent swap, keep "My Devices" but drop the transient "Other Devices"
// Those belong to the previous agent's network scan; the new agent's start_discovery re-populates its own.
const auto my = get_my_machine_list();
for (auto it = localMachineList.begin(); it != localMachineList.end();)
{
if (my.find(it->first) == my.end())
{
// not a "My Device" -> an "Other Device"
delete it->second;
it = localMachineList.erase(it);
}
else
{
++it;
}
}
}
bool DeviceManager::set_selected_machine(std::string dev_id)
{
BOOST_LOG_TRIVIAL(info) << "set_selected_machine=" << dev_id
@@ -558,7 +578,6 @@ namespace Slic3r
}
else
{
Slic3r::GUI::wxGetApp().reset_unsigned_plugin_warning();
if (m_agent)
{
if (it->second->connection_type() != "lan" || it->second->connection_type().empty())
@@ -592,7 +611,6 @@ namespace Slic3r
}
selected_machine = dev_id;
record_user_last_machine(selected_machine);
return true;
}
@@ -851,44 +869,26 @@ namespace Slic3r
int result = m_agent->get_user_print_info(&http_code, &body, provider);
if (result == 0)
{
parse_user_print_info(body);
// parse_user_print_info and on_machine_alive (SSDP for discovery) both mutate the same userMachineList map.
// on_machine_alive mutates the map on the UI thread, do the same for parse_user_print_info.
Slic3r::GUI::wxGetApp().CallAfter([this, body]() { parse_user_print_info(body); });
}
}
void DeviceManager::record_user_last_machine(const std::string& dev_id)
{
if (Slic3r::GUI::wxGetApp().app_config) {
Slic3r::GUI::wxGetApp().app_config->set("user_last_selected_machine", dev_id);
}
}
std::string DeviceManager::get_user_last_machine() const
{
if (Slic3r::GUI::wxGetApp().app_config) {
const auto& user_last_machine = Slic3r::GUI::wxGetApp().app_config->get("user_last_selected_machine");
if (!user_last_machine.empty()) {
return user_last_machine;
} else if (m_agent) {
return m_agent->get_user_selected_machine();
}
}
return "";
}
void DeviceManager::load_last_machine()
{
if (userMachineList.empty()) return;
else if (userMachineList.size() == 1) {
this->set_selected_machine(userMachineList.begin()->second->get_dev_id());
} else {
const auto& last_monitor_machine = get_user_last_machine();
if (userMachineList.find(last_monitor_machine) != userMachineList.end()) {
set_selected_machine(last_monitor_machine);
} else {
this->set_selected_machine(userMachineList.begin()->second->get_dev_id());
}
}
// Get all available machines, include cloud machines and lan machines that have access right
auto all_machines = get_my_machine_list();
if (all_machines.empty())
return;
// Reconnect the machine the user last selected, if it's still available.
// why: no first-available fallback - auto-connecting an arbitrary machine
// fights the agent-swap reset, which intentionally leaves nothing selected.
const std::string last_monitor_machine = m_agent ? m_agent->get_user_selected_machine() : "";
const auto last_machine = all_machines.find(last_monitor_machine);
if (last_machine != all_machines.end())
this->set_selected_machine(last_machine->second->get_dev_id());
}
void DeviceManager::OnMachineBindStateChanged(MachineObject* obj, const std::string& new_state)

View File

@@ -48,8 +48,9 @@ public:
MachineObject* get_selected_machine();
bool set_selected_machine(std::string dev_id);
void record_user_last_machine(const std::string& dev_id);
std::string get_user_last_machine() const;
// why: clears stale sidebar sync-status / AMS visuals. Public so the printer-agent
// swap path can reuse it instead of duplicating the two sidebar calls.
void OnSelectedMachineLost();
// local machine
void set_local_selected_machine(std::string dev_id) { local_selected_machine = dev_id; };
@@ -70,6 +71,8 @@ public:
void erase_user_machine(std::string dev_id) { userMachineList.erase(dev_id); }
void clean_user_info(bool keep_local_selection = false);
void clear_other_devices();
void load_last_machine();
void update_user_machine_list_info(const std::string& provider);
void parse_user_print_info(std::string body);
@@ -110,7 +113,6 @@ private:
void check_pushing();
void OnMachineBindStateChanged(MachineObject* obj, const std::string& new_state);
void OnSelectedMachineLost();
void OnSelectedMachineChanged(const std::string& pre_dev_id, const std::string& new_dev_id);

View File

@@ -2809,16 +2809,58 @@ void GUI_App::init_plugin_gui_wiring()
});
};
// why: a newly loaded plugin only adds a selectable agent
// refresh the dropdown and leave the live agent alone
auto refresh_printer_agent_dropdown_after_load = [](const std::string&)
{
if (!wxTheApp)
return;
GUI_App* app = &GUI::wxGetApp();
if (app->is_closing())
return;
app->CallAfter([app]
{
if (!app->is_closing())
app->refresh_printer_agent_dropdown();
});
};
// why: the unloaded plugin may have been the provider of the live agent
// re-run selection, where a now-missing agent will be cleared
// refresh dropdown after
auto switch_printer_agent_after_unload = [](const std::string&)
{
if (!wxTheApp)
return;
GUI_App* app = &GUI::wxGetApp();
if (app->is_closing())
return;
app->CallAfter([app] {
if (app->is_closing())
return;
app->switch_printer_agent();
app->refresh_printer_agent_dropdown();
});
};
plugin_mgr.subscribe_on_unload_callback(PluginHostUi::close_windows_for_plugin);
plugin_mgr.subscribe_on_load_callback([refresh_plugins_dialog](const std::string&) { refresh_plugins_dialog(); });
plugin_mgr.subscribe_on_unload_callback([refresh_plugins_dialog](const std::string&) { refresh_plugins_dialog(); });
plugin_mgr.subscribe_on_load_callback(NetworkAgentFactory::register_python_plugin);
plugin_mgr.subscribe_on_unload_callback(NetworkAgentFactory::deregister_python_plugin);
plugin_mgr.subscribe_on_load_callback(refresh_printer_agent_dropdown_after_load);
plugin_mgr.subscribe_on_unload_callback(switch_printer_agent_after_unload);
plugin_mgr.subscribe_on_capability_load_callback(
[refresh_plugins_dialog](const PluginCapabilityId& capability) {
[refresh_plugins_dialog, refresh_printer_agent_dropdown_after_load](const PluginCapabilityId& capability) {
if (capability.type == PluginCapabilityType::PrinterConnection)
NetworkAgentFactory::register_python_printer_agent(capability.plugin_key, capability.name);
refresh_plugins_dialog();
refresh_printer_agent_dropdown_after_load(capability.plugin_key);
// A newly loaded capability may satisfy a missing-plugin notification; re-validate the
// current plate (on the UI thread) so the notification clears once its plugin is available.
if (wxTheApp && !wxGetApp().is_closing())
@@ -2828,10 +2870,11 @@ void GUI_App::init_plugin_gui_wiring()
});
});
plugin_mgr.subscribe_on_capability_unload_callback(
[refresh_plugins_dialog](const PluginCapabilityId& capability) {
[refresh_plugins_dialog, switch_printer_agent_after_unload](const PluginCapabilityId& capability) {
if (capability.type == PluginCapabilityType::PrinterConnection)
NetworkAgentFactory::deregister_python_printer_agent(capability.plugin_key, capability.name);
refresh_plugins_dialog();
switch_printer_agent_after_unload(capability.plugin_key);
});
}
@@ -3873,6 +3916,36 @@ unsigned GUI_App::get_colour_approx_luma(const wxColour &colour)
));
}
void GUI_App::refresh_printer_agent_dropdown()
{
if (Tab* tab = get_tab(Preset::TYPE_PRINTER))
{
if (auto* printer_tab = dynamic_cast<TabPrinter*>(tab))
printer_tab->refresh_printer_agent_dropdown();
}
}
void GUI_App::set_live_printer_agent(std::shared_ptr<IPrinterAgent> agent)
{
if (!m_agent)
return;
// why: tearing down the old machine selection is only ever the prefix of setting the live
// agent (to a new one, or to null when the selection is missing) - so it lives here, not as
// a standalone helper. Pass nullptr to clear the selection.
if (DeviceManager* dev = getDeviceManager())
{
dev->set_selected_machine(""); // why: empty id disconnects and deselects the current machine
m_agent->set_user_selected_machine("");
// note: belt-and-suspenders (precedent: DeviceManagerRefresher::on_timer)
dev->OnSelectedMachineLost(); // why: clear stale sidebar sync-status / AMS
dev->clear_other_devices(); // why: drop stale LAN discoveries; keep My Devices
}
m_agent->set_printer_agent(agent);
sidebar().update_all_preset_comboboxes();
}
std::string GUI_App::resolve_printer_agent_id(const std::string& stored_id)
{
if (!stored_id.empty())
@@ -3898,9 +3971,11 @@ void GUI_App::switch_printer_agent()
// Check if agent is registered
const PrinterAgentInfo* agent_info_ptr = NetworkAgentFactory::get_printer_agent_info(effective_agent_id);
if (!agent_info_ptr) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": unregistered agent ID '" << effective_agent_id
<< "', keeping current agent";
// Keep current agent, don't switch
// why: the selected agent's provider is gone (e.g. plugin unloaded); leaving the old
// live agent up would keep talking to a machine the user can no longer select.
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": agent ID '" << effective_agent_id
<< "' is unregistered; clearing live printer agent";
set_live_printer_agent(nullptr);
return;
}
const PrinterAgentInfo agent_info = *agent_info_ptr;
@@ -3914,7 +3989,9 @@ void GUI_App::switch_printer_agent()
NetworkAgentFactory::create_printer_agent_by_id(effective_agent_id, cloud_agent, log_dir);
if (!new_printer_agent) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": failed to create agent '" << effective_agent_id << "', keeping current agent";
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": failed to create agent '" << effective_agent_id
<< "'; clearing live printer agent";
set_live_printer_agent(nullptr);
return;
}
@@ -3937,9 +4014,9 @@ void GUI_App::switch_printer_agent()
return;
}
// Swap the agent
m_agent->set_printer_agent(new_printer_agent);
sidebar().update_all_preset_comboboxes();
// Swap the agent; set_live_printer_agent resets the device selection so the new
// agent starts clean (#124).
set_live_printer_agent(new_printer_agent);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": printer agent switched to " << effective_agent_id;

View File

@@ -803,6 +803,11 @@ private:
void window_pos_center(wxTopLevelWindow *window);
bool select_language();
// Dynamic printer agent selection - internal helpers for switch_printer_agent
// and the plugin load/unload callbacks (init_plugin_gui_wiring).
void refresh_printer_agent_dropdown();
void set_live_printer_agent(std::shared_ptr<IPrinterAgent> agent); // null clears the selection
bool config_wizard_startup();
void check_updates(const bool verbose);

View File

@@ -7907,6 +7907,24 @@ bool TabPrinter::apply_extruder_cnt_from_cache()
return false;
}
void TabPrinter::refresh_printer_agent_dropdown() const
{
auto* choice = dynamic_cast<PrinterAgentChoice*>(get_field("printer_agent"));
if (!choice || !choice->getWindow())
return;
const auto agents = NetworkAgentFactory::get_registered_printer_agents();
if (agents.empty())
return;
// why: rows live on PrinterAgentChoice now; rebuild them from the live registry and re-select the stored id.
const std::string selected_agent = wxGetApp().preset_bundle->printers.get_edited_preset()
.config.opt_string("printer_agent");
choice->reload_rows();
choice->set_value(selected_agent, false);
this->GetParent()->Layout();
}
bool Tab::validate_custom_gcodes()
{
if (m_type != Preset::TYPE_FILAMENT &&

View File

@@ -675,6 +675,7 @@ public:
wxSizer* create_bed_shape_widget(wxWindow* parent);
void cache_extruder_cnt(const DynamicPrintConfig* config = nullptr);
bool apply_extruder_cnt_from_cache();
void refresh_printer_agent_dropdown() const;
};
class TabSLAMaterial : public Tab