Files
Loki/internal/ajean/sys_console_windows.go
T
nathaninline aa965227fc 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.
2026-08-07 21:59:42 +02:00

87 lines
3.4 KiB
Go

//go:build windows
package ajean
// sys_console_windows.go — gestion de la console sous Windows.
//
// 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 (
"syscall"
"unsafe"
)
// 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 {
k := syscall.NewLazyDLL("kernel32.dll")
// 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.
//
// 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
}
// configureConsole passe la console en UTF-8 + traitement des séquences ANSI
// (couleurs/curseur).
func configureConsole() {
const (
cpUTF8 = 65001
enableVirtualTerminalProcessing = 0x0004
stdOutputHandle = ^uintptr(10) // -11
)
k := syscall.NewLazyDLL("kernel32.dll")
_, _, _ = k.NewProc("SetConsoleOutputCP").Call(uintptr(cpUTF8))
_, _, _ = k.NewProc("SetConsoleCP").Call(uintptr(cpUTF8))
h, _, _ := k.NewProc("GetStdHandle").Call(stdOutputHandle)
var mode uint32
if r, _, _ := k.NewProc("GetConsoleMode").Call(h, uintptr(unsafe.Pointer(&mode))); r != 0 {
_, _, _ = k.NewProc("SetConsoleMode").Call(h, uintptr(mode|enableVirtualTerminalProcessing))
}
}
// appWarning : rien à signaler sous Windows (pas d'App Translocation). Voir
// sys_console_darwin.go.
func appWarning() string { return "" }