IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C Discussion :

Read ICANON et ~ICANON


Sujet :

C

  1. #1
    Nouveau membre du Club
    Homme Profil pro
    Étudiant
    Inscrit en
    Octobre 2015
    Messages
    33
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Octobre 2015
    Messages : 33
    Points : 32
    Points
    32
    Par défaut Read ICANON et ~ICANON
    Bonjour!

    Alors voilà, je suis en école (42) et j'ai pas mal de projet qui me demande de récupérer des infos sur l'entré standard (minishell, select, scanf....etc), avec comme contrainte de ne pas utiliser ni ncurse, ni les fonctions définies dans stdio.h.

    Du coup c'est read et termios pour virer l'ICANON et pouvoir reprendre la main de read à chaque caractère tapé. (J'imagine que c'est la seule solution que j'ai pour pouvoir implémenter le comportement des flèches 'up' et 'down' dans le minishell par exemple (récupération des anciennes lignes de commandes (fin de la parenthèseption))).

    Première question: Y a-t-il un moyen de garder le code derrière les caractères de contrôle tout en récupérant la main sur read après chaque caractère tapé?

    Si non, je vais être obligé à chaque fois, de set la struct termios et de gérer chaque type de caractère en fonction du projet, ainsi que certains signaux (genre SIGCONT, pour re-set la struct termios)… Mais le truc c'est que je suis bien trop fainéant…

    Du coup, j'ai imaginé faire une petite lib qui me permette de rajouter-supprimer des caractères de contrôle et signaux. Et surtout de pouvoir modifier le pointeur de fonction rattaché à ce caractère.
    Comme ça il y aurait une fonction par défaut pour chaque type de caractère (même comportement que read), et une autre fonction qui me permette de modifier-ajouter-supprimer le code derrière chaque type de caractère. (Du coup pouvoir reprendre la main avant le '\n' si besoin).

    Autres questions : - Pensez vous que c'est une bonne idée? (que ça va réellement me faire gagner du temps).
    - Pensez vous que je pourrais facilement gérer des projets aussi différents que le minishell et select avec une technique de ce genre ?
    - Avez vous d'autres pistes ?

    Merci!!

  2. #2
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 370
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 370
    Points : 23 625
    Points
    23 625
    Par défaut
    Bonjour et bienvenue,

    Ta problématique a été souvent exposée sur nos forums. L'important est d'être au clair avec la façon dont fonctionnent les terminaux en général, et avec les standards ANSI et ECMA en particulier. Vois par exemple cette discussion : http://www.developpez.net/forums/d15...ation-getchar/

    En général, les paramètres du terminal sont modifiés une seule fois à l'entrée dans l'application et une autre à la sortie, en principe pour restaurer l'état initial du terminal avant de passer la main à autre chose, et le mini-shell n'échappe pas à la règle : tu lis l'état du terminal en entrant et tu le sauvegardes dans une structure, que tu conserves et que tu copies dans une seconde, pour y modifier les paramètres qui t'intéressent en conservant les autres inchangés, et t'en servir pour configurer le terminal à ta sauce. Lorsque ton shell se termine ou qu'il passe la main à une application appelée par l'utilisateur (dans le but de la reprendre après, donc), tu restaures l'état initial à l'aide de la première structure. Dès lors que ton shell repasse à l'avant-plan, soit parce que ton application se termine, soit parce l'application a été lancée en arrière-plan avec « & » sans commande suivante ou que l'utilisateur l'a interrompu avec Ctrl+Z, alors tu reprends cette procédure au départ, pour lire le nouvel état du terminal, qui a pu être modifié par une autre application, voire à la demande de l'utilisateur avec stty.

    Pour les caractères de contrôles, il ne faut pas chercher à entrer directement dans une procédure en ajoutant par dessus une astuce pour « reprendre la main dessus ». Il faut les regarder défiler en « observateur » en maintenant à jour un automate en fonction de ce qui passe, un peu comme quand on joue au bingo et que l'on pose une petite pierre sur un de ses numéros quand il est annoncé. Ce n'est qu'une fois que tu atteins un état final (donc que l'on a une combinaison complète valide) qu'il faut déclencher une action en conséquence et revenir à l'état initial. Si un caractère complètement invalide survient au milieu d'une séquence, alors celle-ci est simplement abandonnée, on remet là encore l'automate à l'état initial et on traite les caractères suivants comme des caractères ordinaires, jusqu'au prochain code escape ou autre valeur spéciale.

  3. #3
    Nouveau membre du Club
    Homme Profil pro
    Étudiant
    Inscrit en
    Octobre 2015
    Messages
    33
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Octobre 2015
    Messages : 33
    Points : 32
    Points
    32
    Par défaut
    Citation Envoyé par Obsidian Voir le message
    Bonjour et bienvenue, ...
    Merci!

    Citation Envoyé par Obsidian Voir le message
    ... Ta problématique a été souvent exposée sur nos forums. L'important est d'être au clair avec la façon dont fonctionnent les terminaux en général, et avec les standards ANSI et ECMA en particulier. Vois par exemple cette discussion : http://www.developpez.net/forums/d15...ation-getchar/...
    Très intéressant! Bon ça fait 3 jours que je me gave de doc sur ANSI-ECMA-ASCII-termios-tty donc je commence a y voir plus clair! ^^ Mais une piqûre de rappel bien organisée ça fait jamais de mal! Surtout quand elle est donné par quelqu'un qui manifestement sait de quoi il parle!

    Citation Envoyé par Obsidian Voir le message
    ... En général, les paramètres du terminal sont modifiés une seule fois à l'entrée dans l'application et une autre à la sortie, en principe pour restaurer l'état initial du terminal avant de passer la main à autre chose, et le mini-shell n'échappe pas à la règle : tu lis l'état du terminal en entrant et tu le sauvegardes dans une structure, que tu conserves et que tu copies dans une seconde, pour y modifier les paramètres qui t'intéressent en conservant les autres inchangés, et t'en servir pour configurer le terminal à ta sauce. Lorsque ton shell se termine ou qu'il passe la main à une application appelée par l'utilisateur (dans le but de la reprendre après, donc), tu restaures l'état initial à l'aide de la première structure. Dès lors que ton shell repasse à l'avant-plan, soit parce que ton application se termine, soit parce l'application a été lancée en arrière-plan avec « & » sans commande suivante ou que l'utilisateur l'a interrompu avec Ctrl+Z, alors tu reprends cette procédure au départ, pour lire le nouvel état du terminal, qui a pu être modifié par une autre application, voire à la demande de l'utilisateur avec stty....
    Cette partie là je pense l'avoir bien intégré mais je vous laisse juge du code qui suit!

    Par contre il semblerait que je n'ai pas besoin de remettre (tcsetattr) la structure sauvegardée lors d'un arrêt par un signal (ctrl+c ou ctrl+z par exemple).
    Mais je suis bien obligé de refaire mes modifications lorsque mon programme repasse en foreground..
    Je ne suis absolument pas sur de cette affirmation, mais elle repose sur l'observation du comportement du code qui suit. Si vous avez une explication je suis preneur! ^^
    Pour info et si ça peut changer qqc à l'implémentation de termios, je suis sur une Debian Jessy 8 64bits

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
     
    /* ************************************************************************** */
    /*                                                                            */
    /*                                                        :::      ::::::::   */
    /*   test_termios.c                                     :+:      :+:    :+:   */
    /*                                                    +:+ +:+         +:+     */
    /*   By: sben**** <sben****@*************>          +#+  +:+       +#+        */
    /*                                                +#+#+#+#+#+   +#+           */
    /*   Created: 2015/11/20 01:23:46 by sben****          #+#    #+#             */
    /*   Updated: 2015/11/20 02:10:19 by sben****         ###   ########.fr       */
    /*                                                                            */
    /* ************************************************************************** */
     
     
    #include <stdlib.h>
    #include <unistd.h>
    #include <ctype.h>
    #include <signal.h>
    #include <strings.h>
    #include <errno.h>
    #include <termios.h>
     
    #define FTIO_BS 5
     
    typedef struct		s_ftio
    {
    	int		fd;
    	struct termios	term;
    	struct termios	cp;
    }			t_ftio;
     
    t_ftio			*ftio(void)
    {
            /*
             * J'aime pas vraiment ça, mais pour les signaux j'ai pas vraiment le choix... (globale ou static)
             * Entre la peste et le choléra :p
            */
    	static t_ftio	ftio_settings;
     
    	return (&ftio_settings);
    }
     
    void			ftio_error_handle(void)
    {
    	/*
    	 * Affichage de l'erreur en fonction d'errno à implementer
    	*/
     
    	exit(EXIT_FAILURE);
    }
     
    void                    ftio_init_term(void);
     
    void			ftio_sigcont_handle(int sig)
    {
    	/*
    	 * -Wall -Wextra // Ce test ne me sert qu'a cacher le warn gcc :/
    	*/
     
    	if (sig) // sig toujours > 0
    		ftio_init_term();
    }
     
    void			ftio_signal_handle(void)
    {
    	/*
    	 * J'ai observé que l'arret du programme par Ctrl+(C|Z)
    	 * remet de lui même la structure termios par defaut...
    	 * Je ne comprend pas vraiment pourquoi.. ^^
    	*/
     
    	signal(SIGCONT, ftio_sigcont_handle);
    }
     
    void			ftio_init_term(void)
    {
    	ftio_signal_handle();
    	if (!isatty(ftio()->fd) || (tcgetattr(ftio()->fd, &ftio()->term) == -1))
    		ftio_error_handle();
    	ftio()->cp = ftio()->term;
    	ftio()->cp.c_lflag &= ~(ICANON | ECHO);
    	ftio()->cp.c_lflag |= ISIG;
    	ftio()->cp.c_cc[VTIME] = 0;
    	ftio()->cp.c_cc[VMIN] = 1;
    	if (tcsetattr(ftio()->fd, TCSADRAIN, &ftio()->cp) == -1)
    		ftio_error_handle();
    }
     
    void			ftio_reset_term(void)
    {
    	if (tcsetattr(ftio()->fd, TCSANOW, &ftio()->term) == -1)
    		ftio_error_handle();
            bzero((void *)ftio(), sizeof(t_ftio));
    }
     
    int			main(void)
    {
    	char		buf[FTIO_BS + 1];
    	int		ret;
     
    	ftio()->fd = 0;
    	ftio_init_term();
     
    	while (42)
    	{
    		bzero((void *)buf, FTIO_BS + 1);
    		ret = read(ftio()->fd, buf, FTIO_BS);
    		if (ret < 0)
    		{
    			ftio_reset_term();
    			ftio_error_handle();
    		}
    		buf[ret] = 0;
     
                    /*
                     * Implementer ici l'automate et les actions à entreprendre 
                     * selon son etat (et surtout son changement d'etat) 
                    */
     
    		/*
    		 * Juste pour la demo, dans la version final,
    		 * j'aimerai bien gerer les wchar_t // Mais la c'est pas le propos
    		*/
     
    		if (isprint(*buf))
    			write(ftio()->fd, buf, ret);
    	}
     
    	ftio_reset_term();
    	return (0);
    }
    Citation Envoyé par Obsidian Voir le message
    ...Pour les caractères de contrôles, il ne faut pas chercher à entrer directement dans une procédure en ajoutant par dessus une astuce pour « reprendre la main dessus ». Il faut les regarder défiler en « observateur » en maintenant à jour un automate en fonction de ce qui passe, un peu comme quand on joue au bingo et que l'on pose une petite pierre sur un de ses numéros quand il est annoncé. Ce n'est qu'une fois que tu atteins un état final (donc que l'on a une combinaison complète valide) qu'il faut déclencher une action en conséquence et revenir à l'état initial. Si un caractère complètement invalide survient au milieu d'une séquence, alors celle-ci est simplement abandonnée, on remet là encore l'automate à l'état initial et on traite les caractères suivants comme des caractères ordinaires, jusqu'au prochain code escape ou autre valeur spéciale.
    Ok, par contre du coup pas vraiment de lib possible.. Et il faut remettre en place l'automate et le comportement selon (l'état/changement d'état) pour chaque nouveau sujet..

    Je ne comprends pas vraiment pourquoi se serait une mauvaise pratique de définir à l'avance le travail associé à une touche? J'ai l'impression que c'est ce que fait le read canonique non? Si une séquence (ou plutôt une valeur) présente dans c_cc[NCCS] est rencontré, un travail prédéfinit par read s’exécute (affichage de "\b \b" et non ajout de la valeur au buffer pour VERASE par exemple). Par défaut et toujours par exemple, cette fonction pourrait renvoyer 1 pour signifier que l'input n'est pas terminée et 0 pour la/les fonctions terminant l'input (VCRNL (Enter) par exemple).

    Ah et tant que j'y pense, pour le moment je cast le buffer de read (5 octets) en *(long int *) pour récupérer la "séquence". Je vois beaucoup de personne qui check le buffer octet par octet pour la reconnaître. Y a-t-il une "best practice" entre les deux manière de faire? (Portabilité-vitesse d’exécution...etc)

    En tout cas merci beaucoup pour la célérité de la réponse! Et fier d'être tombé sur un Cador! ^^

  4. #4
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 370
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 370
    Points : 23 625
    Points
    23 625
    Par défaut
    Bonsoir,

    Citation Envoyé par sben42 Voir le message
    Par contre il semblerait que je n'ai pas besoin de remettre (tcsetattr) la structure sauvegardée lors d'un arrêt par un signal (ctrl+c ou ctrl+z par exemple).
    Mais je suis bien obligé de refaire mes modifications lorsque mon programme repasse en foreground..
    Après quelques tests (avec bash), il semblerait que ce soit vrai, au moins pour ICANON (je ne suis pas encore allé chercher plus loin). Apparemment, le shell se comporte comme indiqué au dessus lorsque le programme se termine normalement (fût-ce avec un code de retour), mais restaure l'état du terminal tel qu'il était au lancement de l'application en cas de Ctrl-Z puisqu'il se considère en état « potentiellement instable », et laisse l'application se remettre en l'état si nécessaire (à la manière des expose et repaint des interfaces graphiques).

    Je n'ai pas trouvé de recommandation officielle à ce sujet mais mon avis est que ton application ne devrait pas s'en soucier, et encore moins aller intercepter des signaux uniquement pour ce cas de figure, mais plutôt suivre le comportement par défaut, donc celui qui peut être attendu par l'utilisateur ou l'administrateur système. Deux raisons à cela : la première est que l'utilisateur lui-même peut interrompre le programme pour modifier l'état du terminal avec stty. La seconde est que cela peut entrer en conflit avec certaines options, comme « set -m », où l'on s'aperçoit que les utilisateurs se plaignent que « cela marche avec la plupart des applications, mais pas avec certaines ». Avec cela, il faut gérer les problèmes inhérents aux signaux et tenir compte du fait que notre terminal n'est pas forcément un xterm. Tant que c'est en local, ça ne pose pas de problème mais s'il s'agit d'un terminal physique distant géré par des commandes escape, alors TCSANOW peut ne pas être honoré s'il y a des caractères dans la file.

    Maintenant, ce n'est pas interdit non plus et si c'est critique pour ton application, ça peut se justifier.

    Ok, par contre du coup pas vraiment de lib possible.. Et il faut remettre en place l'automate et le comportement selon (l'état/changement d'état) pour chaque nouveau sujet..
    Si, si, au contraire : c'est une très bonne idée de mettre cela dans une bibliothèque et en faisant les choses proprement, on peut même gérer plusieurs automates à la fois au besoin. Comme souvent, le tout est affaire de conception initiale et d'architecture. Formellement :

    • Soit tu réécris ta propre bibliothèque ncurses (par exemple « myncurses ») et tu établis ta propre API, sur laquelle tu t'appuies ensuite pour écrire tes applications. C'est instructif, mais ce sera très long pour pas grand chose ;
    • Soit tu écris une bibliothèque que tu nourris avec des caractères ou le contenu d'un buffer :


    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
        int c;
     
        c = fgetc(stdin);
        c = myterminal(c);
     
        if (c == LEFT_ARROW) {}

    On note que fgetc() renvoie un int et pas directement un char car il se réserve la possibilité de renvoyer EOF en cas de problème. EOF est une macro résolue en un entier dont la valeur est réputée ne pas pouvoir être celle d'un caractère (et généralement, il s'agit simplement de « -1 »).

    L'exemple ci-dessus est loin d'être le meilleur (à toi d'inventer l'approche la plus propre) mais il s'agit en substance de lire un caractère depuis l'entrée standard, indépendamment de notre bibliothèque, de le fournir à celle-ci et d'attendre en retour une valeur qui serait soit le caractère lui-même, soit une valeur hors jeu de caractère à la manière de EOF mais qui correspondrait à des combinaisons valides.

    Le principal avantage de cette approche est que ta bibliothèque n'a pas besoin d'être complète pour être utilisable : tu peux l'écrire en commençant par ne prendre en charge que les cas qui t'intéressent et l'enrichir avec de nouveaux codes au fur et à mesure que tu en as besoin. C'est pratique pour gérer son temps quand on a un projet à rendre sans pour autant écrire du code jetable.

    Les autres avantages sont que ça permet de ne pas interférer inutilement avec les flux standards, de pouvoir éventuellement gérer plusieurs flux à la fois, et de maintenir plusieurs automates en parallèle selon un même flux.

    Cela permet aussi de ne rien retourner tant qu'une séquence valide en en cours de lecture, quitte à transmettre le contenu du buffer en cas d'abandon de la séquence. Cela dit, ta fonction DOIT renvoyer une valeur et ne peut pas rester en attente, ce qui est le principal point noir de cette approche. Tu peux cela dit renvoyer EOF quand même (dans l'esprit de O_NONBLOCK), soit une valeur spéciale signifiant « on attend la suite ».

    Je ne comprends pas vraiment pourquoi se serait une mauvaise pratique de définir à l'avance le travail associé à une touche?
    Ce n'est pas ce que j'ai dit. Avoir des fonctions de callbacks est même plutôt une bonne chose. Ce à quoi je pensais en réalité est l'approche « naïve » adoptée, et c'est compréhensible, par certains :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
        c = fgetc(stdin);
     
        if (c==27) /* Escape */
        {
            if (c=='[') /* CSI */
            {}
        }

    C'est-à-dire entrer directement dans une procédure écrite dans le main(), qui les empêche de faire quoi que ce soit d'autre tant qu'une séquence est en cours et qui les oblige à rappeler fgetc() à l'intérieur du corps des if au lieu de s'appuyer sur la boucle principale.

    J'ai l'impression que c'est ce que fait le read canonique non? Si une séquence (ou plutôt une valeur) présente dans c_cc[NCCS] est rencontré, un travail prédéfinit par read s’exécute (affichage de "\b \b" et non ajout de la valeur au buffer pour VERASE par exemple). Par défaut et toujours par exemple, cette fonction pourrait renvoyer 1 pour signifier que l'input n'est pas terminée et 0 pour la/les fonctions terminant l'input (VCRNL (Enter) par exemple).
    Pas tout-à-fait : il s'agit bien d'effectuer une tâche spéciale mais ce n'est pas read qui va s'occuper de cela. C'est le système d'exploitation lui-même via ses gestionnaires de terminaux et les disciplines de ligne. D'autre part, il ne s'agit pas d'associer n'importe quel tâche mais de re-définir si nécessaire les caractères utilisés pour déclencher ces actions, principalement s'ils sont indisponibles (ex : clavier trop limité) ou déjà utilisés à d'autres fins le long de la ligne de transmission.

    Ah et tant que j'y pense, pour le moment je cast le buffer de read (5 octets) en *(long int *) pour récupérer la "séquence". Je vois beaucoup de personne qui check le buffer octet par octet pour la reconnaître. Y a-t-il une "best practice" entre les deux manière de faire? (Portabilité-vitesse d’exécution...etc)
    La meilleure pratique consiste à le lire « caractère par caractère », en entendant par là que, d'une part, c'est comme cela qu'il est exploité par les fonctions de la bibliothèque standard et, d'autre part, parce qu'un caractère ne correspond pas forcément à un octet. En outre, tu risques d'être confronté à deux problèmes en essayant d'optimiser la chose de la sorte : les différences entre big endian et little endian, qui sur certaines machines risquent de permuter inexplicablement certains octets, et le fait qu'un long ne correspond pas forcément non plus à 32 bits. Si tu n'y prends pas garde, tu risques un dépassement ou un écrasement de buffer.

    En tout cas merci beaucoup pour la célérité de la réponse! Et fier d'être tombé sur un Cador! ^^
    Oulala, c'est flatteur mais ce n'est pas vrai. Tu risques d'être déçu à court terme. :-)
    Bon courage à toi, quoi qu'il en soit.

  5. #5
    Nouveau membre du Club
    Homme Profil pro
    Étudiant
    Inscrit en
    Octobre 2015
    Messages
    33
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Octobre 2015
    Messages : 33
    Points : 32
    Points
    32
    Par défaut
    Merci!

    Je suis partis sur autre chose pour le moment mais j'y reviens très vite. En tout cas merci pour les explications

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. memoire read
    Par rabi dans le forum OpenGL
    Réponses: 11
    Dernier message: 02/02/2004, 22h59
  2. read committed, serializable... et par défaut k'en est-il?
    Par superdada dans le forum PostgreSQL
    Réponses: 2
    Dernier message: 01/12/2003, 18h58
  3. [LG]problème de read / readln
    Par jeremie60 dans le forum Langage
    Réponses: 7
    Dernier message: 08/06/2003, 23h33
  4. [controle] propriété read only
    Par Fizgig dans le forum Composants VCL
    Réponses: 6
    Dernier message: 28/08/2002, 10h30
  5. CheckBox en Read Only
    Par MrJéjé dans le forum C++Builder
    Réponses: 7
    Dernier message: 23/06/2002, 15h00

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo