Multicast for login and logout.

Implemented on same base as single instance messages. 
Linux adds new Dbus object for this.
Message format for instance messaging changed to json.
This commit is contained in:
David Kocik 2025-01-13 17:08:34 +01:00 committed by Lukas Matena
parent 2d12c61ea0
commit e99a53079e
9 changed files with 587 additions and 55 deletions

View File

@ -5,6 +5,7 @@
#include "GUI_App.hpp"
#include "InstanceCheck.hpp"
#include "Plater.hpp"
#include "format.hpp"
#ifdef _WIN32
#include "MainFrame.hpp"
@ -16,12 +17,15 @@
#include "boost/nowide/convert.hpp"
#include <boost/log/trivial.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <iostream>
#include <unordered_map>
#include <fcntl.h>
#include <errno.h>
#include <optional>
#include <cstdint>
#include <regex>
#ifdef _WIN32
#include <strsafe.h>
@ -66,9 +70,7 @@ namespace instance_check_internal
static CommandLineAnalysis process_command_line(int argc, char** argv)
{
CommandLineAnalysis ret;
//if (argc < 2)
// return ret;
std::vector<std::string> arguments { argv[0] };
std::vector<std::string> arguments { argv[0] };
bool send_if_url = false;
bool has_url = false;
for (int i = 1; i < argc; ++i) {
@ -90,14 +92,22 @@ namespace instance_check_internal
if (send_if_url && has_url) {
ret.should_send = true;
}
ret.cl_string = escape_strings_cstyle(arguments);
// We do now want escape_strings_cstyle that quotes strings
// It would not be possible to use inside json
for (const std::string& arg : arguments) {
ret.cl_string += escape_string_cstyle(arg);
ret.cl_string += ";";
}
BOOST_LOG_TRIVIAL(info) << "single instance: " <<
(ret.should_send.has_value() ? (*ret.should_send ? "true" : "false") : "undefined") <<
". other params: " << ret.cl_string;
return ret;
}
std::string compose_message_json(const std::string& type, const std::string& data)
{
return GUI::format("{ \"type\" : \"%1%\" , \"data\" : \"%2%\"}", type, data);
}
#ifdef _WIN32
@ -168,6 +178,67 @@ namespace instance_check_internal
return false;
}
static BOOL CALLBACK enum_windows_process_multicast(_In_ HWND hwnd, _In_ LPARAM lParam)
{
if (hwnd == GUI::wxGetApp().mainframe->GetHandle()) {
return true;
}
TCHAR wndText[1000];
TCHAR className[1000];
int err;
err = GetClassName(hwnd, className, 1000);
if (err == 0)
return true;
err = GetWindowText(hwnd, wndText, 1000);
if (err == 0)
return true;
std::wstring classNameString(className);
std::wstring wndTextString(wndText);
if (wndTextString.find(L"PrusaSlicer") != std::wstring::npos && classNameString == L"wxWindowNR") {
//check if other instances has same instance hash
//if not it is not same version(binary) as this version
HANDLE handle = GetProp(hwnd, L"Instance_Hash_Minor");
uint64_t other_instance_hash = PtrToUint(handle);
uint64_t other_instance_hash_major;
uint64_t my_instance_hash = GUI::wxGetApp().get_instance_hash_int();
handle = GetProp(hwnd, L"Instance_Hash_Major");
other_instance_hash_major = PtrToUint(handle);
other_instance_hash_major = other_instance_hash_major << 32;
other_instance_hash += other_instance_hash_major;
handle = GetProp(hwnd, L"Instance_Is_Maximized");
const bool maximized = PtrToUint(handle) == 1;
if (my_instance_hash == other_instance_hash)
{
BOOST_LOG_TRIVIAL(debug) << "win multicast enum - found instance " << hwnd;
std::wstring multicast_message = *reinterpret_cast<std::wstring*>(lParam);
std::unique_ptr<LPWSTR> message = std::make_unique<LPWSTR>(const_cast<LPWSTR>(multicast_message.c_str()));
//Create a COPYDATASTRUCT to send the information
//cbData represents the size of the information we want to send.
//lpData represents the information we want to send.
//dwData is an ID defined by us(this is a type of ID different than WM_COPYDATA).
COPYDATASTRUCT data_to_send = { 0 };
data_to_send.dwData = 1;
data_to_send.cbData = sizeof(TCHAR) * (wcslen(*message.get()) + 1);
data_to_send.lpData = *message.get();
SendMessage(hwnd, WM_COPYDATA, 0, (LPARAM)&data_to_send);
return true;
}
BOOST_LOG_TRIVIAL(trace) << "win enum - found wrong instance";
}
return true;
}
static void multicast_message_inner(const std::string& message)
{
// multicast_message must live until EnumWindows is done, it is passed as pointer parameter.
std::wstring multicast_message = boost::nowide::widen(message);
EnumWindows(enum_windows_process_multicast, reinterpret_cast<LPARAM>(&multicast_message));
}
#else
static bool get_lock(const std::string& name, const std::string& path)
@ -228,6 +299,11 @@ namespace instance_check_internal
#endif //WIN32
#if defined(__APPLE__)
static void multicast_message_inner(const std::string &message_text)
{
multicast_message_mac(message_text);
}
static bool send_message(const std::string &message_text, const std::string &version)
{
//std::string v(version);
@ -242,6 +318,162 @@ namespace instance_check_internal
#elif defined(__linux__)
static void list_matching_objects(const std::string& pattern, std::vector<std::string>& result)
{
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__;
DBusConnection* connection;
DBusError error;
// Initialize the D-Bus error.
dbus_error_init(&error);
// Connect to the session bus.
connection = dbus_bus_get(DBUS_BUS_SESSION, &error);
if (!connection) {
BOOST_LOG_TRIVIAL(error) << "Failed to connect to the D-Bus session bus: " << error.message;
dbus_error_free(&error);
return;
}
// Request a list of all bus names.
DBusMessage* message = dbus_message_new_method_call(
"org.freedesktop.DBus", // Destination (the D-Bus daemon)
"/org/freedesktop/DBus", // Object path
"org.freedesktop.DBus", // Interface
"ListNames" // Method
);
if (!message) {
BOOST_LOG_TRIVIAL(error) << "Failed to create D-Bus message.";
return;
}
DBusMessage* reply = dbus_connection_send_with_reply_and_block(connection, message, -1, &error);
dbus_message_unref(message);
if (!reply) {
BOOST_LOG_TRIVIAL(error) << "Failed to send message: " << error.message;
dbus_error_free(&error);
return;
}
// Parse the reply.
DBusMessageIter args;
if (!dbus_message_iter_init(reply, &args)) {
BOOST_LOG_TRIVIAL(error) << "Reply does not contain arguments.";
dbus_message_unref(reply);
return;
}
if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_ARRAY) {
BOOST_LOG_TRIVIAL(error) << "Unexpected argument type in reply.";
dbus_message_unref(reply);
return;
}
DBusMessageIter array_iter;
dbus_message_iter_recurse(&args, &array_iter);
std::regex instance_regex(pattern);
while (dbus_message_iter_get_arg_type(&array_iter) == DBUS_TYPE_STRING) {
const char* name;
dbus_message_iter_get_basic(&array_iter, &name);
if (std::regex_match(name, instance_regex)) {
result.push_back(name);
}
dbus_message_iter_next(&array_iter);
}
dbus_message_unref(reply);
dbus_error_free(&error);
}
static bool multicast_one_message(const std::string &message_text, const std::string &interface_name)
{
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << " to " << interface_name;
DBusMessage* msg;
DBusConnection* conn;
DBusError err;
dbus_uint32_t serial = 0;
const char* sigval = message_text.c_str();
std::string method_name = "Message";
//std::string interface_name = "com.prusa3d.prusaslicer.MulticastListener.Object" + reciever_id;
//std::string object_name = "/com/prusa3d/prusaslicer/MulticastListener/Object" + reciever_id;
std::string object_name = "/" + interface_name;
std::replace(object_name.begin(), object_name.end(), '.', '/');
// initialise the error value
dbus_error_init(&err);
// connect to bus, and check for errors (use SESSION bus everywhere!)
conn = dbus_bus_get(DBUS_BUS_SESSION, &err);
if (dbus_error_is_set(&err)) {
BOOST_LOG_TRIVIAL(error) << "DBus Connection Error. Message to another instance wont be send.";
BOOST_LOG_TRIVIAL(error) << "DBus Connection Error: " << err.message;
dbus_error_free(&err);
return false;
}
if (NULL == conn) {
BOOST_LOG_TRIVIAL(error) << "DBus Connection is NULL. Message to another instance wont be send.";
return false;
}
//some sources do request interface ownership before constructing msg but i think its wrong.
//create new method call message
msg = dbus_message_new_method_call(interface_name.c_str(), object_name.c_str(), interface_name.c_str(), method_name.c_str());
if (NULL == msg) {
BOOST_LOG_TRIVIAL(error) << "DBus Message is NULL. Message to another instance wont be send.";
dbus_connection_unref(conn);
return false;
}
//the Message method is not sending reply.
dbus_message_set_no_reply(msg, TRUE);
//append arguments to message
if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &sigval, DBUS_TYPE_INVALID)) {
BOOST_LOG_TRIVIAL(error) << "Ran out of memory while constructing args for DBus message. Message to another instance wont be send.";
dbus_message_unref(msg);
dbus_connection_unref(conn);
return false;
}
// send the message and flush the connection
if (!dbus_connection_send(conn, msg, &serial)) {
BOOST_LOG_TRIVIAL(error) << "Ran out of memory while sending DBus message.";
dbus_message_unref(msg);
dbus_connection_unref(conn);
return false;
}
dbus_connection_flush(conn);
BOOST_LOG_TRIVIAL(trace) << "DBus message sent.";
// free the message and close the connection
dbus_message_unref(msg);
dbus_connection_unref(conn);
return true;
}
static void multicast_message_inner(const std::string &message_text)
{
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__;
std::string pattern = R"(com\.prusa3d\.prusaslicer\.MulticastListener\.Object\d+)";
std::vector<std::string> instances;
std::string my_pid = std::to_string(get_current_pid());
list_matching_objects(pattern, instances);
for (const std::string& instance : instances) {
// regex object pid to not send message to myself.
std::regex objectRegex("Object(\\d+)");
std::smatch match;
if (std::regex_search(instance, match, objectRegex) && match[1] != my_pid) {
if (!multicast_one_message(message_text, instance)) {
BOOST_LOG_TRIVIAL(error) << "Failed send DBUS message to " << instance;
} else {
BOOST_LOG_TRIVIAL(debug) << "Successfully sent DBUS message to " << instance;
}
}
}
}
static bool send_message(const std::string &message_text, const std::string &version)
{
/*std::string v(version);
@ -372,7 +604,7 @@ bool instance_check(int argc, char** argv, bool app_config_single_instance)
// get_lock() creates the lockfile therefore *cla.should_send is checked after
if (instance_check_internal::get_lock(lock_name + ".lock", data_dir() + "/cache/") && *cla.should_send) {
#endif
instance_check_internal::send_message(cla.cl_string, lock_name);
instance_check_internal::send_message(instance_check_internal::compose_message_json("CLI", cla.cl_string), lock_name);
BOOST_LOG_TRIVIAL(error) << "Instance check: Another instance found. This instance will terminate. Lock file of current running instance is located at " << data_dir() <<
#ifdef _WIN32
"\\cache\\"
@ -394,6 +626,7 @@ wxDEFINE_EVENT(EVT_LOAD_MODEL_OTHER_INSTANCE, LoadFromOtherInstanceEvent);
wxDEFINE_EVENT(EVT_START_DOWNLOAD_OTHER_INSTANCE, StartDownloadOtherInstanceEvent);
wxDEFINE_EVENT(EVT_LOGIN_OTHER_INSTANCE, LoginOtherInstanceEvent);
wxDEFINE_EVENT(EVT_INSTANCE_GO_TO_FRONT, InstanceGoToFrontEvent);
wxDEFINE_EVENT(EVT_STORE_READ_REQUEST, SimpleEvent);
void OtherInstanceMessageHandler::init(wxEvtHandler* callback_evt_handler)
{
@ -410,7 +643,8 @@ void OtherInstanceMessageHandler::init(wxEvtHandler* callback_evt_handler)
#endif //__APPLE__
#ifdef BACKGROUND_MESSAGE_LISTENER
m_thread = boost::thread((boost::bind(&OtherInstanceMessageHandler::listen, this)));
m_instance_check_thread = boost::thread((boost::bind(&OtherInstanceMessageHandler::listen_instance_check, this)));
m_multicast_listener_thread = boost::thread((boost::bind(&OtherInstanceMessageHandler::listen_multicast, this)));
#endif //BACKGROUND_MESSAGE_LISTENER
}
void OtherInstanceMessageHandler::shutdown(MainFrame* main_frame)
@ -432,17 +666,30 @@ void OtherInstanceMessageHandler::shutdown(MainFrame* main_frame)
this->unregister_for_messages();
#endif //__APPLE__
#ifdef BACKGROUND_MESSAGE_LISTENER
if (m_thread.joinable()) {
if (m_instance_check_thread.joinable()) {
// Stop the worker thread, if running.
{
// Notify the worker thread to cancel wait on detection polling.
std::lock_guard<std::mutex> lck(m_thread_stop_mutex);
m_stop = true;
std::lock_guard<std::mutex> lck(m_instance_check_thread_stop_mutex);
m_instance_check_thread_stop = true;
}
m_thread_stop_condition.notify_all();
m_instance_check_thread_stop_condition.notify_all();
// Wait for the worker thread to stop.
m_thread.join();
m_stop = false;
m_instance_check_thread.join();
m_instance_check_thread_stop = false;
}
if (m_multicast_listener_thread.joinable()) {
// Stop the worker thread, if running.
{
// Notify the worker thread to cancel wait on detection polling.
std::lock_guard<std::mutex> lck(m_multicast_listener_thread_stop_mutex);
m_multicast_listener_thread_stop = true;
}
m_multicast_listener_thread_stop_condition.notify_all();
// Wait for the worker thread to stop.
m_multicast_listener_thread.join();
m_multicast_listener_thread_stop = false;
}
#endif //BACKGROUND_MESSAGE_LISTENER
m_callback_evt_handler = nullptr;
@ -525,15 +772,20 @@ namespace MessageHandlerInternal
}
} //namespace MessageHandlerInternal
void OtherInstanceMessageHandler::handle_message(const std::string& message)
void OtherInstanceMessageHandler::multicast_message(const std::string& message_type, const std::string& message_data)
{
BOOST_LOG_TRIVIAL(info) << "message from other instance: " << message;
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << " " << message_type;
instance_check_internal::multicast_message_inner(instance_check_internal::compose_message_json(message_type, message_data));
}
std::vector<std::string> args;
bool parsed = unescape_strings_cstyle(message, args);
void OtherInstanceMessageHandler::handle_message_type_cli(const std::string& data)
{
std::vector<std::string> args;
bool parsed = unescape_strings_cstyle(data, args);
assert(parsed);
if (! parsed) {
BOOST_LOG_TRIVIAL(error) << "message from other instance is incorrectly formatted: " << message;
BOOST_LOG_TRIVIAL(error) << "message from other instance is incorrectly formatted: " << data;
return;
}
@ -542,10 +794,10 @@ void OtherInstanceMessageHandler::handle_message(const std::string& message)
// Skip the first argument, it is the path to the slicer executable.
auto it = args.begin();
for (++ it; it != args.end(); ++ it) {
BOOST_LOG_TRIVIAL(debug) << *it;
boost::filesystem::path p = MessageHandlerInternal::get_path(*it);
if (! p.string().empty())
paths.emplace_back(p);
// TODO: There is a misterious slash appearing in recieved msg on windows
#ifdef _WIN32
else if (it->rfind("prusaslicer://open/?file=", 0) == 0)
#else
@ -557,16 +809,45 @@ void OtherInstanceMessageHandler::handle_message(const std::string& message)
}
}
if (! paths.empty()) {
//wxEvtHandler* evt_handler = wxGetApp().plater(); //assert here?
//if (evt_handler) {
wxPostEvent(m_callback_evt_handler, LoadFromOtherInstanceEvent(GUI::EVT_LOAD_MODEL_OTHER_INSTANCE, std::vector<boost::filesystem::path>(std::move(paths))));
//}
wxPostEvent(m_callback_evt_handler, LoadFromOtherInstanceEvent(GUI::EVT_LOAD_MODEL_OTHER_INSTANCE, std::vector<boost::filesystem::path>(std::move(paths))));
}
if (!downloads.empty())
{
if (!downloads.empty()) {
wxPostEvent(m_callback_evt_handler, StartDownloadOtherInstanceEvent(GUI::EVT_START_DOWNLOAD_OTHER_INSTANCE, std::vector<std::string>(std::move(downloads))));
}
}
void OtherInstanceMessageHandler::handle_message_type_store_read(const std::string& data)
{
wxPostEvent(m_callback_evt_handler, SimpleEvent(GUI::EVT_STORE_READ_REQUEST));
}
void OtherInstanceMessageHandler::handle_message(const std::string& message)
{
BOOST_LOG_TRIVIAL(info) << "message from other instance: " << message;
// message in format { "type" : "TYPE", "data" : "data" }
// types: CLI, STORE_READ
std::string type;
std::string data;
try {
std::stringstream ss(message);
boost::property_tree::ptree ptree;
boost::property_tree::read_json(ss, ptree);
if (const auto action = ptree.get_optional<std::string>("type"); action) {
type = *action;
}
if (const auto data_opt = ptree.get_optional<std::string>("data"); data_opt) {
data = *data_opt;
}
}
catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "Could not parse other instance message: " << e.what();
return;
}
assert(!type.empty());
assert(m_message_handlers.find(type) != m_message_handlers.end()); // this assert means there is an message type that has no handling.
if (m_message_handlers.find(type) != m_message_handlers.end()) {
m_message_handlers[type](data);
}
}
#ifdef __APPLE__
void OtherInstanceMessageHandler::handle_message_other_closed()
@ -577,7 +858,7 @@ void OtherInstanceMessageHandler::handle_message_other_closed()
#ifdef BACKGROUND_MESSAGE_LISTENER
namespace MessageHandlerDBusInternal
namespace InstanceCheckMessageHandlerDBusInternal
{
//reply to introspect makes our DBus object visible for other programs like D-Feet
static void respond_to_introspect(DBusConnection *connection, DBusMessage *request)
@ -638,26 +919,27 @@ namespace MessageHandlerDBusInternal
std::string our_interface = "com.prusa3d.prusaslicer.InstanceCheck.Object" + wxGetApp().get_instance_hash_string();
BOOST_LOG_TRIVIAL(trace) << "DBus message received: interface: " << interface_name << ", member: " << member_name;
if (0 == strcmp("org.freedesktop.DBus.Introspectable", interface_name) && 0 == strcmp("Introspect", member_name)) {
respond_to_introspect(connection, message);
InstanceCheckMessageHandlerDBusInternal::respond_to_introspect(connection, message);
return DBUS_HANDLER_RESULT_HANDLED;
} else if (0 == strcmp(our_interface.c_str(), interface_name) && 0 == strcmp("AnotherInstance", member_name)) {
handle_method_another_instance(connection, message);
InstanceCheckMessageHandlerDBusInternal::handle_method_another_instance(connection, message);
return DBUS_HANDLER_RESULT_HANDLED;
} else if (0 == strcmp(our_interface.c_str(), interface_name) && 0 == strcmp("Introspect", member_name)) {
respond_to_introspect(connection, message);
InstanceCheckMessageHandlerDBusInternal::respond_to_introspect(connection, message);
return DBUS_HANDLER_RESULT_HANDLED;
}
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
} //namespace MessageHandlerDBusInternal
} //namespace InstanceCheckMessageHandlerDBusInternal
void OtherInstanceMessageHandler::listen()
void OtherInstanceMessageHandler::listen_instance_check()
{
DBusConnection* conn;
DBusError err;
int name_req_val;
DBusObjectPathVTable vtable;
std::string instance_hash = wxGetApp().get_instance_hash_string();
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << " " << instance_hash;
std::string interface_name = "com.prusa3d.prusaslicer.InstanceCheck.Object" + instance_hash;
std::string object_name = "/com/prusa3d/prusaslicer/InstanceCheck/Object" + instance_hash;
@ -694,7 +976,7 @@ void OtherInstanceMessageHandler::listen()
}
// Set callbacks. Unregister function should not be nessary.
vtable.message_function = MessageHandlerDBusInternal::handle_dbus_object_message;
vtable.message_function = InstanceCheckMessageHandlerDBusInternal::handle_dbus_object_message;
vtable.unregister_function = NULL;
// register new object - this is our access to DBus
@ -707,19 +989,169 @@ void OtherInstanceMessageHandler::listen()
return;
}
BOOST_LOG_TRIVIAL(trace) << "Dbus object "<< object_name <<" registered. Starting listening for messages.";
BOOST_LOG_TRIVIAL(debug) << "Dbus object "<< object_name <<" registered. Starting listening for messages.";
for (;;) {
// Wait for 1 second
// Cancellable.
{
std::unique_lock<std::mutex> lck(m_thread_stop_mutex);
m_thread_stop_condition.wait_for(lck, std::chrono::seconds(1), [this] { return m_stop; });
std::unique_lock<std::mutex> lck(m_instance_check_thread_stop_mutex);
m_instance_check_thread_stop_condition.wait_for(lck, std::chrono::seconds(1), [this] { return m_instance_check_thread_stop; });
}
if (m_stop)
if (m_instance_check_thread_stop) {
// Stop the worker thread.
break;
}
//dispatch should do all the work with incoming messages
//second parameter is blocking time that funciton waits for new messages
//that is handled here with our own event loop above
dbus_connection_read_write_dispatch(conn, 0);
}
dbus_connection_unref(conn);
}
namespace MulticastMessageHandlerDBusInternal
{
//reply to introspect makes our DBus object visible for other programs like D-Feet
static void respond_to_introspect(DBusConnection *connection, DBusMessage *request)
{
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__;
DBusMessage *reply;
const char *introspection_data =
" <!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\" "
"\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">"
" <!-- dbus-sharp 0.8.1 -->"
" <node>"
" <interface name=\"org.freedesktop.DBus.Introspectable\">"
" <method name=\"Introspect\">"
" <arg name=\"data\" direction=\"out\" type=\"s\" />"
" </method>"
" </interface>"
" <interface name=\"com.prusa3d.prusaslicer.MulticastListener\">"
" <method name=\"Message\">"
" <arg name=\"data\" direction=\"in\" type=\"s\" />"
" </method>"
" <method name=\"Introspect\">"
" <arg name=\"data\" direction=\"out\" type=\"s\" />"
" </method>"
" </interface>"
" </node>";
reply = dbus_message_new_method_return(request);
dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection_data, DBUS_TYPE_INVALID);
dbus_connection_send(connection, reply, NULL);
dbus_message_unref(reply);
}
//method Message receives message from another PrusaSlicer instance
static void handle_method_message(DBusConnection *connection, DBusMessage *request)
{
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__;
DBusError err;
char* text = nullptr;
wxEvtHandler* evt_handler;
dbus_error_init(&err);
dbus_message_get_args(request, &err, DBUS_TYPE_STRING, &text, DBUS_TYPE_INVALID);
if (dbus_error_is_set(&err)) {
BOOST_LOG_TRIVIAL(debug) << "Dbus method Message received with wrong arguments.";
dbus_error_free(&err);
return;
}
wxGetApp().other_instance_message_handler()->handle_message(text);
}
//every dbus message received comes here
static DBusHandlerResult handle_dbus_object_message(DBusConnection *connection, DBusMessage *message, void *user_data)
{
const char* interface_name = dbus_message_get_interface(message);
const char* member_name = dbus_message_get_member(message);
std::string our_interface = "com.prusa3d.prusaslicer.MulticastListener.Object" + std::to_string(get_current_pid());
BOOST_LOG_TRIVIAL(debug) << "DBus message received: interface: " << interface_name << ", member: " << member_name;
if (0 == strcmp("org.freedesktop.DBus.Introspectable", interface_name) && 0 == strcmp("Introspect", member_name)) {
MulticastMessageHandlerDBusInternal::respond_to_introspect(connection, message);
return DBUS_HANDLER_RESULT_HANDLED;
} else if (0 == strcmp(our_interface.c_str(), interface_name) && 0 == strcmp("Message", member_name)) {
MulticastMessageHandlerDBusInternal::handle_method_message(connection, message);
return DBUS_HANDLER_RESULT_HANDLED;
} else if (0 == strcmp(our_interface.c_str(), interface_name) && 0 == strcmp("Introspect", member_name)) {
MulticastMessageHandlerDBusInternal::respond_to_introspect(connection, message);
return DBUS_HANDLER_RESULT_HANDLED;
}
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
} //namespace MulticastMessageHandlerDBusInternal
void OtherInstanceMessageHandler::listen_multicast()
{
DBusConnection* conn;
DBusError err;
int name_req_val;
DBusObjectPathVTable vtable;
std::string pid = std::to_string(get_current_pid());
std::string interface_name = "com.prusa3d.prusaslicer.MulticastListener.Object" + pid;
std::string object_name = "/com/prusa3d/prusaslicer/MulticastListener/Object" + pid;
dbus_error_init(&err);
// connect to the bus and check for errors (use SESSION bus everywhere!)
conn = dbus_bus_get(DBUS_BUS_SESSION, &err);
if (dbus_error_is_set(&err)) {
BOOST_LOG_TRIVIAL(error) << "DBus Connection Error: "<< err.message;
BOOST_LOG_TRIVIAL(error) << "Dbus Messages listening terminating.";
dbus_error_free(&err);
return;
}
if (NULL == conn) {
BOOST_LOG_TRIVIAL(error) << "DBus Connection is NULL. Dbus Messages listening terminating.";
return;
}
// request our name on the bus and check for errors
name_req_val = dbus_bus_request_name(conn, interface_name.c_str(), DBUS_NAME_FLAG_REPLACE_EXISTING , &err);
if (dbus_error_is_set(&err)) {
BOOST_LOG_TRIVIAL(error) << "DBus Request name Error: "<< err.message;
BOOST_LOG_TRIVIAL(error) << "Dbus Messages listening terminating.";
dbus_error_free(&err);
dbus_connection_unref(conn);
return;
}
if (DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER != name_req_val) {
BOOST_LOG_TRIVIAL(error) << "Not primary owner of DBus name - probably another PrusaSlicer instance is running.";
BOOST_LOG_TRIVIAL(error) << "Dbus Messages listening terminating.";
dbus_connection_unref(conn);
return;
}
// Set callbacks. Unregister function should not be nessary.
vtable.message_function = MulticastMessageHandlerDBusInternal::handle_dbus_object_message;
vtable.unregister_function = NULL;
// register new object - this is our access to DBus
dbus_connection_try_register_object_path(conn, object_name.c_str(), &vtable, NULL, &err);
if ( dbus_error_is_set(&err) ) {
BOOST_LOG_TRIVIAL(error) << "DBus Register object Error: "<< err.message;
BOOST_LOG_TRIVIAL(error) << "Dbus Messages listening terminating.";
dbus_connection_unref(conn);
dbus_error_free(&err);
return;
}
BOOST_LOG_TRIVIAL(debug) << "Dbus object "<< object_name <<" registered. Starting listening for messages.";
for (;;) {
// Wait for 1 second
// Cancellable.
{
std::unique_lock<std::mutex> lck(m_multicast_listener_thread_stop_mutex);
m_multicast_listener_thread_stop_condition.wait_for(lck, std::chrono::seconds(1), [this] { return m_multicast_listener_thread_stop; });
}
if (m_multicast_listener_thread_stop) {
// Stop the worker thread.
break;
}
//dispatch should do all the work with incoming messages
//second parameter is blocking time that funciton waits for new messages
//that is handled here with our own event loop above

View File

@ -12,6 +12,7 @@
#endif //_WIN32
#include <string>
#include <map>
#include <boost/filesystem.hpp>
@ -32,6 +33,7 @@ bool instance_check(int argc, char** argv, bool app_config_single_instance);
// apple implementation of inner functions of instance_check
// in InstanceCheckMac.mm
void send_message_mac(const std::string& msg, const std::string& version);
void multicast_message_mac(const std::string &msg);
void send_message_mac_closing(const std::string& msg, const std::string& version);
@ -54,11 +56,16 @@ wxDECLARE_EVENT(EVT_START_DOWNLOAD_OTHER_INSTANCE, StartDownloadOtherInstanceEve
wxDECLARE_EVENT(EVT_LOGIN_OTHER_INSTANCE, LoginOtherInstanceEvent);
using InstanceGoToFrontEvent = SimpleEvent;
wxDECLARE_EVENT(EVT_INSTANCE_GO_TO_FRONT, InstanceGoToFrontEvent);
wxDECLARE_EVENT(EVT_STORE_READ_REQUEST, SimpleEvent);
class OtherInstanceMessageHandler
{
public:
OtherInstanceMessageHandler() = default;
OtherInstanceMessageHandler()
{
m_message_handlers["CLI"] = std::bind(&OtherInstanceMessageHandler::handle_message_type_cli, this, std::placeholders::_1);
m_message_handlers["STORE_READ"] = std::bind(&OtherInstanceMessageHandler::handle_message_type_store_read, this, std::placeholders::_1);
}
OtherInstanceMessageHandler(OtherInstanceMessageHandler const&) = delete;
void operator=(OtherInstanceMessageHandler const&) = delete;
~OtherInstanceMessageHandler() { assert(!m_initialized); }
@ -68,13 +75,10 @@ public:
// stops listening, on linux stops the background thread
void shutdown(MainFrame* main_frame);
//finds paths to models in message(= command line arguments, first should be prusaSlicer executable)
//and sends them to plater via LoadFromOtherInstanceEvent
//security of messages: from message all existing paths are proccesed to load model
// win32 - anybody who has hwnd can send message.
// mac - anybody who posts notification with name:@"OtherPrusaSlicerTerminating"
// linux - instrospectable on dbus
void handle_message(const std::string& message);
// message in format { "type" : "TYPE", "data" : "data" }
void handle_message(const std::string& message);
void multicast_message(const std::string& message_type, const std::string& message_data = std::string());
#ifdef __APPLE__
// Messege form other instance, that it deleted its lockfile - first instance to get it will create its own.
void handle_message_other_closed();
@ -84,19 +88,37 @@ public:
void update_windows_properties(MainFrame* main_frame);
#endif //WIN32
private:
//finds paths to models in message(= command line arguments, first should be prusaSlicer executable)
//and sends them to plater via LoadFromOtherInstanceEvent
//security of messages: from message all existing paths are proccesed to load model
// win32 - anybody who has hwnd can send message.
// mac - anybody who posts notification with name:@"OtherPrusaSlicerTerminating"
// linux - instrospectable on dbus
void handle_message_type_cli(const std::string& data);
// Passes information to UI to perform store read
void handle_message_type_store_read(const std::string& data);
std::map<std::string, std::function<void(const std::string&)>> m_message_handlers;
bool m_initialized { false };
wxEvtHandler* m_callback_evt_handler { nullptr };
#ifdef BACKGROUND_MESSAGE_LISTENER
//worker thread to listen incoming dbus communication
boost::thread m_thread;
std::condition_variable m_thread_stop_condition;
mutable std::mutex m_thread_stop_mutex;
bool m_stop{ false };
bool m_start{ true };
// background thread method
void listen();
// instance check worker thread to listen incoming dbus communication
// Only one instance has registered dbus object at time
boost::thread m_instance_check_thread;
std::condition_variable m_instance_check_thread_stop_condition;
mutable std::mutex m_instance_check_thread_stop_mutex;
bool m_instance_check_thread_stop{ false };
//bool m_instance_check_thread_start{ true };
void listen_instance_check();
// "multicast" worker thread to listen incoming dbus communication
// every instance has registered its own object
boost::thread m_multicast_listener_thread;
std::condition_variable m_multicast_listener_thread_stop_condition;
mutable std::mutex m_multicast_listener_thread_stop_mutex;
bool m_multicast_listener_thread_stop{ false };
void listen_multicast();
#endif //BACKGROUND_MESSAGE_LISTENER
#if __APPLE__

View File

@ -9,6 +9,7 @@
-(instancetype) init;
-(void) add_observer:(NSString *)version;
-(void) message_update:(NSNotification *)note;
-(void) message_multicast_update:(NSNotification *)note;
-(void) closing_update:(NSNotification *)note;
-(void) bring_forward;
@end

View File

@ -11,12 +11,14 @@
}
-(void)add_observer:(NSString *)version_hash
{
//NSLog(@"adding observer");
//NSLog(@"adding observers");
//NSString *nsver = @"OtherPrusaSlicerInstanceMessage" + version_hash;
NSString *nsver = [NSString stringWithFormat: @"%@%@", @"OtherPrusaSlicerInstanceMessage", version_hash];
[[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(message_update:) name:nsver object:nil suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately];
NSString *nsver2 = [NSString stringWithFormat: @"%@%@", @"OtherPrusaSlicerInstanceClosing", version_hash];
[[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(closing_update:) name:nsver2 object:nil suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately];
NSString *nsnover = [NSString stringWithFormat: @"%@", @"OtherPrusaSlicerMulticastMessage"];
[[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(message_multicast_update:) name:nsnover object:nil suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately];
}
-(void)message_update:(NSNotification *)msg
@ -26,6 +28,13 @@
Slic3r::GUI::wxGetApp().other_instance_message_handler()->handle_message(std::string([msg.userInfo[@"data"] UTF8String]));
}
-(void)message_multicast_update:(NSNotification *)msg
{
//NSLog(@"message_multicast_update");
//pass message
Slic3r::GUI::wxGetApp().other_instance_message_handler()->handle_message(std::string([msg.userInfo[@"data"] UTF8String]));
}
-(void)closing_update:(NSNotification *)msg
{
//[self bring_forward];
@ -51,6 +60,14 @@
namespace Slic3r {
void multicast_message_mac(const std::string &msg)
{
NSString *nsmsg = [NSString stringWithCString:msg.c_str() encoding:[NSString defaultCStringEncoding]];
//NSString *nsver = @"OtherPrusaSlicerInstanceMessage" + [NSString stringWithCString:version.c_str() encoding:[NSString defaultCStringEncoding]];
NSString *notifname = [NSString stringWithFormat: @"%@", @"OtherPrusaSlicerMulticastMessage"];
[[NSDistributedNotificationCenter defaultCenter] postNotificationName:notifname object:nil userInfo:[NSDictionary dictionaryWithObject:nsmsg forKey:@"data"] deliverImmediately:YES];
}
void send_message_mac(const std::string &msg, const std::string &version)
{
NSString *nsmsg = [NSString stringWithCString:msg.c_str() encoding:[NSString defaultCStringEncoding]];

View File

@ -896,6 +896,11 @@ void Plater::priv::init()
BOOST_LOG_TRIVIAL(trace) << "Received login from other instance event.";
user_account->on_login_code_recieved(evt.data);
});
this->q->Bind(EVT_STORE_READ_REQUEST, [this](SimpleEvent& evt) {
BOOST_LOG_TRIVIAL(debug) << "Received store read request from other instance event.";
user_account->on_store_read_request();
});
this->q->Bind(EVT_LOGIN_VIA_WIZARD, [this](Event<std::string> &evt) {
BOOST_LOG_TRIVIAL(trace) << "Received login from wizard.";
user_account->on_login_code_recieved(evt.data);
@ -970,6 +975,9 @@ void Plater::priv::init()
this->notification_manager->push_notification(NotificationType::UserAccountID, NotificationManager::NotificationLevel::ImportantNotificationLevel, text);
this->main_frame->on_account_login(user_account->get_access_token());
// notify other instances
Slic3r::GUI::wxGetApp().other_instance_message_handler()->multicast_message("STORE_READ");
} else {
// refresh do different operations than on_account_login
this->main_frame->on_account_did_refresh(user_account->get_access_token());

View File

@ -14,6 +14,9 @@
#include <boost/property_tree/json_parser.hpp>
#include <boost/log/trivial.hpp>
#include "InstanceCheck.hpp"
#include "GUI_App.hpp"
#include <wx/stdpaths.h>
namespace pt = boost::property_tree;
@ -67,7 +70,9 @@ void UserAccount::do_login()
}
void UserAccount::do_logout()
{
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__;
m_communication->do_logout();
Slic3r::GUI::wxGetApp().other_instance_message_handler()->multicast_message("STORE_READ");
}
std::string UserAccount::get_access_token()

View File

@ -79,6 +79,8 @@ public:
void set_current_printer_data(const std::string& data) { m_current_printer_data_json_from_connect = data; }
void set_refresh_time(int seconds) { m_communication->set_refresh_time(seconds); }
void on_store_read_request() { m_communication->on_store_read_request(); }
private:
void set_username(const std::string& username);

View File

@ -452,6 +452,10 @@ void UserAccountCommunication::do_clear()
m_session->clear();
set_username({});
m_token_timer->Stop();
m_slave_read_timer->Stop();
m_after_race_lost_timer->Stop();
m_next_token_refresh_at = 0;
m_behave_as_master = true;
}
void UserAccountCommunication::on_login_code_recieved(const std::string& url_message)
@ -821,11 +825,51 @@ void UserAccountCommunication::on_after_race_lost_timer(wxTimerEvent& evt)
void UserAccountCommunication::set_tokens(const StoreData store_data)
{
if (m_token_timer->IsRunning()) {
m_token_timer->Stop();
}
if (m_slave_read_timer->IsRunning()) {
m_slave_read_timer->Stop();
}
if (m_after_race_lost_timer->IsRunning()) {
m_after_race_lost_timer->Stop();
}
long long next = store_data.next_timeout.empty() ? 0 : std::stoll(store_data.next_timeout);
m_session->set_tokens(store_data.access_token, store_data.refresh_token, store_data.shared_session_key, next);
enqueue_id_action();
}
void UserAccountCommunication::on_store_read_request()
{
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__;
StoreData stored_data;
read_stored_data(stored_data);
if (stored_data.refresh_token.empty()) {
BOOST_LOG_TRIVIAL(warning) << "Store is empty - logging out.";
do_logout();
return;
}
std::string currrent_access_token = m_session->get_access_token();
if (currrent_access_token == stored_data.access_token)
{
BOOST_LOG_TRIVIAL(debug) << "Current token is up to date.";
return;
}
long long expires_in_second = stored_data.next_timeout.empty() ? 0 : std::stoll(stored_data.next_timeout) - std::time(nullptr);
const auto prior_expiration_secs = std::max(m_last_token_duration_seconds / 24, 10);
if (expires_in_second > 0 /*&& expires_in_second > prior_expiration_secs*/) {
BOOST_LOG_TRIVIAL(debug) << "Token is alive - using it.";
set_tokens(stored_data);
return;
} else {
BOOST_LOG_TRIVIAL(warning) << "Token read from store is expired!";
}
}
void UserAccountCommunication::on_polling_timer(wxTimerEvent& evt)
{

View File

@ -91,6 +91,7 @@ public:
void set_tokens(const StoreData store_data);
void on_race_lost(); // T5
void on_store_read_request();
private:
std::unique_ptr<UserAccountSession> m_session;
std::thread m_thread;