single slicer instance
check for other instances during startup send message with command line arguments if found and terminate listen for those messages and load objects from paths in messages from them
This commit is contained in:
parent
9cb5975956
commit
d828a1e80b
@ -160,6 +160,9 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
# Boost on Raspberry-Pi does not link to pthreads.
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
find_package(DBus REQUIRED)
|
||||
include_directories(${DBUS_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUXX)
|
||||
|
59
cmake/modules/FindDBus.cmake
Normal file
59
cmake/modules/FindDBus.cmake
Normal file
@ -0,0 +1,59 @@
|
||||
# - Try to find DBus
|
||||
# Once done, this will define
|
||||
#
|
||||
# DBUS_FOUND - system has DBus
|
||||
# DBUS_INCLUDE_DIRS - the DBus include directories
|
||||
# DBUS_LIBRARIES - link these to use DBus
|
||||
#
|
||||
# Copyright (C) 2012 Raphael Kubo da Costa <rakuco@webkit.org>
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND ITS CONTRIBUTORS ``AS
|
||||
# IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR ITS
|
||||
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||
# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
FIND_PACKAGE(PkgConfig)
|
||||
PKG_CHECK_MODULES(PC_DBUS QUIET dbus-1)
|
||||
|
||||
FIND_LIBRARY(DBUS_LIBRARIES
|
||||
NAMES dbus-1
|
||||
HINTS ${PC_DBUS_LIBDIR}
|
||||
${PC_DBUS_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
FIND_PATH(DBUS_INCLUDE_DIR
|
||||
NAMES dbus/dbus.h
|
||||
HINTS ${PC_DBUS_INCLUDEDIR}
|
||||
${PC_DBUS_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
GET_FILENAME_COMPONENT(_DBUS_LIBRARY_DIR ${DBUS_LIBRARIES} PATH)
|
||||
FIND_PATH(DBUS_ARCH_INCLUDE_DIR
|
||||
NAMES dbus/dbus-arch-deps.h
|
||||
HINTS ${PC_DBUS_INCLUDEDIR}
|
||||
${PC_DBUS_INCLUDE_DIRS}
|
||||
${_DBUS_LIBRARY_DIR}
|
||||
${DBUS_INCLUDE_DIR}
|
||||
PATH_SUFFIXES include
|
||||
)
|
||||
|
||||
SET(DBUS_INCLUDE_DIRS ${DBUS_INCLUDE_DIR} ${DBUS_ARCH_INCLUDE_DIR})
|
||||
|
||||
INCLUDE(FindPackageHandleStandardArgs)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(DBUS REQUIRED_VARS DBUS_INCLUDE_DIRS DBUS_LIBRARIES)
|
@ -51,13 +51,14 @@
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/3DScene.hpp"
|
||||
#include "slic3r/GUI/InstanceCheck.hpp"
|
||||
#include "slic3r/GUI/AppConfig.hpp"
|
||||
#endif /* SLIC3R_GUI */
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
int CLI::run(int argc, char **argv)
|
||||
{
|
||||
|
||||
#ifdef __WXGTK__
|
||||
// On Linux, wxGTK has no support for Wayland, and the app crashes on
|
||||
// startup if gtk3 is used. This env var has to be set explicitly to
|
||||
@ -524,6 +525,16 @@ int CLI::run(int argc, char **argv)
|
||||
#ifdef SLIC3R_GUI
|
||||
// #ifdef USE_WX
|
||||
GUI::GUI_App *gui = new GUI::GUI_App();
|
||||
|
||||
bool gui_single_instance_setting = gui->app_config->get("single_instance") == "1";
|
||||
if (Slic3r::instance_check(argc, argv, gui_single_instance_setting)) {
|
||||
//TODO: do we have delete gui and other stuff?
|
||||
return -1;
|
||||
}
|
||||
|
||||
//gui->app_config = app_config;
|
||||
//app_config = nullptr;
|
||||
|
||||
// gui->autosave = m_config.opt_string("autosave");
|
||||
GUI::GUI_App::SetInstance(gui);
|
||||
gui->CallAfter([gui, this, &load_configs] {
|
||||
|
@ -7,6 +7,8 @@
|
||||
#include <shellapi.h>
|
||||
#include <wchar.h>
|
||||
|
||||
|
||||
|
||||
#ifdef SLIC3R_GUI
|
||||
extern "C"
|
||||
{
|
||||
@ -216,7 +218,6 @@ int APIENTRY wWinMain(HINSTANCE /* hInstance */, HINSTANCE /* hPrevInstance */,
|
||||
int wmain(int argc, wchar_t **argv)
|
||||
{
|
||||
#endif
|
||||
|
||||
std::vector<wchar_t*> argv_extended;
|
||||
argv_extended.emplace_back(argv[0]);
|
||||
|
||||
|
@ -39,6 +39,11 @@ void PrintConfigDef::init_common_params()
|
||||
{
|
||||
ConfigOptionDef* def;
|
||||
|
||||
def = this->add("single_instance", coBool);
|
||||
def->label = L("Single Instance");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("printer_technology", coEnum);
|
||||
def->label = L("Printer technology");
|
||||
def->tooltip = L("Printer technology");
|
||||
|
@ -161,6 +161,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/DoubleSlider.hpp
|
||||
GUI/ObjectDataViewModel.cpp
|
||||
GUI/ObjectDataViewModel.hpp
|
||||
GUI/InstanceCheck.cpp
|
||||
GUI/InstanceCheck.hpp
|
||||
Utils/Http.cpp
|
||||
Utils/Http.hpp
|
||||
Utils/FixModelByWin10.cpp
|
||||
@ -195,6 +197,8 @@ if (APPLE)
|
||||
GUI/RemovableDriveManagerMM.mm
|
||||
GUI/RemovableDriveManagerMM.h
|
||||
GUI/Mouse3DHandlerMac.mm
|
||||
GUI/InstanceCheckMac.mm
|
||||
GUI/InstanceCheckMac.h
|
||||
)
|
||||
FIND_LIBRARY(DISKARBITRATION_LIBRARY DiskArbitration)
|
||||
|
||||
@ -206,6 +210,10 @@ encoding_check(libslic3r_gui)
|
||||
|
||||
target_link_libraries(libslic3r_gui libslic3r avrdude cereal imgui GLEW::GLEW OpenGL::GL OpenGL::GLU hidapi libcurl ${wxWidgets_LIBRARIES})
|
||||
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
target_link_libraries(libslic3r_gui ${DBUS_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
target_link_libraries(libslic3r_gui ${DISKARBITRATION_LIBRARY})
|
||||
endif()
|
||||
|
@ -69,6 +69,9 @@ void AppConfig::set_defaults()
|
||||
set("use_retina_opengl", "1");
|
||||
#endif
|
||||
|
||||
if (get("single_instance").empty())
|
||||
set("single_instance", "0");
|
||||
|
||||
if (get("remember_output_path").empty())
|
||||
set("remember_output_path", "1");
|
||||
|
||||
|
@ -50,6 +50,7 @@
|
||||
#include "UpdateDialogs.hpp"
|
||||
#include "Mouse3DController.hpp"
|
||||
#include "RemovableDriveManager.hpp"
|
||||
#include "InstanceCheck.hpp"
|
||||
|
||||
#ifdef __WXMSW__
|
||||
#include <dbt.h>
|
||||
@ -209,6 +210,17 @@ static void register_win32_device_notification_event()
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
wxWindow::MSWRegisterMessageHandler(WM_COPYDATA, [](wxWindow* win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) {
|
||||
|
||||
COPYDATASTRUCT* copy_data_structure = { 0 };
|
||||
copy_data_structure = (COPYDATASTRUCT*)lParam;
|
||||
if (copy_data_structure->dwData == 1) {
|
||||
LPCWSTR arguments = (LPCWSTR)copy_data_structure->lpData;
|
||||
Slic3r::GUI::wxGetApp().other_instance_message_handler()->handle_message(boost::nowide::narrow(arguments));
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
#endif // WIN32
|
||||
|
||||
@ -253,7 +265,11 @@ GUI_App::GUI_App()
|
||||
, m_imgui(new ImGuiWrapper())
|
||||
, m_wizard(nullptr)
|
||||
, m_removable_drive_manager(std::make_unique<RemovableDriveManager>())
|
||||
{}
|
||||
, m_other_instance_message_handler(std::make_unique<OtherInstanceMessageHandler>())
|
||||
{
|
||||
//app config initializes early becasuse it is used in instance checking in PrusaSlicer.cpp
|
||||
this->init_app_config();
|
||||
}
|
||||
|
||||
GUI_App::~GUI_App()
|
||||
{
|
||||
@ -284,6 +300,30 @@ bool GUI_App::init_opengl()
|
||||
}
|
||||
#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
|
||||
|
||||
void GUI_App::init_app_config()
|
||||
{
|
||||
// Profiles for the alpha are stored into the PrusaSlicer-alpha directory to not mix with the current release.
|
||||
SetAppName(SLIC3R_APP_KEY);
|
||||
//SetAppName(SLIC3R_APP_KEY "-beta");
|
||||
SetAppDisplayName(SLIC3R_APP_NAME);
|
||||
|
||||
// Set the Slic3r data directory at the Slic3r XS module.
|
||||
// Unix: ~/ .Slic3r
|
||||
// Windows : "C:\Users\username\AppData\Roaming\Slic3r" or "C:\Documents and Settings\username\Application Data\Slic3r"
|
||||
// Mac : "~/Library/Application Support/Slic3r"
|
||||
|
||||
if (data_dir().empty())
|
||||
set_data_dir(wxStandardPaths::Get().GetUserDataDir().ToUTF8().data());
|
||||
|
||||
if (!app_config)
|
||||
app_config = new AppConfig();
|
||||
|
||||
// load settings
|
||||
app_conf_exists = app_config->exists();
|
||||
if (app_conf_exists) {
|
||||
app_config->load();
|
||||
}
|
||||
}
|
||||
bool GUI_App::OnInit()
|
||||
{
|
||||
try {
|
||||
@ -301,34 +341,14 @@ bool GUI_App::on_init_inner()
|
||||
wxCHECK_MSG(wxDirExists(resources_dir), false,
|
||||
wxString::Format("Resources path does not exist or is not a directory: %s", resources_dir));
|
||||
|
||||
// Profiles for the alpha are stored into the PrusaSlicer-alpha directory to not mix with the current release.
|
||||
SetAppName(SLIC3R_APP_KEY);
|
||||
// SetAppName(SLIC3R_APP_KEY "-beta");
|
||||
SetAppDisplayName(SLIC3R_APP_NAME);
|
||||
|
||||
// Enable this to get the default Win32 COMCTRL32 behavior of static boxes.
|
||||
// Enable this to get the default Win32 COMCTRL32 behavior of static boxes.
|
||||
// wxSystemOptions::SetOption("msw.staticbox.optimized-paint", 0);
|
||||
// Enable this to disable Windows Vista themes for all wxNotebooks. The themes seem to lead to terrible
|
||||
// performance when working on high resolution multi-display setups.
|
||||
// Enable this to disable Windows Vista themes for all wxNotebooks. The themes seem to lead to terrible
|
||||
// performance when working on high resolution multi-display setups.
|
||||
// wxSystemOptions::SetOption("msw.notebook.themed-background", 0);
|
||||
|
||||
// Slic3r::debugf "wxWidgets version %s, Wx version %s\n", wxVERSION_STRING, wxVERSION;
|
||||
|
||||
// Set the Slic3r data directory at the Slic3r XS module.
|
||||
// Unix: ~/ .Slic3r
|
||||
// Windows : "C:\Users\username\AppData\Roaming\Slic3r" or "C:\Documents and Settings\username\Application Data\Slic3r"
|
||||
// Mac : "~/Library/Application Support/Slic3r"
|
||||
if (data_dir().empty())
|
||||
set_data_dir(wxStandardPaths::Get().GetUserDataDir().ToUTF8().data());
|
||||
|
||||
app_config = new AppConfig();
|
||||
|
||||
// load settings
|
||||
app_conf_exists = app_config->exists();
|
||||
if (app_conf_exists) {
|
||||
app_config->load();
|
||||
}
|
||||
|
||||
std::string msg = Http::tls_global_init();
|
||||
wxRichMessageDialog
|
||||
dlg(nullptr,
|
||||
@ -407,6 +427,8 @@ bool GUI_App::on_init_inner()
|
||||
if (! plater_)
|
||||
return;
|
||||
|
||||
//m_other_instance_message_handler->report();
|
||||
|
||||
if (app_config->dirty() && app_config->get("autosave") == "1")
|
||||
app_config->save();
|
||||
|
||||
|
@ -35,6 +35,7 @@ class PrintHostJobQueue;
|
||||
|
||||
namespace GUI{
|
||||
class RemovableDriveManager;
|
||||
class OtherInstanceMessageHandler;
|
||||
enum FileType
|
||||
{
|
||||
FT_STL,
|
||||
@ -108,7 +109,7 @@ class GUI_App : public wxApp
|
||||
std::unique_ptr<ImGuiWrapper> m_imgui;
|
||||
std::unique_ptr<PrintHostJobQueue> m_printhost_job_queue;
|
||||
ConfigWizard* m_wizard; // Managed by wxWindow tree
|
||||
|
||||
std::unique_ptr <OtherInstanceMessageHandler> m_other_instance_message_handler;
|
||||
public:
|
||||
bool OnInit() override;
|
||||
bool initialized() const { return m_initialized; }
|
||||
@ -196,6 +197,7 @@ public:
|
||||
std::vector<Tab *> tabs_list;
|
||||
|
||||
RemovableDriveManager* removable_drive_manager() { return m_removable_drive_manager.get(); }
|
||||
OtherInstanceMessageHandler* other_instance_message_handler() { return m_other_instance_message_handler.get(); }
|
||||
|
||||
ImGuiWrapper* imgui() { return m_imgui.get(); }
|
||||
|
||||
@ -211,6 +213,7 @@ public:
|
||||
|
||||
private:
|
||||
bool on_init_inner();
|
||||
void init_app_config();
|
||||
void window_pos_save(wxTopLevelWindow* window, const std::string &name);
|
||||
void window_pos_restore(wxTopLevelWindow* window, const std::string &name, bool default_maximized = false);
|
||||
void window_pos_sanitize(wxTopLevelWindow* window);
|
||||
|
495
src/slic3r/GUI/InstanceCheck.cpp
Normal file
495
src/slic3r/GUI/InstanceCheck.cpp
Normal file
@ -0,0 +1,495 @@
|
||||
#include "GUI_App.hpp"
|
||||
#include "InstanceCheck.hpp"
|
||||
|
||||
#include "boost/nowide/convert.hpp"
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <iostream>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
|
||||
#if __linux__
|
||||
#include <dbus/dbus.h> /* 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<std::mutex> 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<boost::filesystem::path> 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<boost::filesystem::path>(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 =
|
||||
" <!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.InstanceCheck\">"
|
||||
" <method name=\"AnotherInstace\">"
|
||||
" <arg name=\"data\" direction=\"in\" 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 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<std::mutex> 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
|
91
src/slic3r/GUI/InstanceCheck.hpp
Normal file
91
src/slic3r/GUI/InstanceCheck.hpp
Normal file
@ -0,0 +1,91 @@
|
||||
#ifndef slic3r_InstanceCheck_hpp_
|
||||
#define slic3r_InstanceCheck_hpp_
|
||||
|
||||
#include "Event.hpp"
|
||||
|
||||
#if _WIN32
|
||||
#include <windows.h>
|
||||
#endif //_WIN32
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <boost/thread.hpp>
|
||||
#include <tbb/mutex.h>
|
||||
#include <condition_variable>
|
||||
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
// checks for other running instances and sends them argv,
|
||||
// if there is --single-instance argument or AppConfig is set to single_instance=1
|
||||
// returns true if this instance should terminate
|
||||
bool instance_check(int argc, char** argv, bool app_config_single_instance);
|
||||
|
||||
#if __APPLE__
|
||||
// apple implementation of inner functions of instance_check
|
||||
// in InstanceCheckMac.mm
|
||||
void send_message_mac(const std::string msg);
|
||||
#endif //__APPLE__
|
||||
|
||||
namespace GUI {
|
||||
|
||||
#if __linux__
|
||||
#define BACKGROUND_MESSAGE_LISTENER
|
||||
#endif // __linux__
|
||||
|
||||
using LoadFromOtherInstanceEvent = Event<std::vector<boost::filesystem::path>>;
|
||||
wxDECLARE_EVENT(EVT_LOAD_MODEL_OTHER_INSTANCE, LoadFromOtherInstanceEvent);
|
||||
|
||||
using InstanceGoToFrontEvent = SimpleEvent;
|
||||
wxDECLARE_EVENT(EVT_INSTANCE_GO_TO_FRONT, InstanceGoToFrontEvent);
|
||||
|
||||
class OtherInstanceMessageHandler
|
||||
{
|
||||
public:
|
||||
OtherInstanceMessageHandler() = default;
|
||||
OtherInstanceMessageHandler(OtherInstanceMessageHandler const&) = delete;
|
||||
void operator=(OtherInstanceMessageHandler const&) = delete;
|
||||
~OtherInstanceMessageHandler() { assert(!m_initialized); }
|
||||
|
||||
// inits listening, on each platform different. On linux starts background thread
|
||||
void init(wxEvtHandler* callback_evt_handler);
|
||||
// stops listening, on linux stops the background thread
|
||||
void shutdown();
|
||||
|
||||
//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);
|
||||
private:
|
||||
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();
|
||||
#endif //BACKGROUND_MESSAGE_LISTENER
|
||||
|
||||
#if __APPLE__
|
||||
//implemented at InstanceCheckMac.mm
|
||||
void register_for_messages();
|
||||
void unregister_for_messages();
|
||||
// Opaque pointer to RemovableDriveManagerMM
|
||||
void* m_impl_osx;
|
||||
#endif //__APPLE__
|
||||
|
||||
};
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
#endif // slic3r_InstanceCheck_hpp_
|
8
src/slic3r/GUI/InstanceCheckMac.h
Normal file
8
src/slic3r/GUI/InstanceCheckMac.h
Normal file
@ -0,0 +1,8 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface OtherInstanceMessageHandlerMac : NSObject
|
||||
|
||||
-(instancetype) init;
|
||||
-(void) add_observer;
|
||||
-(void) message_update:(NSNotification *)note;
|
||||
@end
|
68
src/slic3r/GUI/InstanceCheckMac.mm
Normal file
68
src/slic3r/GUI/InstanceCheckMac.mm
Normal file
@ -0,0 +1,68 @@
|
||||
#import "InstanceCheck.hpp"
|
||||
#import "InstanceCheckMac.h"
|
||||
#import "GUI_App.hpp"
|
||||
|
||||
@implementation OtherInstanceMessageHandlerMac
|
||||
|
||||
-(instancetype) init
|
||||
{
|
||||
self = [super init];
|
||||
return self;
|
||||
}
|
||||
-(void)add_observer
|
||||
{
|
||||
NSLog(@"adding observer");
|
||||
[[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(message_update:) name:@"OtherPrusaSlicerInstanceMessage" object:nil suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately];
|
||||
}
|
||||
|
||||
-(void)message_update:(NSNotification *)msg
|
||||
{
|
||||
//NSLog(@"recieved msg %@", msg);
|
||||
//demiaturize all windows
|
||||
for(NSWindow* win in [NSApp windows])
|
||||
{
|
||||
if([win isMiniaturized])
|
||||
{
|
||||
[win deminiaturize:self];
|
||||
}
|
||||
}
|
||||
//bring window to front
|
||||
[[NSApplication sharedApplication] activateIgnoringOtherApps : YES];
|
||||
//pass message
|
||||
Slic3r::GUI::wxGetApp().other_instance_message_handler()->handle_message(std::string([msg.userInfo[@"data"] UTF8String]));
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void send_message_mac(const std::string msg)
|
||||
{
|
||||
NSString *nsmsg = [NSString stringWithCString:msg.c_str() encoding:[NSString defaultCStringEncoding]];
|
||||
//NSLog(@"sending msg %@", nsmsg);
|
||||
[[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"OtherPrusaSlicerInstanceMessage" object:nil userInfo:[NSDictionary dictionaryWithObject:nsmsg forKey:@"data"] deliverImmediately:YES];
|
||||
}
|
||||
|
||||
namespace GUI {
|
||||
void OtherInstanceMessageHandler::register_for_messages()
|
||||
{
|
||||
m_impl_osx = [[OtherInstanceMessageHandlerMac alloc] init];
|
||||
if(m_impl_osx) {
|
||||
[m_impl_osx add_observer];
|
||||
}
|
||||
}
|
||||
|
||||
void OtherInstanceMessageHandler::unregister_for_messages()
|
||||
{
|
||||
//NSLog(@"unreegistering other instance messages");
|
||||
if (m_impl_osx) {
|
||||
[m_impl_osx release];
|
||||
m_impl_osx = nullptr;
|
||||
} else {
|
||||
NSLog(@"unreegister not required");
|
||||
}
|
||||
}
|
||||
}//namespace GUI
|
||||
}//namespace Slicer
|
||||
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include "GUI_ObjectList.hpp"
|
||||
#include "Mouse3DController.hpp"
|
||||
#include "RemovableDriveManager.hpp"
|
||||
#include "InstanceCheck.hpp"
|
||||
#include "I18N.hpp"
|
||||
|
||||
#include <fstream>
|
||||
@ -234,7 +235,8 @@ void MainFrame::shutdown()
|
||||
|
||||
// Stop the background thread of the removable drive manager, so that no new updates will be sent to the Plater.
|
||||
wxGetApp().removable_drive_manager()->shutdown();
|
||||
|
||||
//stop listening for messages from other instances
|
||||
wxGetApp().other_instance_message_handler()->shutdown();
|
||||
// Save the slic3r.ini.Usually the ini file is saved from "on idle" callback,
|
||||
// but in rare cases it may not have been called yet.
|
||||
wxGetApp().app_config->save();
|
||||
|
@ -74,6 +74,8 @@
|
||||
#include "../Utils/FixModelByWin10.hpp"
|
||||
#include "../Utils/UndoRedo.hpp"
|
||||
#include "RemovableDriveManager.hpp"
|
||||
#include "InstanceCheck.hpp"
|
||||
|
||||
#if ENABLE_NON_STATIC_CANVAS_MANAGER
|
||||
#ifdef __APPLE__
|
||||
#include "Gizmos/GLGizmosManager.hpp"
|
||||
@ -1946,6 +1948,28 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
||||
|
||||
// Initialize the Undo / Redo stack with a first snapshot.
|
||||
this->take_snapshot(_L("New Project"));
|
||||
|
||||
this->q->Bind(EVT_LOAD_MODEL_OTHER_INSTANCE, [this](LoadFromOtherInstanceEvent &evt) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "received load from other instance event ";
|
||||
this->load_files(evt.data, true, true);
|
||||
});
|
||||
this->q->Bind(EVT_INSTANCE_GO_TO_FRONT, [this](InstanceGoToFrontEvent &) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "prusaslicer window going forward";
|
||||
//this code maximize app window on Fedora
|
||||
wxGetApp().mainframe->Iconize(false);
|
||||
if (wxGetApp().mainframe->IsMaximized())
|
||||
wxGetApp().mainframe->Maximize(true);
|
||||
else
|
||||
wxGetApp().mainframe->Maximize(false);
|
||||
//this code (without code above) maximize window on Ubuntu
|
||||
wxGetApp().mainframe->Restore();
|
||||
wxGetApp().GetTopWindow()->SetFocus(); // focus on my window
|
||||
wxGetApp().GetTopWindow()->Raise(); // bring window to front
|
||||
wxGetApp().GetTopWindow()->Show(true); // show the window
|
||||
|
||||
});
|
||||
wxGetApp().other_instance_message_handler()->init(this->q);
|
||||
|
||||
}
|
||||
|
||||
Plater::priv::~priv()
|
||||
|
@ -100,6 +100,13 @@ void PreferencesDialog::build()
|
||||
option = Option (def,"show_incompatible_presets");
|
||||
m_optgroup_general->append_single_option_line(option);
|
||||
|
||||
def.label = L("Single Instance");
|
||||
def.type = coBool;
|
||||
def.tooltip = L("If this is enabled, when staring PrusaSlicer and another instance is running, that instance will be reactivated instead.");
|
||||
def.set_default_value(new ConfigOptionBool{ app_config->has("single_instance") ? app_config->get("single_instance") == "1" : false });
|
||||
option = Option(def, "single_instance");
|
||||
m_optgroup_general->append_single_option_line(option);
|
||||
|
||||
#if __APPLE__
|
||||
def.label = L("Use Retina resolution for the 3D scene");
|
||||
def.type = coBool;
|
||||
@ -177,6 +184,8 @@ void PreferencesDialog::accept()
|
||||
app_config->set(it->first, it->second);
|
||||
}
|
||||
|
||||
app_config->save();
|
||||
|
||||
EndModal(wxID_OK);
|
||||
|
||||
// Nothify the UI to update itself from the ini file.
|
||||
|
Loading…
Reference in New Issue
Block a user