Olá a todos! Meu nome é Artur Valiev. Recentemente, compartilhei no Habr como construímos nosso próprio RustDesk Pro usando patches e compilações customizadas. No entanto, com o tempo, percebemos que a customização padrão não era suficiente. Queríamos ir muito além.
Para contextualizar, trabalhei por quase cinco anos em um hospital distrital. Quem já deu suporte a instituições médicas sabe o caos que é a infraestrutura: computadores antigos, servidores terminais, máquinas virtuais, Astra Linux, redes fechadas, restrições de segurança e a necessidade constante de ajudar usuários remotamente. Naquela época, eu sonhava com uma ferramenta simples de acesso remoto que funcionasse em qualquer lugar, sem complicações, e que me poupasse de ter que sair da cama no frio. Anos depois, finalmente criamos essa ferramenta: o EvertyDesk Lite.
É um cliente de acesso remoto totalmente nativo em Rust e egui. Um único binário. Sem navegador embutido. Sem Electron, Flutter. Sem dezenas de dependências. Sem a necessidade de baixar metade da internet via repositórios. Projetamos especificamente para rodar mesmo onde o ambiente gráfico já está em colapso. Astra Linux? RED OS? Funciona. Máquinas virtuais antigas sem OpenGL decente? Também roda. Para esses casos, implementamos até mesmo um renderizador de interface por software (e um bem decente!), para que o cliente possa ser aberto onde a aceleração de hardware mais atrapalha do que ajuda. Um problema comum em aplicações desktop no Linux é que a interface pode não abrir não por causa do seu código, mas por causa do stack gráfico. Isso é especialmente notável em ambientes corporativos, em VMs antigas, Astra Linux, RED OS e máquinas com OpenGL instável. O caminho usual para uma aplicação egui é:
rusteframe::run_native( "EvertyDesk Lite", eframe::NativeOptions::default(), Box::new(|_| Box::new(EvertyDeskApp::new())), )?;
Isso funciona bem enquanto um backend gráfico normal está disponível: wgpu, OpenGL, Vulkan, DirectX ou Metal. Mas se o backend gráfico falhar, o cliente de acesso remoto se torna inútil: o usuário está pedindo ajuda justamente na máquina problemática, e nossa ferramenta não inicia. Por isso, criamos um backend de UI por software separado. A ideia é simples: egui continua construindo a interface, mas em vez de usar a GPU, nós mesmos fazemos a tesselação das formas, as desenhamos em um buffer da CPU e exibimos esse buffer através de uma janela comum usando minifb. O ciclo simplificado é:
rustpub fn run_software_ui() -> Result<(), String> { std::env::set_var("EVERTYDESK_EGUI_SOFTWARE", "1"); let mut window = Window::new( APP_NAME, 1100, 760, WindowOptions { resize: true, scale_mode: ScaleMode::UpperLeft, ..WindowOptions::default() }, ) .map_err(|err| format!("open CPU egui window failed: {err}"))?; window.set_target_fps(60); let ctx = egui::Context::default(); ctx.set_pixels_per_point(1.0); configure_software_fonts(&ctx); configure_style(&ctx); let mut app = EvertyDeskApp::new(); let mut painter = SoftwarePainter::default(); let mut pixels = vec![0_u32; 1100 * 760]; while window.is_open() && !window.is_key_down(MiniKey::Escape) { let (width, height) = window.get_size(); if pixels.len() != width * height { pixels.resize(width * height, 0); } let raw_input = collect_input(&window, width, height); let output = ctx.run(raw_input, |ctx| { app.update_egui(ctx); }); let primitives = ctx.tessellate(output.shapes, output.pixels_per_point); painter.apply_textures(output.textures_delta); pixels.fill(0x14181c); painter.paint(&mut pixels, width, height, &primitives); window .update_with_buffer(&pixels, width, height) .map_err(|err| format!("CPU egui window update failed: {err}"))?; } app.shutdown(); Ok(()) }
Essencialmente, há quatro passos: Coletamos a entrada do minifb (mouse, teclado, tamanho da janela). Passamos isso para egui::Context como RawInput. Recebemos do egui um conjunto de formas e as tessellamos em primitivas de pintura. Desenhamos as primitivas em um Vec<u32> comum e enviamos o buffer para a janela. O mais importante: a lógica de negócios da interface não é duplicada. A aplicação principal ainda é atualizada através de um único método: app.update_egui(ctx). Ou seja, o modo GPU normal e o modo de software usam o mesmo código de UI. A única diferença é quem desenha os pixels no final. Um problema separado foi com os caracteres cirílicos. No modo de software, não se pode esperar que o sistema carregue o font correto automaticamente. Por isso, procuramos explicitamente por um font instalado com suporte a cirílico:
rustfn load_cyrillic_font() -> Option<(String, Vec<u8>)> { const FONT_PATHS: &[(&str, &str)] = &[ ("Noto Sans", "/usr/share/fonts/truetype/noto/NotoSans-Regular.ttf"), ("DejaVu Sans", "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"), ("Liberation Sans", "/usr/share/fonts/liberation-fonts/LiberationSans-Regular.ttf"), ("Segoe UI", "C:\\Windows\\Fonts\\segoeui.ttf"), ("Arial", "C:\\Windows\\Fonts\\arial.ttf"), ]; for (name, path) in FONT_PATHS { if let Ok(data) = std::fs::read(path) { if !data.is_empty() { return Some((format!("system-{name}"), data)); } } } None }
E o conectamos no egui:
rustfn configure_software_fonts(ctx: &egui::Context) { let mut fonts = FontDefinitions::default(); if let Some((name, data)) = load_cyrillic_font() { fonts.font_data.insert(name.clone(), FontData::from_owned(data)); for family in [FontFamily::Proportional, FontFamily::Monospace] { fonts.families.entry(family).or_default().insert(0, name.clone()); } } ctx.set_fonts(fonts); }
Isso é iniciado assim:
EVERTYDESK_RENDERER=software ./evertydesk-lite
O resultado são dois modos: o modo normal via eframe, wgpu e glow; e o modo de emergência por software via egui + minifb + buffer da CPU. Isso não substitui o renderizador normal da GPU. É uma entrada de fallback para a aplicação quando os gráficos da máquina já estão quebrados ou muito antigos. Para acesso remoto, isso é crítico: se o cliente é necessário justamente para diagnosticar um sistema problemático, ele não deve falhar no primeiro driver problemático.
EvertyDesk Lite - Compatível com RustDesk
E quero deixar um ponto importante logo de cara. Este material não é sobre a promoção de mais um produto comercial. O código é aberto, permito-me compartilhar a experiência de desenvolvimento e, por favor, leiam o artigo até o "Giardino della Bellezza" técnico. Pelo contrário, todo o projeto foi concebido desde o início como uma plataforma aberta para experimentação. O código-fonte está disponível, qualquer um pode compilar o cliente para suas próprias necessidades, substituir a parte do servidor, integrá-lo à infraestrutura existente ou usar ideias individuais em seus projetos. Para mim, é muito mais interessante falar não sobre o resultado, mas sobre o caminho que levou a ele.
Porque em algum momento ficou claro: o acesso remoto há muito deixou de ser uma tarefa de transmitir uma imagem de um computador para outro. Historicamente, tais sistemas resolviam um problema bastante simples: permitir ver a tela remota e obter controle do mouse e teclado. Isso era suficiente quando havia poucos computadores, as infraestruturas eram relativamente simples e a maioria das operações era realizada manualmente. Hoje, a situação é diferente. O administrador moderno não trabalha com um único computador, mas com centenas e milhares de dispositivos. Ele gerencia serviços, políticas, atualizações, segurança, automação e monitoramento. Sua ferramenta principal é cada vez mais o terminal, e não a interface gráfica. Essencialmente, não estamos falando de inteligência artificial no sentido de marketing usual, mas de uma nova interface de interação com a infraestrutura. E quanto mais o projeto se desenvolvia, mais forte se tornava a sensação de que não estávamos construindo apenas um cliente de acesso remoto. Estávamos gradualmente montando um local de trabalho para um engenheiro de suporte – uma ferramenta na qual a tela remota, o terminal, a automação e a IA se tornam partes de um único sistema.
Quando a "Interface Nativa" de Repente Deixa de Ser Nativa
Praticamente qualquer projeto GUI em Rust começa da mesma forma:
rustfn main() { eframe::run_native( "EvertyDesk Lite", eframe::NativeOptions::default(), Box::new(|_| Box::new(EvertyDeskApp::new())), ) .unwrap(); }
No laptop de trabalho do desenvolvedor, tudo parece ótimo. Os problemas começam depois. Descobre-se que parte das máquinas Linux usa versões antigas do Mesa. Em algum lugar, o OpenGL funciona de forma instável. Em algum lugar, falta Wayland. Em algum lugar, o sistema está em um circuito fechado e o usuário fisicamente não pode instalar dependências adicionais. Nessas condições, a frase "atualize o driver" significa apenas uma coisa: o usuário fica sem ajuda. Portanto, no EvertyDesk Lite, surgiu um modo separado de renderização de interface por software. No modo normal, a aceleração de hardware é usada através do stack padrão do egui. Se o subsistema gráfico começar a se comportar de forma imprevisível, a aplicação pode mudar para um backend de software que desenha a interface usando os recursos do processador. Sim, esse modo é mais lento. Mas funciona. E para acesso remoto, isso é muito mais importante. EVERTYDESK_RENDERER=software ./evertydesk-lite. Se o sistema for capaz de abrir uma janela X11, a chance de iniciar o cliente permanece mesmo em hardware extremamente problemático.
Cirílico - Isso Também é Infraestrutura
Enquanto a aplicação roda no renderizador padrão, a questão das fontes parece insignificante. Mas assim que surge uma interface de fallback, descobre-se que o suporte a texto se torna uma tarefa de engenharia separada. O suporte não funciona apenas com o idioma inglês. A interface inclui: comentários de operadores; notas na agenda; saída do terminal; mensagens de erro; dados de diagnóstico. Portanto, a aplicação configura forçadamente o ambiente UTF-8 e tenta selecionar um font do sistema com suporte a cirílico.
rustfn configure_locale_for_text_input() { // Configuração do locale UTF-8 } fn load_cyrillic_font() -> Option<(String, Vec<u8>)> { // Busca por um font de sistema disponível }
À primeira vista, isso parece um detalhe. Na prática, são justamente esses detalhes que determinam se o operador poderá usar o sistema em seu local de trabalho.
Codecs Devem Degradar de Forma Elegante
Um dos erros mais comuns no desenvolvimento de acesso remoto é a dependência de um único pipeline de mídia ideal. Na operação real, um ambiente ideal não existe. Diferentes sistemas operacionais fornecem diferentes capacidades: decodificação por hardware; decodificação por software; diferentes versões de bibliotecas multimídia; diferentes conjuntos de codecs. Portanto, o cliente deve ser capaz de operar em vários modos. Por exemplo: streaming de vídeo; modo leve; transmissão de quadros de imagem de fallback. Dentro do cliente, isso se parece com a escolha de um modo de operação disponível:
rustpub enum LiveVideoMode { ScreenshotOnly, H264, H264Vpx, }
Se o pipeline de vídeo moderno não estiver disponível, a aplicação não deve parar de funcionar. Ela deve continuar exibindo a área de trabalho remota de forma menos eficiente, mas ainda útil. Isso é crucial para o suporte.
Por Que Boas Mensagens de Erro São Mais Importantes Que Botões Bonitos
A conexão a serviços remotos raramente falha de forma elegante. Senha incorreta, autorização expirada, erro de configuração, problemas de rede – tudo isso parece igual se a aplicação mostra ao usuário apenas a frase: "Erro de conexão". Para um engenheiro de suporte, tal mensagem é inútil. Portanto, no EvertyDesk Lite, atenção especial é dada ao diagnóstico. Em vez de notificações abstratas, o sistema tenta mostrar a causa mais específica possível da falha:
"Erro de autorização Código de resposta: 401 Descrição: acesso negado" ou
"Falha ao estabelecer conexão Causa: nó remoto indisponível"
Essa abordagem reduz significativamente o tempo de resolução do problema.
Conexão Sem Senha Requer Confirmação
O acesso remoto não é apenas conveniência, mas também segurança. Se o operador se conecta sem uma senha previamente conhecida, o lado remoto deve confirmar explicitamente a solicitação de entrada. Portanto, o sistema considera a solicitação de conexão como um evento separado.
rustpub enum HostEvent { ApprovalRequested, ClientConnected, ClientDisconnected, }
Uma janela de confirmação aparece na interface, permitindo aceitar ou rejeitar a conexão. Esse mecanismo parece óbvio, mas é ele que separa a ferramenta de suporte de um potencial incidente de segurança.
O Terminal é Frequentemente Mais Importante Que a Área de Trabalho
A área de trabalho remota é útil para entender o contexto. Mas a maior parte do trabalho administrativo é realizada através do console. Terminal compatível + scripts prontos de IA.
Minutos após a conexão, o operador geralmente começa a executar comandos: systemctl status, journalctl, ip a, df -h. Transmitir tudo isso através da emulação de teclado é inconveniente. Portanto, o terminal é considerado uma ferramenta separada, e não um complemento à tela remota. Ele usa seus próprios canais de transmissão de dados, seus próprios buffers e uma interface de exibição separada.
rustpub enum ShellMessage { Open, Input, Output, Close, }
Isso permite trabalhar de forma significativamente mais rápida e confiável.
Inteligência Artificial Não Deve Gerenciar Servidores
"Por que IA em um sistema de acesso remoto? Na verdade, não é para gerar texto nem para demonstrações da moda. O problema surge quando o administrador se depara com um enorme volume de informações técnicas. Logs, saída de comandos, erros de serviços, rastreamentos de rede, logs de eventos – tudo isso precisa ser analisado e correlacionado rapidamente. Por exemplo, um usuário reclama que um aplicativo não inicia. O administrador se conecta à máquina, abre o terminal, verifica o status do serviço, visualiza o log de eventos e obtém várias dezenas de linhas de erro. Formalmente, todas as informações já estão lá, mas ainda precisam ser interpretadas. A IA pode explicar imediatamente: o serviço não inicia devido à ausência de um certificado, o certificado está expirado e um erro relacionado já está presente no log do sistema. Em vez de pesquisar em documentação e fóruns, o engenheiro obtém uma hipótese pronta em segundos. Outro exemplo é o gerenciamento em massa. Após a atualização em cem servidores, parte dos nós começa a retornar erros. A IA é capaz de analisar os resultados da execução de comandos em todos os nós simultaneamente, agrupar problemas semelhantes e mostrar que 87 servidores estão funcionando normalmente, enquanto em 13 falta a biblioteca necessária ou o pacote de atualização não foi aplicado. Ou seja, a IA aqui funciona não como um operador de infraestrutura, mas como um analisador inteligente de dados. Ela não toma decisões nem executa ações de forma independente. Ela ajuda a pessoa a entender mais rapidamente o que exatamente está acontecendo no sistema e para onde olhar a seguir."
Nos últimos anos, tornou-se popular adicionar IA a praticamente qualquer produto. Na administração, tal abordagem pode ser perigosa. A execução automática de comandos em um sistema remoto cria um risco de erro muito alto. Portanto, no EvertyDesk Lite, a inteligência artificial é vista exclusivamente como um assistente. Ela recebe um contexto limitado: a tarefa do operador; parte do histórico do terminal; as últimas mensagens do sistema. Em seguida, oferece: a causa provável do problema; um comando possível; recomendações para diagnóstico posterior. Por exemplo:
"Diagnóstico: Serviço não iniciado.
Comando recomendado: systemctl restart nome-do-servico
Após a execução, verifique: systemctl status nome-do-servico"
O princípio chave permanece o mesmo: a IA pode aconselhar. A decisão é tomada pelo ser humano.
ai conf RUST+EGUI (White)
O Modo de Serviço - Um Produto Separado Dentro do Produto
Iniciar o cliente manualmente não é difícil. É muito mais difícil garantir o acesso ao sistema antes mesmo do login do usuário. Para isso, é necessário um modo de operação de serviço separado. No Linux, isso geralmente é uma unidade systemd:
ini[Unit] Description=EvertyDesk Lite Service [Service] Type=simple ExecStart=/usr/local/bin/evertydesk-lite --host Restart=always [Install] WantedBy=multi-user.target
No Windows, um modo de serviço análogo é usado. Do ponto de vista da engenharia, este é um cenário de uso completamente diferente, com suas próprias limitações, requisitos de segurança e características de interação com o subsistema gráfico.
A Compilação Não Deve Depender da Memória do Desenvolvedor
Qualquer projeto Linux, mais cedo ou mais tarde, se depara com o mesmo problema. O desenvolvedor escreve: "Para mim, compila tudo." O usuário responde: "Para mim, não." Geralmente, a causa está em pacotes e dependências esquecidos. Portanto, o projeto deve conter suas próprias ferramentas de diagnóstico de ambiente. Por exemplo:
bashecho "OS:" cat /etc/os-release echo "Graphics:" glxinfo -B echo "Rust:" rustc --version cargo --version
Essas verificações permitem entender rapidamente o estado do sistema e evitar longas trocas de e-mails.
O Que Conseguimos no Final
Hoje, o EvertyDesk Lite é um cliente de acesso remoto compacto, focado não em cenários de demonstração, mas em tarefas reais de suporte e administração. O projeto inclui: conexão a dispositivos remotos; confirmação de conexões de entrada; agenda; histórico de sessões; terminal embutido; assistente de IA para diagnóstico; suporte a vários modos de transmissão de imagem; cenários de operação de fallback; renderização de interface por software para sistemas problemáticos; modo de serviço para acesso contínuo. A principal conclusão que surgiu durante o desenvolvimento foi bastante simples. O acesso remoto não é necessário quando tudo funciona perfeitamente. Ele é necessário justamente quando o sistema já está em um estado não padrão. (Sim, sim, clichê =)) Portanto, um bom cliente de acesso remoto deve estar pronto para funcionar em uma máquina imperfeita, em uma rede imperfeita e em um ambiente imperfeito. Sem excesso de pompa: Nosso cliente aprimorado e seu modo de scripts são simplesmente ouro. A impressora no Astra Linux foi resolvida com um único comando. A biblioteca de automação geral certamente salvará os administradores de sistemas de muito trabalho tedioso. É exatamente para tais condições que o EvertyDesk Lite é projetado.
Obrigado por ler até o final! Entendo que não consegui falar sobre todas as nuances. Escrevam comentários e façam suas perguntas – discutiremos tudo com prazer. Uma excelente e produtiva semana a todos!
Contatos pessoais para comunicação: Email: info@everty.ru https://github.com/vaalimusic/deskeverty-lite-4-all https://desk.everty.ru/







