From 882e291daa92626bf852a84a89a62a61e33945ff Mon Sep 17 00:00:00 2001 From: David Kocik Date: Mon, 2 Dec 2024 15:32:22 +0100 Subject: [PATCH 1/4] SPE-2564: Use scripts to add listener to read keyboard shortcuts for reloading webview on Mac. Add listener for hyperlinks in Printables to be opened in external browser. --- src/slic3r/GUI/WebViewPanel.cpp | 203 +++++++++++++++++-- src/slic3r/GUI/WebViewPanel.hpp | 10 +- src/slic3r/GUI/WebViewPlatformUtilsLinux.cpp | 3 +- src/slic3r/GUI/WebViewPlatformUtilsWin32.cpp | 1 - 4 files changed, 194 insertions(+), 23 deletions(-) diff --git a/src/slic3r/GUI/WebViewPanel.cpp b/src/slic3r/GUI/WebViewPanel.cpp index bafc1de21c..81a98324e8 100644 --- a/src/slic3r/GUI/WebViewPanel.cpp +++ b/src/slic3r/GUI/WebViewPanel.cpp @@ -278,6 +278,13 @@ void WebViewPanel::on_loaded(wxWebViewEvent& evt) { if (evt.GetURL().IsEmpty()) return; + + if (evt.GetURL().StartsWith(m_default_url)) { + define_css(); + } else { + m_styles_defined = false; + } + m_load_default_url_on_next_error = false; if (evt.GetURL().Find(GUI::format_wxstr("/web/%1%.html", m_loading_html)) != wxNOT_FOUND && m_load_default_url) { m_load_default_url = false; @@ -554,10 +561,15 @@ void WebViewPanel::do_reload() { if (!m_browser) { return; + } + // IsBusy on Linux very often returns true due to loading about:blank after loading requested url. +#ifndef __linux__ + if (m_browser->IsBusy()) { + return; } +#endif const wxString current_url = m_browser->GetCurrentURL(); - if (current_url.StartsWith(m_default_url)) - { + if (current_url.StartsWith(m_default_url)) { m_browser->Reload(); return; } @@ -569,6 +581,7 @@ void WebViewPanel::load_default_url() if (!m_browser || m_do_late_webview_create) { return; } + m_styles_defined = false; load_url(m_default_url); } @@ -579,6 +592,64 @@ void WebViewPanel::sys_color_changed() #endif } +void WebViewPanel::define_css() +{ + + if (m_styles_defined) { + return; + } + m_styles_defined = true; + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__; + std::string script = R"( + // loading overlay + var style = document.createElement('style'); + style.innerHTML = ` + body {} + .slic3r-loading-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(127 127 127 / 50%); + z-index: 50; + display: flex; + align-items: center; + justify-content: center; + } + .slic3r-loading-anim { + width: 60px; + aspect-ratio: 4; + --_g: no-repeat radial-gradient(circle closest-side,#000 90%,#0000); + background: + var(--_g) 0% 50%, + var(--_g) 50% 50%, + var(--_g) 100% 50%; + background-size: calc(100%/3) 100%; + animation: slic3r-loading-anim 1s infinite linear; + } + @keyframes slic3r-loading-anim { + 33%{background-size:calc(100%/3) 0% ,calc(100%/3) 100%,calc(100%/3) 100%} + 50%{background-size:calc(100%/3) 100%,calc(100%/3) 0% ,calc(100%/3) 100%} + 66%{background-size:calc(100%/3) 100%,calc(100%/3) 100%,calc(100%/3) 0% } + } + + document.head.appendChild(style); + )"; +#ifdef __APPLE__ + // WebView on Windows does read keyboard shortcuts + // Thus doing f.e. Reload twice would make the oparation to fail + script += R"( + document.addEventListener('keydown', function (event) { + if (event.key === 'F5' || (event.ctrlKey && event.key === 'r') || (event.metaKey && event.key === 'r')) { + window.ExternalApp.postMessage(JSON.stringify({ event: 'reloadHomePage', fromKeyboard: 1})); + } + }); + )"; +#endif // !_WIN32 + run_script(script); +} + ConnectWebViewPanel::ConnectWebViewPanel(wxWindow* parent) : WebViewPanel(parent, GUI::from_u8(Utils::ServiceConfig::instance().connect_url()), { "_prusaSlicer" }, "connect_loading", "connect_error", false) { @@ -796,6 +867,12 @@ void ConnectWebViewPanel::on_navigation_request(wxWebViewEvent &evt) m_url->SetValue(evt.GetURL()); #endif BOOST_LOG_TRIVIAL(debug) << "Navigation requested to: " << into_u8(evt.GetURL()); + + // we need to do this to redefine css when reload is hit + if (evt.GetURL().StartsWith(m_default_url) && evt.GetURL() == m_browser->GetCurrentURL()) { + m_styles_defined = false; + } + if (evt.GetURL() == m_default_url) { m_reached_default_url = true; return; @@ -828,7 +905,22 @@ void ConnectWebViewPanel::on_connect_action_error(const std::string &message_dat void ConnectWebViewPanel::on_reload_event(const std::string& message_data) { - load_default_url(); + // Event from our error page button or keyboard shortcut + m_styles_defined = false; + try { + std::stringstream ss(message_data); + pt::ptree ptree; + pt::read_json(ss, ptree); + if (const auto keyboard = ptree.get_optional("fromKeyboard"); keyboard && *keyboard) { + do_reload(); + } else { + // On error page do load of default url. + load_default_url(); + } + } catch (const std::exception &e) { + BOOST_LOG_TRIVIAL(error) << "Could not parse printables message. " << e.what(); + return; + } } void ConnectWebViewPanel::logout() @@ -885,10 +977,17 @@ PrinterWebViewPanel::PrinterWebViewPanel(wxWindow* parent, const wxString& defau void PrinterWebViewPanel::on_navigation_request(wxWebViewEvent &evt) { const wxString url = evt.GetURL(); - if (url.StartsWith(m_default_url) && !m_api_key_sent) { + if (url.StartsWith(m_default_url)) { m_reached_default_url = true; - if (!m_usr.empty() && !m_psk.empty()) { - send_credentials(); + if (url == m_browser->GetCurrentURL()) { + // we need to do this to redefine css when reload is hit + m_styles_defined = false; + } + + if ( !m_api_key_sent) { + if (!m_usr.empty() && !m_psk.empty()) { + send_credentials(); + } } } else if (m_reached_default_url && evt.GetURL().Find(GUI::format_wxstr("/web/%1%.html", m_loading_html)) != wxNOT_FOUND) { // Do not allow back button to loading screen @@ -900,6 +999,13 @@ void PrinterWebViewPanel::on_loaded(wxWebViewEvent& evt) { if (evt.GetURL().IsEmpty()) return; + + if (evt.GetURL().StartsWith(m_default_url)) { + define_css(); + } else { + m_styles_defined = false; + } + m_load_default_url_on_next_error = false; if (evt.GetURL().Find(GUI::format_wxstr("/web/%1%.html", m_loading_html)) != wxNOT_FOUND && m_load_default_url) { m_load_default_url = false; @@ -969,8 +1075,7 @@ PrintablesWebViewPanel::PrintablesWebViewPanel(wxWindow* parent) m_events["downloadFile"] = std::bind(&PrintablesWebViewPanel::on_printables_event_download_file, this, std::placeholders::_1); m_events["sliceFile"] = std::bind(&PrintablesWebViewPanel::on_printables_event_slice_file, this, std::placeholders::_1); m_events["requiredLogin"] = std::bind(&PrintablesWebViewPanel::on_printables_event_required_login, this, std::placeholders::_1); - - + m_events["openExternalUrl"] = std::bind(&PrintablesWebViewPanel::on_printables_event_open_url, this, std::placeholders::_1); } void PrintablesWebViewPanel::handle_message(const std::string& message) @@ -1002,9 +1107,13 @@ void PrintablesWebViewPanel::handle_message(const std::string& message) void PrintablesWebViewPanel::on_navigation_request(wxWebViewEvent &evt) { - const wxString url = evt.GetURL(); + const wxString url = evt.GetURL(); if (url.StartsWith(m_default_url)) { m_reached_default_url = true; + if (url == m_browser->GetCurrentURL()) { + // we need to do this to redefine css when reload is hit + m_styles_defined = false; + } } else if (m_reached_default_url && url.StartsWith("http")) { BOOST_LOG_TRIVIAL(info) << evt.GetURL() << " does not start with default url. Vetoing."; evt.Veto(); @@ -1240,9 +1349,22 @@ void PrintablesWebViewPanel::on_printables_event_access_token_expired(const std: void PrintablesWebViewPanel::on_reload_event(const std::string& message_data) { - // Event from our error / loading html pages + // Event from our error page button or keyboard shortcut m_styles_defined = false; - load_default_url(); + try { + std::stringstream ss(message_data); + pt::ptree ptree; + pt::read_json(ss, ptree); + if (const auto keyboard = ptree.get_optional("fromKeyboard"); keyboard && *keyboard) { + do_reload(); + } else { + // On error page do load of default url. + load_default_url(); + } + } catch (const std::exception &e) { + BOOST_LOG_TRIVIAL(error) << "Could not parse printables message. " << e.what(); + return; + } } namespace { @@ -1342,20 +1464,36 @@ void PrintablesWebViewPanel::on_printables_event_required_login(const std::strin BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << " " << message_data; wxGetApp().printables_login_request(); } +void PrintablesWebViewPanel::on_printables_event_open_url(const std::string& message_data) +{ + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << " " << message_data; + + try { + std::stringstream ss(message_data); + pt::ptree ptree; + pt::read_json(ss, ptree); + if (const auto url = ptree.get_optional("url"); url) { + wxGetApp().open_browser_with_warning_dialog(GUI::from_u8(*url)); + } + } catch (const std::exception &e) { + BOOST_LOG_TRIVIAL(error) << "Could not parse Printables message. " << e.what(); + return; + } +} void PrintablesWebViewPanel::define_css() { + if (m_styles_defined) { return; } m_styles_defined = true; BOOST_LOG_TRIVIAL(debug) << __FUNCTION__; - const std::string script = R"( + std::string script = R"( + // Loading overlay and Notification style var style = document.createElement('style'); style.innerHTML = ` - body { - /* Add your body styles here */ - } + body {} .slic3r-loading-overlay { position: fixed; top: 0; @@ -1436,8 +1574,41 @@ void PrintablesWebViewPanel::define_css() font-weight: bold; } `; - document.head.appendChild(style); + document.head.appendChild(style); + + // Capture click on hypertext + // Rewritten from mobileApp code + (function() { + document.addEventListener('click', function(event) { + const target = event.target.closest('a[href]'); + if (!target) return; // Ignore clicks that are not on links + const url = target.href; + // Allow empty iframe navigation + if (url === 'about:blank') { + return; // Let it proceed + } + // Debug log for navigation + console.log(`Printables:onNavigationRequest: ${url}`); + // Handle all non-printables.com domains in an external browser + if (!/printables\.com/.test(url)) { + window.ExternalApp.postMessage(JSON.stringify({ event: 'openExternalUrl', url })) + event.preventDefault(); + } + // Default: Allow navigation to proceed + }, true); // Capture the event during the capture phase + })(); )"; +#ifdef __APPLE__ + // WebView on Windows does read keyboard shortcuts + // Thus doing f.e. Reload twice would make the oparation to fail + script += R"( + document.addEventListener('keydown', function (event) { + if (event.key === 'F5' || (event.ctrlKey && event.key === 'r') || (event.metaKey && event.key === 'r')) { + window.ExternalApp.postMessage(JSON.stringify({ event: 'reloadHomePage', fromKeyboard: 1})); + } + }); + )"; +#endif // !_WIN32 run_script(script); } diff --git a/src/slic3r/GUI/WebViewPanel.hpp b/src/slic3r/GUI/WebViewPanel.hpp index 7997bbc40f..e3b4319b34 100644 --- a/src/slic3r/GUI/WebViewPanel.hpp +++ b/src/slic3r/GUI/WebViewPanel.hpp @@ -73,10 +73,10 @@ public: virtual void sys_color_changed(); void set_load_default_url_on_next_error(bool val) { m_load_default_url_on_next_error = val; } - + protected: virtual void late_create(); - + virtual void define_css(); virtual void on_page_will_load(); wxWebView* m_browser { nullptr }; @@ -118,6 +118,7 @@ protected: bool m_shown { false }; bool m_load_default_url_on_next_error { false }; bool m_do_late_webview_create {false}; + bool m_styles_defined {false}; std::vector m_script_message_hadler_names; }; @@ -194,6 +195,8 @@ public: void send_will_refresh(); wxString get_default_url() const override; void set_next_show_url(const std::string& url) {m_next_show_url = Utils::ServiceConfig::instance().printables_url() + url; } +protected: + void define_css() override; private: void handle_message(const std::string& message); void on_printables_event_access_token_expired(const std::string& message_data); @@ -202,9 +205,9 @@ private: void on_printables_event_download_file(const std::string& message_data); void on_printables_event_slice_file(const std::string& message_data); void on_printables_event_required_login(const std::string& message_data); + void on_printables_event_open_url(const std::string& message_data); void load_default_url() override; std::string get_url_lang_theme(const wxString& url) const; - void define_css(); void show_download_notification(const std::string& filename); void show_loading_overlay(); void hide_loading_overlay(); @@ -213,7 +216,6 @@ private: std::string m_next_show_url; bool m_refreshing_token {false}; - bool m_styles_defined {false}; #ifdef _WIN32 bool m_remove_request_auth { false }; #endif diff --git a/src/slic3r/GUI/WebViewPlatformUtilsLinux.cpp b/src/slic3r/GUI/WebViewPlatformUtilsLinux.cpp index 80290293c6..f5ffd8bc62 100644 --- a/src/slic3r/GUI/WebViewPlatformUtilsLinux.cpp +++ b/src/slic3r/GUI/WebViewPlatformUtilsLinux.cpp @@ -143,5 +143,4 @@ void load_request(wxWebView* web_view, const std::string& address, const std::st // Load the request in the WebView webkit_web_view_load_request(native_backend, request); } - -} +} \ No newline at end of file diff --git a/src/slic3r/GUI/WebViewPlatformUtilsWin32.cpp b/src/slic3r/GUI/WebViewPlatformUtilsWin32.cpp index 8a9f8c79c0..08fbedcb72 100644 --- a/src/slic3r/GUI/WebViewPlatformUtilsWin32.cpp +++ b/src/slic3r/GUI/WebViewPlatformUtilsWin32.cpp @@ -303,6 +303,5 @@ void load_request(wxWebView* web_view, const std::string& address, const std::st return; } } - } // namespace Slic3r::GUI #endif // WIN32 From cfcb284520f037f8d5d71ac85d22c188cac8dd44 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Tue, 3 Dec 2024 09:32:04 +0100 Subject: [PATCH 2/4] SPE-2564: Use only 1 menu item to reload from disk (on plater) or reload webview Two items on mac. --- src/slic3r/GUI/MainFrame.cpp | 46 ++++++++++++++++++++++++++++++------ src/slic3r/GUI/MainFrame.hpp | 5 +++- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 8d2734835d..7edf86c451 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -691,6 +691,10 @@ void MainFrame::init_tabpanel() old_tab->validate_custom_gcodes(); } +#ifndef __APPLE__ + on_tab_change_rename_reload_item(e.GetSelection()); +#endif // !__APPLE__ + wxWindow* panel = m_tabpanel->GetCurrentPage(); Tab* tab = dynamic_cast(panel); @@ -995,6 +999,35 @@ void MainFrame::reload_selected_webview() m_printer_webview->do_reload(); } +void MainFrame::on_tab_change_rename_reload_item(int new_tab) +{ + if (!m_tabpanel) { + return; + } + if ( new_tab == m_tabpanel->FindPage(m_printables_webview) + || (m_connect_webview_added && new_tab == m_tabpanel->FindPage(m_connect_webview)) + || (m_printer_webview_added && new_tab == m_tabpanel->FindPage(m_printer_webview))) + { + m_menu_item_reload->SetItemLabel(_L("Re&load Web Content") + "\t\xA0" + "F5"); + m_menu_item_reload->SetHelp(_L("Reload Web Content")); + } else { + m_menu_item_reload->SetItemLabel(_L("Re&load from Disk") + "\t\xA0" + "F5"); + m_menu_item_reload->SetHelp(_L("Reload the plater from disk")); + } +} + +bool MainFrame::reload_item_condition_cb() +{ + return is_any_webview_selected() ? true : + !m_plater->model().objects.empty(); +} +void MainFrame::reload_item_function_cb() +{ + is_any_webview_selected() + ? reload_selected_webview() + : m_plater->reload_all_from_disk(); +} + void Slic3r::GUI::MainFrame::refresh_account_menu(bool avatar/* = false */) { // Update User name in TopBar @@ -1605,10 +1638,13 @@ void MainFrame::init_menubar_as_editor() append_menu_item(editMenu, wxID_ANY, _L("Re&load from Disk") + dots + "\tCtrl+Shift+R", _L("Reload the plater from disk"), [this](wxCommandEvent&) { m_plater->reload_all_from_disk(); }, "", nullptr, [this]() {return !m_plater->model().objects.empty(); }, this); + m_menu_item_reload = append_menu_item(editMenu, wxID_ANY, _L("Re&load Web Content") + "\tF5", + _L("Reload Web Content"), [this](wxCommandEvent&) { reload_selected_webview(); }, + "", nullptr, [this]() {return is_any_webview_selected(); }, this); #else - append_menu_item(editMenu, wxID_ANY, _L("Re&load from Disk") + sep + "F5", - _L("Reload the plater from disk"), [this](wxCommandEvent&) { m_plater->reload_all_from_disk(); }, - "", nullptr, [this]() {return !m_plater->model().objects.empty(); }, this); + m_menu_item_reload = append_menu_item(editMenu, wxID_ANY, _L("Re&load from Disk") + sep + "F5", + _L("Reload the plater from disk"), [this](wxCommandEvent&) { reload_item_function_cb(); }, + "", nullptr, [this]() {return reload_item_condition_cb(); }, this); #endif // __APPLE__ editMenu->AppendSeparator(); @@ -1697,10 +1733,6 @@ void MainFrame::init_menubar_as_editor() wxFULLSCREEN_NOSTATUSBAR | wxFULLSCREEN_NOBORDER | wxFULLSCREEN_NOCAPTION); }, this, []() { return true; }, [this]() { return this->IsFullScreen(); }, this); #endif // __APPLE__ - - viewMenu->AppendSeparator(); - append_menu_item(viewMenu, wxID_ANY, _L("&Reload Web Content") + "\tF5", _L("Reload WebView"), - [this](wxCommandEvent&) { reload_selected_webview(); }, "", nullptr, [this]() {return is_any_webview_selected(); }, this); } // Help menu diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 7d94cca4f6..7c855d60fb 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -94,6 +94,7 @@ class MainFrame : public DPIFrame TopBarMenus m_bar_menus; wxMenuItem* m_menu_item_reslice_now { nullptr }; + wxMenuItem* m_menu_item_reload { nullptr }; wxSizer* m_main_sizer{ nullptr }; size_t m_last_selected_tab; @@ -129,7 +130,9 @@ class MainFrame : public DPIFrame void add_connect_webview_tab(); void remove_connect_webview_tab(); - + void on_tab_change_rename_reload_item(int new_tab); + bool reload_item_condition_cb(); + void reload_item_function_cb(); // MenuBar items changeable in respect to printer technology enum MenuItems { // FFF SLA From 189d728b3f81860ee0d5925108ae6f3a7759e0b8 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Thu, 5 Dec 2024 13:39:21 +0100 Subject: [PATCH 3/4] SPE-2564: Add javascript listeners only once/ + keyboard shortcut fix --- src/slic3r/GUI/MainFrame.cpp | 6 +-- src/slic3r/GUI/WebViewPanel.cpp | 85 ++++++++++++++++++++------------- 2 files changed, 54 insertions(+), 37 deletions(-) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 7edf86c451..f26335dd25 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1008,10 +1008,10 @@ void MainFrame::on_tab_change_rename_reload_item(int new_tab) || (m_connect_webview_added && new_tab == m_tabpanel->FindPage(m_connect_webview)) || (m_printer_webview_added && new_tab == m_tabpanel->FindPage(m_printer_webview))) { - m_menu_item_reload->SetItemLabel(_L("Re&load Web Content") + "\t\xA0" + "F5"); + m_menu_item_reload->SetItemLabel(_L("Re&load Web Content") + "\tF5"); m_menu_item_reload->SetHelp(_L("Reload Web Content")); } else { - m_menu_item_reload->SetItemLabel(_L("Re&load from Disk") + "\t\xA0" + "F5"); + m_menu_item_reload->SetItemLabel(_L("Re&load from Disk") + "\tF5"); m_menu_item_reload->SetHelp(_L("Reload the plater from disk")); } } @@ -1642,7 +1642,7 @@ void MainFrame::init_menubar_as_editor() _L("Reload Web Content"), [this](wxCommandEvent&) { reload_selected_webview(); }, "", nullptr, [this]() {return is_any_webview_selected(); }, this); #else - m_menu_item_reload = append_menu_item(editMenu, wxID_ANY, _L("Re&load from Disk") + sep + "F5", + m_menu_item_reload = append_menu_item(editMenu, wxID_ANY, _L("Re&load from Disk") + "\tF5", _L("Reload the plater from disk"), [this](wxCommandEvent&) { reload_item_function_cb(); }, "", nullptr, [this]() {return reload_item_condition_cb(); }, this); #endif // __APPLE__ diff --git a/src/slic3r/GUI/WebViewPanel.cpp b/src/slic3r/GUI/WebViewPanel.cpp index 81a98324e8..d7c4c099a6 100644 --- a/src/slic3r/GUI/WebViewPanel.cpp +++ b/src/slic3r/GUI/WebViewPanel.cpp @@ -636,17 +636,24 @@ void WebViewPanel::define_css() document.head.appendChild(style); )"; -#ifdef __APPLE__ +//#ifdef __APPLE__ +#if defined(__APPLE__) // WebView on Windows does read keyboard shortcuts // Thus doing f.e. Reload twice would make the oparation to fail script += R"( - document.addEventListener('keydown', function (event) { - if (event.key === 'F5' || (event.ctrlKey && event.key === 'r') || (event.metaKey && event.key === 'r')) { - window.ExternalApp.postMessage(JSON.stringify({ event: 'reloadHomePage', fromKeyboard: 1})); + (function() { + const listenerKey = 'custom-click-listener'; + if (!document[listenerKey]) { + document.addEventListener('keydown', function (event) { + if (event.key === 'F5' || (event.ctrlKey && event.key === 'r') || (event.metaKey && event.key === 'r')) { + window.ExternalApp.postMessage(JSON.stringify({ event: 'reloadHomePage', fromKeyboard: 1})); + } + }); + document[listenerKey] = true; } - }); + })(); )"; -#endif // !_WIN32 +#endif // __APPLE__ run_script(script); } @@ -1579,36 +1586,46 @@ void PrintablesWebViewPanel::define_css() // Capture click on hypertext // Rewritten from mobileApp code (function() { - document.addEventListener('click', function(event) { - const target = event.target.closest('a[href]'); - if (!target) return; // Ignore clicks that are not on links - const url = target.href; - // Allow empty iframe navigation - if (url === 'about:blank') { - return; // Let it proceed - } - // Debug log for navigation - console.log(`Printables:onNavigationRequest: ${url}`); - // Handle all non-printables.com domains in an external browser - if (!/printables\.com/.test(url)) { - window.ExternalApp.postMessage(JSON.stringify({ event: 'openExternalUrl', url })) - event.preventDefault(); - } - // Default: Allow navigation to proceed - }, true); // Capture the event during the capture phase - })(); - )"; -#ifdef __APPLE__ - // WebView on Windows does read keyboard shortcuts - // Thus doing f.e. Reload twice would make the oparation to fail - script += R"( - document.addEventListener('keydown', function (event) { - if (event.key === 'F5' || (event.ctrlKey && event.key === 'r') || (event.metaKey && event.key === 'r')) { - window.ExternalApp.postMessage(JSON.stringify({ event: 'reloadHomePage', fromKeyboard: 1})); + const listenerKey = 'custom-click-listener'; + if (!document[listenerKey]) { + document.addEventListener( 'click', function(event) { + const target = event.target.closest('a[href]'); + if (!target) return; // Ignore clicks that are not on links + const url = target.href; + // Allow empty iframe navigation + if (url === 'about:blank') { + return; // Let it proceed + } + // Debug log for navigation + console.log(`Printables:onNavigationRequest: ${url}`); + // Handle all non-printables.com domains in an external browser + if (!/printables\.com/.test(url)) { + window.ExternalApp.postMessage(JSON.stringify({ event: 'openExternalUrl', url })) + event.preventDefault(); + } + // Default: Allow navigation to proceed + },true); // Capture the event during the capture phase + document[listenerKey] = true; } - }); + })(); )"; -#endif // !_WIN32 +#if defined(__APPLE__) + // WebView on Windows does read keyboard shortcuts + // Thus doing f.e. Reload twice would make the oparation to fail + script += R"( + (function() { + const listenerKey = 'custom-click-listener'; + if (!document[listenerKey]) { + document.addEventListener('keydown', function (event) { + if (event.key === 'F5' || (event.ctrlKey && event.key === 'r') || (event.metaKey && event.key === 'r')) { + window.ExternalApp.postMessage(JSON.stringify({ event: 'reloadHomePage', fromKeyboard: 1})); + } + }); + document[listenerKey] = true; + } + })(); + )"; +#endif // defined(__APPLE__) run_script(script); } From 1848bb9396c28c3dd0127d48d43392fa25492b26 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Mon, 9 Dec 2024 13:10:42 +0100 Subject: [PATCH 4/4] SPE-2564: FIx of shortcuts on Mac, including cmd+m and cmd+q --- src/slic3r/GUI/ConnectRequestHandler.cpp | 2 +- src/slic3r/GUI/WebViewPanel.cpp | 215 +++++++++++++++-------- src/slic3r/GUI/WebViewPanel.hpp | 11 ++ 3 files changed, 151 insertions(+), 77 deletions(-) diff --git a/src/slic3r/GUI/ConnectRequestHandler.cpp b/src/slic3r/GUI/ConnectRequestHandler.cpp index 0f4bf4a208..07c85ece54 100644 --- a/src/slic3r/GUI/ConnectRequestHandler.cpp +++ b/src/slic3r/GUI/ConnectRequestHandler.cpp @@ -56,7 +56,7 @@ void ConnectRequestHandler::handle_message(const std::string& message) } if (action_string.empty()) { - BOOST_LOG_TRIVIAL(error) << "Recieved invalid message from _prusaConnect (missing action). Message: " << message; + BOOST_LOG_TRIVIAL(error) << "Received invalid message from _prusaConnect (missing action). Message: " << message; return; } assert(m_actions.find(action_string) != m_actions.end()); // this assert means there is a action that has no handling. diff --git a/src/slic3r/GUI/WebViewPanel.cpp b/src/slic3r/GUI/WebViewPanel.cpp index d7c4c099a6..3e4da88fd7 100644 --- a/src/slic3r/GUI/WebViewPanel.cpp +++ b/src/slic3r/GUI/WebViewPanel.cpp @@ -592,69 +592,20 @@ void WebViewPanel::sys_color_changed() #endif } +void WebViewPanel::on_app_quit_event(const std::string& message_data) +{ + // MacOS only suplement for cmd+Q + wxGetApp().Exit(); +} + +void WebViewPanel::on_app_minimize_event(const std::string& message_data) +{ + // MacOS only suplement for cmd+M + wxGetApp().mainframe->Iconize(true); +} void WebViewPanel::define_css() { - - if (m_styles_defined) { - return; - } - m_styles_defined = true; - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__; - std::string script = R"( - // loading overlay - var style = document.createElement('style'); - style.innerHTML = ` - body {} - .slic3r-loading-overlay { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(127 127 127 / 50%); - z-index: 50; - display: flex; - align-items: center; - justify-content: center; - } - .slic3r-loading-anim { - width: 60px; - aspect-ratio: 4; - --_g: no-repeat radial-gradient(circle closest-side,#000 90%,#0000); - background: - var(--_g) 0% 50%, - var(--_g) 50% 50%, - var(--_g) 100% 50%; - background-size: calc(100%/3) 100%; - animation: slic3r-loading-anim 1s infinite linear; - } - @keyframes slic3r-loading-anim { - 33%{background-size:calc(100%/3) 0% ,calc(100%/3) 100%,calc(100%/3) 100%} - 50%{background-size:calc(100%/3) 100%,calc(100%/3) 0% ,calc(100%/3) 100%} - 66%{background-size:calc(100%/3) 100%,calc(100%/3) 100%,calc(100%/3) 0% } - } - - document.head.appendChild(style); - )"; -//#ifdef __APPLE__ -#if defined(__APPLE__) - // WebView on Windows does read keyboard shortcuts - // Thus doing f.e. Reload twice would make the oparation to fail - script += R"( - (function() { - const listenerKey = 'custom-click-listener'; - if (!document[listenerKey]) { - document.addEventListener('keydown', function (event) { - if (event.key === 'F5' || (event.ctrlKey && event.key === 'r') || (event.metaKey && event.key === 'r')) { - window.ExternalApp.postMessage(JSON.stringify({ event: 'reloadHomePage', fromKeyboard: 1})); - } - }); - document[listenerKey] = true; - } - })(); - )"; -#endif // __APPLE__ - run_script(script); + assert(false); } ConnectWebViewPanel::ConnectWebViewPanel(wxWindow* parent) @@ -663,6 +614,11 @@ ConnectWebViewPanel::ConnectWebViewPanel(wxWindow* parent) auto* plater = wxGetApp().plater(); plater->Bind(EVT_UA_LOGGEDOUT, &ConnectWebViewPanel::on_user_logged_out, this); plater->Bind(EVT_UA_ID_USER_SUCCESS, &ConnectWebViewPanel::on_user_token, this); + + m_actions["appQuit"] = std::bind(&WebViewPanel::on_app_quit_event, this, std::placeholders::_1); + m_actions["appMinimize"] = std::bind(&WebViewPanel::on_app_minimize_event, this, std::placeholders::_1); + m_actions["reloadHomePage"] = std::bind(&ConnectWebViewPanel::on_reload_event, this, std::placeholders::_1); + } void ConnectWebViewPanel::late_create() @@ -976,9 +932,41 @@ void ConnectWebViewPanel::on_connect_action_print(const std::string& message_dat assert(false); } +void ConnectWebViewPanel::define_css() +{ + + if (m_styles_defined) { + return; + } + m_styles_defined = true; + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__; +#if defined(__APPLE__) + // WebView on Windows does read keyboard shortcuts + // Thus doing f.e. Reload twice would make the oparation to fail + std::string script = R"( + document.addEventListener('keydown', function (event) { + if (event.key === 'F5' || (event.ctrlKey && event.key === 'r') || (event.metaKey && event.key === 'r')) { + window.webkit.messageHandlers._prusaSlicer.postMessage(JSON.stringify({ action: 'reloadHomePage', fromKeyboard: 1})); + } + if (event.metaKey && event.key === 'q') { + window.webkit.messageHandlers._prusaSlicer.postMessage(JSON.stringify({ action: 'appQuit'})); + } + if (event.metaKey && event.key === 'm') { + window.webkit.messageHandlers._prusaSlicer.postMessage(JSON.stringify({ action: 'appMinimize'})); + } + }); + )"; + run_script(script); + +#endif // defined(__APPLE__) +} + PrinterWebViewPanel::PrinterWebViewPanel(wxWindow* parent, const wxString& default_url) : WebViewPanel(parent, default_url, {"ExternalApp"}, "other_loading", "other_error", false) { + m_events["reloadHomePage"] = std::bind(&PrinterWebViewPanel::on_reload_event, this, std::placeholders::_1); + m_events["appQuit"] = std::bind(&WebViewPanel::on_app_quit_event, this, std::placeholders::_1); + m_events["appMinimize"] = std::bind(&WebViewPanel::on_app_minimize_event, this, std::placeholders::_1); } void PrinterWebViewPanel::on_navigation_request(wxWebViewEvent &evt) @@ -1025,8 +1013,35 @@ void PrinterWebViewPanel::on_loaded(wxWebViewEvent& evt) } void PrinterWebViewPanel::on_script_message(wxWebViewEvent& evt) { - // Only reload messages are being sent now. - load_default_url(); + BOOST_LOG_TRIVIAL(debug) << "received message from Physical printer page: " << evt.GetString(); + handle_message(into_u8(evt.GetString())); +} + +void PrinterWebViewPanel::handle_message(const std::string& message) +{ + + std::string event_string; + try { + std::stringstream ss(message); + pt::ptree ptree; + pt::read_json(ss, ptree); + if (const auto action = ptree.get_optional("event"); action) { + event_string = *action; + } + } + catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "Could not parse printables message. " << e.what(); + return; + } + + if (event_string.empty()) { + BOOST_LOG_TRIVIAL(error) << "Received invalid message from printables (missing event). Message: " << message; + return; + } + assert(m_events.find(event_string) != m_events.end()); // this assert means there is an event that has no handling. + if (m_events.find(event_string) != m_events.end()) { + m_events[event_string](message); + } } void PrinterWebViewPanel::send_api_key() @@ -1070,19 +1085,67 @@ void PrinterWebViewPanel::send_credentials() setup_webview_with_credentials(m_browser, m_usr, m_psk); } +void PrinterWebViewPanel::define_css() +{ + + if (m_styles_defined) { + return; + } + m_styles_defined = true; + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__; +#if defined(__APPLE__) + // WebView on Windows does read keyboard shortcuts + // Thus doing f.e. Reload twice would make the oparation to fail + std::string script = R"( + document.addEventListener('keydown', function (event) { + if (event.key === 'F5' || (event.ctrlKey && event.key === 'r') || (event.metaKey && event.key === 'r')) { + window.webkit.messageHandlers.ExternalApp.postMessage(JSON.stringify({ event: 'reloadHomePage', fromKeyboard: 1})); + } + if (event.metaKey && event.key === 'q') { + window.webkit.messageHandlers.ExternalApp.postMessage(JSON.stringify({ event: 'appQuit'})); + } + if (event.metaKey && event.key === 'm') { + window.webkit.messageHandlers.ExternalApp.postMessage(JSON.stringify({ event: 'appMinimize'})); + } + }); + )"; + run_script(script); + +#endif // defined(__APPLE__) +} + +void PrinterWebViewPanel::on_reload_event(const std::string& message_data) +{ + // Event from our error page button or keyboard shortcut + m_styles_defined = false; + try { + std::stringstream ss(message_data); + pt::ptree ptree; + pt::read_json(ss, ptree); + if (const auto keyboard = ptree.get_optional("fromKeyboard"); keyboard && *keyboard) { + do_reload(); + } else { + // On error page do load of default url. + load_default_url(); + } + } catch (const std::exception &e) { + BOOST_LOG_TRIVIAL(error) << "Could not parse message. " << e.what(); + return; + } +} PrintablesWebViewPanel::PrintablesWebViewPanel(wxWindow* parent) : WebViewPanel(parent, GUI::from_u8(Utils::ServiceConfig::instance().printables_url()), { "ExternalApp" }, "other_loading", "other_error", false) { - - m_events["accessTokenExpired"] = std::bind(&PrintablesWebViewPanel::on_printables_event_access_token_expired, this, std::placeholders::_1); - m_events["reloadHomePage"] = std::bind(&PrintablesWebViewPanel::on_reload_event, this, std::placeholders::_1); m_events["printGcode"] = std::bind(&PrintablesWebViewPanel::on_printables_event_print_gcode, this, std::placeholders::_1); m_events["downloadFile"] = std::bind(&PrintablesWebViewPanel::on_printables_event_download_file, this, std::placeholders::_1); m_events["sliceFile"] = std::bind(&PrintablesWebViewPanel::on_printables_event_slice_file, this, std::placeholders::_1); m_events["requiredLogin"] = std::bind(&PrintablesWebViewPanel::on_printables_event_required_login, this, std::placeholders::_1); m_events["openExternalUrl"] = std::bind(&PrintablesWebViewPanel::on_printables_event_open_url, this, std::placeholders::_1); + m_events["reloadHomePage"] = std::bind(&PrintablesWebViewPanel::on_reload_event, this, std::placeholders::_1); + m_events["appQuit"] = std::bind(&WebViewPanel::on_app_quit_event, this, std::placeholders::_1); + m_events["appMinimize"] = std::bind(&WebViewPanel::on_app_minimize_event, this, std::placeholders::_1); } void PrintablesWebViewPanel::handle_message(const std::string& message) @@ -1613,17 +1676,17 @@ void PrintablesWebViewPanel::define_css() // WebView on Windows does read keyboard shortcuts // Thus doing f.e. Reload twice would make the oparation to fail script += R"( - (function() { - const listenerKey = 'custom-click-listener'; - if (!document[listenerKey]) { - document.addEventListener('keydown', function (event) { - if (event.key === 'F5' || (event.ctrlKey && event.key === 'r') || (event.metaKey && event.key === 'r')) { - window.ExternalApp.postMessage(JSON.stringify({ event: 'reloadHomePage', fromKeyboard: 1})); - } - }); - document[listenerKey] = true; + document.addEventListener('keydown', function (event) { + if (event.key === 'F5' || (event.ctrlKey && event.key === 'r') || (event.metaKey && event.key === 'r')) { + window.ExternalApp.postMessage(JSON.stringify({ event: 'reloadHomePage', fromKeyboard: 1})); } - })(); + if (event.metaKey && event.key === 'q') { + window.ExternalApp.postMessage(JSON.stringify({ event: 'appQuit'})); + } + if (event.metaKey && event.key === 'm') { + window.ExternalApp.postMessage(JSON.stringify({ event: 'appMinimize'})); + } + }); )"; #endif // defined(__APPLE__) run_script(script); diff --git a/src/slic3r/GUI/WebViewPanel.hpp b/src/slic3r/GUI/WebViewPanel.hpp index e3b4319b34..479c9ed06b 100644 --- a/src/slic3r/GUI/WebViewPanel.hpp +++ b/src/slic3r/GUI/WebViewPanel.hpp @@ -74,11 +74,14 @@ public: void set_load_default_url_on_next_error(bool val) { m_load_default_url_on_next_error = val; } + void on_app_quit_event(const std::string& message_data); + void on_app_minimize_event(const std::string& message_data); protected: virtual void late_create(); virtual void define_css(); virtual void on_page_will_load(); + wxWebView* m_browser { nullptr }; bool m_load_default_url { false }; @@ -144,6 +147,7 @@ protected: void on_reload_event(const std::string& message_data) override; void on_connect_action_close_dialog(const std::string& message_data) override {assert(false);} void on_user_token(UserAccountSuccessEvent& e); + void define_css() override; private: static wxString get_login_script(bool refresh); static wxString get_logout_script(); @@ -172,11 +176,18 @@ public: m_psk = psk; } void clear() { m_api_key.clear(); m_usr.clear(); m_psk.clear(); m_api_key_sent = false; } + + void on_reload_event(const std::string& message_data); +protected: + void define_css() override; private: std::string m_api_key; std::string m_usr; std::string m_psk; bool m_api_key_sent {false}; + + void handle_message(const std::string& message); + std::map> m_events; }; class PrintablesWebViewPanel : public WebViewPanel