HMI leve em Linux com GTK3 em C: ferramenta de chão sem runtime pesado

Em muitas plantas o PC industrial Linux não precisa de um framework web, nem de um interpretador Python com dezenas de pacotes, para cumprir um papel simples: mostrar estado, aceitar um comando e registrar o que o operador fez. Esse papel é o de ferramenta adjacente ao chão — diagnóstico, ajuste fino, visualização de I/O, lançamento de script de manutenção — e costuma ser confundido com “HMI de linha”. Não é. HMI de linha (painel do fabricante, runtime certificado, integração com CLP) tem outro contrato. O que o repositório c_gui_linux treina é a base nativa para o primeiro tipo: GTK3 em C, com dependência explícita e build reproduzível.

Este texto parte do código real do repo — start.c, startGtkApp.c, makefile, README com libgtk-3-dev — e discute quando essa stack faz sentido ao lado da oficina e da linha, sem inventar métricas de desempenho nem prometer “substitui SCADA”. A disciplina que importa é a mesma da bancada embarcada: saber o que está na máquina, como o binário nasce e o que acontece quando o operador fecha a janela no meio de um comando.

Por que “leve” importa no PC de fábrica

O PC de chão (ou o notebook do técnico) costuma ter restrições práticas que raramente aparecem em tutorial de desktop genérico:

  • imagem de SO congelada ou com atualização controlada;
  • pouca tolerância a dependências quebradas depois de um pip install improvisado;
  • necessidade de abrir rápido, fechar limpo e não disputar porta com o runtime do CLP;
  • preferência por binário único ou quase único, linkado contra bibliotecas do sistema;
  • política de TI que bloqueia instaladores “da internet” e favorece pacotes da distro.

GTK3 entra nesse cenário porque já está no ecossistema Debian/Ubuntu via pacotes: o README do repo aponta apt install libgtk-3-dev e sugere checar a versão com dpkg -s libgtk-3-0 | grep 'Version'. Isso não é detalhe cosmética — é a disciplina de saber o que está na máquina antes de escrever widget. Em comissionamento, anotar a versão da lib GTK na imagem é tão útil quanto anotar a versão do firmware do CLP: quando o painel “some” depois de um clone de disco, você sabe se a lib mudou.

Comparado a um empacotamento Python (PyInstaller, venv, wheels), o caminho C+GTK3 troca produtividade de UI por previsibilidade de deploy. Para um painel de três botões e um rótulo de status, essa troca costuma valer. Para um CAD interno com dezenas de telas, provavelmente não. A regra prática de oficina: se o entregável precisa sobreviver a um PC reinstalado só com o ISO da planta e apt, C+GTK3 é candidato forte; se o entregável precisa de grids densos, PDF de OF e menus de engenharia, o estudo PyQt6 (pyqt6_book) é o próximo degrau.

“Leve” aqui não significa “rápido em benchmark inventado”. Significa poucas peças móveis: compilador, pkg-config, libgtk do sistema, makefile. Menos peças móveis = menos surpresa quando o técnico de campo troca o SSD e restaura a imagem.

O que o repositório contém de fato

Árvore mínima:

  • start.c — janela toplevel com a API clássica gtk_init / gtk_main;
  • startGtkApp.c — esqueleto com GtkApplication e sinal activate;
  • makefileclang + pkg-config --cflags/--libs gtk+-3.0;
  • README.md — propósito de aprendizado e instalação da lib de desenvolvimento.

Não há widget de processo, nem binding Modbus, nem tela de alarme. O valor está na progressão didática: primeiro existir uma janela; depois existir um application object com ciclo de vida correto. Quem espera um “mini SCADA” no clone do repo vai se frustrar — e isso é intencional. O laboratório para no passo em que a toolchain e o loop de eventos estão sob controle.

start.c: a janela que só existe

