75 std::unique_ptr<KICAD_API_SERVER> server = std::make_unique<KICAD_API_SERVER>(
false );
81 designBlockLibrariesHandler.
SetKiway( &aKiway );
83 [&server](
KIFACE* aKiface )
90 if( !socketPath.IsEmpty() )
91 server->SetSocketPath( socketPath );
97 std::optional<wxFileName> openProjectPath;
101 types::DocumentType type;
106 std::vector<OPEN_DOCUMENT> openDocuments;
108 auto faceForDocument = []( types::DocumentType aType ) ->
KIWAY::FACE_T
119 auto closeAllDocuments =
122 for(
const OPEN_DOCUMENT& doc : openDocuments )
125 if( doc.type == types::DOCTYPE_PROJECT )
132 openDocuments.clear();
134 if( openProjectPath )
140 openProjectPath.reset();
142 return google::protobuf::Empty();
145 auto openDocument = [&](
const commands::OpenDocument& aRequest )
148 types::DocumentType requestType = aRequest.type();
150 if( requestType != types::DOCTYPE_PCB && requestType != types::DOCTYPE_SCHEMATIC
151 && requestType != types::DOCTYPE_PROJECT && requestType != types::DOCTYPE_FOOTPRINT )
154 e.set_status( ApiStatusCode::AS_UNIMPLEMENTED );
155 e.set_error_message(
"Only PCB, schematic, footprint, and project document types are supported" );
156 return tl::unexpected( e );
159 wxString inputPath = wxString::FromUTF8( aRequest.path() );
161 if( inputPath.IsEmpty() )
164 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
165 e.set_error_message(
"OpenDocument requires a non-empty path" );
166 return tl::unexpected( e );
169 if( requestType == types::DOCTYPE_FOOTPRINT )
173 if( fpid.
Parse( inputPath ) >= 0 )
176 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
177 e.set_error_message( wxString::Format( wxS(
"Invalid footprint LIB_ID: %s" ),
179 return tl::unexpected( e );
186 if( openProjectPath )
187 spec.
path = openProjectPath->GetFullPath();
194 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
195 e.set_error_message( error.ToStdString() );
196 return tl::unexpected( e );
200 doc.type = requestType;
202 openDocuments.push_back( doc );
204 commands::OpenDocumentResponse response;
205 types::DocumentSpecifier* docSpec = response.mutable_document();
206 docSpec->set_type( requestType );
213 wxFileName projectPath( inputPath );
215 projectPath.MakeAbsolute();
217 if( openProjectPath && projectPath.GetFullPath() != openProjectPath->GetFullPath() )
220 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
221 e.set_error_message( wxString::Format(
"cannot open a document from project '%s' because project "
222 "'%s' is already open.",
223 projectPath.GetFullName(), openProjectPath->GetFullName() )
225 return tl::unexpected( e );
228 if( requestType == types::DOCTYPE_PROJECT )
230 if( !openProjectPath )
232 if( !openDocuments.empty() )
234 auto closeResult = closeAllDocuments( commands::CloseAllDocuments() );
237 return tl::unexpected( closeResult.error() );
240 if( !
Pgm().GetSettingsManager().LoadProject( projectPath.GetFullPath(),
true ) )
242 wxLogTrace(
traceApi,
"Warning: no project file found for %s", inputPath );
245 if( !
Pgm().GetSettingsManager().GetProject( projectPath.GetFullPath() ) )
248 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
249 e.set_error_message( wxString::Format(
"failed to load project '%s'", projectPath.GetFullPath() )
251 return tl::unexpected( e );
254 openProjectPath = projectPath;
257 if( std::ranges::find_if( openDocuments,
258 [](
const OPEN_DOCUMENT& d )
260 return d.type == types::DOCTYPE_PROJECT;
261 } ) == openDocuments.end() )
264 doc.type = types::DOCTYPE_PROJECT;
265 doc.fileName = projectPath.GetFullName();
266 openDocuments.push_back( doc );
269 commands::OpenDocumentResponse response;
270 types::DocumentSpecifier* doc = response.mutable_document();
273 doc->set_type( types::DOCTYPE_PROJECT );
274 doc->mutable_project()->set_name(
project.GetProjectName().ToUTF8() );
275 doc->mutable_project()->set_path(
project.GetProjectPath().ToUTF8() );
285 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
286 e.set_error_message(
"unsupported document type" );
287 return tl::unexpected( e );
290 if( requestType == types::DOCTYPE_PCB || requestType == types::DOCTYPE_SCHEMATIC )
292 auto existing = std::ranges::find_if( openDocuments,
293 [&](
const OPEN_DOCUMENT& d )
295 return d.type == requestType;
298 if( existing != openDocuments.end() )
301 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
302 e.set_error_message(
"a document of this type is already open" );
303 return tl::unexpected( e );
309 spec.
path = projectPath.GetFullPath();
316 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
317 e.set_error_message( error.ToStdString() );
318 return tl::unexpected( e );
321 wxFileName docFile( inputPath );
322 docFile.MakeAbsolute();
325 doc.type = requestType;
326 doc.fileName = docFile.GetFullName();
327 openDocuments.push_back( doc );
329 openProjectPath = projectPath;
331 commands::OpenDocumentResponse response;
332 types::DocumentSpecifier* docSpec = response.mutable_document();
335 docSpec->set_type( requestType );
337 if( requestType == types::DOCTYPE_PCB )
338 docSpec->set_board_filename( doc.fileName.ToStdString() );
340 docSpec->mutable_project()->set_name(
project.GetProjectName().ToUTF8() );
341 docSpec->mutable_project()->set_path(
project.GetProjectPath().ToUTF8() );
346 auto createDocument =
349 types::DocumentType requestType = aRequest.type();
353 if( requestType != types::DOCTYPE_PCB && requestType != types::DOCTYPE_SCHEMATIC )
356 e.set_status( ApiStatusCode::AS_UNIMPLEMENTED );
357 e.set_error_message(
"Only PCB and schematic documents can be created" );
358 return tl::unexpected( e );
361 wxString inputPath = wxString::FromUTF8( aRequest.path() );
363 if( inputPath.IsEmpty() )
366 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
367 e.set_error_message(
"CreateDocument requires a non-empty path" );
368 return tl::unexpected( e );
371 wxFileName docPath( inputPath );
372 docPath.MakeAbsolute();
378 spec.
path = docPath.GetFullPath();
386 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
387 e.set_error_message( error.ToStdString() );
388 return tl::unexpected( e );
396 doc.type = requestType;
397 doc.fileName = docPath.GetFullName();
399 openDocuments.push_back( doc );
401 commands::OpenDocumentResponse response;
402 types::DocumentSpecifier* docSpec = response.mutable_document();
404 docSpec->set_type( requestType );
406 if( requestType == types::DOCTYPE_PCB )
407 docSpec->set_board_filename( doc.fileName.ToStdString() );
417 if( openDocuments.empty() )
420 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
421 e.set_error_message(
"No document is currently open" );
422 return tl::unexpected( e );
425 auto it = openDocuments.end();
427 if( aRequest.has_document() )
429 types::DocumentType typeToClose = aRequest.document().type();
431 it = std::ranges::find_if( openDocuments,
432 [&](
const OPEN_DOCUMENT& d )
434 return d.type == typeToClose;
437 if( it == openDocuments.end() )
440 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
441 e.set_error_message(
"Requested document type does not match any open document" );
442 return tl::unexpected( e );
445 if( typeToClose == types::DOCTYPE_PCB
446 && !aRequest.document().board_filename().empty()
447 && it->fileName != wxString::FromUTF8( aRequest.document().board_filename() ) )
450 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
451 e.set_error_message(
"Requested document does not match the open document" );
452 return tl::unexpected( e );
455 if( ( typeToClose == types::DOCTYPE_SCHEMATIC || typeToClose == types::DOCTYPE_PROJECT )
456 && aRequest.document().has_project()
458 && aRequest.document().project().name() != openProjectPath->GetName().ToStdString() )
461 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
462 e.set_error_message(
"Requested document does not match the open project" );
463 return tl::unexpected( e );
466 if( typeToClose == types::DOCTYPE_FOOTPRINT && aRequest.document().has_lib_id() )
473 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
474 e.set_error_message( wxString::Format( wxS(
"Invalid footprint LIB_ID: %s" ),
476 return tl::unexpected( e );
479 if( it->libId != fpid )
482 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
483 e.set_error_message(
"Requested document does not match the open document" );
484 return tl::unexpected( e );
491 it = openDocuments.begin();
494 if( it->type == types::DOCTYPE_PROJECT )
496 return closeAllDocuments( commands::CloseAllDocuments() );
505 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
506 e.set_error_message( error.ToStdString() );
507 return tl::unexpected( e );
511 openDocuments.erase( it );
513 if( openDocuments.empty() && openProjectPath )
517 openProjectPath.reset();
520 return google::protobuf::Empty();
528 server->RegisterHandler( &commonHandler );
529 server->RegisterHandler( &designBlockLibrariesHandler );
532 if( !server->Running() )
534 wxFprintf( stderr,
_(
"Failed to start API server\n" ) );
540 if( !preloadPath.IsEmpty() )
544 wxFileName preloadFile( preloadPath );
545 types::DocumentType preloadType = types::DOCTYPE_PROJECT;
548 preloadType = types::DOCTYPE_SCHEMATIC;
550 preloadType = types::DOCTYPE_PCB;
552 commands::OpenDocument request;
553 request.set_type( preloadType );
554 request.set_path( preloadPath.ToStdString() );
556 auto preloadResult = openDocument( request );
560 wxFprintf( stderr,
"%s\n", preloadResult.error().error_message() );
561 server->DeregisterHandler( &commonHandler );
562 server->DeregisterHandler( &designBlockLibrariesHandler );
567 server->SetReadyToReply(
true );
569 wxString listenPath = wxString::FromUTF8( server->SocketPath() );
570 wxFprintf( stdout,
"KiCad API server listening at %s\n", listenPath );
581 wxTheApp->ProcessPendingEvents();
585 std::signal( SIGINT, oldSigInt );
587 std::signal( SIGTERM, oldSigTerm );
590 wxFprintf( stdout,
"Shutting down\n" );
592 commands::CloseAllDocuments closeAllReq;
593 closeAllDocuments( closeAllReq );
594 server->DeregisterHandler( &commonHandler );
595 server->DeregisterHandler( &designBlockLibrariesHandler );