KiCad PCB EDA Suite
Loading...
Searching...
No Matches
panel_remote_symbol.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation; either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
24#include "panel_remote_symbol.h"
25
26#include <bitmaps.h>
27#include <build_version.h>
28#include <common.h>
29#include <ki_exception.h>
30#include <pgm_base.h>
31#include <project.h>
32#include <project_sch.h>
33#include <sch_edit_frame.h>
34#include <sch_symbol.h>
36#include <eeschema_settings.h>
41#include <lib_symbol.h>
42#include <sch_io/sch_io_mgr.h>
43#include <tool/tool_manager.h>
44#include <tools/sch_actions.h>
47#include <nlohmann/json.hpp>
48#include <kiplatform/ui.h>
49
50#ifndef wxUSE_BASE64
51#define wxUSE_BASE64 1
52#endif
53#include <wx/base64.h>
54
55#include <wx/datetime.h>
56#include <wx/choice.h>
57#include <wx/dir.h>
58#include <wx/ffile.h>
59#include <wx/filefn.h>
60#include <wx/intl.h>
61#include <wx/log.h>
62#include <wx/mstream.h>
63#include <wx/settings.h>
64#include <wx/sizer.h>
65#include <wx/strconv.h>
66#include <wx/webview.h>
67#include <zstd.h>
68
69wxString PANEL_REMOTE_SYMBOL::jsonString( const nlohmann::json& aObject, const char* aKey ) const
70{
71 auto it = aObject.find( aKey );
72
73 if( it != aObject.end() && it->is_string() )
74 return wxString::FromUTF8( it->get<std::string>() );
75
76 return wxString();
77}
78
79bool PANEL_REMOTE_SYMBOL::decodeBase64Payload( const std::string& aEncoded,
80 std::vector<uint8_t>& aOutput,
81 wxString& aError ) const
82{
83 if( aEncoded.empty() )
84 {
85 aError = _( "Missing payload data." );
86 return false;
87 }
88
89 wxMemoryBuffer buffer = wxBase64Decode( wxString::FromUTF8( aEncoded.c_str() ) );
90
91 if( buffer.IsEmpty() )
92 {
93 aError = _( "Failed to decode base64 payload." );
94 return false;
95 }
96
97 aOutput.resize( buffer.GetDataLen() );
98 memcpy( aOutput.data(), buffer.GetData(), buffer.GetDataLen() );
99 return true;
100}
101
102bool PANEL_REMOTE_SYMBOL::decompressIfNeeded( const std::string& aCompression,
103 const std::vector<uint8_t>& aInput,
104 std::vector<uint8_t>& aOutput,
105 wxString& aError ) const
106{
107 if( aCompression.empty() || aCompression == "NONE" )
108 {
109 aOutput = aInput;
110 return true;
111 }
112
113 if( aCompression != "ZSTD" )
114 {
115 aError = wxString::Format( _( "Unsupported compression '%s'." ),
116 wxString::FromUTF8( aCompression ) );
117 return false;
118 }
119
120 if( aInput.empty() )
121 {
122 aError = _( "Compressed payload was empty." );
123 return false;
124 }
125
126 unsigned long long expectedSize =
127 ZSTD_getFrameContentSize( aInput.data(), aInput.size() );
128
129 if( expectedSize == ZSTD_CONTENTSIZE_ERROR || expectedSize == ZSTD_CONTENTSIZE_UNKNOWN )
130 expectedSize = static_cast<unsigned long long>( aInput.size() ) * 4;
131
132 aOutput.resize( expectedSize );
133
134 size_t decompressed = ZSTD_decompress( aOutput.data(), expectedSize,
135 aInput.data(), aInput.size() );
136
137 if( ZSTD_isError( decompressed ) )
138 {
139 aError = wxString::Format( _( "ZSTD decompression failed: %s" ),
140 wxString::FromUTF8( ZSTD_getErrorName( decompressed ) ) );
141 return false;
142 }
143
144 aOutput.resize( decompressed );
145 return true;
146}
147
148wxString PANEL_REMOTE_SYMBOL::sanitizeForScript( const std::string& aJson ) const
149{
150 wxString script = wxString::FromUTF8( aJson.c_str() );
151 script.Replace( "\\", "\\\\" );
152 script.Replace( "'", "\\'" );
153 return script;
154}
155
156wxString PANEL_REMOTE_SYMBOL::sanitizeFileComponent( const wxString& aValue,
157 const wxString& aDefault ) const
158{
159 wxString result = aValue;
160 result.Trim( true ).Trim( false );
161
162 if( result.IsEmpty() )
163 result = aDefault;
164
165 for( size_t i = 0; i < result.length(); ++i )
166 {
167 wxUniChar ch = result[i];
168
169 if( ch == '/' || ch == '\\' || ch == ':' )
170 result[i] = '_';
171 }
172
173 return result;
174}
175
176bool PANEL_REMOTE_SYMBOL::writeBinaryFile( const wxFileName& aOutput,
177 const std::vector<uint8_t>& aPayload,
178 wxString& aError ) const
179{
180 if( aPayload.empty() )
181 {
182 aError = _( "Payload was empty." );
183 return false;
184 }
185
186 wxFileName targetDir = aOutput;
187 targetDir.SetFullName( wxEmptyString );
188
189 if( !targetDir.DirExists() )
190 {
191 if( !targetDir.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
192 {
193 aError = wxString::Format( _( "Unable to create '%s'." ), targetDir.GetFullPath() );
194 return false;
195 }
196 }
197
198 wxFFile file( aOutput.GetFullPath(), wxS( "wb" ) );
199
200 if( !file.IsOpened() )
201 {
202 aError = wxString::Format( _( "Unable to open '%s' for writing." ), aOutput.GetFullPath() );
203 return false;
204 }
205
206 if( file.Write( aPayload.data(), aPayload.size() ) != aPayload.size() )
207 {
208 aError = wxString::Format( _( "Failed to write '%s'." ), aOutput.GetFullPath() );
209 return false;
210 }
211
212 file.Close();
213 return true;
214}
215
216
217std::unique_ptr<LIB_SYMBOL> PANEL_REMOTE_SYMBOL::loadSymbolFromPayload( const std::vector<uint8_t>& aPayload,
218 const wxString& aLibItemName,
219 wxString& aError ) const
220{
221 if( aPayload.empty() )
222 {
223 aError = _( "Symbol payload was empty." );
224 return nullptr;
225 }
226
227 wxString tempPath = wxFileName::CreateTempFileName( wxS( "remote_symbol" ) );
228
229 if( tempPath.IsEmpty() )
230 {
231 aError = _( "Unable to create a temporary file for the symbol payload." );
232 return nullptr;
233 }
234
235 wxFileName tempFile( tempPath );
236
237 wxFFile file( tempFile.GetFullPath(), wxS( "wb" ) );
238
239 if( !file.IsOpened() )
240 {
241 aError = _( "Unable to create a temporary file for the symbol payload." );
242 wxRemoveFile( tempFile.GetFullPath() );
243 return nullptr;
244 }
245
246 if( file.Write( aPayload.data(), aPayload.size() ) != aPayload.size() )
247 {
248 aError = _( "Failed to write the temporary symbol payload." );
249 file.Close();
250 wxRemoveFile( tempFile.GetFullPath() );
251 return nullptr;
252 }
253
254 file.Close();
255
256 IO_RELEASER<SCH_IO> plugin( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_KICAD ) );
257
258 if( !plugin )
259 {
260 aError = _( "Unable to access the KiCad symbol plugin." );
261 wxRemoveFile( tempFile.GetFullPath() );
262 return nullptr;
263 }
264
265 std::unique_ptr<LIB_SYMBOL> symbol;
266
267 try
268 {
269 LIB_SYMBOL* loaded = plugin->LoadSymbol( tempFile.GetFullPath(), aLibItemName );
270
271 if( loaded )
272 {
273 symbol.reset( loaded );
274 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ),
275 "loadSymbolFromPayload: loaded symbol %s from temporary file %s",
276 aLibItemName.ToUTF8().data(), tempFile.GetFullPath().ToUTF8().data() );
277 }
278 else
279 {
280 aError = _( "Symbol payload did not include the expected symbol." );
281 }
282 }
283 catch( const IO_ERROR& e )
284 {
285 aError = wxString::Format( _( "Unable to decode the symbol payload: %s" ), e.What() );
286 }
287
288 wxRemoveFile( tempFile.GetFullPath() );
289 return symbol;
290}
291
292
294 wxPanel( aParent ),
295 m_frame( aParent ),
296 m_dataSourceChoice( nullptr ),
297 m_configButton( nullptr ),
298 m_webView( nullptr ),
299 m_pcm( std::make_shared<PLUGIN_CONTENT_MANAGER>( []( int ) {} ) ),
300 m_sessionId( 0 ),
301 m_messageIdCounter( 0 ),
302 m_pendingHandshake( false )
303{
304 wxBoxSizer* topSizer = new wxBoxSizer( wxVERTICAL );
305
306 wxBoxSizer* controlsSizer = new wxBoxSizer( wxHORIZONTAL );
307 m_dataSourceChoice = new wxChoice( this, wxID_ANY );
308 m_dataSourceChoice->SetMinSize( FromDIP( wxSize( 160, -1 ) ) );
309 m_dataSourceChoice->SetToolTip( _( "Select which remote data source to query." ) );
310 controlsSizer->Add( m_dataSourceChoice, 1, wxEXPAND | wxRIGHT, FromDIP( 2 ) );
311
312 m_configButton = new BITMAP_BUTTON( this, wxID_ANY );
313 m_configButton->SetBitmap( KiBitmapBundle( BITMAPS::config ) );
314 m_configButton->SetPadding( FromDIP( 2 ) );
315 m_configButton->SetToolTip( _( "Configure remote data sources." ) );
316 controlsSizer->Add( m_configButton, 0, wxALIGN_CENTER_VERTICAL );
317
318 topSizer->Add( controlsSizer, 0, wxEXPAND | wxALL, FromDIP( 4 ) );
319
320 m_webView = new WEBVIEW_PANEL( this );
321 m_webView->AddMessageHandler( wxS( "kicad" ),
322 [this]( const wxString& payload )
323 {
324 onKicadMessage( payload );
325 } );
326 m_webView->SetHandleExternalLinks( true );
327
328 if( wxWebView* browser = m_webView->GetWebView() )
329 {
330 browser->Bind( wxEVT_WEBVIEW_LOADED, &PANEL_REMOTE_SYMBOL::onWebViewLoaded, this );
331 }
332
333 topSizer->Add( m_webView, 1, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, FromDIP( 2 ) );
334
335 SetSizer( topSizer );
336
337 m_dataSourceChoice->Bind( wxEVT_CHOICE, &PANEL_REMOTE_SYMBOL::onDataSourceChanged, this );
338 m_configButton->Bind( wxEVT_BUTTON, &PANEL_REMOTE_SYMBOL::onConfigure, this );
339
340 RefreshDataSources();
341
342 Bind( wxEVT_SYS_COLOUR_CHANGED, wxSysColourChangedEventHandler( PANEL_REMOTE_SYMBOL::onDarkModeToggle ), this );
343
344 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "PANEL_REMOTE_SYMBOL constructed (frame=%p)", (void*)aParent );
345}
346
347
349{
350 Unbind( wxEVT_SYS_COLOUR_CHANGED, wxSysColourChangedEventHandler( PANEL_REMOTE_SYMBOL::onDarkModeToggle ), this );
351}
352
353
355{
356 if( wxWebView* browser = m_webView->GetWebView() )
357 {
358 browser->Bind( wxEVT_WEBVIEW_LOADED, &PANEL_REMOTE_SYMBOL::onWebViewLoaded, this );
359 }
360}
361
362
364{
365 if( KICAD_SETTINGS* cfg = GetAppSettings<KICAD_SETTINGS>( "kicad" ) )
366 m_pcm->SetRepositoryList( cfg->m_PcmRepositories );
367
368 m_dataSources.clear();
369 m_dataSourceChoice->Clear();
370
371 const std::vector<PCM_INSTALLATION_ENTRY> installed = m_pcm->GetInstalledPackages();
372
373 for( const PCM_INSTALLATION_ENTRY& entry : installed )
374 {
375 if( entry.package.type != PT_DATASOURCE )
376 continue;
377
378 wxString label = entry.package.name;
379
380 if( !entry.current_version.IsEmpty() )
381 label << wxS( " (" ) << entry.current_version << wxS( ")" );
382
383 m_dataSources.push_back( entry );
384 m_dataSourceChoice->Append( label );
385 }
386
387 if( m_dataSources.empty() )
388 {
389 m_dataSourceChoice->Enable( false );
390 showMessage( _( "No remote data sources are currently installed." ) );
391 return;
392 }
393
394 m_dataSourceChoice->Enable( true );
395 m_dataSourceChoice->SetSelection( 0 );
396 loadDataSource( 0 );
397}
398
399
400void PANEL_REMOTE_SYMBOL::onDataSourceChanged( wxCommandEvent& aEvent )
401{
402 const int selection = aEvent.GetSelection();
403
404 if( selection == wxNOT_FOUND )
405 return;
406
407 loadDataSource( static_cast<size_t>( selection ) );
408}
409
410
411void PANEL_REMOTE_SYMBOL::onConfigure( wxCommandEvent& aEvent )
412{
413 DIALOG_REMOTE_SYMBOL_CONFIG dlg( this );
414
415 dlg.ShowModal();
416
418}
419
420
421void PANEL_REMOTE_SYMBOL::onDarkModeToggle( wxSysColourChangedEvent& aEvent )
422{
424}
425
426
428{
429 if( aIndex >= m_dataSources.size() )
430 return false;
431
432 return loadDataSource( m_dataSources[aIndex] );
433}
434
435
437{
438 std::optional<wxFileName> jsonPath = findDataSourceJson( aEntry );
439
440 if( !jsonPath )
441 {
442 wxLogWarning( "No JSON configuration found for data source %s", aEntry.package.identifier );
443 showMessage( wxString::Format( _( "No configuration JSON found for '%s'." ),
444 aEntry.package.name ) );
445 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "loadDataSource: no json for %s", aEntry.package.identifier );
446 return false;
447 }
448
449 wxFFile file( jsonPath->GetFullPath(), "rb" );
450
451 if( !file.IsOpened() )
452 {
453 wxLogWarning( "Unable to open remote data source JSON: %s", jsonPath->GetFullPath() );
454 showMessage( wxString::Format( _( "Unable to open '%s'." ), jsonPath->GetFullPath() ) );
455 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "loadDataSource: cannot open %s", jsonPath->GetFullPath() );
456 return false;
457 }
458
459 wxString jsonContent;
460
461 if( !file.ReadAll( &jsonContent, wxConvUTF8 ) )
462 {
463 wxLogWarning( "Failed to read remote data source JSON: %s", jsonPath->GetFullPath() );
464 showMessage( wxString::Format( _( "Unable to read '%s'." ), jsonPath->GetFullPath() ) );
465 return false;
466 }
467
468 if( std::optional<wxString> url = extractUrlFromJson( jsonContent ) )
469 {
470 m_pendingHandshake = true;
471 m_webView->LoadURL( *url );
472 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "loadDataSource: loading URL %s", (*url).ToUTF8().data() );
473 return true;
474 }
475
476 wxLogWarning( "Remote data source JSON did not produce a valid URL for %s",
477 aEntry.package.identifier );
478 showMessage( wxString::Format( _( "Unable to load remote data for '%s'." ),
479 aEntry.package.name ) );
480 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "loadDataSource: failed to find URL in %s", jsonPath->GetFullPath() );
481 return false;
482}
483
484
485std::optional<wxFileName>
487{
488 wxString cleanId = aEntry.package.identifier;
489 cleanId.Replace( '.', '_' );
490
491 wxFileName baseDir = wxFileName::DirName( m_pcm->Get3rdPartyPath() );
492 baseDir.AppendDir( wxS( "resources" ) );
493 baseDir.AppendDir( cleanId );
494
495 const wxString resourcesPath = baseDir.GetFullPath();
496
497 if( !wxDirExists( resourcesPath ) )
498 return std::nullopt;
499
500 const std::vector<wxString> preferredNames = {
501 wxS( "remote_symbol.json" ),
502 wxS( "datasource.json" )
503 };
504
505 for( const wxString& candidate : preferredNames )
506 {
507 wxFileName file( baseDir );
508 file.SetFullName( candidate );
509
510 if( file.FileExists() )
511 return file;
512 }
513
514 wxDir dir( resourcesPath );
515
516 if( !dir.IsOpened() )
517 return std::nullopt;
518
519 wxString jsonFile;
520
521 if( dir.GetFirst( &jsonFile, wxS( "*.json" ), wxDIR_FILES ) )
522 {
523 wxFileName fallback( baseDir );
524 fallback.SetFullName( jsonFile );
525 return fallback;
526 }
527
528 return std::nullopt;
529}
530
531
532void PANEL_REMOTE_SYMBOL::showMessage( const wxString& aMessage )
533{
534 if( !m_webView )
535 return;
536
537 wxString escaped = aMessage;
538 escaped.Replace( "&", "&amp;" );
539 escaped.Replace( "<", "&lt;" );
540 escaped.Replace( ">", "&gt;" );
541
542 wxColour bgColour = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW );
543 wxColour fgColour = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOWTEXT );
544
545 wxString html;
546 html << wxS( "<html><head><style>" )
547 << wxString::Format( wxS( "body { background-color: #%02x%02x%02x; color: #%02x%02x%02x; "
548 "font-family: system-ui, sans-serif; padding: 10px; }" ),
549 bgColour.Red(), bgColour.Green(), bgColour.Blue(),
550 fgColour.Red(), fgColour.Green(), fgColour.Blue() )
551 << wxS( "</style></head><body><p>" ) << escaped << wxS( "</p></body></html>" );
552
553 m_webView->SetPage( html );
554}
555
556
557std::optional<wxString> PANEL_REMOTE_SYMBOL::extractUrlFromJson( const wxString& aJsonContent ) const
558{
559 if( aJsonContent.IsEmpty() )
560 return std::nullopt;
561
562 wxScopedCharBuffer utf8 = aJsonContent.ToUTF8();
563
564 if( !utf8 || utf8.length() == 0 )
565 return std::nullopt;
566
567 try
568 {
569 nlohmann::json parsed = nlohmann::json::parse( utf8.data() );
570 std::string url;
571
572 if( parsed.is_string() )
573 {
574 url = parsed.get<std::string>();
575 }
576 else if( parsed.is_object() )
577 {
578 auto extractString = [&]( const char* key ) -> std::string
579 {
580 auto it = parsed.find( key );
581
582 if( it != parsed.end() && it->is_string() )
583 return it->get<std::string>();
584
585 return {};
586 };
587
588 auto extractInt = [&]( const char* key ) -> std::optional<int>
589 {
590 auto it = parsed.find( key );
591
592 if( it == parsed.end() )
593 return std::nullopt;
594
595 if( it->is_number_integer() )
596 return it->get<int>();
597
598 if( it->is_string() )
599 {
600 try
601 {
602 return std::stoi( it->get<std::string>() );
603 }
604 catch( ... )
605 {
606 }
607 }
608
609 return std::nullopt;
610 };
611
612 std::string host = extractString( "host" );
613 std::optional<int> port = extractInt( "port" );
614 std::string path = extractString( "path" );
615
616 if( !host.empty() )
617 {
618 if( path.empty() )
619 path = "/";
620
621 if( port && *port > 0 )
622 url = wxString::Format( "%s:%d%s", host, *port, path ).ToStdString();
623 else
624 url = host + path;
625 }
626
627 if( url.empty() )
628 url = extractString( "url" );
629
630 if( url.empty() )
631 {
632 for( const char* key : { "website", "endpoint" } )
633 {
634 url = extractString( key );
635
636 if( !url.empty() )
637 break;
638 }
639 }
640
641 if( url.empty() )
642 {
643 for( const auto& [name, value] : parsed.items() )
644 {
645 if( value.is_string() )
646 {
647 const std::string candidate = value.get<std::string>();
648
649 if( candidate.rfind( "http", 0 ) == 0 || candidate.rfind( "file", 0 ) == 0 )
650 {
651 url = candidate;
652 break;
653 }
654 }
655 }
656 }
657 }
658
659 if( url.empty() )
660 return std::nullopt;
661
662 return wxString::FromUTF8( url.c_str() );
663 }
664 catch( const std::exception& e )
665 {
666 wxLogWarning( "Failed to parse remote symbol JSON: %s", e.what() );
667 return std::nullopt;
668 }
669}
670
671
672void PANEL_REMOTE_SYMBOL::onKicadMessage( const wxString& aMessage )
673{
674 wxScopedCharBuffer utf8 = aMessage.ToUTF8();
675
676 if( !utf8 || utf8.length() == 0 )
677 {
678 wxLogWarning( "Remote symbol RPC: empty payload." );
679 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "onKicadMessage: empty payload" );
680 return;
681 }
682
683 try
684 {
685 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "onKicadMessage: received payload size=%d", (int)utf8.length() );
686 handleRpcMessage( nlohmann::json::parse( utf8.data() ) );
687 }
688 catch( const std::exception& e )
689 {
690 wxLogWarning( "Remote symbol RPC parse error: %s", e.what() );
691 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "onKicadMessage: parse error %s", e.what() );
692 }
693}
694
695
696void PANEL_REMOTE_SYMBOL::onWebViewLoaded( wxWebViewEvent& aEvent )
697{
698 wxUnusedVar( aEvent );
699
701 {
702 CallAfter( [this]()
703 {
705 {
706 m_pendingHandshake = false;
708 }
709 } );
710 }
711
712 aEvent.Skip();
713}
714
715
717{
718 if( !m_webView )
719 return;
720
721 m_sessionId = KIID();
723
724 nlohmann::json params = nlohmann::json::object();
725 params["client_name"] = "KiCad";
726 params["client_version"] = GetSemanticVersion().ToStdString();
727 params["supported_versions"] = { REMOTE_SYMBOL_SESSION_VERSION };
728
729 sendRpcMessage( wxS( "NEW_SESSION" ), std::move( params ) );
730 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "beginSessionHandshake: NEW_SESSION sent, session=%s", m_sessionId.AsString() );
731}
732
733
734void PANEL_REMOTE_SYMBOL::sendRpcMessage( const wxString& aCommand,
735 nlohmann::json aParameters,
736 std::optional<int> aResponseTo,
737 const wxString& aStatus,
738 const std::string& aData,
739 const wxString& aErrorCode,
740 const wxString& aErrorMessage )
741{
742 if( !m_webView )
743 return;
744
745 nlohmann::json payload = nlohmann::json::object();
746 payload["version"] = REMOTE_SYMBOL_SESSION_VERSION;
747 payload["session_id"] = m_sessionId.AsStdString();
748 payload["message_id"] = ++m_messageIdCounter;
749 payload["command"] = aCommand.ToStdString();
750
751 if( aResponseTo )
752 payload["response_to"] = *aResponseTo;
753
754 if( !aStatus.IsEmpty() )
755 payload["status"] = aStatus.ToStdString();
756
757 if( !aParameters.is_null() && !aParameters.empty() )
758 payload["parameters"] = std::move( aParameters );
759
760 if( !aData.empty() )
761 payload["data"] = aData;
762
763 if( !aErrorCode.IsEmpty() )
764 payload["error_code"] = aErrorCode.ToStdString();
765
766 if( !aErrorMessage.IsEmpty() )
767 payload["error_message"] = aErrorMessage.ToStdString();
768
769 wxString script = wxString::Format( wxS( "window.kiclient.postMessage('%s');" ),
770 sanitizeForScript( payload.dump() ) );
771 m_webView->RunScriptAsync( script );
772 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "sendRpcMessage: command=%s message_id=%d status=%s",
773 aCommand.ToUTF8().data(), payload["message_id"].get<int>(), aStatus.ToUTF8().data() );
774}
775
776
777void PANEL_REMOTE_SYMBOL::respondWithError( const wxString& aCommand, int aResponseTo,
778 const wxString& aErrorCode,
779 const wxString& aErrorMessage )
780{
781 sendRpcMessage( aCommand, nlohmann::json::object(), aResponseTo, wxS( "ERROR" ),
782 std::string(), aErrorCode, aErrorMessage );
783}
784
785
786void PANEL_REMOTE_SYMBOL::handleRpcMessage( const nlohmann::json& aMessage )
787{
788 if( !aMessage.is_object() )
789 return;
790
791 const wxString command = jsonString( aMessage, "command" );
792
793 if( command.IsEmpty() )
794 return;
795
796 auto messageIdIt = aMessage.find( "message_id" );
797
798 if( messageIdIt == aMessage.end() || !messageIdIt->is_number_integer() )
799 return;
800
801 const int messageId = messageIdIt->get<int>();
802
803 const int version = aMessage.value( "version", 0 );
804
805 if( version != REMOTE_SYMBOL_SESSION_VERSION )
806 {
807 respondWithError( command, messageId, wxS( "UNSUPPORTED_VERSION" ),
808 wxString::Format( _( "Unsupported RPC version %d." ), version ) );
809 return;
810 }
811
812 const wxString sessionId = jsonString( aMessage, "session_id" );
813
814 if( sessionId.IsEmpty() )
815 {
816 respondWithError( command, messageId, wxS( "INVALID_PARAMETERS" ),
817 _( "Missing session identifier." ) );
818 return;
819 }
820
821 if( !sessionId.IsSameAs( m_sessionId.AsString() ) )
822 wxLogWarning( "Remote symbol RPC session mismatch (expected %s, got %s).",
823 m_sessionId.AsString(), sessionId );
824
825 const wxString status = jsonString( aMessage, "status" );
826
827 if( status.IsSameAs( wxS( "ERROR" ), false ) )
828 {
829 wxLogWarning( "Remote symbol RPC error (%s): %s", jsonString( aMessage, "error_code" ),
830 jsonString( aMessage, "error_message" ) );
831 return;
832 }
833
834 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "handleRpcMessage: command=%s message_id=%d status=%s session=%s",
835 command.ToUTF8().data(), messageId, status.ToUTF8().data(), sessionId.ToUTF8().data() );
836
837 nlohmann::json params = nlohmann::json::object();
838 auto paramsIt = aMessage.find( "parameters" );
839
840 if( paramsIt != aMessage.end() && paramsIt->is_object() )
841 params = *paramsIt;
842
843 const std::string data = aMessage.value( "data", std::string() );
844
845 if( command == wxS( "NEW_SESSION" ) )
846 {
847 nlohmann::json reply = nlohmann::json::object();
848 reply["client_name"] = "KiCad";
849 reply["client_version"] = GetSemanticVersion().ToStdString();
850 reply["supported_versions"] = { REMOTE_SYMBOL_SESSION_VERSION };
851 sendRpcMessage( command, std::move( reply ), messageId );
852 return;
853 }
854 else if( command == wxS( "GET_KICAD_VERSION" ) )
855 {
856 nlohmann::json reply = nlohmann::json::object();
857 reply["kicad_version"] = GetSemanticVersion().ToStdString();
858 sendRpcMessage( command, std::move( reply ), messageId );
859 return;
860 }
861 else if( command == wxS( "LIST_SUPPORTED_VERSIONS" ) )
862 {
863 nlohmann::json reply = nlohmann::json::object();
864 reply["supported_versions"] = { REMOTE_SYMBOL_SESSION_VERSION };
865 sendRpcMessage( command, std::move( reply ), messageId );
866 return;
867 }
868 else if( command == wxS( "CAPABILITIES" ) )
869 {
870 nlohmann::json reply = nlohmann::json::object();
871 reply["commands"] = { "NEW_SESSION", "GET_KICAD_VERSION", "LIST_SUPPORTED_VERSIONS",
872 "CAPABILITIES", "PING", "PONG", "DL_SYMBOL", "DL_FOOTPRINT",
873 "DL_SPICE", "DL_3DMODEL" };
874 reply["compression"] = { "NONE", "ZSTD" };
875 reply["max_message_size"] = 0;
876 sendRpcMessage( command, std::move( reply ), messageId );
877 return;
878 }
879 else if( command == wxS( "PING" ) )
880 {
881 nlohmann::json reply = nlohmann::json::object();
882
883 if( params.contains( "nonce" ) )
884 reply["nonce"] = params["nonce"];
885
886 sendRpcMessage( wxS( "PONG" ), std::move( reply ), messageId );
887 return;
888 }
889 else if( command == wxS( "PONG" ) )
890 {
891 return;
892 }
893
894 const wxString compression = jsonString( params, "compression" );
895
896 if( command.StartsWith( wxS( "DL_" ) ) )
897 {
898 if( compression.IsEmpty() )
899 {
900 respondWithError( command, messageId, wxS( "INVALID_PARAMETERS" ),
901 _( "Missing compression metadata." ) );
902 return;
903 }
904
905 std::vector<uint8_t> decoded;
906 wxString error;
907
908 if( !decodeBase64Payload( data, decoded, error ) )
909 {
910 respondWithError( command, messageId, wxS( "INVALID_PAYLOAD" ), error );
911 return;
912 }
913
914 std::vector<uint8_t> payload;
915 wxScopedCharBuffer compUtf8 = compression.ToUTF8();
916 std::string compressionStr = compUtf8 ? std::string( compUtf8.data() ) : std::string();
917
918 if( !decompressIfNeeded( compressionStr, decoded, payload, error ) )
919 {
920 respondWithError( command, messageId, wxS( "INVALID_PAYLOAD" ), error );
921 return;
922 }
923
924 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "handleRpcMessage: decoded size=%zu decompressed size=%zu command=%s",
925 decoded.size(), payload.size(), command.ToUTF8().data() );
926
927 bool ok = false;
928
929 if( command == wxS( "DL_SYMBOL" ) )
930 ok = receiveSymbol( params, payload, error );
931 else if( command == wxS( "DL_FOOTPRINT" ) )
932 ok = receiveFootprint( params, payload, error );
933 else if( command == wxS( "DL_3DMODEL" ) )
934 ok = receive3DModel( params, payload, error );
935 else if( command == wxS( "DL_SPICE" ) )
936 ok = receiveSPICEModel( params, payload, error );
937 else
938 {
939 respondWithError( command, messageId, wxS( "UNKNOWN_COMMAND" ),
940 wxString::Format( _( "Command '%s' is not supported." ), command ) );
941 return;
942 }
943
944 if( ok )
945 sendRpcMessage( command, nlohmann::json::object(), messageId );
946 else
947 respondWithError( command, messageId, wxS( "INTERNAL_ERROR" ),
948 error.IsEmpty() ? _( "Unable to store payload." ) : error );
949
950 return;
951 }
952
953 respondWithError( command, messageId, wxS( "UNKNOWN_COMMAND" ),
954 wxString::Format( _( "Command '%s' is not supported." ), command ) );
955}
956
957
958bool PANEL_REMOTE_SYMBOL::ensureDestinationRoot( wxFileName& aOutDir, wxString& aError ) const
959{
961
962 if( !settings )
963 {
964 aError = _( "Unable to load schematic settings." );
965 return false;
966 }
967
968 wxString destination = settings->m_RemoteSymbol.destination_dir;
969
970 if( destination.IsEmpty() )
972
973 destination = ExpandEnvVarSubstitutions( destination,
974 &Pgm().GetSettingsManager().Prj() );
975 destination.Trim( true ).Trim( false );
976
977 if( destination.IsEmpty() )
978 {
979 aError = _( "Destination directory is not configured." );
980 return false;
981 }
982
983 wxFileName dir = wxFileName::DirName( destination );
984 dir.Normalize( FN_NORMALIZE_FLAGS );
985
986 if( !dir.DirExists() )
987 {
988 if( !dir.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
989 {
990 aError = wxString::Format( _( "Unable to create directory '%s'." ), dir.GetFullPath() );
991 return false;
992 }
993 }
994
995 aOutDir = dir;
996 return true;
997}
998
999
1000bool PANEL_REMOTE_SYMBOL::ensureSymbolLibraryEntry( const wxFileName& aLibraryFile,
1001 const wxString& aNickname, bool aGlobalTable,
1002 wxString& aError ) const
1003{
1004 LIBRARY_MANAGER& manager = Pgm().GetLibraryManager();
1005 std::optional<LIBRARY_TABLE*> tableOpt = manager.Table( LIBRARY_TABLE_TYPE::SYMBOL,
1007
1008 if( !tableOpt )
1009 {
1010 aError = _( "Unable to access the symbol library table." );
1011 return false;
1012 }
1013
1014 LIBRARY_TABLE* table = *tableOpt;
1015 const wxString fullPath = aLibraryFile.GetFullPath();
1016
1017 if( table->HasRow( aNickname ) )
1018 {
1019 if( std::optional<LIBRARY_TABLE_ROW*> rowOpt = table->Row( aNickname ); rowOpt )
1020 {
1021 LIBRARY_TABLE_ROW* row = *rowOpt;
1022
1023 if( row->URI() != fullPath )
1024 {
1025 row->SetURI( fullPath );
1026
1027 if( !table->Save() )
1028 {
1029 aError = _( "Failed to update the symbol library table." );
1030 return false;
1031 }
1032 }
1033 }
1034
1035 return true;
1036 }
1037
1038 LIBRARY_TABLE_ROW& row = table->InsertRow();
1039 row.SetNickname( aNickname );
1040 row.SetURI( fullPath );
1041 row.SetType( wxS( "KiCad" ) );
1042 row.SetOptions( wxString() );
1043 row.SetDescription( _( "Remote download" ) );
1044 row.SetOk( true );
1045
1046 if( !table->Save() )
1047 {
1048 aError = _( "Failed to save the symbol library table." );
1049 return false;
1050 }
1051
1052 return true;
1053}
1054
1055
1056bool PANEL_REMOTE_SYMBOL::ensureFootprintLibraryEntry( const wxFileName& aLibraryDir,
1057 const wxString& aNickname,
1058 bool aGlobalTable, wxString& aError ) const
1059{
1060 LIBRARY_MANAGER& manager = Pgm().GetLibraryManager();
1061 std::optional<LIBRARY_TABLE*> tableOpt = manager.Table( LIBRARY_TABLE_TYPE::FOOTPRINT,
1063
1064 if( !tableOpt )
1065 {
1066 aError = _( "Unable to access the footprint library table." );
1067 return false;
1068 }
1069
1070 LIBRARY_TABLE* table = *tableOpt;
1071 const wxString fullPath = aLibraryDir.GetFullPath();
1072
1073 if( table->HasRow( aNickname ) )
1074 {
1075 if( std::optional<LIBRARY_TABLE_ROW*> rowOpt = table->Row( aNickname ); rowOpt )
1076 {
1077 LIBRARY_TABLE_ROW* row = *rowOpt;
1078
1079 if( row->URI() != fullPath )
1080 {
1081 row->SetURI( fullPath );
1082
1083 if( !table->Save() )
1084 {
1085 aError = _( "Failed to update the footprint library table." );
1086 return false;
1087 }
1088 }
1089 }
1090
1091 return true;
1092 }
1093
1094 LIBRARY_TABLE_ROW& row = table->InsertRow();
1095 row.SetNickname( aNickname );
1096 row.SetURI( fullPath );
1097 row.SetType( wxS( "KiCad" ) );
1098 row.SetOptions( wxString() );
1099 row.SetDescription( _( "Remote download" ) );
1100 row.SetOk( true );
1101
1102 if( !table->Save() )
1103 {
1104 aError = _( "Failed to save the footprint library table." );
1105 return false;
1106 }
1107
1108 return true;
1109}
1110
1111
1113{
1114 wxString prefix;
1115
1116 if( EESCHEMA_SETTINGS* settings = GetAppSettings<EESCHEMA_SETTINGS>( "eeschema" ) )
1117 prefix = settings->m_RemoteSymbol.library_prefix;
1118
1119 if( prefix.IsEmpty() )
1121
1122 prefix.Trim( true ).Trim( false );
1123
1124 if( prefix.IsEmpty() )
1125 prefix = wxS( "remote" );
1126
1127 for( size_t i = 0; i < prefix.length(); ++i )
1128 {
1129 wxUniChar ch = prefix[i];
1130
1131 if( !( wxIsalnum( ch ) || ch == '_' || ch == '-' ) )
1132 prefix[i] = '_';
1133 }
1134
1135 return prefix;
1136}
1137
1138
1139bool PANEL_REMOTE_SYMBOL::placeDownloadedSymbol( const wxString& aNickname,
1140 const wxString& aLibItemName,
1141 wxString& aError )
1142{
1143 if( !m_frame )
1144 {
1145 aError = _( "No schematic editor is available for placement." );
1146 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "placeDownloadedSymbol: no frame available" );
1147 return false;
1148 }
1149
1150 if( aNickname.IsEmpty() || aLibItemName.IsEmpty() )
1151 {
1152 aError = _( "Downloaded symbol metadata is incomplete." );
1153 return false;
1154 }
1155
1156 LIB_ID libId;
1157 libId.SetLibNickname( aNickname );
1158 libId.SetLibItemName( aLibItemName );
1159
1160 LIB_SYMBOL* libSymbol = m_frame->GetLibSymbol( libId );
1161
1162 if( !libSymbol )
1163 {
1164 aError = _( "Unable to load the downloaded symbol for placement." );
1165 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "placeDownloadedSymbol: failed to load libSymbol %s:%s",
1166 aNickname.ToUTF8().data(), aLibItemName.ToUTF8().data() );
1167 return false;
1168 }
1169
1170 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "placeDownloadedSymbol: loaded libSymbol %s:%s",
1171 aNickname.ToUTF8().data(), aLibItemName.ToUTF8().data() );
1172
1173 SCH_SYMBOL* symbol = new SCH_SYMBOL( *libSymbol, libId, &m_frame->GetCurrentSheet(), 1 );
1174 symbol->SetParent( m_frame->GetScreen() );
1175
1176 if( EESCHEMA_SETTINGS* cfg = m_frame->eeconfig(); cfg && cfg->m_AutoplaceFields.enable )
1177 {
1178 symbol->AutoplaceFields( nullptr, AUTOPLACE_AUTO );
1179 }
1180
1181 TOOL_MANAGER* toolMgr = m_frame->GetToolManager();
1182
1183 if( !toolMgr )
1184 {
1185 delete symbol;
1186 aError = _( "Unable to access the schematic placement tools." );
1187 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "placeDownloadedSymbol: no tool manager available" );
1188 return false;
1189 }
1190
1191 m_frame->Raise();
1193 SCH_ACTIONS::PLACE_SYMBOL_PARAMS{ symbol, true } );
1194 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "placeDownloadedSymbol: posted placeSymbol action for %s:%s",
1195 aNickname.ToUTF8().data(), aLibItemName.ToUTF8().data() );
1196 return true;
1197}
1198
1199
1200bool PANEL_REMOTE_SYMBOL::receiveSymbol( const nlohmann::json& aParams,
1201 const std::vector<uint8_t>& aPayload,
1202 wxString& aError )
1203{
1204 const wxString mode = jsonString( aParams, "mode" );
1205 const bool placeAfterDownload = mode.IsSameAs( wxS( "PLACE" ), false );
1206
1207 if( !mode.IsEmpty() && !mode.IsSameAs( wxS( "SAVE" ), false )
1208 && !mode.IsSameAs( wxS( "PLACE" ), false ) )
1209 {
1210 aError = wxString::Format( _( "Unsupported transfer mode '%s'." ), mode );
1211 return false;
1212 }
1213
1214 const wxString contentType = jsonString( aParams, "content_type" );
1215
1216 if( !contentType.IsSameAs( wxS( "KICAD_SYMBOL_V1" ), false ) )
1217 {
1218 aError = _( "Unsupported symbol payload type." );
1219 return false;
1220 }
1221
1222 if( !m_frame )
1223 {
1224 aError = _( "No schematic editor is available to store symbols." );
1225 return false;
1226 }
1227
1228 EESCHEMA_SETTINGS* settings = GetAppSettings<EESCHEMA_SETTINGS>( "eeschema" );
1229
1230 if( !settings )
1231 {
1232 aError = _( "Unable to load schematic settings." );
1233 return false;
1234 }
1235
1236 const bool addToGlobal = settings->m_RemoteSymbol.add_to_global_table;
1237
1238 wxFileName baseDir;
1239
1240 if( !ensureDestinationRoot( baseDir, aError ) )
1241 return false;
1242
1243 wxFileName symDir = baseDir;
1244 symDir.AppendDir( wxS( "symbols" ) );
1245
1246 if( !symDir.DirExists() && !symDir.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
1247 {
1248 aError = wxString::Format( _( "Unable to create '%s'." ), symDir.GetFullPath() );
1249 return false;
1250 }
1251
1252 // Get symbol's internal name from "name" parameter
1253 wxString libItemName = jsonString( aParams, "name" );
1254 if( libItemName.IsEmpty() )
1255 libItemName = wxS( "symbol" );
1256 libItemName.Trim( true ).Trim( false );
1257
1258 // Get library name from "library" parameter, fallback to "symbols"
1259 wxString libraryName = jsonString( aParams, "library" );
1260 if( libraryName.IsEmpty() )
1261 libraryName = wxS( "symbols" );
1262 libraryName.Trim( true ).Trim( false );
1263
1264 wxString sanitizedLibName = sanitizeFileComponent( libraryName, wxS( "symbols" ) );
1265 const wxString nickname = sanitizedPrefix() + wxS( "_" ) + sanitizedLibName;
1266
1267 wxFileName outFile( symDir );
1268 outFile.SetFullName( nickname + wxS( ".kicad_sym" ) );
1269
1270 if( !ensureSymbolLibraryEntry( outFile, nickname, addToGlobal, aError ) )
1271 return false;
1272
1274
1275 if( !adapter )
1276 {
1277 aError = _( "Unable to access the symbol library manager." );
1278 return false;
1279 }
1280
1281 std::unique_ptr<LIB_SYMBOL> downloadedSymbol = loadSymbolFromPayload( aPayload, libItemName, aError );
1282
1283 if( !downloadedSymbol )
1284 {
1285 if( aError.IsEmpty() )
1286 aError = _( "Unable to parse the downloaded symbol." );
1287
1288 return false;
1289 }
1290
1291 downloadedSymbol->SetName( libItemName );
1292
1293 LIB_ID savedId;
1294 savedId.SetLibNickname( nickname );
1295 savedId.SetLibItemName( libItemName );
1296 downloadedSymbol->SetLibId( savedId );
1297
1298 if( adapter->SaveSymbol( nickname, downloadedSymbol.get(), true )
1300 {
1301 aError = _( "Unable to save the downloaded symbol." );
1302 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ),
1303 "receiveSymbol: failed to save symbol %s to library %s",
1304 libItemName.ToUTF8().data(), nickname.ToUTF8().data() );
1305 return false;
1306 }
1307
1308 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ),
1309 "receiveSymbol: saved symbol %s into library %s", libItemName.ToUTF8().data(),
1310 nickname.ToUTF8().data() );
1311
1312 const LIBRARY_TABLE_SCOPE scope = addToGlobal ? LIBRARY_TABLE_SCOPE::GLOBAL
1315
1316 if( placeAfterDownload )
1317 {
1318 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "receiveSymbol: placing symbol now (nickname=%s libItem=%s)",
1319 nickname.ToUTF8().data(), libItemName.ToUTF8().data() );
1320 return placeDownloadedSymbol( nickname, libItemName, aError );
1321 }
1322
1323 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "receiveSymbol: saved symbol (nickname=%s libItem=%s)" ,
1324 nickname.ToUTF8().data(), libItemName.ToUTF8().data() );
1325
1326 return true;
1327}
1328
1329
1330bool PANEL_REMOTE_SYMBOL::receiveFootprint( const nlohmann::json& aParams,
1331 const std::vector<uint8_t>& aPayload,
1332 wxString& aError )
1333{
1334 const wxString mode = jsonString( aParams, "mode" );
1335
1336 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "receiveFootprint: mode=%s", mode.ToUTF8().data() );
1337
1338 if( !mode.IsEmpty() && !mode.IsSameAs( wxS( "SAVE" ), false )
1339 && !mode.IsSameAs( wxS( "PLACE" ), false ) )
1340 {
1341 aError = wxString::Format( _( "Unsupported transfer mode '%s'." ), mode );
1342 return false;
1343 }
1344
1345 const wxString contentType = jsonString( aParams, "content_type" );
1346
1347 if( !contentType.IsSameAs( wxS( "KICAD_FOOTPRINT_V1" ), false ) )
1348 {
1349 aError = _( "Unsupported footprint payload type." );
1350 return false;
1351 }
1352
1353 wxFileName baseDir;
1354
1355 if( !ensureDestinationRoot( baseDir, aError ) )
1356 return false;
1357
1358 wxFileName fpRoot = baseDir;
1359 fpRoot.AppendDir( wxS( "footprints" ) );
1360
1361 if( !fpRoot.DirExists() && !fpRoot.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
1362 {
1363 aError = wxString::Format( _( "Unable to create '%s'." ), fpRoot.GetFullPath() );
1364 return false;
1365 }
1366
1367 // Get footprint's internal name from "name" parameter
1368 wxString footprintName = jsonString( aParams, "name" );
1369 if( footprintName.IsEmpty() )
1370 footprintName = wxS( "footprint" );
1371 footprintName = sanitizeFileComponent( footprintName, wxS( "footprint" ) );
1372
1373 // Get library name from "library" parameter, fallback to "footprints"
1374 wxString libraryName = jsonString( aParams, "library" );
1375 if( libraryName.IsEmpty() )
1376 libraryName = wxS( "footprints" );
1377 libraryName.Trim( true ).Trim( false );
1378
1379 wxString sanitizedLibName = sanitizeFileComponent( libraryName, wxS( "footprints" ) );
1380 wxString libNickname = sanitizedPrefix() + wxS( "_" ) + sanitizedLibName;
1381
1382 wxFileName libDir = fpRoot;
1383 libDir.AppendDir( libNickname + wxS( ".pretty" ) );
1384
1385 if( !libDir.DirExists() && !libDir.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
1386 {
1387 aError = wxString::Format( _( "Unable to create '%s'." ), libDir.GetFullPath() );
1388 return false;
1389 }
1390
1391 wxString fileName = footprintName;
1392
1393 if( !fileName.Lower().EndsWith( wxS( ".kicad_mod" ) ) )
1394 fileName += wxS( ".kicad_mod" );
1395
1396 wxFileName outFile( libDir );
1397 outFile.SetFullName( fileName );
1398
1399 if( !writeBinaryFile( outFile, aPayload, aError ) )
1400 return false;
1401
1402 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "receiveFootprint: wrote footprint %s in lib %s",
1403 outFile.GetFullPath(), libNickname.ToUTF8().data() );
1404
1405 EESCHEMA_SETTINGS* settings = GetAppSettings<EESCHEMA_SETTINGS>( "eeschema" );
1406
1407 if( !settings )
1408 {
1409 aError = _( "Unable to load schematic settings." );
1410 return false;
1411 }
1412
1413 const bool addToGlobal = settings->m_RemoteSymbol.add_to_global_table;
1414
1415 if( !ensureFootprintLibraryEntry( libDir, libNickname, addToGlobal, aError ) )
1416 return false;
1417
1418 const LIBRARY_TABLE_SCOPE scope = addToGlobal ? LIBRARY_TABLE_SCOPE::GLOBAL
1421
1422 return true;
1423}
1424
1425
1426bool PANEL_REMOTE_SYMBOL::receive3DModel( const nlohmann::json& aParams,
1427 const std::vector<uint8_t>& aPayload,
1428 wxString& aError )
1429{
1430 const wxString mode = jsonString( aParams, "mode" );
1431
1432 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "receive3DModel: mode=%s", mode.ToUTF8().data() );
1433
1434 if( !mode.IsEmpty() && !mode.IsSameAs( wxS( "SAVE" ), false )
1435 && !mode.IsSameAs( wxS( "PLACE" ), false ) )
1436 {
1437 aError = wxString::Format( _( "Unsupported transfer mode '%s'." ), mode );
1438 return false;
1439 }
1440
1441 const wxString contentType = jsonString( aParams, "content_type" );
1442
1443 if( !contentType.IsSameAs( wxS( "KICAD_3D_MODEL_STEP" ), false ) &&
1444 !contentType.IsSameAs( wxS( "KICAD_3D_MODEL_WRL" ), false ) )
1445 {
1446 aError = _( "Unsupported 3D model payload type." );
1447 return false;
1448 }
1449
1450 wxFileName baseDir;
1451
1452 if( !ensureDestinationRoot( baseDir, aError ) )
1453 return false;
1454
1455 wxFileName modelDir = baseDir;
1456 modelDir.AppendDir( sanitizedPrefix() + wxS( "_3d" ) );
1457
1458 if( !modelDir.DirExists() && !modelDir.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
1459 {
1460 aError = wxString::Format( _( "Unable to create '%s'." ), modelDir.GetFullPath() );
1461 return false;
1462 }
1463
1464 wxString fileName = jsonString( aParams, "name" );
1465
1466 if( fileName.IsEmpty() )
1467 fileName = sanitizedPrefix() + wxS( "_model" );
1468
1469 fileName = sanitizeFileComponent( fileName, sanitizedPrefix() + wxS( "_model" ) );
1470
1471 wxString extension = wxS( ".step" );
1472
1473 if( contentType.IsSameAs( wxS( "KICAD_3D_MODEL_WRL" ), false ) )
1474 extension = wxS( ".wrl" );
1475
1476 if( !fileName.Lower().EndsWith( extension ) )
1477 fileName += extension;
1478
1479 wxFileName outFile( modelDir );
1480 outFile.SetFullName( fileName );
1481
1482 bool ok = writeBinaryFile( outFile, aPayload, aError );
1483
1484 if( ok )
1485 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "receive3DModel: wrote model %s", outFile.GetFullPath() );
1486
1487 return ok;
1488}
1489
1490
1491bool PANEL_REMOTE_SYMBOL::receiveSPICEModel( const nlohmann::json& aParams,
1492 const std::vector<uint8_t>& aPayload,
1493 wxString& aError )
1494{
1495 const wxString mode = jsonString( aParams, "mode" );
1496
1497 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "receiveSPICEModel: mode=%s", mode.ToUTF8().data() );
1498
1499 if( !mode.IsEmpty() && !mode.IsSameAs( wxS( "SAVE" ), false )
1500 && !mode.IsSameAs( wxS( "PLACE" ), false ) )
1501 {
1502 aError = wxString::Format( _( "Unsupported transfer mode '%s'." ), mode );
1503 return false;
1504 }
1505
1506 const wxString contentType = jsonString( aParams, "content_type" );
1507
1508 if( !contentType.IsSameAs( wxS( "KICAD_SPICE_MODEL_V1" ), false ) )
1509 {
1510 aError = _( "Unsupported SPICE payload type." );
1511 return false;
1512 }
1513
1514 wxFileName baseDir;
1515
1516 if( !ensureDestinationRoot( baseDir, aError ) )
1517 return false;
1518
1519 wxFileName spiceDir = baseDir;
1520 spiceDir.AppendDir( sanitizedPrefix() + wxS( "_spice" ) );
1521
1522 if( !spiceDir.DirExists() && !spiceDir.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
1523 {
1524 aError = wxString::Format( _( "Unable to create '%s'." ), spiceDir.GetFullPath() );
1525 return false;
1526 }
1527
1528 wxString fileName = jsonString( aParams, "name" );
1529
1530 if( fileName.IsEmpty() )
1531 fileName = sanitizedPrefix() + wxS( "_model.cir" );
1532
1533 fileName = sanitizeFileComponent( fileName, sanitizedPrefix() + wxS( "_model.cir" ) );
1534
1535 if( !fileName.Lower().EndsWith( wxS( ".cir" ) ) )
1536 fileName += wxS( ".cir" );
1537
1538 wxFileName outFile( spiceDir );
1539 outFile.SetFullName( fileName );
1540
1541 bool ok = writeBinaryFile( outFile, aPayload, aError );
1542
1543 if( ok )
1544 wxLogTrace( wxS( "KI_TRACE_REMOTE_SYMBOL" ), "receiveSPICEModel: wrote spice model %s", outFile.GetFullPath() );
1545
1546 return ok;
1547}
const char * name
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:110
wxString GetSemanticVersion()
Get the semantic version string for KiCad defined inside the KiCadVersion.cmake file in the variable ...
A bitmap button widget that behaves like an AUI toolbar item's button when it is drawn.
int ShowModal() override
virtual void SetParent(EDA_ITEM *aParent)
Definition eda_item.cpp:93
REMOTE_SYMBOL_CONFIG m_RemoteSymbol
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
virtual const wxString What() const
A composite of Problem() and Where()
Definition kiid.h:49
void ReloadLibraryEntry(LIBRARY_TABLE_TYPE aType, const wxString &aNickname, LIBRARY_TABLE_SCOPE aScope=LIBRARY_TABLE_SCOPE::BOTH)
std::optional< LIBRARY_TABLE * > Table(LIBRARY_TABLE_TYPE aType, LIBRARY_TABLE_SCOPE aScope)
Retrieves a given table; creating a new empty project table if a valid project is loaded and the give...
void SetOptions(const wxString &aOptions)
void SetNickname(const wxString &aNickname)
void SetOk(bool aOk=true)
void SetType(const wxString &aType)
void SetDescription(const wxString &aDescription)
void SetURI(const wxString &aUri)
const wxString & URI() const
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:49
int SetLibItemName(const UTF8 &aLibItemName)
Override the library item name portion of the LIB_ID to aLibItemName.
Definition lib_id.cpp:111
int SetLibNickname(const UTF8 &aLibNickname)
Override the logical library name portion of the LIB_ID to aLibNickname.
Definition lib_id.cpp:100
Define a library symbol object.
Definition lib_symbol.h:83
wxString sanitizeFileComponent(const wxString &aComponent, const wxString &aDefault) const
std::optional< wxString > extractUrlFromJson(const wxString &aJsonContent) const
void sendRpcMessage(const wxString &aCommand, nlohmann::json aParameters=nlohmann::json::object(), std::optional< int > aResponseTo=std::nullopt, const wxString &aStatus=wxS("OK"), const std::string &aData=std::string(), const wxString &aErrorCode=wxEmptyString, const wxString &aErrorMessage=wxEmptyString)
bool receiveSymbol(const nlohmann::json &aParams, const std::vector< uint8_t > &aPayload, wxString &aError)
void onWebViewLoaded(wxWebViewEvent &aEvent)
void onKicadMessage(const wxString &aMessage)
wxString sanitizedPrefix() const
void showMessage(const wxString &aMessage)
wxString jsonString(const nlohmann::json &aObject, const char *aKey) const
bool writeBinaryFile(const wxFileName &aFile, const std::vector< uint8_t > &aData, wxString &aError) const
void handleRpcMessage(const nlohmann::json &aMessage)
std::vector< PCM_INSTALLATION_ENTRY > m_dataSources
bool receiveFootprint(const nlohmann::json &aParams, const std::vector< uint8_t > &aPayload, wxString &aError)
bool ensureSymbolLibraryEntry(const wxFileName &aLibraryFile, const wxString &aNickname, bool aGlobalTable, wxString &aError) const
void respondWithError(const wxString &aCommand, int aResponseTo, const wxString &aErrorCode, const wxString &aErrorMessage)
std::optional< wxFileName > findDataSourceJson(const PCM_INSTALLATION_ENTRY &aEntry) const
PANEL_REMOTE_SYMBOL(SCH_EDIT_FRAME *aParent)
bool decompressIfNeeded(const std::string &aCompression, const std::vector< uint8_t > &aInput, std::vector< uint8_t > &aOutput, wxString &aError) const
bool decodeBase64Payload(const std::string &aMessage, std::vector< uint8_t > &aOutPayload, wxString &aError) const
wxString sanitizeForScript(const std::string &aJson) const
std::unique_ptr< LIB_SYMBOL > loadSymbolFromPayload(const std::vector< uint8_t > &aPayload, const wxString &aLibItemName, wxString &aError) const
void onConfigure(wxCommandEvent &aEvent)
bool ensureDestinationRoot(wxFileName &aOutDir, wxString &aError) const
bool loadDataSource(size_t aIndex)
bool ensureFootprintLibraryEntry(const wxFileName &aLibraryDir, const wxString &aNickname, bool aGlobalTable, wxString &aError) const
bool receiveSPICEModel(const nlohmann::json &aParams, const std::vector< uint8_t > &aPayload, wxString &aError)
BITMAP_BUTTON * m_configButton
void onDarkModeToggle(wxSysColourChangedEvent &aEvent)
std::shared_ptr< PLUGIN_CONTENT_MANAGER > m_pcm
bool receive3DModel(const nlohmann::json &aParams, const std::vector< uint8_t > &aPayload, wxString &aError)
void onDataSourceChanged(wxCommandEvent &aEvent)
bool placeDownloadedSymbol(const wxString &aNickname, const wxString &aLibItemName, wxString &aError)
virtual LIBRARY_MANAGER & GetLibraryManager() const
Definition pgm_base.h:133
Main class of Plugin and Content Manager subsystem.
Definition pcm.h:106
static SYMBOL_LIBRARY_ADAPTER * SymbolLibAdapter(PROJECT *aProject)
Accessor for project symbol library manager adapter.
static TOOL_ACTION placeSymbol
Definition sch_actions.h:66
Schematic editor (Eeschema) main window.
Schematic symbol object.
Definition sch_symbol.h:76
void AutoplaceFields(SCH_SCREEN *aScreen, AUTOPLACE_ALGO aAlgo) override
Automatically orient all the fields in the symbol.
An interface to the global shared library manager that is schematic-specific and linked to one projec...
SAVE_T SaveSymbol(const wxString &aNickname, const LIB_SYMBOL *aSymbol, bool aOverwrite=true)
Write aSymbol to an existing library given by aNickname.
Master controller class:
bool PostAction(const std::string &aActionName, T aParam)
Run the specified action after the current action (coroutine) ends.
const wxString ExpandEnvVarSubstitutions(const wxString &aString, const PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition common.cpp:558
The common library.
#define _(s)
std::unique_ptr< T > IO_RELEASER
Helper to hold and release an IO_BASE object when exceptions are thrown.
Definition io_mgr.h:33
PROJECT & Prj()
Definition kicad.cpp:642
LIBRARY_TABLE_SCOPE
STL namespace.
#define REMOTE_SYMBOL_SESSION_VERSION
SETTINGS_MANAGER * GetSettingsManager()
@ PT_DATASOURCE
Definition pcm_data.h:47
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
@ AUTOPLACE_AUTO
Definition sch_item.h:71
T * GetAppSettings(const char *aFilename)
Definition pcm_data.h:158
PCM_PACKAGE package
Definition pcm_data.h:159
wxString identifier
Definition pcm_data.h:118
wxString name
Definition pcm_data.h:115
std::string path
wxString result
Test unit parsing edge cases and error handling.
#define FN_NORMALIZE_FLAGS
Default flags to pass to wxFileName::Normalize().
Definition wx_filename.h:39