#include "GUI_App.hpp" #include "InstanceCheck.hpp" #include "boost/nowide/convert.hpp" #include #include #include #include #if __linux__ #include /* Pull in all of D-Bus headers. */ #endif //__linux__ namespace Slic3r { namespace instance_check_internal { struct CommandLineAnalysis { bool should_send; std::string cl_string; }; static CommandLineAnalysis process_command_line(int argc, char** argv) //d:\3dmodels\Klapka\Klapka.3mf { CommandLineAnalysis ret { false }; if (argc < 2) return ret; ret.cl_string = argv[0]; for (size_t i = 1; i < argc; i++) { std::string token = argv[i]; if (token == "--single-instance") { ret.should_send = true; } else { ret.cl_string += " "; ret.cl_string += token; } } BOOST_LOG_TRIVIAL(debug) << "single instance: "<< ret.should_send << ". other params: " << ret.cl_string; return ret; } } //namespace instance_check_internal #if _WIN32 namespace instance_check_internal { static HWND l_prusa_slicer_hwnd; static BOOL CALLBACK EnumWindowsProc(_In_ HWND hwnd, _In_ LPARAM lParam) { //checks for other instances of prusaslicer, if found brings it to front and return false to stop enumeration and quit this instance //search is done by classname(wxWindowNR is wxwidgets thing, so probably not unique) and name in window upper panel //other option would be do a mutex and check for its existence TCHAR wndText[1000]; TCHAR className[1000]; GetClassName(hwnd, className, 1000); GetWindowText(hwnd, wndText, 1000); std::wstring classNameString(className); std::wstring wndTextString(wndText); if (wndTextString.find(L"PrusaSlicer") != std::wstring::npos && classNameString == L"wxWindowNR") { l_prusa_slicer_hwnd = hwnd; ShowWindow(hwnd, SW_SHOWMAXIMIZED); SetForegroundWindow(hwnd); return false; } return true; } static void send_message(const HWND hwnd) { LPWSTR command_line_args = GetCommandLine(); //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(command_line_args) + 1); data_to_send.lpData = command_line_args; SendMessage(hwnd, WM_COPYDATA, 0, (LPARAM)&data_to_send); } } //namespace instance_check_internal bool instance_check(int argc, char** argv, bool app_config_single_instance) { instance_check_internal::CommandLineAnalysis cla = instance_check_internal::process_command_line(argc, argv); if (cla.should_send || app_config_single_instance) { // Call EnumWidnows with own callback. cons: Based on text in the name of the window and class name which is generic. if (!EnumWindows(instance_check_internal::EnumWindowsProc, 0)) { BOOST_LOG_TRIVIAL(info) << "instance check: Another instance found. This instance will terminate."; instance_check_internal::send_message(instance_check_internal::l_prusa_slicer_hwnd); return true; } } BOOST_LOG_TRIVIAL(info) << "instance check: Another instance not found or single-instance not set."; return false; } #elif defined(__APPLE__) namespace instance_check_internal { static int get_lock() { struct flock fl; int fdlock; fl.l_type = F_WRLCK; fl.l_whence = SEEK_SET; fl.l_start = 0; fl.l_len = 1; if ((fdlock = open("/tmp/prusaslicer.lock", O_WRONLY | O_CREAT, 0666)) == -1) return 0; if (fcntl(fdlock, F_SETLK, &fl) == -1) return 0; return 1; } } //namespace instance_check_internal bool instance_check(int argc, char** argv, bool app_config_single_instance) { instance_check_internal::CommandLineAnalysis cla = instance_check_internal::process_command_line(argc, argv); if (!instance_check_internal::get_lock() && (cla.should_send || app_config_single_instance)) { BOOST_LOG_TRIVIAL(info) << "instance check: Another instance found. This instance will terminate."; send_message_mac(cla.cl_string); return true; } BOOST_LOG_TRIVIAL(info) << "instance check: Another instance not found or single-instance not set."; return false; } #elif defined(__linux__) namespace instance_check_internal { static int get_lock() { struct flock fl; int fdlock; fl.l_type = F_WRLCK; fl.l_whence = SEEK_SET; fl.l_start = 0; fl.l_len = 1; if ((fdlock = open("/tmp/prusaslicer.lock", O_WRONLY | O_CREAT, 0666)) == -1) return 0; if (fcntl(fdlock, F_SETLK, &fl) == -1) return 0; return 1; } static void send_message(std::string message_text) { DBusMessage* msg; DBusMessageIter args; DBusConnection* conn; DBusError err; dbus_uint32_t serial = 0; const char* sigval = message_text.c_str(); std::string interface_name = "com.prusa3d.prusaslicer.InstanceCheck"; std::string method_name = "AnotherInstace"; std::string object_name = "/com/prusa3d/prusaslicer/InstanceCheck"; // 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; } if (NULL == conn) { BOOST_LOG_TRIVIAL(error) << "DBus Connection is NULL. Message to another instance wont be send."; return; } //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; } //the AnotherInstace 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; } // 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; } 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); } } //namespace instance_check_internal bool instance_check(int argc, char** argv, bool app_config_single_instance) { instance_check_internal::CommandLineAnalysis cla = instance_check_internal::process_command_line(argc, argv); if (!instance_check_internal::get_lock() && (cla.should_send || app_config_single_instance)) { BOOST_LOG_TRIVIAL(info) << "instance check: Another instance found. This instance will terminate."; instance_check_internal::send_message(cla.cl_string); return true; } BOOST_LOG_TRIVIAL(info) << "instance check: Another instance not found or single-instance not set."; return false; } #endif //_WIN32/__APPLE__/__linux__ namespace GUI { wxDEFINE_EVENT(EVT_LOAD_MODEL_OTHER_INSTANCE, LoadFromOtherInstanceEvent); wxDEFINE_EVENT(EVT_INSTANCE_GO_TO_FRONT, InstanceGoToFrontEvent); void OtherInstanceMessageHandler::init(wxEvtHandler* callback_evt_handler) { assert(!m_initialized); assert(m_callback_evt_handler == nullptr); if (m_initialized) return; m_initialized = true; m_callback_evt_handler = callback_evt_handler; #if _WIN32 //create_listener_window(); #endif //_WIN32 #if defined(__APPLE__) this->register_for_messages(); #endif //__APPLE__ #ifdef BACKGROUND_MESSAGE_LISTENER m_thread = boost::thread((boost::bind(&OtherInstanceMessageHandler::listen, this))); #endif //BACKGROUND_MESSAGE_LISTENER } void OtherInstanceMessageHandler::shutdown() { BOOST_LOG_TRIVIAL(debug) << "message handler shutdown()."; assert(m_initialized); if (m_initialized) { #if __APPLE__ //delete macos implementation this->unregister_for_messages(); #endif //__APPLE__ #ifdef BACKGROUND_MESSAGE_LISTENER if (m_thread.joinable()) { // Stop the worker thread, if running. { // Notify the worker thread to cancel wait on detection polling. std::lock_guard lck(m_thread_stop_mutex); m_stop = true; } m_thread_stop_condition.notify_all(); // Wait for the worker thread to stop. m_thread.join(); m_stop = false; } #endif //BACKGROUND_MESSAGE_LISTENER m_initialized = false; } } namespace MessageHandlerInternal { // returns ::path to possible model or empty ::path if input string is not existing path static boost::filesystem::path get_path(const std::string possible_path) { BOOST_LOG_TRIVIAL(debug) << "message part: " << possible_path; if (possible_path.empty() || possible_path.size() < 3) { BOOST_LOG_TRIVIAL(debug) << "empty"; return boost::filesystem::path(); } if (boost::filesystem::exists(possible_path)) { BOOST_LOG_TRIVIAL(debug) << "is path"; return boost::filesystem::path(possible_path); } else if (possible_path[0] == '\"') { if(boost::filesystem::exists(possible_path.substr(1, possible_path.size() - 2))) { BOOST_LOG_TRIVIAL(debug) << "is path in quotes"; return boost::filesystem::path(possible_path.substr(1, possible_path.size() - 2)); } } BOOST_LOG_TRIVIAL(debug) << "is NOT path"; return boost::filesystem::path(); } } //namespace MessageHandlerInternal void OtherInstanceMessageHandler::handle_message(const std::string message) { std::vector paths; auto next_space = message.find(' '); size_t last_space = 0; int counter = 0; BOOST_LOG_TRIVIAL(info) << "message from other instance: " << message; while (next_space != std::string::npos) { if (counter != 0) { const std::string possible_path = message.substr(last_space, next_space - last_space); boost::filesystem::path p = MessageHandlerInternal::get_path(possible_path); if(!p.string().empty()) paths.emplace_back(p); } last_space = next_space; next_space = message.find(' ', last_space + 1); counter++; } if (counter != 0 ) { boost::filesystem::path p = MessageHandlerInternal::get_path(message.substr(last_space + 1)); if (!p.string().empty()) paths.emplace_back(p); } 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(std::move(paths)))); //} } } #ifdef BACKGROUND_MESSAGE_LISTENER namespace MessageHandlerDBusInternal { //reply to introspect makes our DBus object visible for other programs like D-Feet static void respond_to_introspect(DBusConnection *connection, DBusMessage *request) { DBusMessage *reply; const char *introspection_data = " " " " " " " " " " " " " " " " " " " " " " " " " " " "; 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 AnotherInstance receives message from another PrusaSlicer instance static void handle_method_another_instance(DBusConnection *connection, DBusMessage *request) { DBusError err; char* text= ""; 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(trace) << "Dbus method AnotherInstance received with wrong arguments."; dbus_error_free(&err); return; } wxGetApp().other_instance_message_handler()->handle_message(text); evt_handler = wxGetApp().plater(); if (evt_handler) { wxPostEvent(evt_handler, InstanceGoToFrontEvent(EVT_INSTANCE_GO_TO_FRONT)); } } //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); 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); return DBUS_HANDLER_RESULT_HANDLED; } else if (0 == strcmp("com.prusa3d.prusaslicer.InstanceCheck", interface_name) && 0 == strcmp("AnotherInstace", member_name)) { handle_method_another_instance(connection, message); return DBUS_HANDLER_RESULT_HANDLED; } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } } //namespace MessageHandlerDBusInternal void OtherInstanceMessageHandler::listen() { DBusConnection* conn; DBusError err; int name_req_val; DBusObjectPathVTable vtable; std::string interface_name = "com.prusa3d.prusaslicer.InstanceCheck"; std::string object_name = "/com/prusa3d/prusaslicer/InstanceCheck"; 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 = MessageHandlerDBusInternal::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(trace) << "Dbus object registered. Starting listening for messages."; for (;;) { // Wait for 1 second // Cancellable. { std::unique_lock lck(m_thread_stop_mutex); m_thread_stop_condition.wait_for(lck, std::chrono::seconds(1), [this] { return m_stop; }); } if (m_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); } #endif //BACKGROUND_MESSAGE_LISTENER } // namespace GUI } // namespace Slic3r