Skip to content

Commit

Permalink
Printables.com support (associate prusaslicer:// link explicitly) (#5416
Browse files Browse the repository at this point in the history
)

* Enable ability to open `prusaslicer://` links

* Add needed function to miniz

* Import Zip Functionality

Allows zip file to be drag and dropped or imported via the menu option

Based on prusa3d/PrusaSlicer@ce38e57 and current master branch files

* Update dialog style to match Orca

* Ensure link is from printables

* add toggle option in preferences

doesn't actually control anything yet

* Add Downloader classes

As-is from PS master

* Create Orca Styled Variant of Icons

* Add Icons to ImGui

* Use PS's Downloader impl for `prusaslicer://` links

* Implement URL Registering on Windows

* Implement prusaslicer:// link on macOS

* Remove unnecessary class name qualifier in Plater.hpp

* Add downloader desktop integration registration and undo

* Revert Info.plist

* register prusaslicer:// on user request only

* fix build error

* fix single instance problem

* format

* add orcalicer:// handler for Mac
Attempt to add Linux support but seems not working

---------

Co-authored-by: Ocraftyone <[email protected]>
  • Loading branch information
SoftFever and Ocraftyone authored May 22, 2024
1 parent a764d83 commit 7725fbe
Show file tree
Hide file tree
Showing 13 changed files with 111 additions and 60 deletions.
1 change: 1 addition & 0 deletions cmake/modules/MacOSXBundleInfo.plist.in
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
<key>CFBundleURLSchemes</key>
<array>
<string>orcasliceropen</string>
<string>orcaslicer</string>
</array>
</dict>
</array>
Expand Down
5 changes: 0 additions & 5 deletions src/libslic3r/AppConfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -333,11 +333,6 @@ void AppConfig::set_defaults()
set("download_path", "");
}

// Orca
if (get("ps_url_registered").empty()) {
set_bool("ps_url_registered", false);
}

if (get("mouse_wheel").empty()) {
set("mouse_wheel", "0");
}
Expand Down
1 change: 1 addition & 0 deletions src/platform/osx/Info.plist.in
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<key>CFBundleURLSchemes</key>
<array>
<string>orcasliceropen</string>
<string>orcaslicer</string>
</array>
</dict>
</array>
Expand Down
16 changes: 8 additions & 8 deletions src/slic3r/GUI/DesktopIntegrationDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,7 @@ void DesktopIntegrationDialog::perform_downloader_desktop_integration()

std::string desktop_file_downloader = GUI::format(
"[Desktop Entry]\n"
"Name=PrusaSlicer URL Protocol%1%\n"
"Name=OrcaSlicer URL Protocol%1%\n"
"Exec=\"%2%\" %%u\n"
"Terminal=false\n"
"Type=Application\n"
Expand All @@ -541,12 +541,12 @@ void DesktopIntegrationDialog::perform_downloader_desktop_integration()
, name_suffix, excutable_path);

