Skip to content

Monky Light: continuidade, QA multiplataforma e distribuição #658

Open

Description

@lucastaonline

Objetivo e passagem de responsabilidade

Continuação do Monky Light (#119 e #306), sob responsabilidade de @JelielSantos. Esta issue concentra as pendências no corpo para que a continuidade não dependa de ler comentários antigos.

O objetivo permanece: comunicação com o menor trabalho de CPU possível, atendendo computadores fracos e quem precisa preservar desempenho para outras tarefas. Recursos opcionais devem consumir recursos apenas enquanto forem usados. Não substituir essa meta por tamanho de instalador ou por preferência de linguagem/framework.

Entrega do núcleo: #659 mesclado na main (d73a0ee). Release do repositório: v20.1.8-beta. Esta issue permanece no Backlog com @JelielSantos para continuidade.

Qualificação automatizada da passagem: CI completo aprovado no commit 0e594f0 em Windows x64, macOS Intel e Apple Silicon, incluindo componentes nativos e voz nativo/Chromium pelo servidor real. Registro: https://github.com/MonkyOrg/Monky/actions/runs/35094596177. Hardware real, macOS 12 e uso humano continuam pendentes abaixo.

O que já existe

  • Núcleo C++ headless em apps\light, sem Electron, Node ou webview residentes no cliente. Node participa da construção e dos cenários automatizados; o servidor continua sendo o Monky existente.
  • Autenticação Ed25519, perfil próprio, conexão WebSocket nativa, canais, voz P2P/SFU, mute/deafen, reconexão, moderação e encerramento.
  • Windows x64 executado localmente, inclusive captura do microfone físico. Cenários com áudio sintético exercitam múltiplos participantes e interoperabilidade nativo/Chromium em P2P e SFU.
  • Adaptadores macOS 12+ Intel/Apple Silicon, autorização assíncrona do microfone, Keychain e bundle preparados. A matriz de CI não equivale a avaliação com dispositivos reais nem comprova funcionamento na versão mínima do macOS.
  • Constantes C++ geradas de @monky/shared; não duplicar versão/tipos/limites manualmente. Use cliente e servidor da mesma branch e protocolo.
  • Construção, cenários e medição documentados nos READMEs PT/EN de apps\light.

Ainda não existe aplicativo de bandeja nem instalador público do Light. A release normal do Monky não passa a conter downloads do Light só porque o código headless entrou no repositório.

Prioridade 1: assumir e concluir a qualificação do núcleo

  • Reproduzir a construção e os cenários em Windows x64, macOS Intel e Apple Silicon; tratar falhas reais do CI sem desabilitar a cobertura para conseguir aprovar o PR.
  • Exercitar macOS com hardware real e conferir compatibilidade com macOS 12: permissão concedida/negada/pendente, entrada já mutada, recepção enquanto o prompt está aberto, encerramento, Keychain bloqueado, identidade persistente e conexões ws:///wss://. Confirmar que certificados não confiáveis continuam recusados.
  • Qualificar a identidade entre recompilações, assinatura e atualizações no macOS. O headless desabilita a UI do Keychain para o próprio processo; definir recuperação/autorização segura quando necessária, sem liberar acesso indiscriminado ao item nem substituir a identidade.
  • Fazer chamada audível entre Light e o aplicativo Monky completo com duas pessoas, em P2P e SFU, incluindo vários participantes, diferentes dispositivos, mute/deafen, moderação, troca de sala, reconexão, expulsão e troca de topologia.
  • Exercitar redes reais: LAN/internet, NAT, ICE/TURN, perdas e reconexões. Não usar servidor de produção para contornar incompatibilidade de protocolo.
  • Implementar/expor seleção de entrada e saída de áudio e tratar troca/desconexão de dispositivo. O headless atual utiliza os dispositivos padrão do sistema; isso ainda não é uma interface completa de gerenciamento de áudio.
  • Ampliar a avaliação de consumo: mesmo hardware, canal, participantes, política de áudio e condições para Light e Monky completo; incluir CPU, RAM, GPU, tráfego, tempo prolongado e recursos após sair/fechar opções. A amostra Windows atual é sintética e curta, não demonstra superioridade geral.

Amostra de referência local do núcleo em Release, não um limite de aceite: aproximadamente 13 MiB conectado sem chamada e 23–24 MiB durante voz sintética; aproximadamente 3% de um processador lógico durante a chamada. O valor não é o percentual normalizado pelo total de núcleos do Gerenciador de Tarefas.

Prioridade 2: interface mínima e uso cotidiano

  • Implementar bandeja e pequenos painéis nativos para servidores, canais, estado da chamada, mute/deafen e dispositivos. Evitar renderização contínua e recursos escondidos ativos.
  • Integrar atalhos e feedback de erros/estado, acessibilidade e localização de acordo com o idioma selecionado. Usar switches para liga/desliga e cards selecionáveis para escolhas exclusivas, nunca checkbox/radio nativo isolado.
  • Definir os fluxos de identidade/configurações antes da distribuição: recuperação explícita, eventual importação/exportação e movimentação de perfil. Hoje corrupção/interrupção é recusada e mover um perfil macOS exige migração do vínculo do Keychain; nunca gerar outra identidade silenciosamente.

Recursos opcionais ainda não implementados

  • Chat e mídias em interface sob demanda, com histórico paginado, cache limitado e sem download/reprodução desnecessários.
  • Soundboard com reprodução nativa e acionamento por atalhos/comandos/controles mínimos; carregar sons conforme necessário e respeitar a escolha de receber sons de outros participantes.
  • Miniapps em ambiente HTML criado e destruído sob demanda, reutilizando conteúdo e contratos existentes, com isolamento e ponte limitada de estado/ações. Não expor módulos nativos ou execução arbitrária ao HTML.
  • Assistir a compartilhamentos por escolha explícita: assinar/decodificar somente os fluxos selecionados e interromper o consumo ao parar. As fábricas VP8 atuais servem à negociação de voz com ofertas que contêm vídeo; não constituem um player implementado.
  • Hospedagem opcional pelo CLI existente em processo separado, com MONKY_HOME, dados e portas próprios. Iniciar somente quando solicitado e conferir pré-requisitos antes de habilitar; não reescrever o servidor nem mantê-lo dentro do processo de voz.

Fora do escopo: sobreposição. Transmitir câmera/tela não faz parte do marco inicial; qualquer expansão precisa de decisão explícita, não deve ser assumida nesta continuidade.

Distribuição e manutenção ainda não implementadas

  • Qualificar e definir manutenção/atualização do SDK WebRTC e libmediasoupclient nas três arquiteturas. O pin WebRTC M140 é uma referência de desenvolvimento, não aprovação automática para distribuição pública.
  • Preservar licenças/notices e as verificações de integridade. A construção inclui FieldTrials da mesma revisão e uma correção estritamente verificada por hash para listas SDP opcionais ausentes; não remover essas proteções sem substituir a dependência e cobrir os casos.
  • Gerar artefatos/instaladores próprios do Light para Windows x64 e macOS Intel/Apple Silicon, com assinatura/notarização quando aplicável e versão obtida do pipeline (os metadados do bundle de desenvolvimento ainda têm versão provisória).
  • Integrar o Light à mesma release do Monky, com nomes de download, identificação de aplicativo, perfis, instalação e metadados de atualização separados. Instalar/atualizar uma edição não pode substituir a outra.
  • Implementar/qualificar atualização do Light e convivência com Monky instalado. Não promover beta para stable automaticamente.
  • Atualizar site/documentação PT-BR e inglês para explicar diferenças entre as edições, disponibilidade por plataforma e servidor CLI + Light. Só anunciar downloads após os artefatos realmente existirem.

Como começar

  1. Atualizar a main (o núcleo já foi mesclado), instalar as dependências normais do monorepo e consultar apps\light\README.md.
  2. Preparar CMake 3.28–3.31; Windows requer Visual Studio 2022/Build Tools e Windows SDK. No Mac, usar ferramentas Xcode compatíveis com o pin de apps\light\dependencies.json.
  3. Executar os comandos abaixo na raiz. Os cenários de voz normais criam servidor/perfis descartáveis e usam fontes sintéticas, sem hardware físico.
  4. Para QA manual, iniciar servidor descartável da mesma branch e usar um perfil absoluto exclusivo com --profile. A pasta pai deve existir. Nunca usar perfis/bancos/credenciais do Monky instalado.
  5. Registrar resultados e atualizar este corpo conforme as pendências forem resolvidas, distinguindo construção, áudio sintético, hardware real e uso humano.
npm run build:light
npm run test:light:scripts
npm run test:light:native
npm run test:light:server
npm run test:light:voice

Comandos opcionais, intencionais:

# Usa o microfone fisico; nao salva audio nem envia para um servidor externo.
npm run test:light:hardware

# Medicao atualmente implementada somente para Windows.
npm run measure:light

No Mac, as construções ficam separadas por arquitetura:

npm run build:light -- --arch x64
npm run build:light -- --arch arm64

O executável de produção está em bin do diretório da arquitetura; no Mac, dentro de monky-light.app. Use --help para as opções e os READMEs para os comandos JSON de controle.

Achados do mega code review (#490)

LIGHT-1 a LIGHT-5 entraram no PR do review (Onda 8). Estes três ficaram fora por decisão. Nenhum muda protocolo.

  • Escrita do stdout fora do loop do núcleo (LIGHT-4, item 3). Cada evento é escrito no stdout de forma síncrona, com flush, dentro do loop do núcleo (apps/light/src/headless_application.cpp:228-238). No Windows, com o console em modo de seleção (QuickEdit), a escrita bloqueia: o loop para, não sai PING e a conexão cai. Não há tratamento de ENABLE_QUICK_EDIT_MODE em apps/light/src.
    • Proposta: thread própria de saída com fila limitada, que junta os media-stats atrasados. Alternativa barata no Windows: desligar o QuickEdit no console do Light.
    • Aceite: com o console em seleção por 60 s numa chamada, o Light continua conectado e não encerra.
  • Dividir o voice_engine.cpp (LIGHT-6). O arquivo apps/light/src/media/voice_engine.cpp tem 2.007 linhas e concentra fila de controle, runtime de áudio, P2P e SFU no mesmo Impl (VoiceEngine::Impl em :319, Peer em :549, SfuListener em :642). Sem um contrato comum, as políticas dos dois modos divergem em silêncio.
    • Proposta: control_queue, audio_stack, p2p_mesh, sfu_session e uma interface Topology implementada pelo P2P e pelo SFU; a API pública de VoiceEngine não muda.
    • Quando: decisão do review, fazer quando vier vídeo ou uma terceira topologia.
    • Aceite: voice_engine_lifecycle_test.cpp, voice_engine_loopback_test.cpp, headless_interop.test.cjs e browser_interop.test.cjs passam sem mudança de comportamento, nos três alvos do CI.
  • Primitivas de arquivo da identidade num lugar só (LIGHT-7). Leitura limitada, publicação por rename, handle com RAII e limpeza da seed estão em três lugares, já com verificações diferentes:
    • handle com RAII: apps/light/src/profile_identity.cpp:114-128 (File) e apps/light/src/platform/identity_store_windows.cpp:37 (Handle);
    • requireOrdinaryFile exige link único (profile_identity.cpp:130-137); requireFileKind não (identity_store_windows.cpp:90-98);
    • rename com guarda de tamanho do nome (profile_identity.cpp:185-199, identity_store_windows.cpp:115-131) e sem guarda no writeSettings do Windows (profile_identity.cpp:252-262);
    • limpeza da seed de três jeitos: SecureZeroMemory (identity_store_windows.cpp:64 e :75), laço volátil (apps/light/src/platform/identity_store_macos.mm:70) e OPENSSL_cleanse (apps/light/src/identity_key.cpp:35 e :82);
    • canonicalProfile duplicado: identity_store_windows.cpp:78 e identity_store_macos.mm:75;
    • Proposta: platform/profile_files.{hpp,cpp} com OwnedHandle, ReadOrdinaryFile, WriteAll, PublishByRename e SecureWipe, junto do checkbox de identidade/configurações acima.
    • Aceite: profile_identity_test.cpp e identity_store_test.cpp passam no Windows e no macOS; link duplo e nome longo são recusados nos três caminhos.

Activity

  1. JelielSantos commented on Sep 16, 2026

    @JelielSantos
    Collaborator

    Seleção de dispositivos de áudio no Light: PR #661

    Primeiro item de código da Prioridade 1: "Implementar/expor seleção de entrada e saída de áudio e tratar troca/desconexão de dispositivo".

    O que foi implementado e por quê

    Até aqui o núcleo headless usava sempre o microfone e a saída padrão do sistema. Agora ele permite escolher os dispositivos e continua funcionando quando eles mudam:

    • Listar dispositivos (devices): mostra entradas e saídas com id e name. A lista só é montada quando o comando é chamado, e o módulo de áudio é liberado logo depois, então fora de uma chamada nenhum dispositivo fica aberto. Isso segue a meta do Light de só gastar recurso quando algo está em uso.
    • Escolher (set-input / set-output): recebem o id listado ou null, que segue o padrão do sistema.
      • A escolha fica salva em monky-light-settings.json, dentro do perfil, e não mexe na identidade. Se o arquivo estiver corrompido, o núcleo avisa e usa o padrão; a identidade continua intacta.
    • Troca durante a chamada: reinicia só o lado que mudou (entrada ou saída). Mute e deafen continuam valendo: trocar o microfone com mute ligado não liga a captura.
    • Dispositivo removido:
      • A chamada não cai: passa pro padrão do sistema e emite audio-device-selected com fallback:true.
      • Quando o dispositivo volta, o Light retorna pra ele sozinho.
      • O sistema avisa das mudanças por evento (Windows: IMMNotificationClient; macOS: listeners do Core Audio), sem ficar consultando a lista o tempo todo.
    • Windows: passou a usar o módulo de áudio Core Audio do WebRTC (kWindowsCoreAudio2), que reinicia o fluxo sozinho quando um dispositivo some. O "padrão" é o mesmo dispositivo padrão que o Monky completo (Chromium) usa.

    Principais arquivos: apps/light/src/media/policy_audio_device.hpp, media/audio_devices.hpp, headless_application.cpp, profile_identity.cpp, platform/audio_device_watcher_{windows.cpp,macos.mm} e main.cpp.

    Testes já feitos (Windows x64)

    Suite Resultado
    build:light + test:light:scripts ✅ 12/12
    test:light:native ✅ 27, 1 pulado (só roda em macOS)
    test:light:server + test:light:voice (2 cenários novos) ✅ 1/1 + 12/12
    test:light:hardware: microfone físico com troca real de entrada ✅
    measure:light ✅ ~12 MiB conectado, ~22–23 MiB e ~3,7% de 1 núcleo em chamada, sem regressão

    Como testar (QA)

    1. Na branch feat/light-audio-devices, rode npm ci, npm run build:light e suba um servidor descartável da mesma branch.
    2. Rode o Light com um perfil exclusivo:
      & .\apps\light\build\windows-x64\bin\monky-light.exe --profile "$env:LOCALAPPDATA\Monky-Light-qa" --server ws://127.0.0.1:8080 --nickname QA
    3. Digite {"command":"devices"}. Esperado: seus microfones e saídas aparecem com id e name.
    4. Entre num canal de voz com outra pessoa no Monky completo e troque a saída: {"command":"set-output","deviceId":"<id>"}. Esperado: o áudio passa a sair no dispositivo escolhido, sem cair a chamada.
    5. Escolha um fone USB/Bluetooth como entrada com set-input e tire ele da tomada durante a chamada. Esperado: aparecem audio-devices-changed e audio-device-selected com fallback:true, e a outra pessoa continua te ouvindo pelo microfone padrão.
    6. Reconecte o fone. Esperado: o Light volta pra ele (fallback:false).
    7. Com mute ligado ({"command":"mute","enabled":true}), troque o microfone. Esperado: a captura continua desligada.
    8. Feche e abra de novo com o mesmo --profile. Esperado: a escolha foi mantida (devices mostra inputDeviceId/outputDeviceId).

    Pendências deste item

    • Remover e reconectar um fone de verdade no Windows (os testes automáticos só simulam o aviso do sistema).
    • macOS: o código compila e é testado pelo CI; a troca real de dispositivos e os nomes ainda precisam de um Mac. O macOS não expõe identificador do dispositivo, então lá o nome é usado.
    • Medir o consumo do módulo Core Audio com hardware real (o measure:light usa o dispositivo sintético).

    🤖 Generated with Claude Code

  2. JelielSantos commented on Oct 4, 2026

    @JelielSantos
    Collaborator

    💡 Como foi implementado & Decisões Técnicas

    PR: #750 — primeiro item da Prioridade 2 (só a bandeja; painéis, atalhos e tela de conexão vêm depois).

    • Resumo Didático: com --tray, o Monky Light ganha um ícone na área de notificação com estado, canais de voz, mute/deafen, dispositivos de áudio, reconectar e sair. O ponto delicado é a thread: o NSApplication do macOS só roda na thread principal, e o message pump do Windows só recebe mensagens na thread que criou a janela. Por isso, com --tray, a interface assume a thread principal e o laço do núcleo de voz vai para uma thread trabalhadora. Sem --tray nada muda, então o caminho já qualificado continua idêntico.
    • Uma fonte de verdade só: a bandeja não consulta o núcleo por um caminho próprio. Ela lê os mesmos eventos JSON do stdout e envia os mesmos comandos do stdin. Assim, terminal e menu nunca discordam, e a validação de comandos é uma só.
    • Arquivos & Camadas Alteradas:
      • apps/light/src/ui/tray_model.hpp: estado e menu sem plataforma, coberto por test/tray_model_test.cpp.
      • apps/light/src/ui/tray_windows.cpp / tray_macos.mm: só desenham (Win32 / Cocoa).
      • apps/light/src/headless_application.*, main.cpp: inversão de threads com --tray.
      • apps/light/src/protocol_session.*: novo evento channels-changed (antes, criar/renomear/apagar canal mudava a lista em silêncio).
      • apps/light/QA.md (seção 6), READMEs e release-notes/658-light-tray.json.
    • Boas Práticas Aplicadas: lógica separada do desenho para poder testar sem GUI; ícone desenhado com a paleta do theme.css (identidade separada do Monky completo); marca de seleção nativa do menu no lugar de switch/card, que um menu de sistema não comporta; encerramento sem ícone fantasma nem processo órfão.

    🧪 Como testar (Guia de Validação para QA)

    1. Pré-requisitos: build com npm ci && npm run build && npm run build:light; servidor descartável da mesma versão (MONKY_HOME e --data próprios); um Monky completo conectado ao mesmo servidor.
    2. Cenário Principal:
      • Passo 1: monky-light --tray --profile <pasta descartável> --server ws://<host>:<porta> --nickname QA
      • Passo 2: abrir o menu do ícone, entrar num canal de voz e falar com quem está no Monky completo.
      • Passo 3: mute/deafen e troca de dispositivos pelo menu.
      • Resultado Esperado: áudio nos dois sentidos; ícone verde em chamada e vermelho com mute; troca de dispositivo sem derrubar a chamada.
    3. Casos de Borda & Resiliência: roteiro completo na seção 6 do apps/light/QA.md (19 passos).
      • Criar/renomear/apagar canais (inclusive o atual), parar o servidor, desconectar o dispositivo escolhido, reiniciar o Explorer, sair pelo menu e por quit no stdin.
      • Resultado Esperado: o menu reflete as mudanças sem reiniciar; o erro aparece como balão (Windows); o ícone volta sozinho após reiniciar o Explorer; o processo encerra com stopped, sem processo órfão nem ícone fantasma.

    Validado: Windows (suítes automatizadas + QA manual da seção 6). Pendente: macOS em hardware real; o tray_macos.mm é compilado pela primeira vez no CI deste PR.

  3. JelielSantos commented on Nov 5, 2026

    @JelielSantos
    Collaborator

    Indicador de fala e push-to-talk no núcleo (#885)

    💡 Como foi implementado & Decisões Técnicas

    • Resumo Didático: o núcleo agora diz quem está falando e sabe abrir/fechar a transmissão por push-to-talk. Isso prepara a Prioridade 2 desta issue: a bandeja e os painéis passam a ter o evento speaking para desenhar e o comando ptt para ligar ao atalho global.
      • Por que não publicar "estou falando" no servidor? O cliente completo também não publica: cada receptor deduz a fala do áudio. Seguir o mesmo modelo mantém a compatibilidade e evita uma mensagem de WebSocket a cada palavra.
      • Remotos: todo remetente WebRTC já coloca o nível de volume em cada pacote RTP (extensão RFC 6464). O Light só lê esse campo nos receptores existentes, a cada 100 ms e apenas durante a chamada. Nada é decodificado e nada roda dentro da thread de áudio.
      • Local: um medidor entre o dispositivo e o WebRTC guarda o nível mais alto de cada intervalo numa variável atômica, sem copiar nem alocar memória.
      • Detecção: o limiar é -40 dBov, o mesmo do cliente completo. A fala só termina após 400 ms de silêncio, para não piscar entre palavras. Todo o estado vive na thread de controle da mídia, então não há travas nem listeners para limpar quando alguém sai.
      • Push-to-talk: com a tecla solta, a faixa envia silêncio (set_enabled(false)), mas o microfone continua capturando. Parar o dispositivo a cada toque atrasaria o começo da fala. Mute, deafen e restrições do servidor continuam valendo mesmo com a tecla pressionada.
    • Arquivos & Camadas Alteradas:
      • apps/light/src/media/speech_activity.hpp (novo): cálculo do nível, detector com histerese e medidor de captura.
      • apps/light/src/media/policy_audio_device.hpp: intercala o medidor entre o dispositivo e o WebRTC.
      • apps/light/src/media/voice_engine.*: AudioPolicy::transmit (controle do push-to-talk), amostragem de fala e evento speaking.
      • apps/light/src/headless_application.cpp: comandos set-input-mode e ptt, evento speaking e os campos inputMode/pushToTalkActive no stats.
      • apps/light/src/profile_identity.*: inputMode salvo no perfil. Perfis antigos continuam em voice_activity.
      • READMEs PT/EN: nova seção "Indicador de fala e push-to-talk".
    • Boas Práticas Aplicadas: nenhuma thread ativa fora da chamada; zero alocação no caminho de áudio; tudo com dono único (thread de controle), sem any nem asserção forçada; preferência validada e gravada de forma atômica sob o lock do perfil. Não há mudança de protocolo.

    🧪 Como testar (Guia de Validação para QA)

    1. Pré-requisitos: servidor descartável da mesma branch; npm run build:light; um --profile próprio do Light. Para comparar, use também o Monky completo da mesma versão.
    2. Cenário Principal:
      • Passo 1: inicie monky-light.exe --profile … --server … --nickname Light e entre num canal de voz com alguém usando o app completo.
      • Passo 2: fale. Resultado Esperado: {"event":"speaking","sessionId":<o seu>,"speaking":true} ao falar e false cerca de 0,4 s depois de parar. Quando a outra pessoa fala, o mesmo evento aparece com o sessionId dela.
      • Passo 3: envie {"command":"set-input-mode","mode":"push_to_talk"}. Resultado Esperado: você para de aparecer falando, e a outra pessoa deixa de te ouvir.
      • Passo 4: envie {"command":"ptt","active":true} e fale, depois {"command":"ptt","active":false}. Resultado Esperado: a outra pessoa só te ouve entre os dois comandos, e o início da fala não é cortado.
    3. Casos de Borda & Resiliência:
      • ptt em modo voice_activity → command-error.
      • Com ptt ativo, mute/deafen ou mute do servidor → não transmite, e speaking vai a false na hora.
      • Reabrir o Light com o mesmo perfil → stats mostra inputMode: push_to_talk.
      • Em deafen → ninguém aparece falando.
      • Ruído de fundo alto e constante pode aparecer como fala (o nível local é medido antes da supressão de ruído). Vale registrar se incomodar no uso real.
      • Resultado Esperado: nenhum media-error, e measure:light sem diferença relevante de CPU em chamada.

    Automatizado (Windows x64): test:light:native com 35 aprovados e 4 ignorados por plataforma; test:light:voice com 29 aprovados e 1 ignorado, incluindo o novo cenário P2P/SFU com dois clientes nativos pelo servidor real. Ainda faltam chamada audível com pessoas, macOS real e medição de consumo.

  4. JelielSantos commented on Nov 7, 2026

    @JelielSantos
    Collaborator

    Volume por participante no núcleo (#892)

    💡 Como foi implementado & Decisões Técnicas

    • Resumo Didático: agora dá para ajustar de 0 a 200% o volume com que você ouve cada pessoa da chamada, como o controle deslizante do app completo. A escolha fica salva no perfil e vale também nas próximas chamadas. É mais uma peça que os painéis da Prioridade 2 vão usar.
      • Mesma chave do app completo: o volume é guardado por sessionId, que identifica uma conexão (um dispositivo) de uma pessoa. Quem entra pelo celular e pelo PC pode ter um volume diferente em cada um, como no app completo.
      • Onde o ganho é aplicado: o WebRTC nativo já tem um ganho de recepção por faixa remota (RemoteAudioSource::SetVolume), aplicado ao áudio decodificado antes da mixagem. Usar esse ganho evita colocar processamento próprio no caminho do áudio, e em 100% o custo é zero. O ganho é reaplicado sempre que aparece uma faixa nova daquela pessoa: P2P, consumo SFU, reconexão ou nova chamada.
      • Limite do perfil: o perfil guarda só os volumes diferentes de 100, até 256 sessões, e voltar a 100 apaga a entrada. Assim o arquivo de preferências não cresce sem limite.
    • Arquivos & Camadas Alteradas:
      • apps/light/src/media/voice_engine.*: JoinConfig::volumes e set_volume(), aplicados nas faixas P2P e nos consumidores SFU.
      • apps/light/src/headless_application.cpp: comando set-volume, evento participant-volume e userVolumes no stats.
      • apps/light/src/profile_identity.*: mapa userVolumes validado (0–200, sessão até 256 bytes, no máximo 256 entradas).
      • READMEs PT/EN e release-notes/658-light-participant-volume.json.
    • Boas Práticas Aplicadas: nenhum processamento extra no caminho do áudio; tudo com dono único (a thread de controle da mídia); preferências validadas e gravadas de forma atômica sob o lock do perfil; sem mudança de protocolo.

    🧪 Como testar (Guia de Validação para QA)

    1. Pré-requisitos: servidor descartável da mesma branch; npm run build:light; um --profile próprio do Light. Entre numa chamada com outra pessoa, no Light ou no app completo.
    2. Cenário Principal:
      • Passo 1: copie o sessionId da outra pessoa da lista de participantes (stats ou eventos).
      • Passo 2: {"command":"set-volume","sessionId":"…","volume":0}. Resultado Esperado: você deixa de ouvir essa pessoa, e os outros continuam ouvindo normalmente.
      • Passo 3: volume: 200. Resultado Esperado: a voz dela fica claramente mais alta, sem chiado.
      • Passo 4: saia, feche o Light e entre de novo. Resultado Esperado: o volume escolhido já vale desde o início, e stats.userVolumes mostra o valor.
    3. Casos de Borda & Resiliência:
      • volume 201, -1 ou 1,5 → command-error.
      • volume: 100 → a entrada some de userVolumes.
      • Troque P2P ↔ SFU, ou reconecte, com volume alterado → o volume continua aplicado.
      • Resultado Esperado: nenhum media-error, e o volume nunca afeta o que os outros ouvem.

    Automatizado (Windows x64): test:light:native com 35 aprovados e 4 ignorados por plataforma; test:light:voice com 29 aprovados e 1 ignorado, incluindo o novo cenário P2P/SFU que mede a energia tocada em 0%, 100% e 200%.

  5. JelielSantos commented on Nov 7, 2026

    @JelielSantos
    Collaborator

    Bandeja com apertar para falar, fala e volume (#893)

    O #893 junta o #885 e o #892, ambos fechados e apontando para ele, e liga tudo na bandeja. O funcionamento do núcleo está nos dois comentários anteriores; aqui fica o que muda na interface.

    💡 Como foi implementado & Decisões Técnicas

    • Resumo Didático: dá para usar o Light no dia a dia sem mandar comandos JSON. O menu ganhou Apertar para falar e Na chamada (quem está falando e o volume de cada pessoa), e o ícone fica verde claro enquanto você fala.
      • Por que um hook global? Para funcionar com o jogo em primeiro plano, a tecla precisa ser vista mesmo quando o Light não tem o foco. O RegisterHotKey do Windows só avisa quando a tecla é apertada, nunca quando é solta, e o apertar para falar precisa das duas coisas. Por isso o Light usa hooks de baixo nível (WH_KEYBOARD_LL). No macOS, usa o monitor global do AppKit, e o sistema só entrega teclas de outros aplicativos a quem foi autorizado em Acessibilidade.
      • Custo zero fora de uso: o hook só existe enquanto pode transmitir, ou seja, em chamada e no modo apertar para falar. O hook de mouse, chamado a cada movimento do ponteiro, só é instalado quando a tecla é Mouse4/Mouse5. A tecla nunca é consumida e continua chegando ao jogo.
      • Sem tecla padrão: qualquer escolha automática poderia tomar uma tecla que a pessoa usa em jogo. A tecla é definida com --ptt-key e fica salva no perfil. Caps Lock e Num Lock ficam de fora porque segurá-los muda o estado deles no sistema.
      • Repetição e tecla presa: o teclado repete o aviso de tecla apertada enquanto ela está segurada. O controlador transforma isso num único aperto. Se o hook for removido com a tecla segurada (a chamada caiu, o modo ou a tecla mudou), a soltura é enviada sempre que o núcleo ainda a aceitar, para a transmissão nunca ficar aberta.
      • A bandeja continua só seguindo eventos (feat(light): adicionar interface de bandeja ao núcleo nativo #750): o núcleo passou a publicar participants, input-mode e push-to-talk-key, e inclui o modo e a tecla no authenticated.
    • Arquivos & Camadas Alteradas:
      • apps/light/src/push_to_talk_key.hpp (novo): tabela das teclas, com os códigos do Windows e do macOS.
      • apps/light/src/ui/tray_model.hpp / tray_controller.hpp: estado, quando observar a tecla, deduplicação e comandos. Não dependem de plataforma e estão cobertos por testes.
      • apps/light/src/ui/tray_windows.cpp / tray_macos.mm: menus, ícone e hook/monitor.
      • apps/light/src/headless_application.cpp / profile_identity.*: --ptt-key, set-ptt-key, members e os eventos novos.
      • READMEs, QA.md (§6.22–6.30) e nota de release.
    • Boas Práticas Aplicadas: nada observado fora de uso; hooks removidos no teardown, inclusive no WM_DESTROY; nenhuma consulta paralela ao núcleo; textos PT/EN centralizados; nenhum checkbox nativo isolado (itens de menu nativos, como já documentado para a bandeja).

    🧪 Como testar (Guia de Validação para QA)

    1. Pré-requisitos: servidor descartável da mesma branch; npm run build:light; monky-light.exe --tray --ptt-key F13 --profile … --server … --nickname QA. Se o teclado não tiver F13, use --ptt-key Mouse4 ou RightControl.
    2. Cenário Principal:
      • Passo 1: abra o menu e ligue Apertar para falar. Esperado: o item fica marcado e mostra (tecla: F13).
      • Passo 2: entre num canal com alguém no app completo, deixe outro aplicativo em primeiro plano e segure a tecla. Esperado: a pessoa só te ouve enquanto a tecla está segurada, e o outro aplicativo continua recebendo a tecla.
      • Passo 3: desligue Apertar para falar e fale. Esperado: o ícone fica verde claro enquanto você fala.
      • Passo 4: abra Na chamada enquanto a outra pessoa fala e escolha 0%, depois 200% e depois 100%. Esperado: ela aparece com (falando); o som some, fica mais alto e volta ao normal.
    3. Casos de Borda & Resiliência: seguir a tabela apps/light/QA.md §6.22–6.30. Em resumo:
      • tecla segurada por vários segundos → um único ptt no terminal;
      • desligar o modo com a tecla segurada → sem command-error;
      • macOS sem Acessibilidade → o sistema pede a autorização uma vez e o aviso aparece no menu;
      • reiniciar o Light → modo, tecla e volumes mantidos.

    Automatizado (Windows x64): test:light:native com 35 aprovados e 4 ignorados por plataforma; test:light:voice com 31 aprovados e 1 ignorado. Também rodei um smoke manual do hook real com F13 injetado por keybd_event: um aperto, mesmo com repetição, abre e fecha a transmissão, e o hook some ao sair da chamada. Ainda falta: qualificação em Mac real, chamada audível com pessoas e measure:light.

  6. JelielSantos commented on Nov 7, 2026

    @JelielSantos
    Collaborator

    Abrir com duplo clique, sem linha de comando (#894, empilhado no #893)

    💡 Como foi implementado & Decisões Técnicas

    • Resumo Didático: o Light deixa de exigir terminal. Sem argumentos, abre só com a bandeja. Na primeira vez, a janela "Conectar a um servidor" pede o link de convite, o apelido e, se precisar, a senha; nas próximas aberturas, ele conecta direto ao último servidor. Trocar de servidor… na bandeja reabre a janela.
      • O núcleo não é reescrito: o modo aplicativo só monta os mesmos argumentos (--tray --profile … --server … --nickname …) e chama o mesmo runHeadless qualificado. O que muda é que ninguém lê o console: com console = false, os eventos vão só para a bandeja, e a falta de leitor deixa de encerrar o núcleo, como acontece de propósito na linha de comando.
      • Por que a senha não é salva: o README já determinava que senha nunca vai para o perfil. Por isso um link de convite é reduzido ao endereço ws(s)://: a chave do servidor já fica fixada no primeiro contato, e a senha fica só em memória. Para não virar um beco sem saída, quando o servidor recusa a senha ou o apelido a janela reabre no campo certo com a explicação, em vez de só um alerta na bandeja.
      • Testável sem tela: a decisão entre reusar a conexão e perguntar, a validação e o laço de "tentar de novo" ficam em runStandalone, que não depende de plataforma. Janela, perfil, alerta e núcleo entram como ganchos e são substituídos por dublês no teste.
      • Isolamento de perfil (AGENTS.md): o perfil de aplicativo é próprio. Em desenvolvimento e QA, MONKY_LIGHT_PROFILE aponta para uma pasta descartável, para nunca tocar o perfil de uma instalação.
    • Arquivos & Camadas Alteradas:
      • apps/light/src/standalone.* (novo): fluxo de abertura, validação e mapeamento das recusas.
      • apps/light/src/ui/connect_windows.cpp / connect_macos.mm / connect.hpp (novos): a janela, o aviso de perfil indisponível e o perfil padrão.
      • apps/light/src/main.cpp: entrada sem argumentos, MONKY_LIGHT_PROFILE e console solto no Windows.
      • apps/light/src/headless_application.*: console, password e askAgain.
      • apps/light/src/profile_identity.*: server e nickname salvos.
      • Bandeja: item Trocar de servidor….
      • READMEs, QA.md §6.31–6.39 e nota de release.
    • Boas Práticas Aplicadas: nenhum segredo em disco; o lock exclusivo do perfil impede duas cópias; nenhum caminho paralelo no núcleo; textos PT/EN centralizados; a janela explica o erro e foca o campo a corrigir.

    Limitação conhecida: no Windows o executável continua sendo de console, então o duplo clique mostra um console por um instante antes de soltá-lo. Trocar para o subsistema GUI quebraria os cenários de console já qualificados (fechar o console faz logout, Ctrl+C e outros), por isso ficou para a etapa de distribuição.


    🧪 Como testar (Guia de Validação para QA)

    1. Pré-requisitos: servidor descartável da mesma branch; npm run build:light; $env:MONKY_LIGHT_PROFILE = "C:\Projetos\Monky-qa\light-app" (pasta descartável).
    2. Cenário Principal:
      • Passo 1: no mesmo terminal, abra apps\light\build\windows-x64\bin\monky-light.exe sem argumentos. Esperado: a janela "Conectar a um servidor".
      • Passo 2: cole um convite (ou ws://127.0.0.1:PORTA), escolha um apelido e conecte. Esperado: você entra no servidor, e a bandeja mostra os canais.
      • Passo 3: saia pela bandeja e abra de novo. Esperado: conecta direto, sem janela.
    3. Casos de Borda & Resiliência: seguir apps/light/QA.md §6.34–6.39:
      • endereço inválido;
      • apelido vazio ou longo;
      • senha errada → a janela reabre no campo da senha;
      • apelido em uso;
      • Trocar de servidor…;
      • segunda cópia aberta → aviso de perfil indisponível;
      • cancelar a janela.

    Automatizado (Windows x64): test:light:native com 36 aprovados e 4 ignorados por plataforma, incluindo o novo monky-light-standalone-test; test:light:voice com 31 aprovados e 1 ignorado. Também rodei um smoke manual com o executável e a janela reais, preenchida via Win32: o endereço inválido foi recusado, o usuário entrou, o perfil guardou só servidor e apelido, e a reabertura conectou direto. O CI foi disparado à mão na branch, porque o workflow só roda para PRs contra a main.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions