|
|
|
@ -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
|
|
|
|
|