gtk_init (&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_widget_show_all(window);
gtk_main();

Isso ensina três ideias que qualquer HMI leve precisa internalizar:

  1. Inicialização do toolkit antes de criar widget.
  2. Widget como árvore — aqui a árvore tem só a janela, mas o padrão é o mesmo quando você acrescenta GtkBox, GtkLabel, GtkButton.
  3. Loop de eventos (gtk_main) — sem ele, a janela nasce e o processo termina; com ele, o processo vive enquanto o operador interage.

O que falta (de propósito) é conectar o sinal destroy da janela a gtk_main_quit. Em um painel de bancada isso aparece logo: fechar o X e deixar o processo zumbi no terminal. Em ferramenta de chão, processo órfão é bug operacional, não detalhe de tutorial. O operador acha que “fechou o diagnóstico”; o processo continua ocupando porta serial ou arquivo de log. Na próxima abertura, “dispositivo ocupado” — e a caça ao fantasma começa.

Hábitos mínimos a acrescentar assim que o start.c compilar:

  • g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
  • título e tamanho iniciais (gtk_window_set_title, gtk_window_set_default_size) para o painel não nascer minúsculo no canto;
  • um GtkLabel de status mesmo que diga só “ok / aguardando” — treina o reflexo de estado visível.

startGtkApp.c: o modelo de aplicação

O segundo arquivo muda o contrato:

app = gtk_application_new("in.gtk", G_APPLICATION_FLAGS_NONE);
g_signal_connect (app, "activate", G_CALLBACK(activate), NULL);
ret = g_application_run (G_APPLICATION(app), argc, argv);
g_object_unref (app);

A função activate está vazia (comentário // gtk code comes here). Isso não é falha do estudo — é o ponto em que o aluno passa do “mostrar janela” para o “possuir uma aplicação GApplication”. Em HMI adjacente ao chão, esse modelo ajuda em:

  • instância única (evitar dois painéis abertos brigando pelo mesmo recurso);
  • argumentos de linha de comando tratados pelo GLib;
  • ciclo de vida com g_object_unref explícito.

O application_id ("in.gtk") é placeholder de estudo. Em ferramenta real você usa um ID estável no estilo DNS reverso da empresa — não por estética, mas para o desktop e o D-Bus saberem quem é quem. Dois utilitários com o mesmo ID genérico competem de formas difíceis de debugar em sessão gráfica compartilhada.

Quando activate deixar de ser vazio, o padrão saudável é: criar a GtkWindow, anexar o layout, conectar destroy, e só então gtk_widget_show_all. Misturar criação de widget fora de activate com GtkApplication é caminho clássico para “abre na segunda execução e na primeira não”.

Build: clang, pkg-config e a disciplina do makefile

O makefile compila os dois alvos:

CC = clang
CFLAGS = `pkg-config --cflags gtk+-3.0`
LIBS = `pkg-config --libs gtk+-3.0`

Três decisões práticas:

  1. pkg-config evita hardcodar caminhos de include/lib que mudam entre distros.
  2. clang como compilador default do estudo — o que importa é a flag set do GTK, não a marca do compilador; gcc com as mesmas flags também resolve.
  3. alvo clean remove os binários — hábito pequeno, útil quando a imagem do PC de fábrica é clonada e você não quer deixar artefato de debug.

Para o técnico que versiona ferramenta interna: o makefile é a documentação executável. Se o build não passa em uma máquina limpa com só libgtk-3-dev e clang/gcc, a ferramenta ainda não está pronta para o chão. “Compila no meu notebook com dez PPAs” não conta.

Checklist de aceitação de build (sem métrica inventada — só pass/fail):

  1. VM ou PC com imagem mínima da planta.
  2. Instalar apenas o que o README lista (libgtk-3-dev e compilador).
  3. make sem editar o makefile.
  4. Abrir os dois binários, fechar pelo X, confirmar que o processo some (ps / gerenciador de tarefas).
  5. make clean e repetir uma vez — garante que não há lixo escondido.

Se o passo 3 falha, o problema é ambiente ou makefile — não “UI”. Corrigir isso antes de desenhar botão.

Thread, I/O e o loop GTK: onde a oficina quebra o tutorial

O repo não mostra I/O de equipamento. Ainda assim, quem leva GTK para o chão esbarra cedo no mesmo erro: chamar leitura serial bloqueante no callback do botão. O loop GTK para, a janela congela, o operador clica de novo, e você tem fila de eventos e dispositivo em estado indefinido.

Padrão mental (sem inventar API que o repo não tem):

  • UI thread só atualiza widget e despacha comando;
  • trabalho longo (serial, socket, arquivo grande) fora do callback direto — idle, timeout curto, ou thread com cuidado de voltar à UI só via mecanismos thread-safe do GLib;
  • timeout e cancelamento pensados antes do primeiro botão “Ler CLP”.

Isso é o equivalente, em desktop, da separação entre tarefa de ciclo rápido e tarefa de comunicação no firmware. A metáfora ajuda quem vem de PIC/STM32: o gtk_main é o loop cooperativo; bloquear nele é como meter delay longo na ISR.

Estilo visual e legibilidade a distância

GTK3 permite CSS via GtkCssProvider. O repo não traz tema. Em ferramenta de chão, resista à tentação de “bonito de portfolio”:

  • contraste alto (texto claro em fundo escuro ou o inverso, mas sem cinza médio em cinza médio);
  • fontes grandes o bastante para leitura em pé;
  • botões com área generosa — mesmo em mouse, o técnico pode estar de luva fina ou com pressa;
  • cores de estado com significado estável (ok / atenção / falha), não paleta decorativa.

Nada disso exige framework novo. Exige disciplina de layout (GtkGrid/GtkBox) e teste sob a iluminação real do painel — lâmpada de LED barata muda percepção de contraste.

Onde encaixa (e onde não encaixa) como HMI de chão

Faz sentido

  • painel de diagnóstico local (status de serviço, último log, botão “reiniciar daemon”);
  • utilitário de comissionamento que fala com um dispositivo via serial/socket e mostra resultado;
  • overlay simples em PC Linux que já roda o resto da stack nativa;
  • ensino de eventos, widgets e empacotamento sem misturar Python;
  • ferramenta interna versionada em git com build de uma linha na imagem da planta.

Não substitui

  • HMI certificado / safety-related da célula;
  • SCADA multiestação com histórico e trilha de auditoria completa;
  • UI touch rica com animações e temas multiplataforma (aí entram Qt/QML, Kivy, ou runtime comercial);
  • portal web multiusuário com autenticação corporativa — outro domínio, outro contrato.

A fronteira honesta: GTK3 em C é excelente para ferramenta adjacente; é um caminho possível para HMI leve de uso interno; não é atalho para painel de segurança. Se o requisito cita norma de máquina, PL/SIL, ou “substitui a IHM do fabricante”, pare e reavalie escopo — o makefile não te salva de requisito errado.

Deploy na imagem da planta

Empacotar para Linux industrial costuma seguir uma destas rotas, em ordem de preferência quando a imagem já tem GTK3:

  1. Binário + dependência de sistema — o ideal deste estudo: apt já resolve GTK; você entrega o executável e um unit file ou atalho .desktop se precisar.
  2. Pacote .deb interno — útil quando a TI quer instalar via repositório da planta; o makefile atual não gera .deb, mas a disciplina de dependência explícita facilita escrever o control depois.
  3. AppImage / bundle — só se o PC alvo não tiver GTK3 de sistema ou houver conflito de versão grave. Bundle aumenta superfície de atualização e de auditoria.

Evite “copiar a pasta do home do desenvolvedor”. Isso é o equivalente Windows de mandar o venv zipado: funciona uma vez e falha no clone.

Próximos passos naturais a partir deste repo

Sem inventar features que o código ainda não tem, a progressão típica de oficina seria:

  1. Em activate, criar GtkWindow, conectar destroy → sair limpo.
  2. Adicionar GtkGrid ou GtkBox com labels de estado e botões de comando.
  3. Separar UI de I/O: idle callback ou thread cuidadosa para não travar o loop GTK enquanto espera resposta de equipamento.
  4. Persistir último estado relevante (arquivo local simples) — o técnico odeia redigitar IP e porta a cada abertura.
  5. Empacotar com o gerenciador da distro (.deb) ou AppImage só se o PC alvo não tiver GTK3 de sistema — a maioria dos industriais Linux modernos tem.
  6. Documentar no README da ferramenta: versão da distro, versão do libgtk-3-0, comando de build, e o que o painel não controla (ex.: não é circuito de emergência).

O repositório para no passo zero de propósito. Quem precisa de formulários longos, PDF de OF e menus de engenharia vai olhar o estudo PyQt6 (pyqt6_book). Quem precisa de touch multiplataforma olha Kivy. Quem precisa de Win32 nativo olha c_gui_win. A família de estudos é deliberada: cada runtime com um contrato claro.

Relação com o irmão Windows

O paralelo com c_gui_win não é coincidência de portfólio: é o mesmo cérebro de SOP. No Linux, pkg-config + GTK; no Windows, Chocolatey + LLVM + WinMain/MessageBox. Nos dois casos:

  • validar toolchain com programa mínimo antes da UI “de verdade”;
  • makefile como documentação executável;
  • honestidade sobre o que o hello não é (ainda não é message loop completo / ainda não é painel de tags).

Quem domina os dois não fica refém de “só roda no meu Ubuntu” ou “só no Visual Studio da fábrica”. Em oficina brasileira misturada — engenharia em Windows, painel em Linux — essa dualidade é operacional, não acadêmica.

Conclusão

c_gui_linux não é um produto de HMI. É o menor laboratório confiável para responder: “consigo entregar uma janela nativa em Linux com dependência de sistema e build de uma linha?” Se a resposta for sim, você tem base para ferramenta de chão adjacente — leve, auditável e sem runtime improvisado. Se a resposta for “preciso de dezenas de telas e relatórios”, o próximo passo não é forçar GTK3: é escolher a stack certa sem abandonar a disciplina de build que este makefile já ensina. O chão de fábrica perdoa UI simples; não perdoa processo zumbi, dependência fantasma e build que só o autor consegue repetir.