Costruire un Emulatore di Terminale con libghostty: Guida Pratica

2026-03-23 · 11 min read · gen:2m 43s · tok:20021
#libghostty #terminal-emulator #c-programming #backend #beginner-tutorial #italiano

Scopri come funziona l’emulazione terminale costruendo Ghostling con libghostty. Pseudo-terminali, escape sequence ANSI e architettura spiegati passo passo.

Building Your Own Terminal Emulator with libghostty: A Deep Dive into Ghostling’s Architecture

Ogni giorno apri il terminale decine di volte senza pensarci. Digiti comandi, vedi output colorato, scorri tra migliaia di righe di log. Ma cosa succede realmente tra la pressione di un tasto e i pixel che appaiono sullo schermo?

La risposta coinvolge pseudo-terminali, parsing di escape sequence ANSI, gestione di buffer circolari e un intero sistema di comunicazione inter-processo che risale agli anni ‘70 ma rimane fondamentale nell’infrastruttura moderna.

Ghostling rappresenta un’opportunitΓ  unica: un emulatore di terminale minimale costruito su libghostty, la libreria C che alimenta Ghostty, uno dei terminali piΓΉ performanti disponibili oggi. Studiare questa implementazione ti permetterΓ  di capire come funziona davvero l’emulazione terminale e, soprattutto, ti darΓ  gli strumenti per costruire soluzioni custom: terminali embedded in IDE, tool CLI specializzati o widget terminale per applicazioni desktop.

Prerequisiti

Per seguire questo articolo avrai bisogno di:

  • Competenze: familiaritΓ  con C e concetti base di sistemi operativi Unix-like (processi, file descriptor)
  • Ambiente: Linux o macOS (Windows con WSL2 funziona, ma con limitazioni)
  • Strumenti: GCC o Clang, Make, Git
  • Librerie: libghostty (la compileremo dai sorgenti)
1
2
3
4
# Verifica di avere gli strumenti necessari
gcc --version          # GCC 11+ o Clang 14+
make --version         # GNU Make 4+
pkg-config --version   # Per gestire le dipendenze

πŸ’‘ Se non hai mai lavorato con PTY o file descriptor, non preoccuparti: spiegherΓ² ogni concetto quando diventa rilevante.

Architettura e concetti chiave

Prima di scrivere codice, devi capire i tre pilastri dell’emulazione terminale:

  1. PTY (Pseudo-Terminal): il canale di comunicazione bidirezionale tra shell ed emulatore
  2. State Machine: il parser che interpreta i byte in arrivo (testo, colori, movimento cursore)
  3. Rendering Pipeline: la trasformazione dello stato interno in pixel sullo schermo
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                              USER INPUT                                     β”‚
β”‚                                                                             β”‚
β”‚    β”Œβ”€β”€β”€β”€οΏ½οΏ½οΏ½β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                    β”‚
β”‚    β”‚ Tastiera │────────▢│ Eventi Input β”‚                                    β”‚
β”‚    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜                                    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                  β”‚
                                  β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    TERMINAL EMULATOR (Ghostling)                            β”‚
β”‚                                                                             β”‚
β”‚    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”            β”‚
β”‚    β”‚ PTY Master │◀─────▢│ PTY Slave  │◀─────▢│  Shell Process  β”‚            β”‚
β”‚    β”‚            β”‚       β”‚            β”‚       β”‚  (bash/zsh/fish)β”‚            β”‚
β”‚    β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜            β”‚
β”‚          β”‚                                            β”‚                     β”‚
β”‚          β”‚ Byte Stream                                β”‚                     β”‚
β”‚          β–Ό                                            β–Ό                     β”‚
β”‚    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”             β”‚
β”‚    β”‚ Escape Sequence     β”‚                   β”‚ Programmi CLI  β”‚             β”‚
β”‚    β”‚ Parser (libghostty) β”‚                   β”‚ (ls, vim, etc) β”‚             β”‚
β”‚    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜             β”‚
β”‚               β”‚                                                             β”‚
β”‚               β–Ό                                                             β”‚
β”‚    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                                  β”‚
β”‚    β”‚  Terminal State     β”‚                                                  β”‚
β”‚    β”‚  (colori, cursore)  β”‚                                                  β”‚
β”‚    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                                  β”‚
β”‚               β”‚                                                             β”‚
β”‚               β–Ό                                                             β”‚
β”‚    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                                  β”‚
β”‚    β”‚ Character Grid      β”‚                                                  β”‚
β”‚    β”‚ Buffer              β”‚                                                  β”‚
β”‚    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                                  β”‚
β”‚               β”‚                                                             β”‚
β”‚               β–Ό                                                             β”‚
β”‚    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                                  β”‚
β”‚    β”‚ Rendering Pipeline  β”‚                                                  β”‚
β”‚    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                β”‚
                β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                              OUTPUT                                         β”‚
