mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-23 06:58:14 +02:00
v0.5.4 : app macOS Jean.app (clic = UI web + icone barre de menus)
- Jean.app publie par la CI (zip par archi) : ouverture depuis le Finder -> mode application, comme le double-clic sur jean.exe sous Windows - icone dans la barre de menus macOS (systray/Cocoa) avec Ouvrir/Quitter, bundle marque LSUIElement (pas d'icone Dock) - builds darwin deplacees sur un runner macOS (CGO requis pour Cocoa) - signature ad-hoc du bundle + icone .icns
This commit is contained in:
1 parent
2f1f40c604
commit
ac171e5461
12 files changed
+261
-75
No files matched your search
@@ -4,6 +4,10 @@
|
|||||||
# ⚠️ NE PAS uploader de binaires à la main en plus : ce workflow écrase les
|
# ⚠️ NE PAS uploader de binaires à la main en plus : ce workflow écrase les
|
||||||
# assets du même nom (softprops remplace), et des sommes qui ne correspondent
|
# assets du même nom (softprops remplace), et des sommes qui ne correspondent
|
||||||
# plus aux binaires font échouer `jean update` (vécu sur v0.4.7).
|
# plus aux binaires font échouer `jean update` (vécu sur v0.4.7).
|
||||||
|
#
|
||||||
|
# Deux jobs de build : macOS a besoin d'un vrai runner Apple (l'icône de barre de
|
||||||
|
# menus passe par Cocoa → CGO, pas cross-compilable depuis Linux) ; Linux et
|
||||||
|
# Windows restent CGO_ENABLED=0 sur ubuntu.
|
||||||
name: release
|
name: release
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -13,17 +17,84 @@ permissions:
|
|||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
macos:
|
||||||
|
runs-on: macos-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
- name: build macOS (CGO, arm64 + amd64)
|
||||||
|
run: |
|
||||||
|
mkdir -p dist
|
||||||
|
# arm64 = natif ; amd64 = cross avec le SDK universel (-arch x86_64).
|
||||||
|
CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 \
|
||||||
|
go build -trimpath -ldflags="-s -w" -o dist/jean-darwin-arm64 ./cmd/jean
|
||||||
|
CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 \
|
||||||
|
CGO_CFLAGS="-arch x86_64" CGO_LDFLAGS="-arch x86_64" \
|
||||||
|
go build -trimpath -ldflags="-s -w" -o dist/jean-darwin-amd64 ./cmd/jean
|
||||||
|
file dist/jean-darwin-*
|
||||||
|
- name: bundles Jean.app
|
||||||
|
run: |
|
||||||
|
# Un binaire nu double-cliqué ouvrirait le Terminal. On publie donc
|
||||||
|
# aussi un vrai bundle Jean.app (zippé pour garder le bit exécutable) :
|
||||||
|
# au clic, le Finder lance Contents/MacOS/jean, qui détecte le bundle
|
||||||
|
# et bascule en mode « application » (UI web + navigateur + icône dans
|
||||||
|
# la barre de menus), comme le double-clic sur jean.exe sous Windows.
|
||||||
|
# LSUIElement : app de barre de menus, aucune icône dans le Dock.
|
||||||
|
mkdir -p /tmp/jean.iconset
|
||||||
|
sips -s format png 'cmd/jean/icon.ico' --out /tmp/icon.png >/dev/null 2>&1 || true
|
||||||
|
for s in 16 32 128 256 512; do
|
||||||
|
sips -z $s $s /tmp/icon.png --out "/tmp/jean.iconset/icon_${s}x${s}.png" >/dev/null 2>&1 || true
|
||||||
|
done
|
||||||
|
iconutil -c icns /tmp/jean.iconset -o /tmp/jean.icns >/dev/null 2>&1 || true
|
||||||
|
v="${GITHUB_REF_NAME#v}"
|
||||||
|
for arch in amd64 arm64; do
|
||||||
|
app="dist/mac-$arch/Jean.app"
|
||||||
|
mkdir -p "$app/Contents/MacOS" "$app/Contents/Resources"
|
||||||
|
cp "dist/jean-darwin-$arch" "$app/Contents/MacOS/jean"
|
||||||
|
chmod +x "$app/Contents/MacOS/jean"
|
||||||
|
cp /tmp/jean.icns "$app/Contents/Resources/jean.icns" 2>/dev/null || true
|
||||||
|
printf '%s\n' \
|
||||||
|
'<?xml version="1.0" encoding="UTF-8"?>' \
|
||||||
|
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">' \
|
||||||
|
'<plist version="1.0"><dict>' \
|
||||||
|
'<key>CFBundleName</key><string>Jean</string>' \
|
||||||
|
'<key>CFBundleDisplayName</key><string>Jean</string>' \
|
||||||
|
'<key>CFBundleExecutable</key><string>jean</string>' \
|
||||||
|
'<key>CFBundleIdentifier</key><string>link.ajean.jean</string>' \
|
||||||
|
'<key>CFBundleIconFile</key><string>jean</string>' \
|
||||||
|
'<key>CFBundlePackageType</key><string>APPL</string>' \
|
||||||
|
"<key>CFBundleShortVersionString</key><string>$v</string>" \
|
||||||
|
"<key>CFBundleVersion</key><string>$v</string>" \
|
||||||
|
'<key>LSMinimumSystemVersion</key><string>11.0</string>' \
|
||||||
|
'<key>LSUIElement</key><true/>' \
|
||||||
|
'<key>NSHighResolutionCapable</key><true/>' \
|
||||||
|
'</dict></plist>' > "$app/Contents/Info.plist"
|
||||||
|
# Signature ad-hoc : sans elle, macOS refuse purement et simplement
|
||||||
|
# de lancer un binaire arm64 non signé (pas juste un avertissement).
|
||||||
|
codesign --force --deep -s - "$app" || true
|
||||||
|
(cd "dist/mac-$arch" && zip -qry "../Jean-macos-$arch.zip" Jean.app)
|
||||||
|
rm -rf "dist/mac-$arch"
|
||||||
|
done
|
||||||
|
ls -l dist
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: macos
|
||||||
|
path: dist/*
|
||||||
|
|
||||||
release:
|
release:
|
||||||
|
needs: macos
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
- name: build 6 cibles
|
- name: build Linux + Windows
|
||||||
run: |
|
run: |
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
for t in darwin/amd64 darwin/arm64 linux/amd64 linux/arm64 windows/amd64 windows/arm64; do
|
for t in linux/amd64 linux/arm64 windows/amd64 windows/arm64; do
|
||||||
os=${t%/*}; arch=${t#*/}; ext=""; ldflags="-s -w"
|
os=${t%/*}; arch=${t#*/}; ext=""; ldflags="-s -w"
|
||||||
# Windows : sous-système GUI → aucune console noire au double-clic
|
# Windows : sous-système GUI → aucune console noire au double-clic
|
||||||
# (l'app se rattache à la console du terminal parent si lancée en CLI).
|
# (l'app se rattache à la console du terminal parent si lancée en CLI).
|
||||||
@@ -31,7 +102,14 @@ jobs:
|
|||||||
GOOS=$os GOARCH=$arch CGO_ENABLED=0 \
|
GOOS=$os GOARCH=$arch CGO_ENABLED=0 \
|
||||||
go build -trimpath -ldflags="$ldflags" -o "dist/jean-$os-$arch$ext" ./cmd/jean
|
go build -trimpath -ldflags="$ldflags" -o "dist/jean-$os-$arch$ext" ./cmd/jean
|
||||||
done
|
done
|
||||||
(cd dist && sha256sum jean-* > SHA256SUMS)
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: macos
|
||||||
|
path: dist
|
||||||
|
- name: SHA256SUMS
|
||||||
|
run: |
|
||||||
|
chmod +x dist/jean-darwin-*
|
||||||
|
(cd dist && sha256sum jean-* Jean-macos-*.zip > SHA256SUMS)
|
||||||
ls -l dist && cat dist/SHA256SUMS
|
ls -l dist && cat dist/SHA256SUMS
|
||||||
- name: publier la release
|
- name: publier la release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ Faire tourner llama.cpp comme un vrai service, ça veut dire d'habitude : trouve
|
|||||||
- **Protection par clé API** (`jean set-api-key`) — auth Bearer pour exposer le serveur publiquement ; la clé est stockée à part pour survivre aux changements de preset
|
- **Protection par clé API** (`jean set-api-key`) — auth Bearer pour exposer le serveur publiquement ; la clé est stockée à part pour survivre aux changements de preset
|
||||||
- **Benchmark** (`jean bench`) — tok/s prefill/decode honnêtes avec un corpus varié
|
- **Benchmark** (`jean bench`) — tok/s prefill/decode honnêtes avec un corpus varié
|
||||||
- **Auto-mise à jour** (`jean update`) — récupère la dernière release GitHub et remplace le binaire en place (`--check` pour seulement vérifier)
|
- **Auto-mise à jour** (`jean update`) — récupère la dernière release GitHub et remplace le binaire en place (`--check` pour seulement vérifier)
|
||||||
- **Binaire statique unique** — compilé avec `CGO_ENABLED=0`, se cross-compile trivialement
|
- **Binaire unique** — statique (`CGO_ENABLED=0`) sur Linux et Windows, se cross-compile trivialement ; la build macOS ajoute juste Cocoa pour l’icône de barre de menus
|
||||||
|
|
||||||
## Démarrage rapide
|
## Démarrage rapide
|
||||||
|
|
||||||
@@ -96,6 +96,14 @@ jean logs # suit %ProgramData%\jean\jean.log
|
|||||||
|
|
||||||
`jean llamacpp install` peut compiler llama.cpp si `git` et `cmake` sont présents ; sinon récupère un binaire `llama-server.exe` pré-compilé et pointe `BIN` dessus avec `jean edit`.
|
`jean llamacpp install` peut compiler llama.cpp si `git` et `cmake` sont présents ; sinon récupère un binaire `llama-server.exe` pré-compilé et pointe `BIN` dessus avec `jean edit`.
|
||||||
|
|
||||||
|
## macOS
|
||||||
|
|
||||||
|
Comme sous Windows, tu peux lancer Jean **d'un simple clic** : la page [Releases](../../releases) publie `Jean-macos-arm64.zip` / `Jean-macos-amd64.zip`, un bundle **`Jean.app`**. Dézippe, glisse-le dans *Applications*, ouvre-le : Jean démarre son UI web sur `http://localhost:8090`, l'ouvre dans le navigateur et pose son **icône dans la barre de menus** (en haut à droite) — pendant exact du system tray Windows, avec « Ouvrir Jean » / « Quitter ». Pas de fenêtre de Terminal, pas d'icône dans le Dock.
|
||||||
|
|
||||||
|
L'app n'est **pas signée** par un certificat Apple (seulement ad-hoc) : au premier lancement, fais **clic droit → Ouvrir** (ou *Réglages Système → Confidentialité et sécurité → Ouvrir quand même*).
|
||||||
|
|
||||||
|
Pour l'usage CLI, prends le binaire nu `jean-darwin-arm64` (ou `-amd64`) et mets-le dans `/usr/local/bin/jean` : hors bundle, il garde son comportement en ligne de commande. Les services utilisent **launchd** (pas systemd).
|
||||||
|
|
||||||
## Commandes
|
## Commandes
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -3,13 +3,13 @@
|
|||||||
"FileVersion": {
|
"FileVersion": {
|
||||||
"Major": 0,
|
"Major": 0,
|
||||||
"Minor": 5,
|
"Minor": 5,
|
||||||
"Patch": 3,
|
"Patch": 4,
|
||||||
"Build": 0
|
"Build": 0
|
||||||
},
|
},
|
||||||
"ProductVersion": {
|
"ProductVersion": {
|
||||||
"Major": 0,
|
"Major": 0,
|
||||||
"Minor": 5,
|
"Minor": 5,
|
||||||
"Patch": 3,
|
"Patch": 4,
|
||||||
"Build": 0
|
"Build": 0
|
||||||
},
|
},
|
||||||
"FileFlagsMask": "3f",
|
"FileFlagsMask": "3f",
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
"LegalCopyright": "Copyright (c) 2026 Jean contributors. MIT License.",
|
"LegalCopyright": "Copyright (c) 2026 Jean contributors. MIT License.",
|
||||||
"OriginalFilename": "jean.exe",
|
"OriginalFilename": "jean.exe",
|
||||||
"ProductName": "Jean",
|
"ProductName": "Jean",
|
||||||
"ProductVersion": "0.5.3",
|
"ProductVersion": "0.5.4",
|
||||||
"Comments": "https://github.com/nathaninline/jean — projet open source (MIT)"
|
"Comments": "https://github.com/nathaninline/jean — projet open source (MIT)"
|
||||||
},
|
},
|
||||||
"VarFileInfo": {
|
"VarFileInfo": {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const Version = "0.5.3"
|
const Version = "0.5.4"
|
||||||
|
|
||||||
// Main est le vrai main() du binaire (cmd/jean ne fait que l'appeler).
|
// Main est le vrai main() du binaire (cmd/jean ne fait que l'appeler).
|
||||||
func Main() {
|
func Main() {
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
//go:build darwin
|
||||||
|
|
||||||
|
package jean
|
||||||
|
|
||||||
|
// sys_console_darwin.go — détection du lancement « par clic » sous macOS.
|
||||||
|
//
|
||||||
|
// Sur macOS il n'y a pas d'équivalent d'AttachConsole : un binaire lancé depuis
|
||||||
|
// le Finder hérite quand même de descripteurs stdout/stderr (vers les logs
|
||||||
|
// système). Le signal fiable, c'est l'emplacement de l'exécutable : la release
|
||||||
|
// publie un bundle Jean.app dont le binaire vit dans
|
||||||
|
// Jean.app/Contents/MacOS/jean. Si on tourne depuis là, c'est un double-clic
|
||||||
|
// dans le Finder → expérience « application » (UI web + navigateur), exactement
|
||||||
|
// comme le double-clic sur jean.exe sous Windows.
|
||||||
|
//
|
||||||
|
// Le même binaire copié dans /usr/local/bin garde évidemment le comportement CLI.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setupConsole() bool {
|
||||||
|
exe, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if p, err := filepath.EvalSymlinks(exe); err == nil {
|
||||||
|
exe = p
|
||||||
|
}
|
||||||
|
// .../Jean.app/Contents/MacOS/jean
|
||||||
|
if strings.HasSuffix(filepath.Dir(exe), "/Contents/MacOS") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
//go:build !windows
|
//go:build !windows && !darwin
|
||||||
|
|
||||||
package jean
|
package jean
|
||||||
|
|
||||||
// setupConsole : hors Windows, le process a toujours une vraie console/tty
|
// setupConsole : sous Linux, le process a toujours une vraie console/tty
|
||||||
// standard (stdout hérité). Rien à faire → on signale simplement « on a une
|
// standard (stdout hérité). Rien à faire → on signale simplement « on a une
|
||||||
// console » pour que le double-clic Windows soit le seul cas « mode application ».
|
// console ». Les cas « lancé par un clic » sont traités par
|
||||||
|
// sys_console_windows.go (double-clic sur jean.exe) et sys_console_darwin.go
|
||||||
|
// (ouverture de Jean.app depuis le Finder).
|
||||||
func setupConsole() bool { return true }
|
func setupConsole() bool { return true }
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
//go:build darwin
|
||||||
|
|
||||||
|
package jean
|
||||||
|
|
||||||
|
// sys_tray_darwin.go — icône de Jean dans la barre de menus macOS (en haut à
|
||||||
|
// droite, à côté de l'horloge), pendant exact du system tray Windows : quand on
|
||||||
|
// ouvre Jean.app, l'UI web s'ouvre dans le navigateur ET l'icône apparaît, avec
|
||||||
|
// le menu « Ouvrir Jean » / « Quitter ».
|
||||||
|
//
|
||||||
|
// Contrairement à Windows, systray passe ici par Cocoa → CGO obligatoire : les
|
||||||
|
// binaires macOS sont donc compilés sur un runner macOS (voir release.yml), et
|
||||||
|
// le bundle est marqué LSUIElement (pas d'icône dans le Dock, uniquement la
|
||||||
|
// barre de menus).
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/getlantern/systray"
|
||||||
|
)
|
||||||
|
|
||||||
|
// runTray fait tourner l'icône de la barre de menus. Bloque jusqu'à « Quitter ».
|
||||||
|
// À appeler depuis la goroutine principale : Cocoa exige le thread main.
|
||||||
|
func runTray(url string) {
|
||||||
|
systray.Run(func() {
|
||||||
|
systray.SetIcon(trayIconPNG())
|
||||||
|
systray.SetTooltip("Jean — votre IA locale")
|
||||||
|
mOpen := systray.AddMenuItem("Ouvrir Jean", "Ouvrir l'interface")
|
||||||
|
systray.AddSeparator()
|
||||||
|
mQuit := systray.AddMenuItem("Quitter", "Arrêter Jean")
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-mOpen.ClickedCh:
|
||||||
|
_ = openBrowser(url)
|
||||||
|
case <-mQuit.ClickedCh:
|
||||||
|
systray.Quit()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}, func() {
|
||||||
|
os.Exit(0) // « Quitter » → arrête tout le process (serveur inclus)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
//go:build windows || darwin
|
||||||
|
|
||||||
|
package jean
|
||||||
|
|
||||||
|
// sys_tray_icon.go — icône de Jean pour la zone de notification (Windows) et la
|
||||||
|
// barre de menus (macOS). RÉPLIQUE EXACTE du favicon de l'UI web : carré bleu
|
||||||
|
// arrondi #1f6feb + « j » blanc (rects (6,3) (6,5) (4,7) sur une grille 12x12).
|
||||||
|
// Générée à la volée — pas d'asset binaire à embarquer.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"image/png"
|
||||||
|
)
|
||||||
|
|
||||||
|
const trayIconSize = 32
|
||||||
|
|
||||||
|
// trayIconPNG rend l'icône en PNG 32x32 avec alpha (coins arrondis).
|
||||||
|
func trayIconPNG() []byte {
|
||||||
|
const n = 32
|
||||||
|
blue := color.RGBA{0x1f, 0x6f, 0xeb, 0xff}
|
||||||
|
white := color.RGBA{0xff, 0xff, 0xff, 0xff}
|
||||||
|
clear := color.RGBA{0, 0, 0, 0}
|
||||||
|
const r = 2.0 // rayon des coins, en unités de la grille 12
|
||||||
|
// rects blancs du favicon (x, y, w, h en unités 12)
|
||||||
|
rects := [][4]float64{{6, 3, 2, 2}, {6, 5, 2, 2}, {4, 7, 2, 2}}
|
||||||
|
// hors du rectangle à coins arrondis (grille 12) → pixel transparent
|
||||||
|
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, clear)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c := blue
|
||||||
|
for _, rc := range rects {
|
||||||
|
if gx >= rc[0] && gx < rc[0]+rc[2] && gy >= rc[1] && gy < rc[1]+rc[3] {
|
||||||
|
c = white
|
||||||
|
}
|
||||||
|
}
|
||||||
|
img.Set(x, y, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var pngBuf bytes.Buffer
|
||||||
|
_ = png.Encode(&pngBuf, img)
|
||||||
|
return pngBuf.Bytes()
|
||||||
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
//go:build !windows
|
//go:build !windows && !darwin
|
||||||
|
|
||||||
package jean
|
package jean
|
||||||
|
|
||||||
// sys_tray_other.go — pas d'icône de zone de notification hors Windows (la demande
|
// sys_tray_other.go — pas d'icône de zone de notification sous Linux (serveur
|
||||||
// vise le tray Windows). On se contente d'ouvrir l'UI dans le navigateur et de
|
// sans session graphique la plupart du temps). On se contente d'ouvrir l'UI dans
|
||||||
// garder le serveur en vie. Aucun import systray ici → les builds Linux/macOS
|
// le navigateur et de garder le serveur en vie. Aucun import systray ici → les
|
||||||
// restent purs (CGO_ENABLED=0).
|
// builds Linux restent purs (CGO_ENABLED=0).
|
||||||
|
|
||||||
func runTray(url string) {
|
func runTray(url string) {
|
||||||
select {} // garde le serveur en vie (Ctrl-C pour quitter) ; le navigateur est déjà ouvert par cmdApp
|
select {} // garde le serveur en vie (Ctrl-C pour quitter) ; le navigateur est déjà ouvert par cmdApp
|
||||||
|
|||||||
@@ -8,15 +8,12 @@ package jean
|
|||||||
// Jean tourne et offre un menu « Ouvrir Jean » / « Quitter ».
|
// Jean tourne et offre un menu « Ouvrir Jean » / « Quitter ».
|
||||||
//
|
//
|
||||||
// getlantern/systray est pur Go sur Windows (Win32 via x/sys, aucun CGO) — le
|
// getlantern/systray est pur Go sur Windows (Win32 via x/sys, aucun CGO) — le
|
||||||
// binaire reste statique. L'import est confiné à ce fichier windows pour que les
|
// binaire reste statique. L'équivalent macOS vit dans sys_tray_darwin.go (Cocoa,
|
||||||
// builds Linux/macOS (CGO_ENABLED=0) ne le touchent jamais.
|
// donc CGO) ; les builds Linux ne touchent jamais systray.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
"image/png"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/getlantern/systray"
|
"github.com/getlantern/systray"
|
||||||
@@ -49,61 +46,12 @@ func runTray(url string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// trayIcon génère l'icône (.ico) de Jean, RÉPLIQUE EXACTE du favicon de l'UI
|
// trayIcon emballe le PNG de trayIconPNG() dans un conteneur .ico (ICONDIR +
|
||||||
// web : carré bleu arrondi #1f6feb + « j » blanc (rects (6,3) (6,5) (4,7) sur
|
// une ICONDIRENTRY) : Windows accepte un PNG (avec alpha pour les coins
|
||||||
// une grille 12x12). Générée à la volée — pas d'asset binaire à embarquer.
|
// arrondis) comme image d'un .ico.
|
||||||
// Windows accepte un PNG (avec alpha pour les coins arrondis) dans le .ico.
|
|
||||||
func trayIcon() []byte {
|
func trayIcon() []byte {
|
||||||
const n = 32
|
p := trayIconPNG()
|
||||||
blue := color.RGBA{0x1f, 0x6f, 0xeb, 0xff}
|
const n = trayIconSize
|
||||||
white := color.RGBA{0xff, 0xff, 0xff, 0xff}
|
|
||||||
clear := color.RGBA{0, 0, 0, 0}
|
|
||||||
const r = 2.0 // rayon des coins, en unités de la grille 12
|
|
||||||
// rects blancs du favicon (x, y, w, h en unités 12)
|
|
||||||
rects := [][4]float64{{6, 3, 2, 2}, {6, 5, 2, 2}, {4, 7, 2, 2}}
|
|
||||||
// hors du rectangle à coins arrondis (grille 12) → pixel transparent
|
|
||||||
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, clear)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
c := blue
|
|
||||||
for _, rc := range rects {
|
|
||||||
if gx >= rc[0] && gx < rc[0]+rc[2] && gy >= rc[1] && gy < rc[1]+rc[3] {
|
|
||||||
c = white
|
|
||||||
}
|
|
||||||
}
|
|
||||||
img.Set(x, y, c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var pngBuf bytes.Buffer
|
|
||||||
_ = png.Encode(&pngBuf, img)
|
|
||||||
p := pngBuf.Bytes()
|
|
||||||
|
|
||||||
// Conteneur ICO : ICONDIR (6) + 1 ICONDIRENTRY (16) + PNG.
|
|
||||||
var ico bytes.Buffer
|
var ico bytes.Buffer
|
||||||
binary.Write(&ico, binary.LittleEndian, uint16(0)) // réservé
|
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(1)) // type = icône
|
||||||
|
|||||||
Reference in new issue
Block a user