mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-15 14:46:05 +08:00
Merge branch 'dk_external_login'
This commit is contained in:
commit
4414f24bc7
@ -7,6 +7,8 @@
|
|||||||
#include "slic3r/GUI/I18N.hpp"
|
#include "slic3r/GUI/I18N.hpp"
|
||||||
#include "format.hpp"
|
#include "format.hpp"
|
||||||
#include "Event.hpp"
|
#include "Event.hpp"
|
||||||
|
#include "slic3r/GUI/WebViewPlatformUtils.hpp"
|
||||||
|
|
||||||
#include <wx/webview.h>
|
#include <wx/webview.h>
|
||||||
|
|
||||||
wxDEFINE_EVENT(EVT_OPEN_EXTERNAL_LOGIN_WIZARD, wxCommandEvent);
|
wxDEFINE_EVENT(EVT_OPEN_EXTERNAL_LOGIN_WIZARD, wxCommandEvent);
|
||||||
@ -123,6 +125,10 @@ void ConfigWizardWebViewPage::on_navigation_request(wxWebViewEvent &evt)
|
|||||||
{
|
{
|
||||||
wxString url = evt.GetURL();
|
wxString url = evt.GetURL();
|
||||||
if (url.starts_with(L"prusaslicer")) {
|
if (url.starts_with(L"prusaslicer")) {
|
||||||
|
delete_cookies(m_browser, "https://account.prusa3d.com");
|
||||||
|
delete_cookies(m_browser, "https://accounts.google.com");
|
||||||
|
delete_cookies(m_browser, "https://appleid.apple.com");
|
||||||
|
delete_cookies(m_browser, "https://facebook.com");
|
||||||
evt.Veto();
|
evt.Veto();
|
||||||
m_vetoed = true;
|
m_vetoed = true;
|
||||||
wxPostEvent(wxGetApp().plater(), Event<std::string>(EVT_LOGIN_VIA_WIZARD, into_u8(url)));
|
wxPostEvent(wxGetApp().plater(), Event<std::string>(EVT_LOGIN_VIA_WIZARD, into_u8(url)));
|
||||||
|
@ -903,15 +903,15 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
|||||||
#endif // SLIC3R_DESKTOP_INTEGRATION
|
#endif // SLIC3R_DESKTOP_INTEGRATION
|
||||||
#endif // __linux__
|
#endif // __linux__
|
||||||
std::string service;
|
std::string service;
|
||||||
if (evt.GetString().Find("accounts.google.com") != wxString::npos) {
|
if (evt.GetString().Find(L"accounts.google.com") != wxString::npos) {
|
||||||
service = "google";
|
service = "google";
|
||||||
} else if (evt.GetString().Find("appleid.apple.com") != wxString::npos) {
|
} else if (evt.GetString().Find(L"appleid.apple.com") != wxString::npos) {
|
||||||
service = "apple";
|
service = "apple";
|
||||||
} else if (evt.GetString().Find("facebook.com") != wxString::npos) {
|
} else if (evt.GetString().Find(L"facebook.com") != wxString::npos) {
|
||||||
service = "facebook";
|
service = "facebook";
|
||||||
}
|
}
|
||||||
wxString url = user_account->get_login_redirect_url(service);
|
wxString url = user_account->get_login_redirect_url(service);
|
||||||
wxGetApp().open_login_browser_with_dialog(into_u8(url), nullptr, false);
|
wxGetApp().open_login_browser_with_dialog(into_u8(url));
|
||||||
};
|
};
|
||||||
|
|
||||||
this->q->Bind(EVT_OPEN_EXTERNAL_LOGIN_WIZARD, open_external_login);
|
this->q->Bind(EVT_OPEN_EXTERNAL_LOGIN_WIZARD, open_external_login);
|
||||||
@ -928,10 +928,6 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
|||||||
sidebar->update_printer_presets_combobox();
|
sidebar->update_printer_presets_combobox();
|
||||||
wxGetApp().update_wizard_login_page();
|
wxGetApp().update_wizard_login_page();
|
||||||
this->show_action_buttons(this->ready_to_slice);
|
this->show_action_buttons(this->ready_to_slice);
|
||||||
// Needed only when using internal web browser to login (which is case of config wizard)
|
|
||||||
LogoutWebViewDialog dlg(this->q);
|
|
||||||
dlg.ShowModal();
|
|
||||||
//
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this->q->Bind(EVT_UA_ID_USER_SUCCESS, [this](UserAccountSuccessEvent& evt) {
|
this->q->Bind(EVT_UA_ID_USER_SUCCESS, [this](UserAccountSuccessEvent& evt) {
|
||||||
|
@ -1459,13 +1459,16 @@ void LoginWebViewDialog::on_navigation_request(wxWebViewEvent &evt)
|
|||||||
{
|
{
|
||||||
wxString url = evt.GetURL();
|
wxString url = evt.GetURL();
|
||||||
if (url.starts_with(L"prusaslicer")) {
|
if (url.starts_with(L"prusaslicer")) {
|
||||||
|
delete_cookies(m_browser, "https://account.prusa3d.com");
|
||||||
|
delete_cookies(m_browser, "https://accounts.google.com");
|
||||||
|
delete_cookies(m_browser, "https://appleid.apple.com");
|
||||||
|
delete_cookies(m_browser, "https://facebook.com");
|
||||||
evt.Veto();
|
evt.Veto();
|
||||||
m_ret_val = into_u8(url);
|
m_ret_val = into_u8(url);
|
||||||
EndModal(wxID_OK);
|
EndModal(wxID_OK);
|
||||||
} else if (url.Find("accounts.google.com") != wxString::npos
|
} else if (url.Find(L"accounts.google.com") != wxString::npos
|
||||||
|| url.Find("appleid.apple.com") != wxString::npos
|
|| url.Find(L"appleid.apple.com") != wxString::npos
|
||||||
|| url.Find("facebook.com") != wxString::npos)
|
|| url.Find(L"facebook.com") != wxString::npos) {
|
||||||
{
|
|
||||||
auto& sc = Utils::ServiceConfig::instance();
|
auto& sc = Utils::ServiceConfig::instance();
|
||||||
if (!m_evt_sent && !url.starts_with(GUI::from_u8(sc.account_url()))) {
|
if (!m_evt_sent && !url.starts_with(GUI::from_u8(sc.account_url()))) {
|
||||||
wxCommandEvent* evt = new wxCommandEvent(EVT_OPEN_EXTERNAL_LOGIN);
|
wxCommandEvent* evt = new wxCommandEvent(EVT_OPEN_EXTERNAL_LOGIN);
|
||||||
@ -1475,6 +1478,7 @@ void LoginWebViewDialog::on_navigation_request(wxWebViewEvent &evt)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoginWebViewDialog::on_dpi_changed(const wxRect &suggested_rect)
|
void LoginWebViewDialog::on_dpi_changed(const wxRect &suggested_rect)
|
||||||
{
|
{
|
||||||
const wxSize &size = wxSize(50 * wxGetApp().em_unit(), 80 * wxGetApp().em_unit());
|
const wxSize &size = wxSize(50 * wxGetApp().em_unit(), 80 * wxGetApp().em_unit());
|
||||||
@ -1482,20 +1486,5 @@ void LoginWebViewDialog::on_dpi_changed(const wxRect &suggested_rect)
|
|||||||
Fit();
|
Fit();
|
||||||
Refresh();
|
Refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
LogoutWebViewDialog::LogoutWebViewDialog(wxWindow *parent)
|
|
||||||
: WebViewDialog(parent
|
|
||||||
, GUI::from_u8(Utils::ServiceConfig::instance().account_logout_url())
|
|
||||||
, _L("Logout dialog")
|
|
||||||
, wxSize(std::max(parent->GetClientSize().x / 4, 10 * wxGetApp().em_unit()), std::max(parent->GetClientSize().y / 4, 10 * wxGetApp().em_unit()))
|
|
||||||
, {})
|
|
||||||
{
|
|
||||||
Centre();
|
|
||||||
}
|
|
||||||
|
|
||||||
void LogoutWebViewDialog::on_loaded(wxWebViewEvent &evt)
|
|
||||||
{
|
|
||||||
EndModal(wxID_OK);
|
|
||||||
}
|
|
||||||
} // GUI
|
} // GUI
|
||||||
} // Slic3r
|
} // Slic3r
|
||||||
|
@ -282,21 +282,12 @@ public:
|
|||||||
LoginWebViewDialog(wxWindow *parent, std::string &ret_val, const wxString& url, wxEvtHandler* evt_handler);
|
LoginWebViewDialog(wxWindow *parent, std::string &ret_val, const wxString& url, wxEvtHandler* evt_handler);
|
||||||
void on_navigation_request(wxWebViewEvent &evt) override;
|
void on_navigation_request(wxWebViewEvent &evt) override;
|
||||||
void on_dpi_changed(const wxRect &suggested_rect) override;
|
void on_dpi_changed(const wxRect &suggested_rect) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string& m_ret_val;
|
std::string& m_ret_val;
|
||||||
wxEvtHandler* p_evt_handler;
|
wxEvtHandler* p_evt_handler;
|
||||||
bool m_evt_sent{ false };
|
bool m_evt_sent{ false };
|
||||||
};
|
};
|
||||||
|
|
||||||
class LogoutWebViewDialog : public WebViewDialog
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
LogoutWebViewDialog(wxWindow* parent);
|
|
||||||
void on_loaded(wxWebViewEvent &evt) override;
|
|
||||||
void on_dpi_changed(const wxRect &suggested_rect) override {}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // GUI
|
} // GUI
|
||||||
} // Slic3r
|
} // Slic3r
|
||||||
|
|
||||||
|
@ -6,4 +6,6 @@
|
|||||||
namespace Slic3r::GUI {
|
namespace Slic3r::GUI {
|
||||||
void setup_webview_with_credentials(wxWebView* web_view, const std::string& username, const std::string& password);
|
void setup_webview_with_credentials(wxWebView* web_view, const std::string& username, const std::string& password);
|
||||||
void remove_webview_credentials(wxWebView* web_view);
|
void remove_webview_credentials(wxWebView* web_view);
|
||||||
|
void delete_cookies(wxWebView* web_view, const std::string& url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "WebViewPlatformUtils.hpp"
|
#include "WebViewPlatformUtils.hpp"
|
||||||
|
#include <boost/log/trivial.hpp>
|
||||||
|
|
||||||
|
|
||||||
namespace Slic3r::GUI {
|
namespace Slic3r::GUI {
|
||||||
@ -55,5 +56,56 @@ void remove_webview_credentials(wxWebView* web_view)
|
|||||||
g_webview_authorize_handlers.erase(it);
|
g_webview_authorize_handlers.erase(it);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
namespace {
|
||||||
|
void delete_cookie_callback (GObject* source_object, GAsyncResult* result, void* user_data)
|
||||||
|
{
|
||||||
|
WebKitCookieManager *cookie_manager = WEBKIT_COOKIE_MANAGER(source_object);
|
||||||
|
GError* err = nullptr;
|
||||||
|
gboolean b = webkit_cookie_manager_delete_cookie_finish(cookie_manager, result, &err);
|
||||||
|
if (err) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "Error deleting cookies: " << err->message;
|
||||||
|
g_error_free(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void get_cookie_callback (GObject* source_object, GAsyncResult* result, void* user_data)
|
||||||
|
{
|
||||||
|
GError* err = nullptr;
|
||||||
|
WebKitCookieManager *cookie_manager = WEBKIT_COOKIE_MANAGER(source_object);
|
||||||
|
GList * cookies = webkit_cookie_manager_get_cookies_finish(cookie_manager, result, &err);
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "Error retrieving cookies: " << err->message;
|
||||||
|
g_error_free(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (GList *l = cookies; l != nullptr; l = l->next) {
|
||||||
|
SoupCookie *cookie = static_cast<SoupCookie *>(l->data);
|
||||||
|
/*
|
||||||
|
printf("Cookie Name: %s\n", cookie->name);
|
||||||
|
printf("Cookie Value: %s\n", cookie->value);
|
||||||
|
printf("Domain: %s\n", cookie->domain);
|
||||||
|
printf("Path: %s\n", cookie->path);
|
||||||
|
printf("Expires: %s\n", soup_date_to_string(cookie->expires, SOUP_DATE_HTTP));
|
||||||
|
printf("Secure: %s\n", cookie->secure ? "true" : "false");
|
||||||
|
printf("HTTP Only: %s\n", cookie->http_only ? "true" : "false");
|
||||||
|
*/
|
||||||
|
webkit_cookie_manager_delete_cookie(cookie_manager, cookie, nullptr, (GAsyncReadyCallback)Slic3r::GUI::delete_cookie_callback, nullptr);
|
||||||
|
soup_cookie_free(cookie);
|
||||||
|
}
|
||||||
|
g_list_free(cookies);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void delete_cookies(wxWebView* web_view, const std::string& url)
|
||||||
|
{
|
||||||
|
// Call webkit_cookie_manager_get_cookies
|
||||||
|
// set its callback to call webkit_cookie_manager_get_cookies_finish
|
||||||
|
// then for each cookie call webkit_cookie_manager_delete_cookie
|
||||||
|
// set callback to call webkit_cookie_manager_delete_cookie_finish
|
||||||
|
const gchar* uri = url.c_str();
|
||||||
|
WebKitWebView* native_backend = static_cast<WebKitWebView *>(web_view->GetNativeBackend());
|
||||||
|
WebKitWebContext* context= webkit_web_view_get_context(native_backend);
|
||||||
|
WebKitCookieManager* cookieManager = webkit_web_context_get_cookie_manager(context);
|
||||||
|
webkit_cookie_manager_get_cookies(cookieManager, uri, nullptr, (GAsyncReadyCallback)Slic3r::GUI::get_cookie_callback, nullptr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
#import <WebKit/WKWebView.h>
|
#import <WebKit/WKWebView.h>
|
||||||
#import <WebKit/WKNavigationDelegate.h>
|
#import <WebKit/WKNavigationDelegate.h>
|
||||||
|
#import <WebKit/WKWebViewConfiguration.h>
|
||||||
|
#import <WebKit/WKWebsiteDataStore.h>
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
#import <Foundation/NSURLSession.h>
|
#import <Foundation/NSURLSession.h>
|
||||||
|
|
||||||
@ -114,7 +116,6 @@
|
|||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
||||||
namespace Slic3r::GUI {
|
namespace Slic3r::GUI {
|
||||||
void setup_webview_with_credentials(wxWebView* web_view, const std::string& username, const std::string& password)
|
void setup_webview_with_credentials(wxWebView* web_view, const std::string& username, const std::string& password)
|
||||||
{
|
{
|
||||||
@ -137,5 +138,24 @@ void remove_webview_credentials(wxWebView* web_view)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void delete_cookies(wxWebView* web_view, const std::string& url)
|
||||||
|
{
|
||||||
|
WKWebView* backend = static_cast<WKWebView*>(web_view->GetNativeBackend());
|
||||||
|
NSString *url_string = [NSString stringWithCString:url.c_str() encoding:[NSString defaultCStringEncoding]];
|
||||||
|
WKWebsiteDataStore *data_store = backend.configuration.websiteDataStore;
|
||||||
|
NSSet *website_data_types = [NSSet setWithObject:WKWebsiteDataTypeCookies];
|
||||||
|
[data_store fetchDataRecordsOfTypes:website_data_types completionHandler:^(NSArray<WKWebsiteDataRecord *> *records) {
|
||||||
|
for (WKWebsiteDataRecord *record in records) {
|
||||||
|
if ([url_string containsString:record.displayName]) {
|
||||||
|
[data_store removeDataOfTypes:website_data_types
|
||||||
|
forDataRecords:@[record]
|
||||||
|
completionHandler:^{
|
||||||
|
//NSLog(@"Deleted cookies for domain: %@", record.displayName);
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
#include "WebView2.h"
|
#include "WebView2.h"
|
||||||
#include <wrl.h>
|
#include <wrl.h>
|
||||||
#include <atlbase.h>
|
#include <atlbase.h>
|
||||||
#include <boost/log/trivial.hpp>
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
#include "wx/msw/private/comptr.h"
|
#include "wx/msw/private/comptr.h"
|
||||||
@ -13,6 +12,12 @@
|
|||||||
#include "format.hpp"
|
#include "format.hpp"
|
||||||
#include "Mainframe.hpp"
|
#include "Mainframe.hpp"
|
||||||
|
|
||||||
|
#include <boost/property_tree/ptree.hpp>
|
||||||
|
#include <boost/property_tree/json_parser.hpp>
|
||||||
|
#include <boost/log/trivial.hpp>
|
||||||
|
|
||||||
|
namespace pt = boost::property_tree;
|
||||||
|
|
||||||
namespace Slic3r::GUI {
|
namespace Slic3r::GUI {
|
||||||
|
|
||||||
std::unordered_map<ICoreWebView2*,EventRegistrationToken> g_basic_auth_handler_tokens;
|
std::unordered_map<ICoreWebView2*,EventRegistrationToken> g_basic_auth_handler_tokens;
|
||||||
@ -20,6 +25,9 @@ std::unordered_map<ICoreWebView2*,EventRegistrationToken> g_basic_auth_handler_t
|
|||||||
void setup_webview_with_credentials(wxWebView* webview, const std::string& username, const std::string& password)
|
void setup_webview_with_credentials(wxWebView* webview, const std::string& username, const std::string& password)
|
||||||
{
|
{
|
||||||
ICoreWebView2 *webView2 = static_cast<ICoreWebView2 *>(webview->GetNativeBackend());
|
ICoreWebView2 *webView2 = static_cast<ICoreWebView2 *>(webview->GetNativeBackend());
|
||||||
|
if (!webView2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
wxCOMPtr<ICoreWebView2_10> wv2_10;
|
wxCOMPtr<ICoreWebView2_10> wv2_10;
|
||||||
HRESULT hr = webView2->QueryInterface(IID_PPV_ARGS(&wv2_10));
|
HRESULT hr = webView2->QueryInterface(IID_PPV_ARGS(&wv2_10));
|
||||||
if (FAILED(hr)) {
|
if (FAILED(hr)) {
|
||||||
@ -59,6 +67,9 @@ void setup_webview_with_credentials(wxWebView* webview, const std::string& usern
|
|||||||
void remove_webview_credentials(wxWebView* webview)
|
void remove_webview_credentials(wxWebView* webview)
|
||||||
{
|
{
|
||||||
ICoreWebView2 *webView2 = static_cast<ICoreWebView2 *>(webview->GetNativeBackend());
|
ICoreWebView2 *webView2 = static_cast<ICoreWebView2 *>(webview->GetNativeBackend());
|
||||||
|
if (!webView2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
wxCOMPtr<ICoreWebView2_10> wv2_10;
|
wxCOMPtr<ICoreWebView2_10> wv2_10;
|
||||||
HRESULT hr = webView2->QueryInterface(IID_PPV_ARGS(&wv2_10));
|
HRESULT hr = webView2->QueryInterface(IID_PPV_ARGS(&wv2_10));
|
||||||
if (FAILED(hr)) {
|
if (FAILED(hr)) {
|
||||||
@ -78,6 +89,62 @@ void remove_webview_credentials(wxWebView* webview)
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
void delete_cookies(wxWebView* webview, const std::string& url)
|
||||||
|
{
|
||||||
|
ICoreWebView2 *webView2 = static_cast<ICoreWebView2 *>(webview->GetNativeBackend());
|
||||||
|
if (!webView2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
"cookies": [{
|
||||||
|
"domain": ".google.com",
|
||||||
|
"expires": 1756464458.304917,
|
||||||
|
"httpOnly": true,
|
||||||
|
"name": "__Secure-1PSIDCC",
|
||||||
|
"path": "/",
|
||||||
|
"priority": "High",
|
||||||
|
"sameParty": false,
|
||||||
|
"secure": true,
|
||||||
|
"session": false,
|
||||||
|
"size": 90,
|
||||||
|
"sourcePort": 443,
|
||||||
|
"sourceScheme": "Secure",
|
||||||
|
"value": "AKEyXzUvV_KBqM4aOlsudROI_VZ-ToIH41LRbYJFtFjmKq_rOmx1owoyUGvQHbwr5be380fKuQ"
|
||||||
|
},...]}
|
||||||
|
*/
|
||||||
|
wxString parameters = GUI::format_wxstr(L"{\"urls\": [\"%1%\"]}", url);
|
||||||
|
webView2->CallDevToolsProtocolMethod(L"Network.getCookies", parameters.c_str(),
|
||||||
|
Microsoft::WRL::Callback<ICoreWebView2CallDevToolsProtocolMethodCompletedHandler>(
|
||||||
|
[webView2, url](HRESULT errorCode, LPCWSTR resultJson) -> HRESULT {
|
||||||
|
if (FAILED(errorCode)) {
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
// Handle successful call (resultJson contains the list of cookies)
|
||||||
|
pt::ptree ptree;
|
||||||
|
try {
|
||||||
|
std::stringstream ss(GUI::into_u8(resultJson));
|
||||||
|
pt::read_json(ss, ptree);
|
||||||
|
}
|
||||||
|
catch (const std::exception& e) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "Failed to parse cookies json: " << e.what();
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
for (const auto& cookie : ptree.get_child("cookies")) {
|
||||||
|
std::string name = cookie.second.get<std::string>("name");
|
||||||
|
std::string domain = cookie.second.get<std::string>("domain");
|
||||||
|
//BOOST_LOG_TRIVIAL(debug) << "Deleting cookie: " << name << " : " << domain;
|
||||||
|
// Delete cookie by name and domain
|
||||||
|
wxString name_and_domain = GUI::format_wxstr(L"{\"name\": \"%1%\", \"domain\": \"%2%\"}", name, domain);
|
||||||
|
webView2->CallDevToolsProtocolMethod(L"Network.deleteCookies", name_and_domain.c_str(),
|
||||||
|
Microsoft::WRL::Callback<ICoreWebView2CallDevToolsProtocolMethodCompletedHandler>(
|
||||||
|
[](HRESULT errorCode, LPCWSTR resultJson) -> HRESULT { return S_OK; }).Get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).Get());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
} // namespace Slic3r::GUI
|
} // namespace Slic3r::GUI
|
||||||
#endif // WIN32
|
#endif // WIN32
|
||||||
|
Loading…
x
Reference in New Issue
Block a user