β”‚                                                                             β”‚
β”‚                         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                         β”‚
β”‚                         β”‚ Display β”‚                                         β”‚
β”‚                         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Il flusso Γ¨ circolare: l’utente preme un tasto β†’ l’emulatore scrive sul PTY master β†’ il kernel inoltra al PTY slave β†’ la shell legge, elabora e scrive l’output β†’ l’output torna al PTY master β†’ il parser interpreta le escape sequence β†’ lo stato del terminale si aggiorna β†’ il renderer disegna.

Come libghostty si inserisce in questo flusso

libghostty astrae la complessitΓ  del parser e della gestione dello stato. Tu ti occupi di:

  • Creare e gestire il PTY
  • Passare i byte ricevuti a libghostty
  • Leggere lo stato risultante e renderizzarlo

libghostty si occupa di:

  • Parsing completo delle escape sequence (ANSI, xterm, VT100)
  • Gestione del character grid (inserimenti, cancellazioni, scroll)
  • Tracking di attributi (colori, grassetto, sottolineato)
  • Gestione delle modalitΓ  terminale (alternate screen, mouse reporting)

Implementazione passo-passo

Sezione 1: Setup del PTY e fork del processo shell

Il PTY Γ¨ il cuore di tutto. Senza un PTY funzionante, non hai un terminale β€” hai solo un programma che stampa testo.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// pty_manager.c - Gestione del ciclo di vita PTY
#define _XOPEN_SOURCE 600
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>

// Struttura che rappresenta il nostro PTY
typedef struct {
    int master_fd;          // File descriptor lato emulatore
    pid_t child_pid;        // PID del processo shell
    char slave_name[256];   // Path del PTY slave (es: /dev/pts/3)
} PtyHandle;

// Configura le dimensioni della finestra terminale.
// Questo Γ¨ CRITICO: senza dimensioni corrette, programmi come vim
// non funzionano.
static int pty_set_window_size(int fd, int rows, int cols) {
    struct winsize ws = {
        .ws_row = rows,
        .ws_col = cols,
        .ws_xpixel = 0,  // Non sempre usati, ma inizializzali
        .ws_ypixel = 0
    };
    
    if (ioctl(fd, TIOCSWINSZ, &ws) == -1) {
        perror("ioctl TIOCSWINSZ");
        return -1;
    }
    return 0;
}

// Crea un nuovo PTY e forka un processo shell
PtyHandle* pty_create(int initial_rows, int initial_cols) {
    PtyHandle* handle = malloc(sizeof(PtyHandle));
    if (!handle) return NULL;
    
    // posix_openpt crea un nuovo PTY master
    // O_RDWR: lettura e scrittura
    // O_NOCTTY: non rendere questo il terminale di controllo del processo
    handle->master_fd = posix_openpt(O_RDWR | O_NOCTTY);
    if (handle->master_fd == -1) {
        perror("posix_openpt");
        free(handle);
        return NULL;
    }
    
    // grantpt imposta i permessi corretti sul PTY slave
    if (grantpt(handle->master_fd) == -1) {
        perror("grantpt");
        close(handle->master_fd);
        free(handle);
        return NULL;
    }
    
    // unlockpt sblocca il PTY slave per l'uso
    if (unlockpt(handle->master_fd) == -1) {
        perror("unlockpt");
        close(handle->master_fd);
        free(handle);
        return NULL;
    }
    
    // Ottieni il nome del device PTY slave
    char* slave_name = ptsname(handle->master_fd);
    if (!slave_name) {
        perror("ptsname");
        close(handle->master_fd);
        free(handle);
        return NULL;
    }
    strncpy(handle->slave_name, slave_name, sizeof(handle->slave_name) - 1);
    handle->slave_name[sizeof(handle->slave_name) - 1] = '\0';
    
    // Imposta le dimensioni PRIMA del fork
    pty_set_window_size(handle->master_fd, initial_rows, initial_cols);
    
    // Fork: il processo figlio diventerΓ  la shell
    handle->child_pid = fork();
    
    if (handle->child_pid == -1) {
        perror("fork");
        close(handle->master_fd);
        free(handle);
        return NULL;
    }
    
    if (handle->child_pid == 0) {
        // === PROCESSO FIGLIO ===
        
        // Chiudi il master fd - il figlio usa solo lo slave
        close(handle->master_fd);
        
        // Crea una nuova sessione e diventa il leader.
        // Questo Γ¨ necessario per il controllo del terminale.
        if (setsid() == -1) {
            _exit(1);
        }
        
        // Apri il PTY slave - questo diventa il nostro terminale
        int slave_fd = open(handle->slave_name, O_RDWR);
        if (slave_fd == -1) {
            _exit(1);
        }
        
        // Configura il PTY slave come stdin, stdout, stderr
        dup2(slave_fd, STDIN_FILENO);
        dup2(slave_fd, STDOUT_FILENO);
        dup2(slave_fd, STDERR_FILENO);
        
        if (slave_fd > STDERR_FILENO) {
            close(slave_fd);
        }
        
        // Configura le variabili d'ambiente
        setenv("TERM", "xterm-256color", 1);
        setenv("COLORTERM", "truecolor", 1);
        
        // Esegui la shell dell'utente
        char* shell = getenv("SHELL");
        if (!shell) shell = "/bin/sh";
        
        // execl sostituisce questo processo con la shell
        execl(shell, shell, "-i", NULL);
        
        // Se arriviamo qui, execl ha fallito
        _exit(127);
    }
    
    // === PROCESSO PADRE ===
    // Rendi il master fd non-bloccante per il polling
    int flags = fcntl(handle->master_fd, F_GETFL);
    fcntl(handle->master_fd, F_SETFL, flags | O_NONBLOCK);
    
    return handle;
}

