Annexe : Application - Imprimante

Rôle et utilité

apps/imprimante/app.py est une interface minimaliste pour interagir avec le gestionnaire d'impression standard de Linux (CUPS - Common UNIX Printing System). Elle permet de lister les imprimantes disponibles et d'envoyer rapidement une page de test pour vérifier la connexion matérielle.

Implémentation technique

Pistes de modification

Code Source

import tkinter as tk
from tkinter import messagebox, simpledialog
import subprocess
import os

def start(window, app_manager=None, **kwargs):
    frame = tk.Frame(window, bg="white")
    frame.pack(fill="both", expand=True)

    icon_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "icons")
    window.icons = getattr(window, "icons", {})
    for iname in ['btn_search', 'btn_print', 'btn_refresh']:
        if iname not in window.icons:
            ipath = os.path.join(icon_dir, iname + ".png")
            if os.path.exists(ipath):
                window.icons[iname] = tk.PhotoImage(file=ipath)

    header = tk.Label(frame, text="Gestionnaire d'Impression (CUPS)", font=("Arial", 12, "bold"), bg="white", fg="blue")
    header.pack(pady=10)

    list_frame = tk.Frame(frame, bg="white")
    list_frame.pack(fill="both", expand=True, padx=10, pady=5)

    listbox = tk.Listbox(list_frame, font=("Arial", 10), height=8)
    listbox.pack(side="left", fill="both", expand=True)

    scrollbar = tk.Scrollbar(list_frame, orient="vertical", command=listbox.yview)
    scrollbar.pack(side="right", fill="y")
    listbox.config(yscrollcommand=scrollbar.set)

    # Actions Frame
    btn_frame = tk.Frame(frame, bg="white")
    btn_frame.pack(fill="x", padx=10, pady=10)

    def refresh_printers():
        listbox.delete(0, tk.END)
        try:
            # Récupérer l'imprimante par défaut
            res_def = subprocess.run(['lpstat', '-d'], capture_output=True, text=True)
            default_printer = ""
            if res_def.returncode == 0 and "destination système par défaut :" in res_def.stdout or "system default destination:" in res_def.stdout:
                default_printer = res_def.stdout.split(":")[1].strip()

            res = subprocess.run(['lpstat', '-p'], capture_output=True, text=True)
            if res.returncode == 0 and res.stdout.strip():
                for line in res.stdout.split('\n'):
                    if line.startswith("printer"):
                        parts = line.split()
                        if len(parts) >= 2:
                            pname = parts[1]
                            state = " ".join(parts[2:])
                            display_text = f"{pname} - {state}"
                            if pname == default_printer:
                                display_text = f"⭐ [DÉFAUT] {display_text}"
                            listbox.insert(tk.END, display_text)
            else:
                listbox.insert(tk.END, "Aucune imprimante trouvée ou CUPS non installé.")
        except FileNotFoundError:
            listbox.insert(tk.END, "Erreur: 'lpstat' introuvable. Avez-vous installé 'cups' ?")

    def set_default():
        pwd = app_manager.desktop.settings.get("sudo_pwd") if app_manager else None
        if not pwd:
            messagebox.showerror("Erreur", "Veuillez configurer votre mot de passe système dans les Paramètres.")
            return

        selection = listbox.curselection()
        if not selection:
            messagebox.showwarning("Attention", "Veuillez sélectionner une imprimante.")
            return

        item = listbox.get(selection[0])
        printer_name = item.replace("⭐ [DÉFAUT] ", "").split()[0]

        if printer_name == "Aucune" or printer_name == "Erreur:": return

        try:
            cmd = f"lpadmin -d {printer_name}"
            res = subprocess.run(['sudo', '-S', 'bash', '-c', cmd], input=pwd+'\n', text=True, capture_output=True)
            if res.returncode == 0:
                messagebox.showinfo("Succès", f"{printer_name} est maintenant l'imprimante par défaut !")
                refresh_printers()
            else:
                messagebox.showerror("Erreur", f"Impossible de définir l'imprimante par défaut :\n{res.stderr}")
        except Exception as e:
            messagebox.showerror("Exception", str(e))

    def print_test():
        selection = listbox.curselection()
        if not selection:
            messagebox.showwarning("Attention", "Veuillez sélectionner une imprimante.")
            return

        item = listbox.get(selection[0])
        printer_name = item.replace("⭐ [DÉFAUT] ", "").split()[0]

        if printer_name == "Aucune" or printer_name == "Erreur:":
            return

        try:
            # lp -d printer_name /usr/share/cups/data/testprint
            res = subprocess.run(['lp', '-d', printer_name, '/usr/share/cups/data/testprint'], capture_output=True, text=True)
            if res.returncode == 0:
                messagebox.showinfo("Succès", f"Page de test envoyée à {printer_name}.")
            else:
                messagebox.showerror("Erreur", f"Échec de l'impression:\n{res.stderr}")
        except FileNotFoundError:
            messagebox.showerror("Erreur", "La commande 'lp' est introuvable.")

    def add_network_printer():
        pwd = app_manager.desktop.settings.get("sudo_pwd") if app_manager else None
        if not pwd:
            messagebox.showerror("Erreur", "Veuillez configurer votre mot de passe système (Sudo) dans les Paramètres.")
            return

        ip = simpledialog.askstring("Ajouter Imprimante", "Entrez l'adresse IP de l'imprimante réseau :\n(ex: 192.168.1.50)", parent=window)
        if not ip:
            return

        name = simpledialog.askstring("Nom de l'imprimante", "Donnez un nom court à l'imprimante (sans espace) :\n(ex: Bureau)", parent=window)
        if not name or " " in name:
            messagebox.showerror("Erreur", "Nom invalide (ne doit pas contenir d'espaces).")
            return

        # lpadmin -p <name> -v ipp://<ip>/ipp/print -E -m everywhere
        cmd = f"lpadmin -p {name} -v ipp://{ip}/ipp/print -E -m everywhere"
        try:
            res = subprocess.run(['sudo', '-S', 'bash', '-c', cmd], input=pwd+'\n', text=True, capture_output=True)
            if res.returncode == 0:
                # Set as default
                subprocess.run(['sudo', '-S', 'bash', '-c', f"lpadmin -d {name}"], input=pwd+'\n', text=True, capture_output=True)
                messagebox.showinfo("Succès", f"L'imprimante {name} a été ajoutée avec succès et définie par défaut.")
                refresh_printers()
            else:
                # Fallback to simple socket if IPP Everywhere fails (some older printers)
                cmd_fallback = f"lpadmin -p {name} -v socket://{ip} -E"
                res_fb = subprocess.run(['sudo', '-S', 'bash', '-c', cmd_fallback], input=pwd+'\n', text=True, capture_output=True)
                if res_fb.returncode == 0:
                    messagebox.showinfo("Succès", f"L'imprimante {name} a été ajoutée (Mode basique sans driver IPP).")
                    refresh_printers()
                else:
                    messagebox.showerror("Erreur", f"L'ajout a échoué.\n{res.stderr}\n{res_fb.stderr}")
        except Exception as e:
            messagebox.showerror("Exception", str(e))

    def auto_search():
        pwd = app_manager.desktop.settings.get("sudo_pwd") if app_manager else None
        if not pwd:
            messagebox.showerror("Erreur", "Veuillez configurer votre mot de passe système (Sudo) dans les Paramètres.")
            return

        messagebox.showinfo("Recherche", "La détection automatique va démarrer (cela peut prendre quelques secondes)...")
        try:
            res = subprocess.run(['ippfind'], capture_output=True, text=True)
            if res.returncode == 0 and res.stdout.strip():
                uris = res.stdout.strip().split('\n')
                uri = uris[0] # Prendre la première imprimante trouvée

                name = simpledialog.askstring("Imprimante trouvée", f"Une imprimante a été détectée !\nURI: {uri}\n\nDonnez-lui un nom court (sans espace) :", parent=window)
                if not name or " " in name:
                    return

                cmd = f"lpadmin -p {name} -v {uri} -E -m everywhere"
                res_add = subprocess.run(['sudo', '-S', 'bash', '-c', cmd], input=pwd+'\n', text=True, capture_output=True)
                if res_add.returncode == 0:
                    subprocess.run(['sudo', '-S', 'bash', '-c', f"lpadmin -d {name}"], input=pwd+'\n', text=True, capture_output=True)
                    messagebox.showinfo("Succès", f"L'imprimante {name} a été configurée et définie par défaut !")
                    refresh_printers()
                else:
                    messagebox.showerror("Erreur", f"L'ajout a échoué.\n{res_add.stderr}")
            else:
                messagebox.showinfo("Résultat", "Aucune imprimante IPP détectée sur le réseau local.")
        except FileNotFoundError:
            messagebox.showerror("Erreur", "L'outil de recherche (ippfind) est introuvable. Installez cups-client.")

    btn_add = tk.Button(btn_frame, text="➕ Ajouter (IP)", command=add_network_printer, bg="lightblue")
    btn_add.pack(side="left", padx=5)

    btn_search = tk.Button(btn_frame, text=" Détection Auto", image=window.icons.get('btn_search'), compound="left", command=auto_search, bg="#ffeb3b")
    btn_search.pack(side="left", padx=5)

    btn_test = tk.Button(btn_frame, text=" Page de Test", image=window.icons.get('btn_print'), compound="left", command=print_test, bg="lightgreen")
    btn_test.pack(side="left", padx=5)

    btn_default = tk.Button(btn_frame, text="⭐ Définir Défaut", command=set_default, bg="orange")
    btn_default.pack(side="left", padx=5)

    btn_ref = tk.Button(btn_frame, text=" Actualiser", image=window.icons.get('btn_refresh'), compound="left", command=refresh_printers)
    btn_ref.pack(side="right", padx=5)

    # Note d'installation
    note = tk.Label(frame, text="Note: L'impression nécessite d'installer les utilitaires système:\nsudo apt-get install cups cups-client printer-driver-all", 
                    font=("Arial", 8, "italic"), bg="white", fg="gray")
    note.pack(pady=5)

    refresh_printers()