Ce projet est un monorepo Cargo workspace contenant deux binaires Windows :
| Binaire | Rôle |
|---|---|
launcher.exe |
Orchestrateur de démarrage : lance les bons programmes selon le mode (jeu / bureau) |
cec-daemon.exe |
Daemon autonome : veille/réveil de la TV HDMI CEC sur extinction écran et arrêt PC |
Cargo.toml ← [workspace] members = ["launcher", "cec-daemon"]
config.toml ← configuration unique partagée
.github/workflows/release.yml
.githooks/pre-commit
launcher/
Cargo.toml
src/
main.rs ← orchestrateur : charge la config, dispatche vers le mode actif
config.rs ← structs de configuration (une sous-struct par module)
modes/
mod.rs
game.rs ← mode jeu : CEC TV → display → sound → … → afterburner → steam → daemon
desktop.rs ← mode bureau : no-op (extensible)
modules/
mod.rs
cec.rs ← allume TV + source HDMI + lance cec-daemon (Pulse-Eight)
steam.rs ← lance Steam
afterburner.rs ← lance MSI Afterburner
...
cec-daemon/
Cargo.toml
src/main.rs ← daemon Windows : écoute extinctions écran + arrêt PC → CEC standby/wake
cec-daemon.exe --path <cec-client.exe> :
- Spawn
cec-client.exe -savec stdin pipe (connexion CEC ouverte, réponse <100ms) SetConsoleCtrlHandler:CTRL_SHUTDOWN_EVENT→standby 0- Fenêtre cachée +
RegisterPowerSettingNotification(GUID_CONSOLE_DISPLAY_STATE):- Écran OFF (valeur 0) →
standby 0← couvre S3 + Modern Standby S0 + inactivité - Écran ON (valeur 1) →
on 0+as← allume TV + bascule source HDMI au réveil
- Écran OFF (valeur 0) →
Pourquoi GUID_CONSOLE_DISPLAY_STATE et non PBT_APMSUSPEND :
Modern Standby (S0) est le mode veille par défaut depuis Windows 10 — le CPU reste actif
à très basse consommation (comme un smartphone). PBT_APMSUSPEND n'est pas déclenché en S0.
GUID_CONSOLE_DISPLAY_STATE se déclenche pour tous les types de veille car l'écran s'éteint
toujours, quelle que soit la méthode.
Pourquoi stdin-pipe et non libcec-sys :
libcec-sys utilise des .lib MSVC incompatibles avec la toolchain MinGW-w64 (cross-compilation WSL).
Écrit une ou plusieurs valeurs dans le registre Windows. Retourne bool directement depuis set_value().is_ok(). Pas de vérification de chemin, pas de PID.
Exemples : gamemode.rs, notifications.rs, hags.rs
pub fn enable(cfg: &FooConfig) -> bool {
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
if let Ok(key) = hkcu.open_subkey_with_flags(r"SOFTWARE\...", KEY_SET_VALUE) {
return key.set_value("SomeKey", &1u32).is_ok();
}
false
}Lance un processus qui reste actif (Steam, Afterburner, TimerResolution…).
- Vérifier
Path::new(path).exists()avant de spawner — couvre 90% des échecs réels - Logger le PID en cas de succès (
log::info!), l'erreur OS en cas d'échec (log::warn!) - Retourner
bool
pub fn launch(cfg: &FooConfig) -> bool {
let path = ...; // extraire depuis cfg, retourner false si absent/vide
if !std::path::Path::new(path).exists() {
log::warn!("foo: path not found: {}", path);
return false;
}
match Command::new(path).spawn() {
Ok(child) => { log::info!("foo spawned pid={}", child.id()); true }
Err(e) => { log::warn!("foo spawn error: {}", e); false }
}
}Exécute une commande système qui se termine rapidement (net, powercfg, sc, taskkill…).
- Utiliser
.status()(bloquant ~100ms max) pour obtenir le vrai code de sortie - Logger le code de sortie si échec (
log::warn!)
pub fn apply(...) -> bool {
match Command::new("powercfg").args([...]).status() {
Ok(s) if s.success() => true,
Ok(s) => { log::warn!("foo: exit={:?}", s.code()); false }
Err(e) => { log::warn!("foo: error: {}", e); false }
}
}- main.rs ne fait qu'une chose : lire la config et appeler le bon mode.
- Chaque mode orchestre un scénario (appelle des modules dans l'ordre voulu).
- Chaque module fait une seule chose (lancer un programme, configurer un outil, etc.).
- Chaque module a sa propre section dans
config.tomlet sa propre struct dansconfig.rs.
- Créer
launcher/src/modules/foo.rsavec une fonctionpub fn launch(cfg: &FooConfig) - Ajouter
pub mod foo;danslauncher/src/modules/mod.rs - Ajouter
FooConfigdanslauncher/src/config.rset un champpub foo: FooConfigdansConfig - Ajouter la section
[foo]dansconfig.toml - Appeler
modules::foo::launch(&config.foo)depuis le(s) mode(s) concerné(s)
- Créer
launcher/src/modes/bar.rsavec une fonctionpub fn run(config: &Config) - Ajouter
pub mod bar;danslauncher/src/modes/mod.rs - Ajouter un variant au dispatch dans
launcher/src/main.rs - Documenter le mode dans
config.tomletREADME.md
Règle absolue : chaque fonctionnalité, correction de bug ou refactoring fait l'objet d'un commit + push dédié. Ne jamais grouper plusieurs changements non liés dans un seul commit.
git add <fichiers modifiés>
git commit -m "feat: ..." # ou fix: / refactor: / docs: / chore:
git pushLe script .githooks/pre-commit lance cargo build --release avant chaque commit et annule le commit si le build échoue. Fonctionne depuis Windows (Git Bash) et depuis WSL directement.
Activer après un clone frais (une seule fois) :
git config core.hooksPath .githookscd /mnt/d/developpement.code/launcher
source ~/.cargo/env
cargo build --release
# binaires :
# target/x86_64-pc-windows-gnu/release/launcher.exe
# target/x86_64-pc-windows-gnu/release/cec-daemon.exeLe workflow .github/workflows/release.yml se déclenche sur un tag v*.*.* et produit automatiquement un zip launcher-vX.Y.Z.zip publié en release GitHub avec un changelog généré depuis les commits conventionnels.
Le projet utilise git-cliff pour générer automatiquement le changelog à partir des préfixes de commit.
cliff.toml— configuration des groupes et du formatCHANGELOG.md— mis à jour à chaque release parrelease.sh(si git-cliff est installé)- GitHub Release body — généré par
orhun/git-cliff-actiondans le CI (toujours à jour)
Installer git-cliff (une seule fois, optionnel en local) :
cargo install git-cliffLes préfixes impactent directement le changelog généré :
| Préfixe | Section dans le changelog |
|---|---|
feat: |
Nouveautés |
fix: |
Corrections |
refactor: |
Refactoring |
perf: |
Performance |
docs: |
Documentation |
style: |
Style |
test: |
Tests |
chore: |
Maintenance |
chore: bump version |
(ignoré dans le changelog) |
Règle absolue : toujours commiter les modifications fonctionnelles AVANT de lancer le script de release. L'historique doit toujours avoir un commit de modification suivi d'un commit chore: bump version.
# 1. Commiter les modifications avec le bon préfixe
git add <fichiers modifiés>
git commit -m "fix: ..." # ou feat: / refactor: / docs: / chore: / etc.
git push
# 2. Lancer le script de release
bash scripts/release.sh 0.3.5
# → met à jour CHANGELOG.md, bump version, commit, tag, push
# → GitHub Actions publie automatiquement (~2-3 min)Le script release.sh abortera si des changements non commités sont détectés.
ubuntu-latest+mingw-w64→ cross-compilelauncher.exe+cec-daemon.exeorhun/git-cliff-action→ génère les notes de release depuis les commits conventionnelszip launcher-vX.Y.Z.zip→ package binaires +config.toml+ scripts.ps1+.batsoftprops/action-gh-release→ publie la release avec le changelog formaté