# Annexe : Noyau - app_manager.py

## Rôle et utilité
Le fichier `core/app_manager.py` est le "Chef d'Orchestre" des logiciels de GrimOS. Puisqu'il n'y a pas de gestionnaire de fenêtres Unix classique, c'est ce script qui est responsable de lancer les applications, de les confiner dans de fausses fenêtres, et de les fermer. Il lit le registre (`config/applications.json`) pour savoir quelles applications existent.

## Implémentation technique

*   **Import dynamique** : Plutôt que d'importer toutes les applications au démarrage (ce qui saturerait la RAM), le gestionnaire utilise la fonction Python `importlib.import_module()`. L'application n'est chargée en mémoire que lorsque l'utilisateur clique sur son icône.
*   **Le conteneur `Window`** : Lorsqu'une application est lancée, l'`AppManager` demande d'abord à `window.py` de créer un cadre vide (la fausse fenêtre). Ensuite, il appelle la fonction obligatoire `start(fenetre_interne)` du module de l'application en lui passant ce cadre.
*   **Isolation des plantages** : L'exécution se fait dans un bloc `try/except`. Si le code d'une application (par exemple l'Éditeur) contient une erreur fatale, le crash est intercepté par l'`AppManager`. Au lieu de faire planter tout GrimOS, le système affiche simplement une petite boîte de dialogue d'erreur et détruit la fenêtre fautive.

## Pistes de modification

*   **Mode "Plein écran automatique"** : Si l'on souhaitait créer un système type "Tablette", on pourrait modifier `AppManager` pour qu'il force les fenêtres à prendre la taille maximale de l'écran dès leur instanciation.
*   **Limitation des instances** : Actuellement, on peut ouvrir 10 fois l'Explorateur. Un développeur pourrait ajouter un dictionnaire des processus actifs dans ce fichier pour empêcher l'ouverture multiple d'une même application.


## Code Source

```python
import importlib
import traceback
import json
import os
import tkinter as tk
from tkinter import messagebox
from core.window import Window

class AppManager:
    def __init__(self, desktop):
        self.desktop = desktop
        self.active_windows = []
        self.session_file = os.path.join(os.path.dirname(__file__), '..', 'data', 'session.json')

    def launch_app(self, app_config, geometry=None, is_maximized=False, **kwargs):
        module_name = app_config.get("module")
        app_name = app_config.get("name", "Application")
        
        try:
            module = importlib.import_module(module_name)
            importlib.reload(module)
            
            if geometry:
                win = Window(self.desktop.desktop_frame, desktop=self.desktop, app_config=app_config, 
                             title=app_name, 
                             x=geometry["x"], y=geometry["y"], 
                             width=geometry["width"], height=geometry["height"],
                             is_maximized=is_maximized, filepath=kwargs.get("filepath"))
            else:
                w = app_config.get("width", 400)
                h = app_config.get("height", 300)
                x = app_config.get("x", 50)
                y = app_config.get("y", 50)
                win = Window(self.desktop.desktop_frame, desktop=self.desktop, app_config=app_config, title=app_name, width=w, height=h, x=x, y=y, filepath=kwargs.get("filepath"))
            
            self.active_windows.append(win)
            
            if hasattr(module, 'start'):
                module.start(win.content, app_manager=self, **kwargs)
                win.after(100, win.lift)
            else:
                raise AttributeError(f"Le module {module_name} n'a pas de fonction 'start(window, ...)'.")
                
        except Exception as e:
            if 'win' in locals() and win.winfo_exists():
                win.destroy()
            err_msg = traceback.format_exc()
            self.show_error(app_name, err_msg)

    def show_error(self, app_name, error_traceback):
        messagebox.showerror(f"Erreur application: {app_name}", f"Une erreur est survenue :\n\n{error_traceback}")

    def save_session(self):
        session_data = []
        for win in self.active_windows:
            win.update_saved_geometry()
            session_data.append({
                "app_config": win.app_config,
                "geometry": win.saved_geometry,
                "is_maximized": win.is_maximized
            })
            
        try:
            os.makedirs(os.path.dirname(self.session_file), exist_ok=True)
            with open(self.session_file, 'w', encoding='utf-8') as f:
                json.dump(session_data, f)
        except Exception as e:
            print(f"Erreur sauvegarde session: {e}")

    def restore_session(self):
        if not os.path.exists(self.session_file):
            return
            
        try:
            with open(self.session_file, 'r', encoding='utf-8') as f:
                session_data = json.load(f)
                
            for data in session_data:
                geom = data.get("geometry")
                if geom and (geom.get("width", 0) <= 10 or geom.get("height", 0) <= 10):
                    geom = None
                self.launch_app(data["app_config"], geom, data.get("is_maximized", False))
        except Exception as e:
            print(f"Erreur restauration session: {e}")
```
