Files
Loki/internal/ajean/sys_brand_icon.go
T
nathaninline 110458c133 Renommage AJEAN (1/3) : module, chemins de donnees et auto-MAJ
Socle du renommage jean -> AJEAN, sans aucun impact visible pour le parc
installe. Trois briques :

- module github.com/nathaninline/ajean, cmd/ajean, internal/ajean,
  package ajean. Purement interne.

- AjeanHome() remplace JeanHome(). Les defauts deviennent
  %ProgramData%\ajean et /etc/ajean, avec migration du dossier existant
  au premier lancement. La migration est un os.Rename dans le dossier
  parent de l'ancien chemin : intra-volume, donc atomique et instantane
  meme avec des .gguf de plusieurs dizaines de Go. Si le rename echoue
  (handle ouvert sous Windows, droits, volume en lecture seule) on
  CONTINUE sur l'ancien chemin et on retentera : jamais de copie, jamais
  de suppression, le pire cas est "rien n'a bouge".
  $AJEAN_HOME et $JEAN_HOME restent tous deux honores, ainsi que
  /etc/default/ajean et /etc/default/jean : un chemin impose a la main
  est un ordre, on ne le migre pas.

- l'auto-MAJ accepte desormais ajean-<os>-<arch> ET jean-<os>-<arch>,
  dans cet ordre. Indispensable dans les deux sens : les binaires deja
  installes ne connaissent que jean-*, et ce binaire-ci doit pouvoir
  s'installer depuis une release anterieure au renommage.

Couvert par des tests, dont le repli quand un handle ouvert bloque le
rename sous Windows.
2026-08-04 13:59:25 +02:00

130 lines
4.5 KiB
Go

package ajean
// sys_brand_icon.go — l'icône de la marque AJEAN, rendue à la volée.
//
// RÉPLIQUE EXACTE du favicon de l'UI web : carré à coins arrondis NOIR + « j »
// blanc (rects (6,3) (6,5) (4,7) sur une grille 12x12). Une seule source pour
// tous les usages — zone de notification Windows, barre de menus macOS, icône du
// .exe — pour qu'ils ne puissent plus diverger comme quand le favicon est passé
// au noir en laissant les icônes système en bleu.
//
// Aucun asset binaire à committer : tout est dessiné ici, et l'icône du .exe est
// produite par `go generate ./cmd/jean` (voir tools/gen-icon).
import (
"bytes"
"encoding/binary"
"image"
"image/color"
"image/png"
)
const trayIconSize = 32
var (
brandBlack = color.RGBA{0x00, 0x00, 0x00, 0xff}
brandWhite = color.RGBA{0xff, 0xff, 0xff, 0xff}
brandClear = color.RGBA{0, 0, 0, 0}
)
// glyphRects : les trois rectangles blancs du « j », en unités de la grille 12.
var glyphRects = [][4]float64{{6, 3, 2, 2}, {6, 5, 2, 2}, {4, 7, 2, 2}}
// brandIconImage dessine l'icône à la taille n. bg peint le carré arrondi, fg le
// « j ». Les deux peuvent être transparents : c'est ce qui produit l'icône
// « template » de macOS (voir brandTemplatePNG).
func brandIconImage(n int, bg, fg color.RGBA) *image.RGBA {
const r = 2.0 // rayon des coins, en unités de la grille 12
outside := func(gx, gy float64) bool {
corner := func(cx, cy float64) bool {
dx, dy := gx-cx, gy-cy
return dx*dx+dy*dy > r*r
}
switch {
case gx < r && gy < r:
return corner(r, r)
case gx > 12-r && gy < r:
return corner(12-r, r)
case gx < r && gy > 12-r:
return corner(r, 12-r)
case gx > 12-r && gy > 12-r:
return corner(12-r, 12-r)
}
return false
}
img := image.NewRGBA(image.Rect(0, 0, n, n))
scale := float64(n) / 12
for y := 0; y < n; y++ {
for x := 0; x < n; x++ {
gx := (float64(x) + 0.5) / scale
gy := (float64(y) + 0.5) / scale
if outside(gx, gy) {
img.Set(x, y, brandClear)
continue
}
c := bg
for _, rc := range glyphRects {
if gx >= rc[0] && gx < rc[0]+rc[2] && gy >= rc[1] && gy < rc[1]+rc[3] {
c = fg
}
}
img.Set(x, y, c)
}
}
return img
}
func encodePNG(img *image.RGBA) []byte {
var buf bytes.Buffer
_ = png.Encode(&buf, img)
return buf.Bytes()
}
// BrandIconPNG rend l'icône de marque (noir + « j » blanc) en PNG de n pixels.
// Exporté pour le générateur d'icône du .exe (tools/gen-icon).
func BrandIconPNG(n int) []byte { return encodePNG(brandIconImage(n, brandBlack, brandWhite)) }
// brandTemplatePNG rend la variante « template » attendue par macOS : seule la
// couche alpha compte, le système colore la forme selon le thème de la barre de
// menus. Le « j » est donc DÉCOUPÉ (transparent) dans un carré opaque — sans ça,
// une icône entièrement noire disparaît sur une barre de menus sombre.
func brandTemplatePNG(n int) []byte {
return encodePNG(brandIconImage(n, brandBlack, brandClear))
}
// BrandICO emballe une ou plusieurs tailles PNG dans un conteneur .ico. Windows
// accepte le PNG comme image d'une entrée .ico (alpha conservé pour les coins
// arrondis). Plusieurs tailles = un rendu net partout, de la barre des tâches
// (16 px) à la grande tuile de l'explorateur (256 px).
func BrandICO(sizes ...int) []byte {
if len(sizes) == 0 {
sizes = []int{trayIconSize}
}
imgs := make([][]byte, 0, len(sizes))
for _, n := range sizes {
imgs = append(imgs, BrandIconPNG(n))
}
var ico bytes.Buffer
binary.Write(&ico, binary.LittleEndian, uint16(0)) // réservé
binary.Write(&ico, binary.LittleEndian, uint16(1)) // type = icône
binary.Write(&ico, binary.LittleEndian, uint16(len(imgs))) // nombre d'images
offset := uint32(6 + 16*len(imgs)) // fin du répertoire
for i, p := range imgs {
n := sizes[i]
// 256 px se code 0 dans un .ico (le champ ne fait qu'un octet).
ico.WriteByte(byte(n % 256))
ico.WriteByte(byte(n % 256))
ico.WriteByte(0) // couleurs de la palette (0 = truecolor)
ico.WriteByte(0) // réservé
binary.Write(&ico, binary.LittleEndian, uint16(1)) // plans
binary.Write(&ico, binary.LittleEndian, uint16(32)) // bits/pixel
binary.Write(&ico, binary.LittleEndian, uint32(len(p))) // taille données
binary.Write(&ico, binary.LittleEndian, offset) // offset données
offset += uint32(len(p))
}
for _, p := range imgs {
ico.Write(p)
}
return ico.Bytes()
}