feat: add exit confirmation dialog, system tray enhancements, and diagnostic options
This commit is contained in:
@@ -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
|
||||
```
|
||||
+14
@@ -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());
|
||||
|
||||
+17
-2
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+30
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+49
-12
@@ -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() {
|
||||
|
||||
+55
-40
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+111
@@ -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;
|
||||
}
|
||||
}
|
||||
+23
-14
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user