// Invia dati alla shell (tipicamente input da tastiera)
ssize_t pty_write(PtyHandle* handle, const char* data, size_t len) {
    return write(handle->master_fd, data, len);
}

// Leggi output dalla shell
ssize_t pty_read(PtyHandle* handle, char* buffer, size_t max_len) {
    ssize_t n = read(handle->master_fd, buffer, max_len);
    if (n == -1 && errno == EAGAIN) {
        return 0;  // Non-blocking, nessun dato disponibile
    }
    return n;
}

// Cleanup
void pty_destroy(PtyHandle* handle) {
    if (!handle) return;
    
    // Invia SIGHUP alla shell (comportamento standard alla chiusura terminale)
    if (handle->child_pid > 0) {
        kill(handle->child_pid, SIGHUP);
    }
    
    close(handle->master_fd);
    free(handle);
}

⚠️ Attenzione: il codice nel processo figlio dopo fork() è delicato. Non usare malloc, printf o altre funzioni non async-signal-safe prima di execl. Possono causare deadlock.

πŸ“ Nota: O_NOCTTY nel posix_openpt Γ¨ importante. Senza di esso, il PTY potrebbe diventare il terminale di controllo del processo emulatore, causando comportamenti inaspettati con i segnali.

Sezione 2: Integrazione con libghostty per il parsing

Ora che abbiamo un PTY funzionante, dobbiamo interpretare i byte che arrivano dalla shell. Questo Γ¨ dove libghostty brilla.

⚠️ Nota importante: le API di libghostty mostrate di seguito sono illustrative e basate su pattern comuni nelle librerie di emulazione terminale. Consulta la documentazione ufficiale di libghostty per le firme esatte delle funzioni e le strutture dati.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
// terminal_state.c - Wrapper per libghostty
#include <ghostty.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

// Struttura che wrappa lo stato libghostty
typedef struct {
    ghostty_terminal_t* terminal;    // Handle libghostty
    ghostty_surface_t* surface;      // Superficie di rendering
    int rows;
    int cols;
    
    // Callback per notificare il layer di rendering
    void (*on_damage)(int start_row, int end_row, void* user_data);
    void (*on_bell)(void* user_data);
    void (*on_title_change)(const char* title, void* user_data);
    void* callback_user_data;
} TerminalState;

// Callback chiamato da libghostty quando una regione dello schermo cambia
static void damage_callback(
    ghostty_surface_t* surface,
    int start_row,
    int end_row,
    void* user_data
) {
    (void)surface;  // Parametro non utilizzato
    TerminalState* state = (TerminalState*)user_data;
    if (state->on_damage) {
        state->on_damage(start_row, end_row, state->callback_user_data);
    }
}

// Callback per il bell (quando la shell invia \a)
static void bell_callback(ghostty_surface_t* surface, void* user_data) {
    (void)surface;  // Parametro non utilizzato
    TerminalState* state = (TerminalState*)user_data;
    if (state->on_bell) {
        state->on_bell(state->callback_user_data);
    }
}

// Callback per cambio titolo finestra (escape sequence OSC 0)
static void title_callback(
    ghostty_surface_t* surface,
    const char* title,
    void* user_data
) {
    (void)surface;  // Parametro non utilizzato
    TerminalState* state = (TerminalState*)user_data;
    if (state->on_title_change) {
        state->on_title_change(title, state->callback_user_data);
    }
}

