For KiCad Developers
API Documentation for KiCad developers
The IPC API is implemented inside KiCad by a server thread that listens on a UNIX socket for connections using the nanomsg next generation (nng) protocol. At the moment, this socket operates in request-reply mode, with KiCad as the server, so asynchronous notifications to API clients is not possible. This server passes messages to the rest of KiCad using a wxWidgets event to cross onto the main (UI) thread. As with all other interactive KiCad events, the rest of API processing happens on the UI thread.
Messages are contained within an envelope structure, which is unpacked and then dispatched to a
request handler according to the type of the interior Protobuf message. Request handlers are
functions that typically belong to a subclass of the API_HANDLER
class. The low-level
infrastructure of the API all lives in kicommon
and is shared between all parts of KiCad, but
request handlers are dynamically registered and so can be attached to specific frames. If a
certain message does not have a handler registered to it, the API server will return an error to
the client.
Handlers are processed one by one, and it is possible to register more than one handler for a given
message type. This is useful for situations where a message has some specifying target information
that can narrow down its effect to a subset of available handlers. For example, the
GetOpenDocuments
message takes a DocumentType
parameter that specifies which type of documents
to query. Each open editor frame can register a handler for the same message, and return
ApiStatusCode::AS_UNHANDLED
if they don’t handle that type of document. In this way, the
dispatcher can try to have a message handled by multiple handlers, and only return an error to the
client if none of the available handlers return success.
A block diagram showing a typical request from a Python plugin is shown below. The API is
language-agnostic; kicad-python
is used here as one example of a language binding.
┌─────────────────┐
│ KiCad Internals │
└───────▲─────────┘
│
┌───────┴─────────┐
│ REQUEST_HANDLER │
└───────▲─────────┘
│
┌──────┴────────┐
│ API_HANDLER_* │
└──────▲────────┘
│
┌─────┴──────┐ ┌─────────────┐
│ API_SERVER │ │ Plugin Code │
└─────▲──────┘ └─────┬───────┘
│ │
┌───┴───┐ ┌──────▼───────┐
│ KINNG │ │ kicad-python │
└───▲───┘ └──────┬───────┘
│ │
┌──┴──┐ ┌───▼───┐
│ nng │ │ pynng │
└──▲──┘ └───┬───┘
│ │
┌──────┴──────┐ ┌──────▼──────┐
│ UNIX socket ◄───────────────────┤ UNIX socket │
└─────────────┘ └─────────────┘
Transport and wire protocol
The underlying transport mechanism for the API is nng, which is a library providing messaging primatives, including inter-process communication (IPC). The IPC mechanism used by KiCad’s API is to serialize protocol buffers to bytes and send them over a UNIX socket. KiCad does not currently make use of any of the more advanced features of nng.
The socket is opened at /tmp/kicad/api.sock
on macOS and Linux, and in a subdirectory of the
user’s temporary file directory on Windows. If multiple instances of KiCad are launched, the
socket filename for every KiCad instance other than the first will be api-<PID>.sock
, where
<PID>
is the process identifier of the KiCad instance.
Protocol Buffers
The API itself is implemented as Protocol Buffers (protobufs), a way of describing and serializing messages that, if used correctly, can provide stability for API clients while allowing KiCad internals to evolve. It is important for every KiCad developer who works on the API to read and understand the protobuf programming best practices and API best practices, as the KiCad API attempts to follow these guidelines in most circumstances. Some of the most important practices are:
-
Care must be taken whenever evolving the API in a way that seeks to deprecate, rename, or otherwise change the existing messages and fields. In general, the API should be designed as much as possible to support both the current state of KiCad and anything we know about our desired future state, to avoid needing to deprecate lots of messages in the future.
-
Plain data types should used sparingly. It is almost always a better idea in terms of future- proofing and API evolution to wrap data types in a container message, even if these types are represented by a single plain old data variable in KiCad’s C++ internals. This is particularly true for boolean values: they should almost never be included in protobuf messages. Instead, an enum type should be defined that distinguishes between UNKNOWN (which should always have the value of
0
) andfalse
. This also makes it possible to swap out the boolean for a tri-state at a later point in time with no compatibility issues. -
Be careful about message nesting. It is not always a good idea to attempt to duplicate the object hierarchy of KiCad’s internal C classes into a hierarchy of messages. Sometimes, KiCad's object hierarchy comes more from developer convenience or history than logical design, and this hierarchy doesn't always make sense at an abstract conceptual level. The API should, in general, be *conceptually* logical and not have its design influenced by how KiCad's C is designed.
Enumerations
Exposing enumerations to the API requires special care to minimize the chance of bugs and compatibility issues. To expose a new C++ enum type to the API, do the following:
-
Define a new protobuf enum for the type. This enum should have a CamelCase name that clearly describes the type — use this as a chance to double-check if the KiCad type is named clearly.
-
Fill out the values for the protobuf enum. Each value should be an UPPER_SNAKE_CASE name, and begin with a prefix to avoid namespace conflicts (protobuf enums are implemented as plain enums, not C enum classes). That prefix is conventionally the abbreviation of the CamelCase enum name, for example `StrokeLineStyle` has values like `SLS_SOLID`, but this can be deviated from where it makes sense. The first enum value must always be `<prefix>_UNKNOWN` and must be assigned the value `0`. This value represents a message where this enum has not been set, and does not map to an equivalent C enum value. Because of this, and because protobuf enums may not contain negative numbers, it is generally not going to be the case that there is a 1:1 mapping between C++ enum value and protobuf enum value.
-
Define a specialization of
FromProtoEnum
andToProtoEnum
for your new enum. There are several source files where these specializations could go, based on what compilation unit your enum’s C++ definition lives in. For example, shared (common) enums go incommon/api/api_enums.cpp
. -
Add your enum to the QA tests in
qa/tests/api/test_api_enums.cpp
. Check that the tests pass, which will confirm that your To/From function implementations are correct.
Note that as enum definitions evolve over time in KiCad, the protobuf versions must never have
values deleted or modified: they can only have values deprecated and new values added! Use the
ToProtoEnum
/FromProtoEnum
functions to handle any changes in how the values should map to
KiCad internals.
Object Serialization
Objects on the drawing canvas (in other words, the KiCad classes that represent "real things" that
the user places, modifies, etc) are all subclasses of the SERIALIZABLE
interface. This means
that in order to expose them to the API, all you need to do is:
-
Define a protobuf message describing that object
-
Add the object to the QA tests in
qa/test/api/test_api_proto.cpp
— you may need to extend theapi_kitchen_sink.kicad_pcb
test file with instances of your object. Make sure there is at least one instance of your object that has all its properties set to non-default values for best test coverage. -
Implement the
::Serialize
and::Deserialize
methods, and iterate on them until the QA tests pass.
When adding new object messages, look at what exists already and try to be consistent. Design for the desired conceptual representation of an object, even if that doesn’t completely match the current C++ implementation. And only include "concrete" data about the objects in the message. Calculated/computed properties of an object should rarely be included in an object’s main message.
Be careful about datatypes as well: as described above, plain data types (ints, bools, strings) are usually not the right choice to describe a property, as they can limit the ability to evolve that property over time. Prefer enums and container messages.
Use protobuf message hierarchies sparingly — it is generally not a good idea to encode KiCad’s C++
class hierarchies into messages as described above. For example, in KiCad, a PCB_TRACK
and a
PCB_VIA
are related by inheritance, but there are separate Track
and Arc
protobuf messages
that duplicate their shared properties, rather than defining the Arc
message as containing an
inner Track
message. The cost of a slight increase in code duplication is worth it because it
prevents confusing/convoluted API surfaces (protobuf does not support inheritance, so the arc
properties would need to be accessed as arc.track.start
and arc.mid
for example) and reduces
interdependence between messages that could make API evolution more painful in the future.
Common data types
The base_types.proto
file contains a number of data type definitions that are used throughout the
API. Use these (and expand them) instead of defining more specific types, where it makes sense.
Some of the types to be aware of: KIID
, Vector2
, Distance
, Angle
, Ratio
, Color
, etc.
Note that all physical distances that represent concrete object properties in the API are represented as 64-bit integer nanometer values, regardless of what part of KiCad they come from.
API Handlers
A handler function is any function-like construct with the following signature:
HANDLER_RESULT<ResponseType>( HandlerType::* aHandler )( RequestType&, const HANDLER_CONTEXT& )
In this signature, RequestType
and ResponseType
are protobuf message classes, and
HANDLER_RESULT<T>
is a typedef for tl::expected<T, ApiResponseStatus>
. tl::expected
is a
library that backports std::expected
to older C++ and adds some functionality beyond the STL
spec. This type represents either an expected value, or an unexpected value that contains more
information about why something failed. The expected value is always just the response message
that we want to send back to the client, and the unexpected value, ApiResponseStatus
, is a
message that contains a status code as well as an error message string.
For example:
e.set_status( ApiStatusCode::AS_BAD_REQUEST );
e.set_error_message( fmt::format( "the client {} already has a commit in progress",
aCtx.ClientName ) );
return tl::unexpected( e );
Notice that the error message begins lowercase and has no punctuation. This is because the error
returned by this handler will almost always be composed with other fragments to form a full
sentence (for example, the text "KiCad API error: "
might be prepended).
The status code ApiStatusCode::AS_UNHANDLED
is used as a special flag to note that this handler
can’t handle the given request, but there was nothing in particular wrong with the request. This
is used in situations when the same request is routed to multiple handlers, one after the other, in
case one of them is able to handle the request.
The base class API_HANDLER_EDITOR
is used to provide many of the common handlers that all the
graphical editors in KiCad need, such as object CRUD, document retrieval, etc. This is then
specialized into API_HANDLER_PCB
, API_HANDLER_SCH
, and so on. Handlers that don’t require an
editor frame to be open are in API_HANDLER_COMMON
.
Handlers always get passed a HANDLER_CONTEXT
structure that contains information about the API
request that is in flight. This can be used to tell apart multiple different API clients, for
example.