From edccfff7ff44a91234fc5f452546e40b173863db Mon Sep 17 00:00:00 2001 From: Claudecio Martins Date: Mon, 12 Oct 2026 12:32:35 -0300 Subject: [PATCH] feat: add exit confirmation dialog, system tray enhancements, and diagnostic options --- docs/DIAGNOSTICO_JANELA.md | 65 ++++++++ .../app/CyberCoreSignerApplication.java | 14 ++ .../core/provider/CertificateManager.java | 19 ++- .../core/provider/CertificateManagerTest.java | 30 ++++ .../ui/CyberCoreDesktopApplication.java | 61 ++++++-- .../signer/ui/tray/SystemTrayManager.java | 95 +++++++----- .../ui/view/ExitConfirmationDialog.java | 111 ++++++++++++++ .../cybercore/signer/ui/view/MainAppView.java | 37 +++-- .../src/main/resources/styles/no-effects.css | 21 +++ scripts/diag-window.ps1 | 142 ++++++++++++++++++ 10 files changed, 527 insertions(+), 68 deletions(-) create mode 100644 docs/DIAGNOSTICO_JANELA.md create mode 100644 modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/ExitConfirmationDialog.java create mode 100644 modules/cybercore-ui/src/main/resources/styles/no-effects.css create mode 100644 scripts/diag-window.ps1 diff --git a/docs/DIAGNOSTICO_JANELA.md b/docs/DIAGNOSTICO_JANELA.md new file mode 100644 index 0000000..521163e --- /dev/null +++ b/docs/DIAGNOSTICO_JANELA.md @@ -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 +``` diff --git a/modules/cybercore-app/src/main/java/br/com/cybercore/signer/app/CyberCoreSignerApplication.java b/modules/cybercore-app/src/main/java/br/com/cybercore/signer/app/CyberCoreSignerApplication.java index 3b0831a..90546ae 100644 --- a/modules/cybercore-app/src/main/java/br/com/cybercore/signer/app/CyberCoreSignerApplication.java +++ b/modules/cybercore-app/src/main/java/br/com/cybercore/signer/app/CyberCoreSignerApplication.java @@ -36,6 +36,17 @@ public class CyberCoreSignerApplication { } 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); printBanner(); @@ -67,6 +78,9 @@ public class CyberCoreSignerApplication { try { logger.info("Iniciando interface gráfica desktop..."); 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); } catch (Throwable t) { logger.warn("Não foi possível inicializar ambiente gráfico ({}). Continuando em modo console.", t.getMessage()); diff --git a/modules/cybercore-core/src/main/java/br/com/cybercore/signer/core/provider/CertificateManager.java b/modules/cybercore-core/src/main/java/br/com/cybercore/signer/core/provider/CertificateManager.java index 707fad0..0ca13a4 100644 --- a/modules/cybercore-core/src/main/java/br/com/cybercore/signer/core/provider/CertificateManager.java +++ b/modules/cybercore-core/src/main/java/br/com/cybercore/signer/core/provider/CertificateManager.java @@ -33,12 +33,27 @@ public class CertificateManager { /** * 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() { 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 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; } diff --git a/modules/cybercore-core/src/test/java/br/com/cybercore/signer/core/provider/CertificateManagerTest.java b/modules/cybercore-core/src/test/java/br/com/cybercore/signer/core/provider/CertificateManagerTest.java index f537f1d..b732443 100644 --- a/modules/cybercore-core/src/test/java/br/com/cybercore/signer/core/provider/CertificateManagerTest.java +++ b/modules/cybercore-core/src/test/java/br/com/cybercore/signer/core/provider/CertificateManagerTest.java @@ -63,4 +63,34 @@ class CertificateManagerTest { assertThat(byCnpj).isPresent(); 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"); + } + } } diff --git a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/CyberCoreDesktopApplication.java b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/CyberCoreDesktopApplication.java index 397a28e..769e0fb 100644 --- a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/CyberCoreDesktopApplication.java +++ b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/CyberCoreDesktopApplication.java @@ -117,10 +117,14 @@ public class CyberCoreDesktopApplication extends Application { mainAppView.setOnValidateTimestamp(() -> new SignatureValidationDialog(primaryStage, true).show()); mainAppView.setOnManageSeals(() -> new SealManagementDialog(primaryStage).show()); mainAppView.setOnExit(() -> { - logger.info("Encerrando CyberCore Signer pela interface gráfica."); - primaryStage.close(); - Platform.exit(); - System.exit(0); + logger.info("Ação 'Sair' acionada na interface gráfica."); + if (systemTrayManager != null) { + systemTrayManager.handleWindowCloseRequest(); + } else { + primaryStage.close(); + Platform.exit(); + System.exit(0); + } }); // Ações do Painel Lateral @@ -130,9 +134,21 @@ public class CyberCoreDesktopApplication extends Application { mainAppView.setOnCertificates(() -> new CertificatesManagerDialog(primaryStage, certificateManager).show()); mainAppView.setOnAbout(() -> openAboutDialog(primaryStage)); - // Exibe imediatamente a janela principal em primeiro plano para o usuário - mainAppView.show(); - logger.info("Janela principal JavaFX exibida."); + // Observabilidade do ciclo de vida da janela + primaryStage.showingProperty().addListener((obs, oldVal, newVal) -> + 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 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 initSmartCardWatcher(); - // Configura bandeja do sistema (System Tray) para operação contínua em segundo plano 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 - CompletableFuture.runAsync(this::refreshCertificates); + // Carrega os certificados instalados na máquina do usuário de forma estável + 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() { @@ -473,12 +499,23 @@ public class CyberCoreDesktopApplication extends Application { this.ccspServer = new CcspServer(this.certificateManager, this.signingService, promptListener); this.ccspServer.start(); 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) { - 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() { + 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 { this.smartCardWatcher = new SmartCardWatcher(); this.smartCardWatcher.addListener(new SmartCardWatcher.CardEventListener() { diff --git a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/tray/SystemTrayManager.java b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/tray/SystemTrayManager.java index fc4e557..80f90b3 100644 --- a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/tray/SystemTrayManager.java +++ b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/tray/SystemTrayManager.java @@ -1,5 +1,6 @@ package br.com.cybercore.signer.ui.tray; +import br.com.cybercore.signer.ui.view.ExitConfirmationDialog; import javafx.application.Platform; import javafx.stage.Stage; import org.slf4j.Logger; @@ -15,8 +16,6 @@ import java.awt.PopupMenu; import java.awt.RenderingHints; import java.awt.SystemTray; import java.awt.TrayIcon; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; 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. */ - public synchronized boolean setup() { + public boolean setup() { if (!SystemTray.isSupported()) { logger.info("SystemTray não é suportado pelo ambiente atual ou sistema operacional."); 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 { SystemTray tray = SystemTray.getSystemTray(); Image iconImage = createTrayIconImage(); @@ -78,69 +84,78 @@ public class SystemTrayManager { this.trayIcon = new TrayIcon(iconImage, "CyberCore Signer (CCSP/1 Ativo)", popup); this.trayIcon.setImageAutoSize(true); - // Clique com o botão esquerdo (single-click) restaura a janela imediatamente - this.trayIcon.addMouseListener(new MouseAdapter() { + // Duplo clique (ActionListener) e clique simples (MouseListener) restauram a janela + 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 - public void mouseClicked(MouseEvent e) { - if (e.getButton() == MouseEvent.BUTTON1) { + public void mouseClicked(java.awt.event.MouseEvent e) { + if (e.getButton() == java.awt.event.MouseEvent.BUTTON1) { + logger.info("Ícone da bandeja acionado (clique simples)."); 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); 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(() -> { stage.setOnCloseRequest(event -> { event.consume(); - stage.hide(); - showMessage("CyberCore Signer", "O agente continua ativo na bandeja atendendo aplicações web.", TrayIcon.MessageType.INFO); + handleWindowCloseRequest(); }); }); logger.info("System Tray configurado com sucesso."); - return true; } catch (AWTException e) { logger.warn("Falha ao registrar ícone no System Tray: {}", e.getMessage()); - return false; } catch (Throwable t) { - logger.warn("Erro ao inicializar System Tray: {}", t.getMessage()); - return false; + logger.warn("Erro ao inicializar System Tray: {}", t.getMessage(), t); + } + } + + /** + * 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() { - if (stage == null) { - return; - } - - try { - if (!stage.isShowing()) { - stage.show(); - } - - if (stage.isIconified()) { - stage.setIconified(false); - } - + if (stage != null) { + logger.info("Restaurando janela principal (visível={}, minimizada={}).", + stage.isShowing(), stage.isIconified()); + stage.show(); + stage.setIconified(false); stage.toFront(); 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()); } } diff --git a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/ExitConfirmationDialog.java b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/ExitConfirmationDialog.java new file mode 100644 index 0000000..0af15f8 --- /dev/null +++ b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/ExitConfirmationDialog.java @@ -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; + } +} diff --git a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/MainAppView.java b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/MainAppView.java index 4b15621..e3b7339 100644 --- a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/MainAppView.java +++ b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/MainAppView.java @@ -16,6 +16,8 @@ import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import javafx.stage.Stage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.Objects; @@ -27,6 +29,8 @@ import java.util.Objects; @SuppressWarnings("this-escape") public final class MainAppView { + private static final Logger logger = LoggerFactory.getLogger(MainAppView.class); + private final Stage stage; private final VBox root = new VBox(16); private final Button themeToggleBtn = new Button(); @@ -136,6 +140,14 @@ public final class MainAppView { getClass().getResource(CyberCoreUiConstants.MAIN_CSS_PATH) ).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 ThemeManager.applyTheme(scene); updateThemeToggleButton(); @@ -144,11 +156,17 @@ public final class MainAppView { stage.setTitle(CyberCoreUiConstants.APP_NAME); stage.setScene(scene); - stage.setResizable(false); - stage.setMinWidth(780); - stage.setMinHeight(480); - stage.setMaxWidth(820); - stage.setMaxHeight(520); + + if (Boolean.getBoolean("cybercore.diag.plain-stage")) { + logger.warn("Diagnóstico: Plain Stage ATIVADO via -Dcybercore.diag.plain-stage=true (janela redimensionável, sem restrições min/max)"); + stage.setResizable(true); + } else { + stage.setResizable(false); + stage.setMinWidth(780); + stage.setMinHeight(480); + stage.setMaxWidth(820); + stage.setMaxHeight(520); + } } private HBox createHeaderBar() { @@ -297,17 +315,8 @@ public final class MainAppView { public void show() { stage.centerOnScreen(); - if (stage.isIconified()) { - stage.setIconified(false); - } stage.show(); stage.toFront(); - stage.requestFocus(); - try { - stage.setAlwaysOnTop(true); - stage.setAlwaysOnTop(false); - } catch (Throwable ignored) { - } } public void hide() { diff --git a/modules/cybercore-ui/src/main/resources/styles/no-effects.css b/modules/cybercore-ui/src/main/resources/styles/no-effects.css new file mode 100644 index 0000000..b34747d --- /dev/null +++ b/modules/cybercore-ui/src/main/resources/styles/no-effects.css @@ -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; +} diff --git a/scripts/diag-window.ps1 b/scripts/diag-window.ps1 new file mode 100644 index 0000000..a72bb54 --- /dev/null +++ b/scripts/diag-window.ps1 @@ -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 +}