Integration

type termpaint_integration

The core termpaint library does not come with integration into the operating system’s input/output channels. It’s designed to be integrated into synchronous or asynchronous program designs by a pluggable integration abstraction.

There are auxiliary functions in the termpaintx_* namespace that have some common code that can be used for integrations. But this code is fairly limited, so if more capabilities are needed feel free to copy this code into your project.

To use the simple premade integration see the termpaintx addon.

A user supplied integration consists of 3 major parts: terminal to termpaint communication, termpaint to terminal communication and terminal interface setup.

Termpaint uses a semi-opaque struct which contains callbacks for calling the application provided integration code when needed. The termpaint_integration structure can be used as part of a struct in the application provided integration code. It must be initialized by calling termpaint_integration_init() with the mandatory callbacks. Additional optional callbacks can then be set with additional functions.

When the integration is no longer needed the allocated resources have to be freed by calling termpaint_integration_deinit(), possibly from the integrations free callback.

The integration is connected to a terminal object by passing a pointer to its termpaint_integration struct to termpaint_terminal_new() when creating the terminal object.

Input bytes from the terminal to termpaint need to be passed to termpaint_terminal_add_input_data(). If enough bytes have accumulated to identify a input sequence termpaint will call the event callback set by the application using termpaint_terminal_set_event_cb() with the interpreted event. The integration needs to take care, that termpaint_terminal_add_input_data() is not called recursively from the callbacks set on the terminal.

Some platforms have kernel level terminal processing that needs to be configured for termpaint to work. On *nix-like platforms the kernel tty interface can be setup with termpaintx_fd_set_termios(). For details see the implementation of that function. In general the terminal interface should be set to disable all kernel interpretation and transformation features. If keyboard signal handling (ctrl-c, etc) is needed it can be left enabled. But in that case the terminal object needs to be configured with +kbdsig to avoid switching keyboard input into advanced modes that would be incompatible with kernel signal generation.

In addition to the kernel interface the terminal needs to be setup using configuration sequences. For this termpaint_terminal_setup_fullscreen() needs to be called with the size of the terminal.

All callbacks of the integration receive a pointer to the integration struct as the first parameter. If the integration just uses global variables the pointer can be ignored. If the integration itself uses a struct with data members the recommended setup is to begin the custom struct with termpaint_integration:

struct custom_integration {
    termpaint_integration base;
    // additional members go here.
}

Then the callbacks can just cast their first argument to a pointer to the custom struct.

On *nix-like operating systems the integration should arrange for proper cleanup if the application is suddenly terminated (e.g. a crash). The traditional way is to install signal handlers for various fatal signals and do the cleanup before terminating the application. All functions in termpaint are unsafe for use in signal handlers, so it’s the job of the integration to save all needed information before the signal happens. There are two major parts of state to restore. The first is the kernel terminal layer configuration, which can simply be saved before changing it to the needed values for termpaint. The second is the state of the terminal itself that needs to be restored by outputting a sequence of characters to the terminal. This sequence can change as different features are used, thus the integration should set a callback via termpaint_integration_set_restore_sequence_updated() and save a copy of that data in a place where the signal handler can safely access it.

An alternative without installing signal handlers is to use a auxiliary watchdog process to restore the terminal state. The termpaintx addon contains functions for such an watchdog process. See termpaintx_ttyrescue_start_or_nullptr() for details.

Another signal handler is needed to detect terminal size changes. *nix-like systems raise an SIGWINCH signal if the terminal size changes. This signal is best handled asynchronously (e.g. by using an event loop’s signal support or using a self pipe). Outside of signal context the integration can retrieve the new terminal size using the TIOCGWINSZ ioctl and resize the terminals primary surface to match using termpaint_surface_resize().

Functions

See Safety for general rules for calling functions in termpaint.

void termpaint_integration_init(termpaint_integration *integration, void (*free)(termpaint_integration *integration), void (*write)(termpaint_integration *integration, const char *data, int length), void (*flush)(termpaint_integration *integration))

This function initializes a termpaint_integration structure and sets the 3 mandatory callback functions. All of the callbacks must be set to a non-NULL value.

The callbacks are

void (*write)(termpaint_integration *integration, char *data, int length)

This callback is called by termpaint to write bytes to the terminal. The application needs to implement this function so that length bytes of data starting at data are passed to the terminal. The data should be buffered for best performance. Termpaint will call the flush callback when the buffered data needs to be transmitted to the terminal.

void (*flush)(termpaint_integration *integration)

This callback will be called when the data written using the write callback needs to be transmitted to the terminal.

void (*free)(termpaint_integration *integration)

This callback is invoked when the terminal using this integration is deallocated. This function has to be provided, but may be just a empty function if the memory of the integration is managed externally.

void termpaint_integration_deinit(termpaint_integration *integration)

This function frees resources internally held by a initialized termpaint_integration structure. It must be called exactly once for each termpaint_integration structure initialized by termpaint_integration_init().

void termpaint_integration_set_request_callback(termpaint_integration *integration, void (*request_callback)(termpaint_integration *integration))

Sets the optional callback request_callback:

void (*request_callback)(termpaint_integration *integration)

With terminal input there are often cases where sequences might be finished or just the start of a longer sequence. In this case termpaint forces to terminal to output additional data so it can make the decision what interpretation is correct. If this callback is set it allows termpaint to delay these commands for a short while to wait for additional bytes from the terminal.

If this callback is implemented the application needs to remember that this callback was called and after a short delay (while processing terminal input in the usual way) call termpaint_terminal_callback() on the terminal. If this callback is invoked multiple times before the application calls termpaint_terminal_callback() one call is sufficient.

See also Handling of ambiguous input.

void termpaint_integration_set_restore_sequence_updated(termpaint_integration *integration, void (*restore_sequence_updated)(termpaint_integration *integration, const char *data, int length))

Sets the optional callback restore_sequence_updated:

void (*restore_sequence_updated)(termpaint_integration *integration, const char *data, int length)

This callback is invoked every time the sequence to reset the terminal changes. This allows to cache a current value to be used in crash recovery or suspend signal handlers where termpaint_terminal_restore_sequence() can not be used.

The restore sequence can change over time as additional terminal configuration is requested (e.g. mouse modes, set title or global color changes).

void termpaint_integration_set_is_bad(termpaint_integration *integration, _Bool (*is_bad)(termpaint_integration *integration))

Sets the optional callback is_bad:

_Bool (*is_bad)(termpaint_integration *integration)

This callback should return false, as long as the connection to the terminal is functional.

void termpaint_integration_set_awaiting_response(termpaint_integration *integration, void (*awaiting_response)(termpaint_integration *integration))

Sets the optional callback awaiting_response:

void (*awaiting_response)(termpaint_integration *integration)

This callback is invoked when termpaint sends queries to the terminal. This can be used to decide if the integration should wait for a little while when restoring the terminal while reading and discarding input to avoid leaving responses to these queries in flight that might confuse the next application accessing the terminal.

void termpaint_integration_set_logging_func(termpaint_integration *integration, void (*logging_func)(termpaint_integration *integration, const char *data, int length))

Sets the optional callback logging_func:

void (*logging_func)(termpaint_integration *integration, const char *data, int length)

This callback receives logging messages. Some error messages are always logged if this callback is specified. Additional messages can be enabled by termpaint_terminal_set_log_mask().