feat: add exit confirmation dialog, system tray enhancements, and diagnostic options

This commit is contained in:
Claudecio Martins
2026-10-12 12:32:35 -03:00
parent 16212702cb
commit edccfff7ff
10 changed files with 527 additions and 68 deletions
+65
View File
@@ -0,0 +1,65 @@
# Diagnóstico: Janela Principal no Windows 11 (Bisseção e Monitoramento)
Este documento descreve o procedimento de diagnóstico para isolar a causa de ocultamento nativo da janela principal no Windows 11 ao passar o mouse.
---
## 1. Monitor Nativo Win32/DWM (`scripts/diag-window.ps1`)
O script [scripts/diag-window.ps1](file:///var/www/cybercore/cybercore-signer/scripts/diag-window.ps1) inspeciona a cada 300ms a janela nativa via P/Invoke da Win32 API e DWM:
- `IsWindowVisible`: se a janela está visível perante o subsistema Win32;
- `DWMWA_CLOAKED`: se o gerenciador gráfico do Windows 11 ocultou a composição;
- `IsIconic`: se foi minimizada;
- `GetWindowRect`: coordenadas reais (detecta se foi deslocada para fora da tela).
### Como executar o monitor:
Em uma janela separada do PowerShell:
```powershell
powershell -ExecutionPolicy Bypass -File .\scripts\diag-window.ps1
```
---
## 2. Roteiro de Bisseção (Fase 2)
Execute cada um dos comandos abaixo individualmente (garantindo antes que instâncias anteriores foram encerradas com `Stop-Process -Name java -Force` se necessário):
### Teste A: Desativar Middleware PKCS#11 / SafeSign (`aetpkss1.dll`)
Isola hooks nativos do SafeSign e middlewares A3:
```powershell
java -Dcybercore.diag.no-pkcs11=true -jar .\cybercore-signer.jar --dev
```
*Se a janela NÃO sumir ao passar o mouse, a causa é o hook nativo do middleware PKCS#11.*
---
### Teste B: Plain Stage (Janela livre sem `setResizable(false)` e sem limites min/max)
Isola o bug de composição do DWM / Glass no Windows 11 com janelas de tamanho fixo:
```powershell
java -Dcybercore.diag.plain-stage=true -jar .\cybercore-signer.jar --dev
```
*Se a janela NÃO sumir ao passar o mouse, a causa é a restrição fixa de tamanho no Windows 11.*
---
### Teste C: Desativar Efeitos de Sombra no Mouse (`-fx-effect`)
Isola recálculo de dropshadow e troca de cursor:
```powershell
java -Dcybercore.diag.no-effects=true -jar .\cybercore-signer.jar --dev
```
---
### Teste D: Desativar Monitor Dinâmico de Smart Cards (PC/SC)
Isola threads em segundo plano de `javax.smartcardio`:
```powershell
java -Dcybercore.diag.no-smartcard-watcher=true -jar .\cybercore-signer.jar --dev
```
---
### Teste E: Desativar Windows Certificate Store (Windows-MY)
Isola inicialização de CryptoAPI / SunMSCAPI:
```powershell
java -Dcybercore.diag.no-wincert=true -jar .\cybercore-signer.jar --dev
```
@@ -36,6 +36,17 @@ public class CyberCoreSignerApplication {
} }
public static void main(String[] args) { public static void main(String[] args) {
// Previne conflitos nativos de UI Automation/Acessibilidade do Glass no Windows 11
if (System.getProperty("javafx.accessibility") == null) {
System.setProperty("javafx.accessibility", "false");
}
if (System.getProperty("glass.accessible.force") == null) {
System.setProperty("glass.accessible.force", "false");
}
Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
logger.error("Exceção não tratada na thread '{}': {}", thread.getName(), throwable.getMessage(), throwable);
});
parseArguments(args); parseArguments(args);
printBanner(); printBanner();
@@ -67,6 +78,9 @@ public class CyberCoreSignerApplication {
try { try {
logger.info("Iniciando interface gráfica desktop..."); logger.info("Iniciando interface gráfica desktop...");
CyberCoreDesktopApplication.setDevelopmentMode(developmentMode); CyberCoreDesktopApplication.setDevelopmentMode(developmentMode);
Runtime.getRuntime().addShutdownHook(new Thread(() ->
logger.warn("JVM em encerramento (sinal externo, System.exit ou fim do aplicativo)."),
"cybercore-shutdown-diagnostic"));
Application.launch(CyberCoreDesktopApplication.class, args); Application.launch(CyberCoreDesktopApplication.class, args);
} catch (Throwable t) { } catch (Throwable t) {
logger.warn("Não foi possível inicializar ambiente gráfico ({}). Continuando em modo console.", t.getMessage()); logger.warn("Não foi possível inicializar ambiente gráfico ({}). Continuando em modo console.", t.getMessage());
@@ -33,12 +33,27 @@ public class CertificateManager {
/** /**
* Cria e inicializa uma instância padrão do CertificateManager com os provedores nativos da plataforma. * Cria e inicializa uma instância padrão do CertificateManager com os provedores nativos da plataforma.
* Suporta chaves de diagnóstico para isolamento de problemas de middleware:
* -Dcybercore.diag.no-wincert=true (não inicializa Windows Certificate Store)
* -Dcybercore.diag.no-pkcs11=true (não inicializa PKCS#11 / DLLs nativas)
*/ */
public static CertificateManager createDefault() { public static CertificateManager createDefault() {
CertificateManager manager = new CertificateManager(); CertificateManager manager = new CertificateManager();
manager.registerProvider(new WindowsCertificateStoreProvider());
if (Boolean.getBoolean("cybercore.diag.no-wincert")) {
logger.warn("Diagnóstico: Provedor Windows Certificate Store DESATIVADO via -Dcybercore.diag.no-wincert=true");
} else {
manager.registerProvider(new WindowsCertificateStoreProvider());
}
manager.registerProvider(new Pkcs12CertificateProvider()); manager.registerProvider(new Pkcs12CertificateProvider());
manager.registerProvider(new Pkcs11CertificateProvider());
if (Boolean.getBoolean("cybercore.diag.no-pkcs11")) {
logger.warn("Diagnóstico: Provedor PKCS#11 DESATIVADO via -Dcybercore.diag.no-pkcs11=true");
} else {
manager.registerProvider(new Pkcs11CertificateProvider());
}
return manager; return manager;
} }
@@ -63,4 +63,34 @@ class CertificateManagerTest {
assertThat(byCnpj).isPresent(); assertThat(byCnpj).isPresent();
assertThat(byCnpj.get().id()).isEqualTo("cert-1"); assertThat(byCnpj.get().id()).isEqualTo("cert-1");
} }
@Test
@DisplayName("Deve desativar provider PKCS#11 quando chave de diagnóstico estiver ativa")
void shouldRespectDiagnosticsNoPkcs11() {
System.setProperty("cybercore.diag.no-pkcs11", "true");
try {
CertificateManager manager = CertificateManager.createDefault();
assertThat(manager.getProviders())
.extracting(CertificateProvider::getProviderId)
.doesNotContain("provider-pkcs11")
.contains("provider-a1");
} finally {
System.clearProperty("cybercore.diag.no-pkcs11");
}
}
@Test
@DisplayName("Deve desativar provider Windows Certificate Store quando chave de diagnóstico estiver ativa")
void shouldRespectDiagnosticsNoWinCert() {
System.setProperty("cybercore.diag.no-wincert", "true");
try {
CertificateManager manager = CertificateManager.createDefault();
assertThat(manager.getProviders())
.extracting(CertificateProvider::getProviderId)
.doesNotContain("provider-win-store")
.contains("provider-a1");
} finally {
System.clearProperty("cybercore.diag.no-wincert");
}
}
} }
@@ -117,10 +117,14 @@ public class CyberCoreDesktopApplication extends Application {
mainAppView.setOnValidateTimestamp(() -> new SignatureValidationDialog(primaryStage, true).show()); mainAppView.setOnValidateTimestamp(() -> new SignatureValidationDialog(primaryStage, true).show());
mainAppView.setOnManageSeals(() -> new SealManagementDialog(primaryStage).show()); mainAppView.setOnManageSeals(() -> new SealManagementDialog(primaryStage).show());
mainAppView.setOnExit(() -> { mainAppView.setOnExit(() -> {
logger.info("Encerrando CyberCore Signer pela interface gráfica."); logger.info("Ação 'Sair' acionada na interface gráfica.");
primaryStage.close(); if (systemTrayManager != null) {
Platform.exit(); systemTrayManager.handleWindowCloseRequest();
System.exit(0); } else {
primaryStage.close();
Platform.exit();
System.exit(0);
}
}); });
// Ações do Painel Lateral // Ações do Painel Lateral
@@ -130,9 +134,21 @@ public class CyberCoreDesktopApplication extends Application {
mainAppView.setOnCertificates(() -> new CertificatesManagerDialog(primaryStage, certificateManager).show()); mainAppView.setOnCertificates(() -> new CertificatesManagerDialog(primaryStage, certificateManager).show());
mainAppView.setOnAbout(() -> openAboutDialog(primaryStage)); mainAppView.setOnAbout(() -> openAboutDialog(primaryStage));
// Exibe imediatamente a janela principal em primeiro plano para o usuário // Observabilidade do ciclo de vida da janela
mainAppView.show(); primaryStage.showingProperty().addListener((obs, oldVal, newVal) ->
logger.info("Janela principal JavaFX exibida."); logger.info("Visibilidade da janela principal JavaFX alterada: visível={}", newVal));
primaryStage.iconifiedProperty().addListener((obs, oldVal, newVal) ->
logger.info("Estado de minimização da janela JavaFX alterado: minimizada={}", newVal));
primaryStage.focusedProperty().addListener((obs, oldVal, newVal) ->
logger.info("Foco da janela principal alterado: focada={}", newVal));
primaryStage.xProperty().addListener((obs, oldVal, newVal) ->
logger.debug("Janela principal movida: x={}, y={}", newVal, primaryStage.getY()));
primaryStage.yProperty().addListener((obs, oldVal, newVal) ->
logger.debug("Janela principal movida: x={}, y={}", primaryStage.getX(), newVal));
primaryStage.widthProperty().addListener((obs, oldVal, newVal) ->
logger.debug("Janela principal redimensionada: largura={}, altura={}", newVal, primaryStage.getHeight()));
primaryStage.heightProperty().addListener((obs, oldVal, newVal) ->
logger.debug("Janela principal redimensionada: largura={}, altura={}", primaryStage.getWidth(), newVal));
// Inicializa o servidor local do protocolo CCSP/1 para atender requisições do navegador // Inicializa o servidor local do protocolo CCSP/1 para atender requisições do navegador
initCcspServer(primaryStage); initCcspServer(primaryStage);
@@ -140,12 +156,22 @@ public class CyberCoreDesktopApplication extends Application {
// Monitoramento em tempo real de conexão/remoção de Smart Cards e Tokens USB // Monitoramento em tempo real de conexão/remoção de Smart Cards e Tokens USB
initSmartCardWatcher(); initSmartCardWatcher();
// Configura bandeja do sistema (System Tray) para operação contínua em segundo plano
this.systemTrayManager = new SystemTrayManager(primaryStage, this::refreshCertificates); this.systemTrayManager = new SystemTrayManager(primaryStage, this::refreshCertificates);
this.systemTrayManager.setup();
// Carrega os certificados instalados na máquina em segundo plano para não congelar a interface // Carrega os certificados instalados na máquina do usuário de forma estável
CompletableFuture.runAsync(this::refreshCertificates); refreshCertificates();
// Exibe a janela principal em primeiro plano após os serviços estarem prontos
mainAppView.show();
logger.info("Janela principal JavaFX exibida.");
// Configura a bandeja (System Tray) somente depois que a janela JavaFX está visível,
// para não inicializar o toolkit AWT concorrentemente com a criação da janela.
if (Boolean.getBoolean("cybercore.no.tray")) {
logger.warn("System Tray desativado via -Dcybercore.no.tray=true (diagnóstico).");
} else {
Platform.runLater(() -> this.systemTrayManager.setup());
}
} }
private void refreshCertificates() { private void refreshCertificates() {
@@ -473,12 +499,23 @@ public class CyberCoreDesktopApplication extends Application {
this.ccspServer = new CcspServer(this.certificateManager, this.signingService, promptListener); this.ccspServer = new CcspServer(this.certificateManager, this.signingService, promptListener);
this.ccspServer.start(); this.ccspServer.start();
logger.info("Servidor local CCSP/1 inicializado na porta {}", this.ccspServer.getPort()); logger.info("Servidor local CCSP/1 inicializado na porta {}", this.ccspServer.getPort());
} catch (java.net.BindException be) {
logger.warn("Porta 21325 já está ocupada por outra instância do CyberCore Signer em execução. O agente continuará ativo na interface gráfica.");
} catch (Exception e) { } catch (Exception e) {
logger.error("Falha ao inicializar servidor local CCSP/1: {}", e.getMessage(), e); if (e.getCause() instanceof java.net.BindException || (e.getMessage() != null && e.getMessage().contains("Address already in use"))) {
logger.warn("Porta 21325 já está ocupada por outra instância do CyberCore Signer em execução. O agente continuará ativo na interface gráfica.");
} else {
logger.error("Falha ao inicializar servidor local CCSP/1: {}", e.getMessage(), e);
}
} }
} }
private void initSmartCardWatcher() { private void initSmartCardWatcher() {
if (Boolean.getBoolean("cybercore.diag.no-smartcard-watcher")) {
logger.warn("Diagnóstico: Monitor dinâmico de smart cards DESATIVADO via -Dcybercore.diag.no-smartcard-watcher=true");
return;
}
try { try {
this.smartCardWatcher = new SmartCardWatcher(); this.smartCardWatcher = new SmartCardWatcher();
this.smartCardWatcher.addListener(new SmartCardWatcher.CardEventListener() { this.smartCardWatcher.addListener(new SmartCardWatcher.CardEventListener() {
@@ -1,5 +1,6 @@
package br.com.cybercore.signer.ui.tray; package br.com.cybercore.signer.ui.tray;
import br.com.cybercore.signer.ui.view.ExitConfirmationDialog;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.stage.Stage; import javafx.stage.Stage;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -15,8 +16,6 @@ import java.awt.PopupMenu;
import java.awt.RenderingHints; import java.awt.RenderingHints;
import java.awt.SystemTray; import java.awt.SystemTray;
import java.awt.TrayIcon; import java.awt.TrayIcon;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.util.Objects; import java.util.Objects;
@@ -42,12 +41,19 @@ public class SystemTrayManager {
/** /**
* Inicializa o ícone e menu de contexto na bandeja do sistema, caso haja suporte. * Inicializa o ícone e menu de contexto na bandeja do sistema, caso haja suporte.
*/ */
public synchronized boolean setup() { public boolean setup() {
if (!SystemTray.isSupported()) { if (!SystemTray.isSupported()) {
logger.info("SystemTray não é suportado pelo ambiente atual ou sistema operacional."); logger.info("SystemTray não é suportado pelo ambiente atual ou sistema operacional.");
return false; return false;
} }
// O TrayIcon AWT é criado na thread de eventos do AWT, nunca na thread do JavaFX,
// para evitar conflito entre os dois loops de mensagens nativos no Windows.
java.awt.EventQueue.invokeLater(this::setupOnAwtThread);
return true;
}
private synchronized void setupOnAwtThread() {
try { try {
SystemTray tray = SystemTray.getSystemTray(); SystemTray tray = SystemTray.getSystemTray();
Image iconImage = createTrayIconImage(); Image iconImage = createTrayIconImage();
@@ -78,69 +84,78 @@ public class SystemTrayManager {
this.trayIcon = new TrayIcon(iconImage, "CyberCore Signer (CCSP/1 Ativo)", popup); this.trayIcon = new TrayIcon(iconImage, "CyberCore Signer (CCSP/1 Ativo)", popup);
this.trayIcon.setImageAutoSize(true); this.trayIcon.setImageAutoSize(true);
// Clique com o botão esquerdo (single-click) restaura a janela imediatamente // Duplo clique (ActionListener) e clique simples (MouseListener) restauram a janela
this.trayIcon.addMouseListener(new MouseAdapter() { this.trayIcon.addActionListener(e -> {
logger.info("Ícone da bandeja acionado (duplo clique).");
Platform.runLater(this::showStage);
});
this.trayIcon.addMouseListener(new java.awt.event.MouseAdapter() {
@Override @Override
public void mouseClicked(MouseEvent e) { public void mouseClicked(java.awt.event.MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1) { if (e.getButton() == java.awt.event.MouseEvent.BUTTON1) {
logger.info("Ícone da bandeja acionado (clique simples).");
Platform.runLater(SystemTrayManager.this::showStage); Platform.runLater(SystemTrayManager.this::showStage);
} }
} }
}); });
// Duplo clique ou acionamento nativo padrão também restaura a janela
this.trayIcon.addActionListener(e -> Platform.runLater(this::showStage));
tray.add(this.trayIcon); tray.add(this.trayIcon);
this.initialized = true; this.initialized = true;
// Intercepta fechamento da janela para ocultar em vez de encerrar // Intercepta fechamento da janela (botão X) para confirmar ação com o usuário
Platform.runLater(() -> { Platform.runLater(() -> {
stage.setOnCloseRequest(event -> { stage.setOnCloseRequest(event -> {
event.consume(); event.consume();
stage.hide(); handleWindowCloseRequest();
showMessage("CyberCore Signer", "O agente continua ativo na bandeja atendendo aplicações web.", TrayIcon.MessageType.INFO);
}); });
}); });
logger.info("System Tray configurado com sucesso."); logger.info("System Tray configurado com sucesso.");
return true;
} catch (AWTException e) { } catch (AWTException e) {
logger.warn("Falha ao registrar ícone no System Tray: {}", e.getMessage()); logger.warn("Falha ao registrar ícone no System Tray: {}", e.getMessage());
return false;
} catch (Throwable t) { } catch (Throwable t) {
logger.warn("Erro ao inicializar System Tray: {}", t.getMessage()); logger.warn("Erro ao inicializar System Tray: {}", t.getMessage(), t);
return false; }
}
/**
* Exibe o diálogo de confirmação ao clicar no 'X' da janela principal,
* permitindo minimizar para a bandeja, encerrar definitivamente ou cancelar.
*/
public void handleWindowCloseRequest() {
try {
ExitConfirmationDialog dialog = new ExitConfirmationDialog(stage);
ExitConfirmationDialog.ExitChoice choice = dialog.showAndGetChoice();
switch (choice) {
case MINIMIZE_TO_TRAY -> {
logger.info("Usuário optou por minimizar o CyberCore Signer para a bandeja.");
stage.hide();
showMessage("CyberCore Signer", "O agente continua ativo na bandeja atendendo aplicações web.", TrayIcon.MessageType.INFO);
}
case EXIT_COMPLETELY -> {
logger.info("Usuário optou por encerrar definitivamente o CyberCore Signer.");
shutdown();
}
case CANCEL -> {
logger.debug("Fechamento da janela cancelado pelo usuário.");
}
}
} catch (Throwable t) {
logger.warn("Erro ao exibir diálogo de fechamento, minimizando para bandeja por segurança: {}", t.getMessage());
stage.hide();
} }
} }
public void showStage() { public void showStage() {
if (stage == null) { if (stage != null) {
return; logger.info("Restaurando janela principal (visível={}, minimizada={}).",
} stage.isShowing(), stage.isIconified());
stage.show();
try { stage.setIconified(false);
if (!stage.isShowing()) {
stage.show();
}
if (stage.isIconified()) {
stage.setIconified(false);
}
stage.toFront(); stage.toFront();
stage.requestFocus(); stage.requestFocus();
// No Windows (Win32 / DWM), chamadas de toFront() partindo da bandeja do sistema
// podem ser suprimidas por políticas de foco do SO (Foreground Lock Timeout).
// A elevação momentânea via setAlwaysOnTop força o gerenciador de janelas a trazer
// o assinador diretamente para o primeiro plano.
stage.setAlwaysOnTop(true);
stage.setAlwaysOnTop(false);
} catch (Throwable t) {
logger.debug("Falha secundária ao restaurar stage da bandeja: {}", t.getMessage());
} }
} }
@@ -0,0 +1,111 @@
package br.com.cybercore.signer.ui.view;
import br.com.cybercore.signer.ui.components.SvgIcons;
import br.com.cybercore.signer.ui.theme.ThemeManager;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Modality;
import javafx.stage.Stage;
/**
* Diálogo modal de confirmação ao fechar a janela principal do CyberCore Signer.
* Permite ao usuário escolher entre manter o agente ativo na bandeja do sistema
* ou encerrar completamente a aplicação.
*/
public final class ExitConfirmationDialog {
public enum ExitChoice {
MINIMIZE_TO_TRAY,
EXIT_COMPLETELY,
CANCEL
}
private final Stage stage;
private ExitChoice chosenAction = ExitChoice.CANCEL;
public ExitConfirmationDialog(Stage owner) {
this.stage = new Stage();
this.stage.initModality(Modality.APPLICATION_MODAL);
if (owner != null) {
this.stage.initOwner(owner);
}
this.stage.setTitle("CyberCore Signer — Encerrar ou Manter na Bandeja");
this.stage.setResizable(false);
this.stage.setMinWidth(520);
this.stage.setMinHeight(190);
initLayout();
}
private void initLayout() {
VBox root = new VBox(22);
root.setPadding(new Insets(24));
root.getStyleClass().add("dialog-container");
// Cabeçalho com ícone e texto explicativo
HBox header = new HBox(16);
header.setAlignment(Pos.CENTER_LEFT);
VBox titleBox = new VBox(4);
Label title = new Label("Como você deseja proceder?");
title.setStyle("-fx-font-size: 15px; -fx-font-weight: bold;");
Label subtitle = new Label(
"Você pode manter o CyberCore Signer ativo na bandeja do sistema\n" +
"para atender assinaturas de aplicações web, ou encerrá-lo por completo."
);
subtitle.setStyle("-fx-text-fill: -fx-text-secondary; -fx-font-size: 12px;");
titleBox.getChildren().addAll(title, subtitle);
header.getChildren().addAll(SvgIcons.cyberShieldLogo(38), titleBox);
// Barra de Ações com os botões
HBox actionsRow = new HBox(10);
actionsRow.setAlignment(Pos.CENTER_RIGHT);
Button btnCancel = new Button("Cancelar");
btnCancel.getStyleClass().addAll("btn-cancel", "btn-dialog");
btnCancel.setOnAction(e -> {
chosenAction = ExitChoice.CANCEL;
stage.close();
});
HBox spacer = new HBox();
HBox.setHgrow(spacer, Priority.ALWAYS);
Button btnExit = new Button("Encerrar Definitivamente");
btnExit.getStyleClass().addAll("btn-danger", "btn-dialog");
btnExit.setOnAction(e -> {
chosenAction = ExitChoice.EXIT_COMPLETELY;
stage.close();
});
Button btnMinimize = new Button("Minimizar para a Bandeja");
btnMinimize.getStyleClass().addAll("btn-primary", "btn-dialog");
btnMinimize.setDefaultButton(true);
btnMinimize.setOnAction(e -> {
chosenAction = ExitChoice.MINIMIZE_TO_TRAY;
stage.close();
});
actionsRow.getChildren().addAll(btnCancel, spacer, btnExit, btnMinimize);
root.getChildren().addAll(header, actionsRow);
Scene scene = new Scene(root);
ThemeManager.applyTheme(scene);
stage.setScene(scene);
stage.setOnCloseRequest(e -> chosenAction = ExitChoice.CANCEL);
}
public ExitChoice showAndGetChoice() {
stage.showAndWait();
return chosenAction;
}
}
@@ -16,6 +16,8 @@ import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import javafx.stage.Stage; import javafx.stage.Stage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Objects; import java.util.Objects;
@@ -27,6 +29,8 @@ import java.util.Objects;
@SuppressWarnings("this-escape") @SuppressWarnings("this-escape")
public final class MainAppView { public final class MainAppView {
private static final Logger logger = LoggerFactory.getLogger(MainAppView.class);
private final Stage stage; private final Stage stage;
private final VBox root = new VBox(16); private final VBox root = new VBox(16);
private final Button themeToggleBtn = new Button(); private final Button themeToggleBtn = new Button();
@@ -136,6 +140,14 @@ public final class MainAppView {
getClass().getResource(CyberCoreUiConstants.MAIN_CSS_PATH) getClass().getResource(CyberCoreUiConstants.MAIN_CSS_PATH)
).toExternalForm()); ).toExternalForm());
if (Boolean.getBoolean("cybercore.diag.no-effects")) {
logger.warn("Diagnóstico: Efeitos/sombras visuais DESATIVADOS via -Dcybercore.diag.no-effects=true");
var noEffectsUrl = getClass().getResource("/styles/no-effects.css");
if (noEffectsUrl != null) {
scene.getStylesheets().add(noEffectsUrl.toExternalForm());
}
}
// Configuração e inicialização do Tema // Configuração e inicialização do Tema
ThemeManager.applyTheme(scene); ThemeManager.applyTheme(scene);
updateThemeToggleButton(); updateThemeToggleButton();
@@ -144,11 +156,17 @@ public final class MainAppView {
stage.setTitle(CyberCoreUiConstants.APP_NAME); stage.setTitle(CyberCoreUiConstants.APP_NAME);
stage.setScene(scene); stage.setScene(scene);
stage.setResizable(false);
stage.setMinWidth(780); if (Boolean.getBoolean("cybercore.diag.plain-stage")) {
stage.setMinHeight(480); logger.warn("Diagnóstico: Plain Stage ATIVADO via -Dcybercore.diag.plain-stage=true (janela redimensionável, sem restrições min/max)");
stage.setMaxWidth(820); stage.setResizable(true);
stage.setMaxHeight(520); } else {
stage.setResizable(false);
stage.setMinWidth(780);
stage.setMinHeight(480);
stage.setMaxWidth(820);
stage.setMaxHeight(520);
}
} }
private HBox createHeaderBar() { private HBox createHeaderBar() {
@@ -297,17 +315,8 @@ public final class MainAppView {
public void show() { public void show() {
stage.centerOnScreen(); stage.centerOnScreen();
if (stage.isIconified()) {
stage.setIconified(false);
}
stage.show(); stage.show();
stage.toFront(); stage.toFront();
stage.requestFocus();
try {
stage.setAlwaysOnTop(true);
stage.setAlwaysOnTop(false);
} catch (Throwable ignored) {
}
} }
public void hide() { public void hide() {
@@ -0,0 +1,21 @@
/* CyberCore Signer - Modo Diagnóstico: Remoção de sombras/efeitos visuais dinâmicos */
.cyber-action-card,
.serpro-action-btn,
.cyber-side-card,
.serpro-side-btn,
.cyber-header-bar,
.theme-toggle-btn,
.cyber-status-badge {
-fx-effect: null !important;
}
.theme-light .cyber-action-card:hover,
.theme-light .serpro-action-btn:hover,
.theme-dark .cyber-action-card:hover,
.theme-dark .serpro-action-btn:hover,
.cyber-side-card:hover,
.serpro-side-btn:hover,
.theme-toggle-btn:hover {
-fx-effect: null !important;
}
+142
View File
@@ -0,0 +1,142 @@
# ==============================================================================
# CyberCore Signer - Diagnóstico e Monitor de Janela Win32 / DWM
# Suporta matar instâncias anteriores órfãs e monitorar a janela real.
# Uso simples:
# powershell -ExecutionPolicy Bypass -File .\scripts\diag-window.ps1
# Ou iniciando o app automaticamente junto com o monitor:
# powershell -ExecutionPolicy Bypass -File .\scripts\diag-window.ps1 -AutoLaunch
# ==============================================================================
param (
[switch]$AutoLaunch,
[string]$ExtraArgs = "--dev"
)
Write-Host "============================================================" -ForegroundColor Cyan
Write-Host " CyberCore Signer - Monitor de Janela Win32 / DWM" -ForegroundColor Cyan
Write-Host "============================================================" -ForegroundColor Cyan
# 1. Encerra processos java zumbis anteriores que possam estar travando a porta 21325 ou a bandeja
$oldProcs = Get-Process -Name java -ErrorAction SilentlyContinue
if ($oldProcs) {
Write-Host "[INFO] Encerrando $($oldProcs.Count) processo(s) Java em segundo plano para limpar o ambiente..." -ForegroundColor Yellow
$oldProcs | Stop-Process -Force -ErrorAction SilentlyContinue
Start-Sleep -Milliseconds 800
}
$signature = @"
using System;
using System.Runtime.InteropServices;
public class Win32WindowWatcher {
[StructLayout(LayoutKind.Sequential)]
public struct RECT {
public int Left;
public int Top;
public int Right;
public int Bottom;
}
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool IsWindow(IntPtr hWnd);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool IsIconic(IntPtr hWnd);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
[DllImport("dwmapi.dll")]
public static extern int DwmGetWindowAttribute(IntPtr hwnd, int dwAttribute, out int pvAttribute, int cbAttribute);
public const int DWMWA_CLOAKED = 14;
}
"@
Add-Type -TypeDefinition $signature -PassThru | Out-Null
if ($AutoLaunch) {
Write-Host "[LAUNCH] Iniciando cybercore-signer.jar com argumentos: $ExtraArgs" -ForegroundColor Green
$jarPath = ".\cybercore-signer.jar"
if (-not (Test-Path $jarPath)) {
$jarPath = ".\modules\cybercore-app\target\cybercore-signer.jar"
}
Start-Process -FilePath "java" -ArgumentList "-jar", $jarPath, $ExtraArgs -PassThru | Out-Null
}
Write-Host "Aguardando janela principal abrir (verificando processos e Win32)..." -ForegroundColor Yellow
$lastState = ""
$found = $false
$monitoredHwnd = [IntPtr]::Zero
while ($true) {
# Procura via FindWindow pelo título ou pelo MainWindowHandle do processo Java
$hWnd = [Win32WindowWatcher]::FindWindow($null, "CyberCore Signer")
if ($hWnd -eq [IntPtr]::Zero) {
$javaProcs = Get-Process -Name java -ErrorAction SilentlyContinue
foreach ($p in $javaProcs) {
if ($p.MainWindowHandle -ne [IntPtr]::Zero) {
$hWnd = $p.MainWindowHandle
break
}
}
}
if ($hWnd -ne [IntPtr]::Zero -and [Win32WindowWatcher]::IsWindow($hWnd)) {
if (-not $found -or $hWnd -ne $monitoredHwnd) {
$found = $true
$monitoredHwnd = $hWnd
Write-Host "[OK] JANELA DETECTADA! HWND: $hWnd. Monitorando em tempo real a cada 200ms..." -ForegroundColor Green
Write-Host ">>> Passe o mouse sobre a janela agora para observar o que o Windows faz. <<<" -ForegroundColor Magenta
}
$visible = [Win32WindowWatcher]::IsWindowVisible($hWnd)
$iconic = [Win32WindowWatcher]::IsIconic($hWnd)
$foreground = ([Win32WindowWatcher]::GetForegroundWindow() -eq $hWnd)
$rect = New-Object Win32WindowWatcher+RECT
[Win32WindowWatcher]::GetWindowRect($hWnd, [ref]$rect) | Out-Null
$w = $rect.Right - $rect.Left
$h = $rect.Bottom - $rect.Top
$cloakedVal = 0
[Win32WindowWatcher]::DwmGetWindowAttribute($hWnd, [Win32WindowWatcher]::DWMWA_CLOAKED, [ref]$cloakedVal, 4) | Out-Null
$cloaked = ($cloakedVal -ne 0)
$currentState = "Visible=$visible | Cloaked(DWM)=$cloaked | Minimized=$iconic | Focused=$foreground | Rect=($($rect.Left),$($rect.Top)) ${w}x${h}"
if ($currentState -ne $lastState) {
$timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss.fff")
if (-not $visible -or $cloaked -or $w -le 0 -or $h -le 0) {
Write-Host "[$timestamp] [ALERTA-JANELA-SUMIU] $currentState" -ForegroundColor Red
} else {
Write-Host "[$timestamp] [STATUS] $currentState" -ForegroundColor Gray
}
$lastState = $currentState
}
} else {
if ($found) {
$timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss.fff")
Write-Host "[$timestamp] [DESTRUÍDA/FECHADA] O handle da janela não existe mais no Windows!" -ForegroundColor Red
$found = $false
$lastState = ""
$monitoredHwnd = [IntPtr]::Zero
}
}
Start-Sleep -Milliseconds 200
}