// Inizializza lo stato del terminale
TerminalState* terminal_state_create(int rows, int cols) {
    TerminalState* state = calloc(1, sizeof(TerminalState));
    if (!state) return NULL;
    
    state->rows = rows;
    state->cols = cols;
    
    // Configura le opzioni del terminale
    ghostty_terminal_config_t config = {
        .rows = rows,
        .cols = cols,
        .scrollback_lines = 10000,      // 10k righe di scrollback
        .tab_width = 8,
        .cursor_style = GHOSTTY_CURSOR_BLOCK,
        .cursor_blink = true
    };
    
    // Crea l'istanza del terminale
    state->terminal = ghostty_terminal_create(&config);
    if (!state->terminal) {
        free(state);
        return NULL;
    }
    
    // Configura i callback
    ghostty_surface_callbacks_t callbacks = {
        .damage = damage_callback,
        .bell = bell_callback,
        .title_change = title_callback,
        .user_data = state
    };
    
    // Crea la superficie (rappresentazione visuale del terminale)
    state->surface = ghostty_surface_create(state->terminal, &callbacks);
    if (!state->surface) {
        ghostty_terminal_destroy(state->terminal);
        free(state);
        return NULL;
    }
    
    return state;
}

// Processa i byte ricevuti dal PTY.
// Questa Γ¨ la funzione chiamata piΓΉ frequentemente.
void terminal_state_process(TerminalState* state, const char* data, size_t len) {
    if (!state || !data || len == 0) return;
    
    // libghostty gestisce internamente il parsing delle escape sequence.
    // PuΓ² ricevere dati parziali: mantiene lo stato tra le chiamate.
    ghostty_terminal_feed(state->terminal, (const uint8_t*)data, len);
}

// Struttura per rappresentare una cella del terminale
typedef struct {
    uint32_t codepoint;      // Carattere Unicode
    uint32_t fg_color;       // Colore foreground (RGB)
    uint32_t bg_color;       // Colore background (RGB)
    uint8_t attributes;      // Bold, italic, underline, etc.
    bool wide;               // Carattere wide (CJK)
} TerminalCell;

// Ottieni una cella specifica dal grid
bool terminal_state_get_cell(
    TerminalState* state,
    int row,
    int col,
    TerminalCell* out_cell
) {
    if (!state || !out_cell) return false;
    if (row < 0 || col < 0 || row >= state->rows || col >= state->cols) {
        return false;
    }
    
    ghostty_cell_t cell;
    if (!ghostty_surface_get_cell(state->surface, row, col, &cell)) {
        return false;
    }
    
    out_cell->codepoint = cell.codepoint;
    out_cell->fg_color = cell.fg;
    out_cell->bg_color = cell.bg;
    out_cell->attributes = cell.attrs;
    out_cell->wide = cell.wide;
    
    return true;
}

// Ottieni la posizione corrente del cursore
void terminal_state_get_cursor(TerminalState* state, int* row, int* col) {
    if (!state || !row || !col) return;
    ghostty_surface_get_cursor(state->surface, row, col);
}

// Resize del terminale (quando la finestra cambia dimensioni)
void terminal_state_resize(TerminalState* state, int new_rows, int new_cols) {
    if (!state || new_rows <= 0 || new_cols <= 0) return;
    
    state->rows = new_rows;
    state->cols = new_cols;
    
    // libghostty gestisce il reflow del contenuto automaticamente
    ghostty_terminal_resize(state->terminal, new_rows, new_cols);
}

// Cleanup
void terminal_state_destroy(TerminalState* state) {
    if (!state) return;
    
    if (state->surface) {
        ghostty_surface_destroy(state->surface);
    }
    if (state->terminal) {
        ghostty_terminal_destroy(state->terminal);
    }
    free(state);
}

πŸ’‘ Tip: ghostty_terminal_feed Γ¨ progettata per gestire dati parziali. Se una escape sequence Γ¨ troncata a metΓ  (es: ricevi \x1b[ ma non il resto), libghostty mantiene lo stato e completa il parsing alla prossima chiamata.

Sezione 3: Il main loop e l’event handling

Ora uniamo i pezzi in un loop principale che gestisce input e output:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// main.c - Loop principale dell'emulatore
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <signal.h>
#include <errno.h>
#include <sys/wait.h>

// Include dei nostri moduli
#include "pty_manager.h"
#include "terminal_state.h"

// Flag globale per gestire SIGCHLD (morte del processo figlio)
static volatile sig_atomic_t child_exited = 0;

static void sigchld_handler(int sig) {
    (void)sig;
    child_exited = 1;
}

// Callback per il damage - qui connetti il tuo renderer
static void handle_damage(int start_row, int end_row, void* user_data) {
    (void)user_data;
    // In un'implementazione reale, qui segnali al renderer
    // che le righe da start_row a end_row devono essere ridisegnate
    printf("[DAMAGE] Righe %d-%d modificate\n", start_row, end_row);
}

// Callback per il bell
static void handle_bell(void* user_data) {
    (void)user_data;
    printf("[BELL] πŸ””\n");
    // In un'implementazione reale: suono, flash visivo, notifica desktop
}

// Callback per cambio titolo
static void handle_title(const char* title