mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-22 06:28:11 +02:00
Windows : sous-systeme console, pour qu'ajean se comporte en vraie commande
Dans un terminal, cmd rendait la main avant qu'ajean n'ait rien ecrit : l'invite se reaffichait puis la sortie arrivait par-dessus. Windows n'attend jamais la fin d'un programme en sous-systeme graphique. La meme cause empechait les redirections et les tubes, et rendait 'ajean chat' inutilisable (le shell et ajean se disputaient l'entree). Le binaire passe donc en sous-systeme console. La fenetre noire au double-clic, que le sous-systeme graphique evitait, est traitee autrement : quand aucun autre processus ne partage notre console, c'est que Windows l'a creee pour nous, donc que personne ne nous a lances depuis un terminal. On la masque et on la libere. Les raccourcis demandent en plus un demarrage minimise. La relance interne (apres mise a jour) passe desormais 'app' explicitement : la detection ne repond qu'a la question du double-clic, et un processus sans console du tout ne doit pas avoir a y repondre. Verifie : ordre d'affichage correct en session cmd, redirection et tube fonctionnels, 'ajean' sans argument affiche l'aide, et aucune fenetre console visible au lancement facon double-clic.
This commit is contained in:
1 parent
922b7a6862
commit
aa965227fc
4 files changed
+68
-85
No files matched your search
@@ -4,96 +4,67 @@ package ajean
|
||||
|
||||
// sys_console_windows.go — gestion de la console sous Windows.
|
||||
//
|
||||
// Le binaire est compilé en sous-système GUI (`-H=windowsgui`) : au double-clic,
|
||||
// Windows n'alloue AUCUNE console → plus jamais de « fenêtre noire de terminal »
|
||||
// qui s'ouvre et se ferme. En contrepartie, un binaire GUI lancé depuis un
|
||||
// terminal (cmd/PowerShell) n'écrit nulle part par défaut : on se rattache alors
|
||||
// explicitement à la console du parent et on réouvre stdout/stderr/stdin dessus,
|
||||
// pour que l'usage CLI (`ajean web`, `ajean status`, …) reste lisible.
|
||||
// Le binaire est compilé en sous-système CONSOLE. C'est ce qui fait que `cmd`
|
||||
// ATTEND la fin du programme : avec un binaire graphique, il rendait la main
|
||||
// aussitôt et réaffichait son invite AVANT la sortie d'AJEAN, ce qui donnait
|
||||
//
|
||||
// C:\Users\Nathan>ajean
|
||||
//
|
||||
// C:\Users\Nathan>ajean 0.8.0 — manager llama.cpp + UI web
|
||||
//
|
||||
// La même cause cassait la redirection et les tubes, et rendait `ajean chat`
|
||||
// inutilisable depuis un terminal (le shell et AJEAN se disputaient l'entrée).
|
||||
//
|
||||
// EN CONTREPARTIE, Windows alloue une console à tout lancement sans terminal —
|
||||
// un double-clic. On la referme alors immédiatement : voir setupConsole. Les
|
||||
// raccourcis créés par l'installation demandent en plus un démarrage minimisé,
|
||||
// si bien que cette console n'est jamais peinte à l'écran.
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// setupConsole rattache le process à la console de son parent si elle existe.
|
||||
// Retourne true si on dispose d'une console (→ usage CLI), false sinon (double-
|
||||
// clic sans console → expérience « application »).
|
||||
// setupConsole décide dans quel monde on tourne et renvoie true en usage CLI.
|
||||
//
|
||||
// Le signal est le nombre de processus rattachés à notre console : si nous
|
||||
// sommes le SEUL, c'est que Windows l'a créée pour nous, donc qu'aucun terminal
|
||||
// ne nous a lancés. On la fait disparaître et on bascule en mode application.
|
||||
// Un shell rattaché (cmd, PowerShell, Windows Terminal) en fait au moins deux.
|
||||
func setupConsole() bool {
|
||||
// Relevé AVANT le rattachement, et c'est tout l'enjeu : AttachConsole
|
||||
// REMPLACE lui-même les handles standard du processus par ceux de la
|
||||
// console. Interrogé après, on ne voit donc plus jamais qu'une console, et
|
||||
// la trace de la redirection posée par le shell est déjà perdue.
|
||||
outRedirected := stdRedirected(stdOutputHandle)
|
||||
errRedirected := stdRedirected(stdErrorHandle)
|
||||
inRedirected := stdRedirected(stdInputHandle)
|
||||
|
||||
const attachParentProcess = ^uintptr(0) // (DWORD)-1 = ATTACH_PARENT_PROCESS
|
||||
k := syscall.NewLazyDLL("kernel32.dll")
|
||||
r, _, _ := k.NewProc("AttachConsole").Call(attachParentProcess)
|
||||
if r == 0 {
|
||||
return false // aucune console parente = lancé par double-clic (mode app)
|
||||
}
|
||||
// Réouvre les flux standard sur la console fraîchement rattachée : sans ça,
|
||||
// os.Stdout/Stderr pointent sur des handles invalides (sous-système GUI).
|
||||
|
||||
// Combien de processus partagent notre console ? Nous seul (1) = personne ne
|
||||
// nous a lancés depuis un terminal, Windows a créé cette console pour nous :
|
||||
// c'est un double-clic. Un shell rattaché en fait au moins deux.
|
||||
//
|
||||
// MAIS seulement ceux que le shell n'a pas déjà branchés ailleurs. Réouvrir
|
||||
// systématiquement sur CONOUT$ écrasait la redirection : « ajean where >
|
||||
// fichier.txt » n'écrivait rien dans le fichier, et « ajean where | findstr »
|
||||
// ne transmettait rien au tube — tout partait sur la console. On ne touche
|
||||
// donc qu'aux flux réellement inutilisables.
|
||||
if !outRedirected {
|
||||
if con, err := os.OpenFile("CONOUT$", os.O_WRONLY, 0); err == nil {
|
||||
os.Stdout = con
|
||||
}
|
||||
}
|
||||
if !errRedirected {
|
||||
if con, err := os.OpenFile("CONOUT$", os.O_WRONLY, 0); err == nil {
|
||||
os.Stderr = con
|
||||
}
|
||||
}
|
||||
if !inRedirected {
|
||||
if cin, err := os.OpenFile("CONIN$", os.O_RDONLY, 0); err == nil {
|
||||
os.Stdin = cin
|
||||
// Le compte vaut 0 quand il n'y a aucune console (parent détaché). On répond
|
||||
// alors « CLI », le cas contraire étant couvert autrement : les relances
|
||||
// internes passent « app » explicitement (voir launch, sys_firstrun_windows.go).
|
||||
// Mieux vaut afficher une aide inutile que démarrer un serveur en silence.
|
||||
var pids [8]uint32
|
||||
n, _, _ := k.NewProc("GetConsoleProcessList").Call(
|
||||
uintptr(unsafe.Pointer(&pids[0])), uintptr(len(pids)))
|
||||
if n == 1 {
|
||||
if hwnd, _, _ := k.NewProc("GetConsoleWindow").Call(); hwnd != 0 {
|
||||
// Masquer AVANT de libérer : sinon la fenêtre reste peinte le temps
|
||||
// que Windows la détruise.
|
||||
const swHide = 0
|
||||
_, _, _ = k.NewProc("ShowWindow").Call(hwnd, swHide)
|
||||
}
|
||||
_, _, _ = k.NewProc("FreeConsole").Call()
|
||||
return false
|
||||
}
|
||||
|
||||
// Lancé depuis un terminal : les flux standard sont déjà ceux du shell,
|
||||
// redirections et tubes compris. Il n'y a rien à rebrancher, seulement à
|
||||
// régler l'encodage et les couleurs.
|
||||
configureConsole()
|
||||
return true
|
||||
}
|
||||
|
||||
// Handles standard, tels que les nomme GetStdHandle.
|
||||
const (
|
||||
stdInputHandle = ^uintptr(9) // -10
|
||||
stdOutputHandle = ^uintptr(10) // -11
|
||||
stdErrorHandle = ^uintptr(11) // -12
|
||||
)
|
||||
|
||||
// stdRedirected dit si un flux standard est DÉJÀ branché sur un fichier ou un
|
||||
// tube par le processus parent. Dans ce cas il ne faut surtout pas le remplacer
|
||||
// par la console : c'est ce qui cassait « > fichier » et « | commande ».
|
||||
//
|
||||
// Un flux de type « caractère » (la console elle-même) ou inutilisable n'est pas
|
||||
// considéré comme redirigé : on le réouvre alors sur CONOUT$/CONIN$, ce dont un
|
||||
// binaire GUI a besoin pour écrire quelque part.
|
||||
func stdRedirected(which uintptr) bool {
|
||||
const (
|
||||
invalidHandle = ^uintptr(0) // INVALID_HANDLE_VALUE
|
||||
fileTypeDisk = 1
|
||||
fileTypePipe = 3
|
||||
)
|
||||
k := syscall.NewLazyDLL("kernel32.dll")
|
||||
h, _, _ := k.NewProc("GetStdHandle").Call(which)
|
||||
if h == 0 || h == invalidHandle {
|
||||
return false
|
||||
}
|
||||
t, _, _ := k.NewProc("GetFileType").Call(h)
|
||||
return t == fileTypeDisk || t == fileTypePipe
|
||||
}
|
||||
|
||||
// configureConsole passe la console en UTF-8 + traitement des séquences ANSI
|
||||
// (couleurs/curseur), comme le faisait l'init() au chargement — mais APRÈS le
|
||||
// rattachement, sinon ça ciblait une console encore inexistante.
|
||||
// (couleurs/curseur).
|
||||
func configureConsole() {
|
||||
const (
|
||||
cpUTF8 = 65001
|
||||
|
||||
Reference in new issue
Block a user