Um desktop Wayland do zero, em C (compositor + host) e JS/CSS/HTML (em web/).
Em vez de uma barra de tarefas, um deck trapezoidal que cresce ao passar o mouse. Em vez de um wallpaper, um canvas infinito onde todas as janelas abertas aparecem como fichas, e onde dá para escrever notas, colocar imagens e vídeos.
| Parte | O que é |
|---|---|
compositor/ |
Compositor Wayland em C sobre wlroots 0.20. Aninhado na sessão atual (janela WL-1), sem precisar de TTY nem root. |
shell-host/ |
Processo GTK3 + gtk-layer-shell + WebKitGTK. Cria as layer surfaces (canvas de fundo, deck de topo) e hospeda a UI. |
tools/padrao-shot.c |
Cliente Wayland que fala wlr-screencopy-v1 e grava PNG da saída (ferramenta de teste; alternativa autossuficiente ao grim). |
web/ |
Toda a interface web (fonte única): index.html+app.js+style.css (a página que o compositor mostra como fundo), canvas/, deck/ e shared/ (o shell). |
deps/ |
fetch-deps.sh baixa os RPMs de desenvolvimento sem sudo; env.sh aponta pkg-config/headers para o sysroot extraído. |
cd padrao
make # compila compositor + host
./run.sh # sobe tudo aninhado na sessão Wayland atualrun.sh sobe o compositor, espera o socket de IPC, sobe o host apontando
WAYLAND_DISPLAY=padrao, e vigia os dois (se um cai, derruba o outro e limpa
o socket). Ctrl-C encerra.
Para capturar a tela de teste:
make tools/padrao-shot
WAYLAND_DISPLAY=padrao ./tools/padrao-shot saida.pngE para capturar os estados do deck (o crescido e a grade de aplicativos) de uma vez, com o desktop já rodando:
./run.sh & # o compositor precisa estar no ar
tools/capturar.sh # a porta do host é lida do logO script usa o gancho /debug/emit (abaixo) para forçar cada estado e
grava em docs/screenshots/.
O host expõe uma rota só em loopback que injeta um evento Padrao.emit() numa
página, útil para forçar estados do deck sem depender de mouse/teclado reais:
# sobe o estado "open" (hover) do deck
curl "http://127.0.0.1:<porta>/debug/emit?page=deck&json=%7B%22t%22%3A%22dbg%22%2C%22state%22%3A%22open%22%7D"O json é um objeto JSON URL-encoded e precisa do campo "t", porque
Padrao.emit() despacha pelo tipo do evento (o deck registra t:"dbg").
Sem "t" o evento é entregue a nenhum handler e nada acontece — silenciosamente.
Aceitos: {"t":"dbg","state":"open"}, {"t":"dbg","state":"grid"} e
{"t":"dbg","state":"rest"}. A porta do host aparece no stderr dele ao subir.
O mesmo caminho serve para o canvas (page=canvas): injete o evento que a
página escuta, por exemplo {"t":"view","x":0,"y":0,"z":1}.
O app de web/ (o fundo do compositor) tem três temas, trocáveis pelos
botões .theme-chip no canto superior direito:
| Tema | Seletor | Visual |
|---|---|---|
| Azul Padrão | default |
o original: azul profundo, aciano/teal, grade azul |
| Minimalista | minimalist |
só tons de cinza, sem brilho, grade quase invisível |
| Ferro Oxidado | oxidized-iron |
ferrugem, verdete e metal, grade quente |
No CSS cada tema é um bloco [data-canvas-theme="…"] que define as
variáveis de cor, bordas, fundos da grade e os estilos da moldura
#active-window-glow (o contorno do item em foco). O seletor é escrito no
<html>.
Em app.js, setCanvasTheme(nome) troca o seletor, guarda a escolha no
localStorage (padrao.canvas.theme) e marca o botão ativo. A URL tem
precedência sobre a escolha salva:
web/index.html?theme=oxidized-iron
É o que o compositor usa para abrir o fundo já num tema:
PADRAO_WEB_FILE="web/index.html?theme=oxidized-iron" ./build/padrao-compositorO compositor cria em C (compositor/src/main.c) uma layer surface com
WebKitGTK: uma janela GTK conecta no socket do próprio Padrão
(WAYLAND_DISPLAY=padrao) e o gtk-layer-shell a transforma numa camada do
compositor, mostrando web/index.html como fundo da tela — sem processo
extra.
A camada roda numa thread própria porque o GDK faz um roundtrip para buscar os globals do servidor, e esse servidor é o próprio compositor: só responde dentro do event loop dele, que fica na thread principal.
Por isso o compositor não usa wl_display_run(): usa um loop manual
(wl_display_flush_clients + wl_event_loop_dispatch) que continua rodando
durante o encerramento, para que o cliente GTK se despida com elegância. Sem
isso, o teardown do GTK/GL corre em paralelo com a limpeza do display e o
heap do processo acaba corrompido.
Variáveis de ambiente:
| Variável | Padrão | Efeito |
|---|---|---|
PADRAO_WEB_FILE |
web/index.html |
página local a abrir |
PADRAO_WEB_LAYER |
bottom |
camada: background, bottom, top ou overlay |
PADRAO_WEB_GPU |
(desligado) | 1 reativa o OpenGL do WebKit (por padrão a camada renderiza por software: o driver GL do Mesa neste ambiente corrompe o heap) |
O
bottomé o padrão de propósito: o canvas do shell (web/canvas) é opaco e, na camadabackground, cobriria a página web.
canvas.js / deck.js (WebKit)
| webkit.messageHandlers.p
shell-host (GTK)
| Unix socket $XDG_RUNTIME_DIR/padrao.sock JSON por linha
compositor (wlroots)
O host é o único cliente do socket. Ele espelha os eventos do compositor para
as páginas JS via Padrao.emit(<json>), e reúne os comandos vindos da UI
(mover câmera, focar/fechar janela, redimensionar o deck) para mandar de volta.
Contrato das mensagens está descrito em shell-host/src/padrao_host.h.
O canvas manda ao compositor a transformação em uso, e o compositor reposiciona cada janela para que ela acompanhe o zoom exatamente:
Com isso, a posição de cada janela na tela é
screen_x = (window_x * scale) + offset_x
screen_y = (window_y * scale) + offset_y
A escala visual é a da saída (o zoom do canvas), então as janelas crescem junto com o canvas.
O shell-host varre todos os .desktop de /usr/share/applications,
/usr/local/share/applications e ~/.local/share/applications, extrai
Name, Exec e Icon, ordena alfabeticamente e descarta apenas o que tem
NoDisplay=true. A lista completa vai para a UI:
{ "t": "app_list", "apps": [ { "id": "firefox", "name": "Firefox",
"icon": "firefox", "exec": "firefox %u" } ] }O deck consome esse evento e desenha a grade dinamicamente.
O compositor roda como uma janela normal dentro da sessão KDE/GNOME atual, o
que permite desenvolver e depurar sem matar a sessão de trabalho nem escrever
em /dev. Em TTY viria o modo direto, mas o aninhamento é o caminho de
desenvolvimento.
compositor/src/main.c bootstrap wlroots, registra todos os listeners
compositor/src/output.c cria saídas, comite de frame, zoom (escala de saída)
compositor/src/view.c janelas xdg-shell (foco, mover, fechar)
compositor/src/input.c cursor, teclado, roda (zoom)
compositor/src/layer.c layer surfaces (canvas e deck)
compositor/src/ipc.c socket Unix, protocolo JSON
shell-host/src/main.c ponte JS<->compositor, estado, apps, ícones
shell-host/src/http.c servidor local que serve a UI, mídia e ícones
shell-host/src/ipc.c cliente do socket, reconexão
shell-host/src/jx.c JSON mínimo sem dependências
-
As dependências ficam em
~/.local/share/padrao/{rpms,sysroot}; nada em/usre nada com sudo. -
Os headers de protocolo (
wlr-*-protocol.h) são gerados porwayland-scannerporque os RPMs de dev do wlroots não os instalam. -
Os caminhos no
Makefilesão relativos de propósito: o diretório do projeto contém espaços, e o make não lida bem com espaços em$(wildcard). -
Há também um
meson.buildequivalente (meson + ninja), que convive com oMakefile:source deps/env.sh # obrigatório: aponta pkg-config para o sysroot meson setup build ninja -C build
Os binários saem em
build/(padrao-compositor,padrao-host,padrao-shot). Orun.shusa os debuild/quando existem e cai para os domakecaso contrário.