// desktop file for downloader as part of main app
std::string desktop_path = GUI::format("%1%/applications/PrusaSlicerURLProtocol%2%.desktop", target_dir_desktop, version_suffix);
std::string desktop_path = GUI::format("%1%/applications/OrcaSlicerURLProtocol%2%.desktop", target_dir_desktop, version_suffix);
if (create_desktop_file(desktop_path, desktop_file_downloader)) {
// save path to desktop file
app_config->set("desktop_integration_URL_path", desktop_path);
// finish registration on mime type
std::string command = GUI::format("xdg-mime default PrusaSlicerURLProtocol%1%.desktop x-scheme-handler/prusaslicer", version_suffix);
std::string command = GUI::format("xdg-mime default OrcaSlicerURLProtocol%1%.desktop x-scheme-handler/prusaslicer", version_suffix);
BOOST_LOG_TRIVIAL(debug) << "system command: " << command;
int r = system(command.c_str());
BOOST_LOG_TRIVIAL(debug) << "system result: " << r;
Expand All @@ -558,16 +558,16 @@ void DesktopIntegrationDialog::perform_downloader_desktop_integration()
if (contains_path_dir(target_candidates[i], "applications")) {
target_dir_desktop = target_candidates[i];
// Write slicer desktop file
std::string path = GUI::format("%1%/applications/PrusaSlicerURLProtocol%2%.desktop", target_dir_desktop, version_suffix);
std::string path = GUI::format("%1%/applications/OrcaSlicerURLProtocol%2%.desktop", target_dir_desktop, version_suffix);
if (create_desktop_file(path, desktop_file_downloader)) {
app_config->set("desktop_integration_URL_path", path);
candidate_found = true;
BOOST_LOG_TRIVIAL(debug) << "PrusaSlicerURLProtocol.desktop file installation success.";
BOOST_LOG_TRIVIAL(debug) << "OrcaSlicerURLProtocol.desktop file installation success.";
break;
}
else {
// write failed - try another path
BOOST_LOG_TRIVIAL(debug) << "Attempt to PrusaSlicerURLProtocol.desktop file installation failed. failed path: " << target_candidates[i];
BOOST_LOG_TRIVIAL(debug) << "Attempt to OrcaSlicerURLProtocol.desktop file installation failed. failed path: " << target_candidates[i];
target_dir_desktop.clear();
}
}
Expand All @@ -578,7 +578,7 @@ void DesktopIntegrationDialog::perform_downloader_desktop_integration()
create_path(boost::nowide::narrow(wxFileName::GetHomeDir()), ".local/share/applications");
// create desktop file
target_dir_desktop = GUI::format("%1%/.local/share", wxFileName::GetHomeDir());
std::string path = GUI::format("%1%/applications/PrusaSlicerURLProtocol%2%.desktop", target_dir_desktop, version_suffix);
std::string path = GUI::format("%1%/applications/OrcaSlicerURLProtocol%2%.desktop", target_dir_desktop, version_suffix);
if (contains_path_dir(target_dir_desktop, "applications")) {
if (!create_desktop_file(path, desktop_file_downloader)) {
// Desktop file not written - end desktop integration
Expand All @@ -602,7 +602,7 @@ void DesktopIntegrationDialog::perform_downloader_desktop_integration()
}

// finish registration on mime type
std::string command = GUI::format("xdg-mime default PrusaSlicerURLProtocol%1%.desktop x-scheme-handler/prusaslicer", version_suffix);
std::string command = GUI::format("xdg-mime default OrcaSlicerURLProtocol%1%.desktop x-scheme-handler/prusaslicer", version_suffix);
BOOST_LOG_TRIVIAL(debug) << "system command: " << command;
int r = system(command.c_str());
BOOST_LOG_TRIVIAL(debug) << "system result: " << r;
Expand Down
27 changes: 11 additions & 16 deletions src/slic3r/GUI/Downloader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,6 @@ void Downloader::start_download(const std::string& full_url)
{
assert(m_initialized);

// Orca: check if url is registered
if (!wxGetApp().app_config->has("ps_url_registered") || !wxGetApp().app_config->get_bool("ps_url_registered")) {
BOOST_LOG_TRIVIAL(error) << "PrusaSlicer links are not enabled. Download aborted: " << full_url;
show_error(nullptr, "PrusaSlicer links are not enabled in preferences. Download aborted.");
return;
}

// Orca: Move to the 3D view
MainFrame* mainframe = wxGetApp().mainframe;
Plater* plater = wxGetApp().plater();
Expand All @@ -155,7 +148,7 @@ void Downloader::start_download(const std::string& full_url)

// Orca: Replace PS workaround for "mysterious slash" with a more dynamic approach
// Windows seems to have fixed the issue and this provides backwards compatability for those it still affects
boost::regex re(R"(^prusaslicer:\/\/open[\/]?\?file=)", boost::regbase::icase);
boost::regex re(R"(^(orcaslicer|prusaslicer):\/\/open[\/]?\?file=)", boost::regbase::icase);
boost::smatch results;

if (!boost::regex_search(full_url, results, re)) {
Expand All @@ -168,14 +161,16 @@ void Downloader::start_download(const std::string& full_url)
}
size_t id = get_next_id();
std::string escaped_url = FileGet::escape_url(full_url.substr(results.length()));
if (!boost::starts_with(escaped_url, "https://") || !FileGet::is_subdomain(escaped_url, "printables.com")) {
std::string msg = format(_L("Download won't start. Download URL doesn't point to https://printables.com : %1%"), escaped_url);
BOOST_LOG_TRIVIAL(error) << msg;
NotificationManager* ntf_mngr = wxGetApp().notification_manager();
ntf_mngr->push_notification(NotificationType::CustomNotification, NotificationManager::NotificationLevel::ErrorNotificationLevel,
"Download failed. Download URL doesn't point to https://printables.com.");
return;
}
// Orca:: any website that supports orcaslicer://open/?file= can be downloaded

// if (!boost::starts_with(escaped_url, "https://") || !FileGet::is_subdomain(escaped_url, "printables.com")) {
// std::string msg = format(_L("Download won't start. Download URL doesn't point to https://printables.com : %1%"), escaped_url);
// BOOST_LOG_TRIVIAL(error) << msg;
// NotificationManager* ntf_mngr = wxGetApp().notification_manager();
// ntf_mngr->push_notification(NotificationType::CustomNotification, NotificationManager::NotificationLevel::ErrorNotificationLevel,
// "Download failed. Download URL doesn't point to https://printables.com.");
// return;
// }

std::string text(escaped_url);
m_downloads.emplace_back(std::make_unique<Download>(id, std::move(escaped_url), this, m_dest_folder));
Expand Down
14 changes: 7 additions & 7 deletions src/slic3r/GUI/DownloaderFileGet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -201,12 +201,12 @@ void FileGet::priv::get_perform()
return;
}

std:: string range_string = std::to_string(m_written) + "-";
std:: string range_string = std::to_string(m_written) + "-";

size_t written_previously = m_written;
size_t written_this_session = 0;
Http::get(m_url)
.size_limit(DOWNLOAD_SIZE_LIMIT) //more?
size_t written_previously = m_written;
size_t written_this_session = 0;
Http::get(m_url)
.size_limit(DOWNLOAD_SIZE_LIMIT) //more?
.set_range(range_string)
.on_progress([&](Http::Progress progress, bool& cancel) {
// to prevent multiple calls into following ifs (m_cancel / m_pause)
Expand Down Expand Up @@ -244,7 +244,7 @@ void FileGet::priv::get_perform()
}

if (progress.dlnow != 0) {
if (progress.dlnow - written_this_session > DOWNLOAD_MAX_CHUNK_SIZE || progress.dlnow == progress.dltotal) {
if (progress.dlnow - written_this_session > DOWNLOAD_MAX_CHUNK_SIZE || progress.dlnow == progress.dltotal) {
try
{
std::string part_for_write = progress.buffer.substr(written_this_session, progress.dlnow);
Expand All @@ -264,7 +264,7 @@ void FileGet::priv::get_perform()
m_written = written_previously + written_this_session;
}
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_PROGRESS);
int percent_total = (written_previously + progress.dlnow) * 100 / m_absolute_size;
int percent_total = m_absolute_size == 0 ? 0 : (written_previously + progress.dlnow) * 100 / m_absolute_size;
evt->SetString(std::to_string(percent_total));
evt->SetInt(m_id);
m_evt_handler->QueueEvent(evt);
Expand Down
51 changes: 38 additions & 13 deletions src/slic3r/GUI/GUI_App.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -821,7 +821,8 @@ void GUI_App::post_init()
(boost::starts_with(this->init_params->input_files.front(), "orcaslicer://open") ||
boost::starts_with(this->init_params->input_files.front(), "prusaslicer://open"))) {

if (boost::starts_with(this->init_params->input_files.front(), "prusaslicer://open")) {
if (boost::starts_with(this->init_params->input_files.front(), "orcaslicer://open")||
boost::starts_with(this->init_params->input_files.front(), "prusaslicer://open")) {
switch_to_3d = true;
start_download(this->init_params->input_files.front());
} else if (vector<string> input_str_arr = split_str(this->init_params->input_files.front(), "orcaslicer://open/?file="); input_str_arr.size() > 1) {
Expand Down Expand Up @@ -1098,7 +1099,7 @@ GUI_App::GUI_App()
, m_em_unit(10)
, m_imgui(new ImGuiWrapper())
, m_removable_drive_manager(std::make_unique<RemovableDriveManager>())
, m_downloader(std::make_unique<Downloader>())
, m_downloader(std::make_unique<Downloader>())
, m_other_instance_message_handler(std::make_unique<OtherInstanceMessageHandler>())
{
//app config initializes early becasuse it is used in instance checking in OrcaSlicer.cpp
Expand Down Expand Up @@ -2371,10 +2372,8 @@ bool GUI_App::on_init_inner()
associate_files(L"step");
associate_files(L"stp");
}
// Orca: add PS url functionality
if (app_config->get_bool("ps_url_registered")) {
associate_url(L"prusaslicer");
}
associate_url(L"orcaslicer");

if (app_config->get("associate_gcode") == "true")
associate_files(L"gcode");
#endif // __WXMSW__
Expand Down Expand Up @@ -5533,10 +5532,7 @@ void GUI_App::open_preferences(size_t open_on_tab, const std::string& highlight_
associate_files(L"step");
associate_files(L"stp");
}
// Orca: add PS url functionality
if (app_config->get_bool("ps_url_registered")) {
associate_url(L"prusaslicer");
}
associate_url(L"orcaslicer");
}
else {
if (app_config->get("associate_gcode") == "true")
Expand Down Expand Up @@ -6577,9 +6573,11 @@ static bool del_win_registry(HKEY hkeyHive, const wchar_t *pszVar, const wchar_t
return false;
}

#endif // __WXMSW__

void GUI_App::associate_files(std::wstring extend)
{
#ifdef WIN32
wchar_t app_path[MAX_PATH];
::GetModuleFileNameW(nullptr, app_path, sizeof(app_path));

Expand All @@ -6599,10 +6597,12 @@ void GUI_App::associate_files(std::wstring extend)
if (is_new)
// notify Windows only when any of the values gets changed
::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
#endif // WIN32
}

void GUI_App::disassociate_files(std::wstring extend)
{
#ifdef WIN32
wchar_t app_path[MAX_PATH];
::GetModuleFileNameW(nullptr, app_path, sizeof(app_path));

Expand All @@ -6629,12 +6629,33 @@ void GUI_App::disassociate_files(std::wstring extend)

if (is_new)
::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
#endif // WIN32
}

void GUI_App::associate_url(std::wstring url_prefix)
bool GUI_App::check_url_association(std::wstring url_prefix, std::wstring& reg_bin)
{
// Registry key creation for "prusaslicer://" URL
reg_bin = L"";
#ifdef WIN32
wxRegKey key_full(wxRegKey::HKCU, "Software\\Classes\\" + url_prefix + "\\shell\\open\\command");
if (!key_full.Exists()) {
return false;
}
reg_bin = key_full.QueryDefaultValue().ToStdWstring();

boost::filesystem::path binary_path(boost::filesystem::canonical(boost::dll::program_location()));
// wxString wbinary = wxString::FromUTF8(binary_path.string());
// std::string binary_string = (boost::format("%1%") % wbinary).str();
std::wstring key_string = L"\"" + binary_path.wstring() + L"\" L\"%1\"";
// return boost::iequals(key_string,(boost::format("%1%") % reg_bin).str());
return key_string == reg_bin;
#else
return false;
#endif // WIN32
}

void GUI_App::associate_url(std::wstring url_prefix)
{
#ifdef WIN32
boost::filesystem::path binary_path(boost::filesystem::canonical(boost::dll::program_location()));
// the path to binary needs to be correctly saved in string with respect to localized characters
wxString wbinary = wxString::FromUTF8(binary_path.string());
Expand All @@ -6654,18 +6675,22 @@ void GUI_App::associate_url(std::wstring url_prefix)
key_full.Create(false);
}
key_full = key_string;
#elif defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)
DesktopIntegrationDialog::perform_downloader_desktop_integration();
#endif // WIN32
}

void GUI_App::disassociate_url(std::wstring url_prefix)
{
#ifdef WIN32
wxRegKey key_full(wxRegKey::HKCU, "Software\\Classes\\" + url_prefix + "\\shell\\open\\command");
if (!key_full.Exists()) {
return;
}
key_full = "";
#endif // WIN32
}

#endif // __WXMSW__

void GUI_App::start_download(std::string url)
{
Expand Down
3 changes: 1 addition & 2 deletions src/slic3r/GUI/GUI_App.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -646,13 +646,12 @@ class GUI_App : public wxApp
bool is_glsl_version_greater_or_equal_to(unsigned int major, unsigned int minor) const { return m_opengl_mgr.get_gl_info().is_glsl_version_greater_or_equal_to(major, minor); }
int GetSingleChoiceIndex(const wxString& message, const wxString& caption, const wxArrayString& choices, int initialSelection);

#ifdef __WXMSW__
// extend is stl/3mf/gcode/step etc
void associate_files(std::wstring extend);
void disassociate_files(std::wstring extend);
bool check_url_association(std::wstring url_prefix, std::wstring& reg_bin);
void associate_url(std::wstring url_prefix);
void disassociate_url(std::wstring url_prefix);
#endif // __WXMSW__

// URL download - PrusaSlicer gets system call to open prusaslicer:// URL which should contain address of download
void start_download(std::string url);
Expand Down
12 changes: 12 additions & 0 deletions src/slic3r/GUI/InstanceCheck.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "GUI_App.hpp"
#include "InstanceCheck.hpp"
#include "Plater.hpp"
#include <boost/regex.hpp>

#ifdef _WIN32
#include "MainFrame.hpp"
Expand Down Expand Up @@ -370,6 +371,7 @@ bool instance_check(int argc, char** argv, bool app_config_single_instance)
namespace GUI {

wxDEFINE_EVENT(EVT_LOAD_MODEL_OTHER_INSTANCE, LoadFromOtherInstanceEvent);
wxDEFINE_EVENT(EVT_START_DOWNLOAD_OTHER_INSTANCE, StartDownloadOtherInstanceEvent);
wxDEFINE_EVENT(EVT_INSTANCE_GO_TO_FRONT, InstanceGoToFrontEvent);

void OtherInstanceMessageHandler::init(wxEvtHandler* callback_evt_handler)
Expand Down Expand Up @@ -498,19 +500,29 @@ void OtherInstanceMessageHandler::handle_message(const std::string& message)
}

std::vector<boost::filesystem::path> paths;
std::vector<std::string> downloads;
boost::regex re(R"(^(orcaslicer|prusaslicer):\/\/open[\/]?\?file=)", boost::regbase::icase);
boost::smatch results;

// Skip the first argument, it is the path to the slicer executable.
auto it = args.begin();
for (++ it; it != args.end(); ++ it) {
boost::filesystem::path p = MessageHandlerInternal::get_path(*it);
if (! p.string().empty())
paths.emplace_back(p);
else if (boost::regex_search(*it, results, re))
downloads.emplace_back(*it);
}
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))));
//}
}
if (!downloads.empty())
{
wxPostEvent(m_callback_evt_handler, StartDownloadOtherInstanceEvent(GUI::EVT_START_DOWNLOAD_OTHER_INSTANCE, std::vector<std::string>(std::move(downloads))));
}
}

#ifdef __APPLE__
Expand Down
2 changes: 2 additions & 0 deletions src/slic3r/GUI/InstanceCheck.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ class MainFrame;
#endif // __linux__

using LoadFromOtherInstanceEvent = Event<std::vector<boost::filesystem::path>>;
using StartDownloadOtherInstanceEvent = Event<std::vector<std::string>>;
wxDECLARE_EVENT(EVT_LOAD_MODEL_OTHER_INSTANCE, LoadFromOtherInstanceEvent);
wxDECLARE_EVENT(EVT_START_DOWNLOAD_OTHER_INSTANCE, StartDownloadOtherInstanceEvent);

using InstanceGoToFrontEvent = SimpleEvent;
wxDECLARE_EVENT(EVT_INSTANCE_GO_TO_FRONT, InstanceGoToFrontEvent);
Expand Down
9 changes: 9 additions & 0 deletions src/slic3r/GUI/Plater.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3175,6 +3175,15 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
wxGetApp().mainframe->Raise();
this->q->load_files(input_files);
});

this->q->Bind(EVT_START_DOWNLOAD_OTHER_INSTANCE, [](StartDownloadOtherInstanceEvent& evt) {
BOOST_LOG_TRIVIAL(trace) << "Received url from other instance event.";
wxGetApp().mainframe->Raise();
for (size_t i = 0; i < evt.data.size(); ++i) {
wxGetApp().start_download(evt.data[i]);
}

});
this->q->Bind(EVT_INSTANCE_GO_TO_FRONT, [this](InstanceGoToFrontEvent &) {
bring_instance_forward();
});
Expand Down
Loading

0 comments on commit 7725fbe

Please sign in to comment.