/***************************************************************************** Application ASH: ipcctl.c (c) Pierre Adriaans 1994 ------------------------------------------------------------------------------ Module de controle des IPC. *****************************************************************************/ #include "ash.h" /* Argument de IPC_Status() */ #define MSG 0 #define SEM 1 #define SHM 2 /* #defines pour compilation sur osf/1 release 1.1: reprise des valeurs standard */ #ifndef SHM_INIT #define SHM_INIT 01000 #endif #ifndef SHM_DEST #define SHM_DEST 02000 #endif /* #define pour compilation sur Linux 1.0.9: reprise des valeurs standard */ #ifndef MSG_RWAIT #define MSG_RWAIT 01000 #endif #ifndef MSG_WWAIT #define MSG_WWAIT 02000 #endif /* Pour SCO: le man renseigne le type, mais il n'est pas defini. Deconnecter pour Linux */ /*union semun { int val; struct semid_ds *buf; ushort *array; };*/ /* LLB de la fenetre d'affichage */ struct IPC_Status_s { int ID; /* ID de l'IPC device */ struct ipc_perm Info; /* Caracteristiques de permission */ char DispLine[80]; /* Ligne a afficher */ struct IPC_Status_s *Suivant; struct IPC_Status_s *Prec; }; typedef struct IPC_Status_s IPC_Status_t; extern AnswerBoxItem_t *InfoMsg; extern char DummyStr[DUMMYSTR_SIZE]; extern ListBoxItem_t *IPC_Menu; void Build_IPC_Line(IPC_Status_t *IPC,int Type); void IPC_Status(int Type); /***************************************************************************** IPC_Control() ------------------------------------------------------------------------------ Presente le menu de gestion des IPC et lance IPC_Status avec le bon argument *****************************************************************************/ void IPC_Control(void) { int Fini = 0; while(!Fini) { switch(ListBox(8,25,8,30,FileOpNorm,FileOpInv," I.P.C. Control ", IPC_Menu,LB_DOUBLE,DOUBLE_FRAMED)) { case 1: IPC_Status(MSG); break; case 2: IPC_Status(SEM); break; case 3: IPC_Status(SHM); break; case -1: case 4: Fini = 1; } } } /***************************************************************************** Build_IPC_Line() ------------------------------------------------------------------------------ Construit le ligen d'affichage d'un IPC device en fonction de ses caracteristiques *****************************************************************************/ void Build_IPC_Line(IPC_Status_t *IPC,int Type) { struct passwd *pw; struct group *grp; /* ID */ sprintf(IPC->DispLine,"%-10d",IPC->ID); /* User et groupe */ if((pw = getpwuid(IPC->Info.uid)) == NULL) sprintf(IPC->DispLine + strlen(IPC->DispLine),"??? "); else sprintf(IPC->DispLine + strlen(IPC->DispLine),"%-13s",pw->pw_name); if((grp = getgrgid(IPC->Info.gid)) == NULL) sprintf(IPC->DispLine + strlen(IPC->DispLine),"??? "); else sprintf(IPC->DispLine + strlen(IPC->DispLine),"%-13s",grp->gr_name); /* Status actuel */ switch(Type) { case MSG: if(__TEST__(IPC->Info.mode,MSG_WWAIT)) sprintf(IPC->DispLine + strlen(IPC->DispLine),"S"); else sprintf(IPC->DispLine + strlen(IPC->DispLine),"-"); if(__TEST__(IPC->Info.mode,MSG_RWAIT)) sprintf(IPC->DispLine + strlen(IPC->DispLine),"R"); else sprintf(IPC->DispLine + strlen(IPC->DispLine),"-"); break; case SEM: sprintf(IPC->DispLine + strlen(IPC->DispLine),"--"); break; case SHM: if(__TEST__(IPC->Info.mode,SHM_DEST)) sprintf(IPC->DispLine + strlen(IPC->DispLine),"D"); else sprintf(IPC->DispLine + strlen(IPC->DispLine),"-"); if(__TEST__(IPC->Info.mode,SHM_INIT)) sprintf(IPC->DispLine + strlen(IPC->DispLine),"C"); else sprintf(IPC->DispLine + strlen(IPC->DispLine),"-"); break; } /* Permissions: */ if(IS_RUSR(IPC->Info.mode)) sprintf(IPC->DispLine + strlen(IPC->DispLine),"r"); else sprintf(IPC->DispLine + strlen(IPC->DispLine),"-"); if(IS_WUSR(IPC->Info.mode)) switch(Type) { case MSG: case SHM: sprintf(IPC->DispLine + strlen(IPC->DispLine),"w"); break; case SEM: sprintf(IPC->DispLine + strlen(IPC->DispLine),"a"); break; } else sprintf(IPC->DispLine + strlen(IPC->DispLine),"-"); sprintf(IPC->DispLine + strlen(IPC->DispLine),"-"); if(IS_RGRP(IPC->Info.mode)) sprintf(IPC->DispLine + strlen(IPC->DispLine),"r"); else sprintf(IPC->DispLine + strlen(IPC->DispLine),"-"); if(IS_WGRP(IPC->Info.mode)) switch(Type) { case MSG: case SHM: sprintf(IPC->DispLine + strlen(IPC->DispLine),"w"); break; case SEM: sprintf(IPC->DispLine + strlen(IPC->DispLine),"a"); break; } else sprintf(IPC->DispLine + strlen(IPC->DispLine),"-"); sprintf(IPC->DispLine + strlen(IPC->DispLine),"-"); if(IS_ROTH(IPC->Info.mode)) sprintf(IPC->DispLine + strlen(IPC->DispLine),"r"); else sprintf(IPC->DispLine + strlen(IPC->DispLine),"-"); if(IS_WOTH(IPC->Info.mode)) switch(Type) { case MSG: case SHM: sprintf(IPC->DispLine + strlen(IPC->DispLine),"w"); break; case SEM: sprintf(IPC->DispLine + strlen(IPC->DispLine),"a"); break; } else sprintf(IPC->DispLine + strlen(IPC->DispLine),"-"); sprintf(IPC->DispLine + strlen(IPC->DispLine),"-"); } /***************************************************************************** IPC_Status() ------------------------------------------------------------------------------ Affiche dans un boite de liste les IPC et permet leur destruction. *****************************************************************************/ void IPC_Status(int Type) { IPC_Status_t *IPC = (IPC_Status_t *)NULL, /* Liste des IPC */ *End_IPC = (IPC_Status_t *)NULL, /* Derniere IPC */ *Dummy,*Current,*Temp; /* Pointeurs de manip */ char Line[80]; /* Ligne transversale */ int Lig,Col,Height,Length, /* Coordonnees exterieures de la fenetre */ ActLig,ActCol, ActHeight,ActLength, /* Coordonnees de la fenetre d'affichage */ Fini = 0, /* Temoin de fin de boucle */ i, /* Compteurs */ SelIndex, /* Index de selection */ X,Y, /* Coord du curseur */ rc, /* Code de retour */ NbreEntries, /* Nombre de ss-rep */ InvLig,InvCol; /* Coord de la ligne inverse actuelle */ /* Pour les boites */ AnswerBoxItem_t *MsgAB = (AnswerBoxItem_t *)NULL, *Choices = (AnswerBoxItem_t *)NULL; /* Recuperation de l'owner */ struct passwd *pw; struct msqid_ds bufM; struct semid_ds bufS; struct shmid_ds bufE; union semun arg; arg.buf = &bufS; /* Fenetre de patience */ OpenWin(11,20,3,40,DoingNorm,SINGLE_FRAMED,SHADED); switch(Type) { case MSG: Printf(" Searching for Message Queues..."); break; case SEM: Printf(" Searching for Semaphores..."); break; case SHM: Printf(" Searching for Shared Memories..."); break; } fflush(stdout); /* Allons a la peche... */ for(i=0;i<=32768;i++) { switch(Type) { case MSG: if(msgctl(i,IPC_STAT,&bufM) == 0) { /* Trouve: enfiler */ Dummy = (IPC_Status_t *)malloc(sizeof(IPC_Status_t)); if(Dummy == (IPC_Status_t *)NULL) raise(SIGME); Dummy->Suivant = (IPC_Status_t *)NULL; Dummy->ID = i; memcpy(&(Dummy->Info),&(bufM.msg_perm),sizeof(struct ipc_perm)); if(IPC == (IPC_Status_t *)NULL) { IPC = Dummy; IPC->Prec = (IPC_Status_t *)NULL; End_IPC = IPC; } else { End_IPC->Suivant = Dummy; Dummy->Prec = End_IPC; End_IPC = Dummy; } } break; case SEM: if(semctl(i,0,IPC_STAT,arg) == 0) { /* Trouve: enfiler */ Dummy = (IPC_Status_t *)malloc(sizeof(IPC_Status_t)); if(Dummy == (IPC_Status_t *)NULL) raise(SIGME); Dummy->Suivant = (IPC_Status_t *)NULL; Dummy->ID = i; memcpy(&(Dummy->Info),&(bufS.sem_perm),sizeof(struct ipc_perm)); if(IPC == (IPC_Status_t *)NULL) { IPC = Dummy; IPC->Prec = (IPC_Status_t *)NULL; End_IPC = IPC; } else { End_IPC->Suivant = Dummy; Dummy->Prec = End_IPC; End_IPC = Dummy; } } break; case SHM: if(shmctl(i,IPC_STAT,&bufE) == 0) { /* Trouve: enfiler */ Dummy = (IPC_Status_t *)malloc(sizeof(IPC_Status_t)); if(Dummy == (IPC_Status_t *)NULL) raise(SIGME); Dummy->Suivant = (IPC_Status_t *)NULL; Dummy->ID = i; memcpy(&(Dummy->Info),&(bufE.shm_perm),sizeof(struct ipc_perm)); if(IPC == (IPC_Status_t *)NULL) { IPC = Dummy; IPC->Prec = (IPC_Status_t *)NULL; End_IPC = IPC; } else { End_IPC->Suivant = Dummy; Dummy->Prec = End_IPC; End_IPC = Dummy; } } break; } } CloseWin(); if(IPC == (IPC_Status_t *)NULL) { InitAnswerBoxItem(&InfoMsg); switch(Type) { case MSG: AddAnswerBoxItem(&InfoMsg," No Message Queue found "); strcpy(DummyStr," Message Queues "); break; case SEM: AddAnswerBoxItem(&InfoMsg," No Semaphore found "); strcpy(DummyStr," Semaphores "); break; case SHM: AddAnswerBoxItem(&InfoMsg," No Shared Memory found "); strcpy(DummyStr," Shared Memories"); break; } InfoBox(ErrorNorm,ErrorInv,DummyStr,InfoMsg,0,0,DOUBLE_FRAMED); InitAnswerBoxItem(&InfoMsg); return; } else { /* Construire les lignes d'affichage */ for(Dummy = IPC; Dummy != (IPC_Status_t *)NULL; Dummy = Dummy->Suivant) Build_IPC_Line(Dummy,Type); } /* Recuperer la position du curseur */ GetXY(&Y,&X); /* Fixer les coordonnes de la fenetre */ Lig = 5; Col = 11, Height = 16; Length = 60; /* Construire la fenetre */ Line[0] = F_BDSD; for(i=1;i<(Length-7);i++) Line[i] = F_S_HO; Line[i] = F_SDBD; Line[i+1] = 0; OpenWin(Lig,Col,Height,Length,IPCNorm,UNFRAMED,SHADED); Lig +=1; Col += 3; Height -= 2; Length -= 6; PaintFrame(Lig,Col,Height,Length,IPCNorm,DOUBLE_FRAMED); switch(Type) { case MSG: WriteString(" Message Queues ",IPCNorm,Lig,33); break; case SEM: WriteString(" Semaphores ",IPCNorm,Lig,35); break; case SHM: WriteString(" Shared Memories ",IPCNorm,Lig,34); break; } WriteString(Line,IPCNorm,Lig+2,Col); WriteString(" ID OWNER GROUP MODE",IPCNorm, Lig + 1,Col + 1); WriteString(Line,IPCNorm,Lig+Height-4,Col); WriteString("Up/Down arrows to move, Enter to destroy IPC.", IPCNorm,Lig+Height-3,18); WriteString("Ctrl-C (Esc) to cancel.",IPCNorm,Lig+Height-2,30); ActLig = Lig + 3; ActCol = Col + 2; ActHeight = Height - 7; ActLength = Length - 4; /* Mettre le curseur hors du chemin */ GotoXY(Y,X); /* Initialiser le pointeur End_IPC sur le dernier item de la liste */ for(Current = IPC,End_IPC = (IPC_Status_t *)NULL; Current != (IPC_Status_t *)NULL; End_IPC = Current,Current = Current->Suivant); /* Afficher la portion superieure de la liste dans la fenetre */ for(i=ActLig,Current = IPC; i < (ActLig + ActHeight) && Current != (IPC_Status_t *)NULL; i++,Current = Current->Suivant) WriteString(Current->DispLine,IPCNorm,i,ActCol+1); /* Fixer les coordonnees de la premiere zone d'invertion video */ InvLig = ActLig; InvCol = ActCol; /* Inverser la premiere option */ PaintString(InvLig,InvCol,ActLength,IPCInv); /* Pour le moment, l'option choisie est la premiere de la liste */ Current = IPC; SelIndex = 0; GotoXY(Y,X); fflush(stdout); while(!Fini) { switch(ReadKbd()) { case CR: InitAnswerBoxItem(&MsgAB); InitAnswerBoxItem(&Choices); switch(Type) { case MSG: AddAnswerBoxItem(&MsgAB, " You want to destroy the Message Queue "); break; case SEM: AddAnswerBoxItem(&MsgAB, " You want to destroy the Semaphore "); break; case SHM: AddAnswerBoxItem(&MsgAB, " You want to destroy the Shared Memory "); break; } if((pw = getpwuid(Current->Info.uid)) == NULL) sprintf(DummyStr,"Owner: ??? - "); else sprintf(DummyStr,"Owner: %s - ",pw->pw_name); sprintf(DummyStr + strlen(DummyStr),"ID: %d",Current->ID); AddAnswerBoxItem(&MsgAB,DummyStr); AddAnswerBoxItem(&Choices,"Yes"); AddAnswerBoxItem(&Choices,"Cancel"); rc = AnswerBox(ConfirmNorm,ConfirmInv," Confirm ",MsgAB,Choices, 0,0,DOUBLE_FRAMED); InitAnswerBoxItem(&MsgAB); InitAnswerBoxItem(&Choices); if(rc == 1) { switch(Type) { case MSG: if(msgctl(Current->ID,IPC_RMID,&bufM) == 0) { InitAnswerBoxItem(&InfoMsg); AddAnswerBoxItem(&InfoMsg, " The Message Queue is destroyed "); InfoBox(OpOKNorm,OpOKInv," msgctl ",InfoMsg,0,0,DOUBLE_FRAMED); InitAnswerBoxItem(&InfoMsg); Fini = 1; } else { InitAnswerBoxItem(&InfoMsg); AddAnswerBoxItem(&InfoMsg, " ERROR: impossible to destroy this Message Queue. "); switch(errno) { case EINVAL: AddAnswerBoxItem(&InfoMsg,"Invalid Queue ID."); break; case EPERM: AddAnswerBoxItem(&InfoMsg,"Permission denied."); break; } InfoBox(ErrorNorm,ErrorInv," msgctl ",InfoMsg,0,0,DOUBLE_FRAMED); InitAnswerBoxItem(&InfoMsg); } break; case SEM: if(semctl(Current->ID,0,IPC_RMID,arg) == 0) { InitAnswerBoxItem(&InfoMsg); AddAnswerBoxItem(&InfoMsg," The Semaphore is destroyed "); InfoBox(OpOKNorm,OpOKInv," semctl ",InfoMsg,0,0,DOUBLE_FRAMED); InitAnswerBoxItem(&InfoMsg); Fini = 1; } else { InitAnswerBoxItem(&InfoMsg); AddAnswerBoxItem(&InfoMsg, " ERROR: impossible to destroy this Semaphore. "); switch(errno) { case EINVAL: AddAnswerBoxItem(&InfoMsg,"Invalid Semaphore ID."); break; case EPERM: AddAnswerBoxItem(&InfoMsg,"Permission denied."); break; } InfoBox(ErrorNorm,ErrorInv," semctl ",InfoMsg,0,0,DOUBLE_FRAMED); InitAnswerBoxItem(&InfoMsg); } break; case SHM: if(shmctl(Current->ID,IPC_RMID,&bufE) == 0) { InitAnswerBoxItem(&InfoMsg); AddAnswerBoxItem(&InfoMsg, " The Shared Memory is destroyed "); InfoBox(OpOKNorm,OpOKInv," shmctl ",InfoMsg,0,0,DOUBLE_FRAMED); InitAnswerBoxItem(&InfoMsg); Fini = 1; } else { InitAnswerBoxItem(&InfoMsg); AddAnswerBoxItem(&InfoMsg, " ERROR: impossible to destroy this Shared Memory. "); switch(errno) { case EINVAL: AddAnswerBoxItem(&InfoMsg,"Invalid Memory ID."); break; case EPERM: AddAnswerBoxItem(&InfoMsg,"Permission denied."); break; } InfoBox(ErrorNorm,ErrorInv," msgctl ",InfoMsg,0,0,DOUBLE_FRAMED); InitAnswerBoxItem(&InfoMsg); } break; } } break; case DELETE: case CTRL_C: case ESC: CloseWin(); Fini = 1; break; case K_UP: if(Current->Prec != (IPC_Status_t *)NULL) { /* Si le choix courant a un predecesseur, y deplacer la barre avec scrolling bas si necessaire */ SelIndex--; PaintString(InvLig,InvCol,ActLength,IPCNorm); Current = Current->Prec; if(InvLig == ActLig) { ScrollDownWin(ActLig,ActCol,ActHeight,ActLength,1); WriteString(Current->DispLine,IPCNorm,ActLig,ActCol+1); } else InvLig--; PaintString(InvLig,InvCol,ActLength,IPCInv); } break; case K_DOWN: /* Si le choix courant a un suivant, y deplacer la barre avec scrolling haut si necessaire */ if(Current->Suivant != (IPC_Status_t *)NULL) { SelIndex++; /* Si le choix courant a un suivant, y deplacer la barre avec scrolling haut si necessaire */ PaintString(InvLig,InvCol,ActLength,IPCNorm); Current = Current->Suivant; if(InvLig == ActLig + ActHeight - 1) { ScrollUpWin(ActLig,ActCol,ActHeight,ActLength,1); WriteString(Current->DispLine,IPCNorm,ActLig+ActHeight-1,ActCol+1); } else InvLig++; PaintString(InvLig,InvCol,ActLength,IPCInv); } break; case K_HOME: case CTRL_B: /* Si on est pas deja sur la premiere option, s'y deplacer, avec mise a jour de la porion de liste affichee si necessaire */ if(SelIndex != 0) { if(SelIndex < ActHeight && InvLig - SelIndex >= ActLig) /* L'item 0 est tjs dans la fenetre: simplement deplacer la barre */ PaintString(InvLig,InvCol,ActLength,IPCNorm); else { /* L'item 0 n'est plus dans la fenetre: effacer tous les items et les reafficher a partir du 0, puis y placer la barre */ ClrZone(ActLig,ActCol,ActHeight,ActLength,IPCNorm); for(i = ActLig,Current = IPC; i < (ActLig + ActHeight) && Current != (IPC_Status_t *)NULL; i++,Current = Current->Suivant) WriteString(Current->DispLine,IPCNorm,i,ActCol+1); } Current = IPC; SelIndex = 0; InvLig = ActLig; PaintString(InvLig,InvCol,ActLength,IPCInv); } break; case K_END: case CTRL_E: /* Si on est pas sur la derniere option, s'y placer, avec mise a jour de la porion de liste affichee si necessaire */ if(SelIndex != NbreEntries - 1) { if(InvLig + NbreEntries - SelIndex <= ActLig + ActHeight) /* Le dernier item est deja dans la fenetre: simplement deplacer la barre */ PaintString(InvLig,InvCol,ActLength,IPCNorm); else { /* Le dernier item n'est pas dans la fenetre: effacer tout et les reafficher pour avoir le dernier item en derniere position de la fenetre */ ClrZone(ActLig,ActCol,ActHeight,ActLength,IPCNorm); /* Positionner Current sur le premier item a devoir etre reaffiche en remontant la liste a partir de la fin */ for(i = 0,Current = End_IPC; i < ActHeight - 1 && Current != (IPC_Status_t *)NULL; i++,Current = Current->Prec); /* Affichage */ for(i = ActLig; i < (ActLig + ActHeight) && Current != (IPC_Status_t *)NULL; i++,Current = Current->Suivant) WriteString(Current->DispLine,IPCNorm,i,ActCol+1); } Current = End_IPC; SelIndex = NbreEntries - 1; if(NbreEntries < ActHeight) InvLig = ActLig + SelIndex; else InvLig = ActLig + ActHeight - 1; PaintString(InvLig,InvCol,ActLength,IPCInv); } break; case CTRL_U: case K_PGUP: /* Si on est au milieu ou a la fin de la fenetre, remonter sur la premiere option Si on est au sommet de la fenetre, rectifier l'affichage vers le haut d'une fenetre moins un element. Si il ne reste pas assez d'elements pour faire ca, reafficher depuis le debut et placer la barre sur le premier element */ if(SelIndex != 0) { PaintString(InvLig,InvCol,ActLength,IPCNorm); if(InvLig != ActLig) { /* La barre est au milieu de la fenetre: simplement la placer au sommet */ for(i=InvLig;i>ActLig;i--,Current = Current->Prec,SelIndex--); InvLig = ActLig; } else { /* La barre est deja au sommet: effacer tout et remonter dans la liste d'un nombre d'items egal a la hauteur de la fenetre. */ for(i = 0; i < ActHeight - 1 && Current != (IPC_Status_t *)NULL; i++,Current = Current->Prec,SelIndex--); /* Si Current est a NULL, c'est que ce qui restait de la liste est plus petit qu'une hauteur de fenetre: on va donc reafficher depuis le debut */ if(Current == (IPC_Status_t *)NULL) { Current = IPC; SelIndex = 0; } /* Affichage */ Temp = Current; ClrZone(ActLig,ActCol,ActHeight,ActLength,IPCNorm); for(i = ActLig; i < (ActLig + ActHeight) && Temp != (IPC_Status_t *)NULL; i++,Temp = Temp->Suivant) WriteString(Temp->DispLine,IPCNorm,i,ActCol+1); InvLig = ActLig; } PaintString(InvLig,InvCol,ActLength,IPCInv); } break; case CTRL_D: case K_PGDN: /* Idem PgUp, mais vers le bas */ if(SelIndex != NbreEntries - 1) { PaintString(InvLig,InvCol,ActLength,IPCNorm); if(InvLig != ActLig + ActHeight - 1) { /* La barre est au milieu de la fenetre: simplement la placer a la fin */ if(NbreEntries < ActHeight) { /* La liste est plus petite que la fenetre */ Current = End_IPC; InvLig = ActLig + NbreEntries - 1; SelIndex = NbreEntries - 1; } else { /* La liste est plus grande que la fenetre */ for(i=InvLig;i < ActLig + ActHeight - 1; i++,Current = Current->Suivant,SelIndex++); InvLig = ActLig + ActHeight - 1; } } else { /* La barre est deja a la fin: effacer tout et redescendre dans la liste d'un nombre d'items egal a la hauteur de la fenetre. - Temp memorisera le premier item a etre reaffiche, c-a-d celui qui sera au sommet de la fenetre - Current memorisera celui ou la barre en inverse se trouvera, dans ce cas, toujours sur la derniere ligne de la fenetre */ /* Donc, si il reste plus qu'une hauteur de fenetre dans la liste, lors d'un PGDN, l'item qui etait en inverse lorsque la barre est en fin de fenetre sera celui qui commencera les affichage a la fenetre suivante */ Temp = Current; /* Tester si il reste plus d'un fenetre a afficher */ for(i = 0; i < ActHeight - 1 && Current != (IPC_Status_t *)NULL; i++,Current = Current->Suivant,SelIndex++); /* Si Current est a NULL, c'est que ce qui restait de la liste est plus petit qu'une hauteur de fenetre: on va donc reafficher une fenetre ayant le dernier item sur la derniere ligne */ if(Current == (IPC_Status_t *)NULL) { /* Positionner Temp sur le premier item a devoir etre reaffiche en remontant la liste a partir de la fin. Current sera dans ce cas toujour le dernier item de la liste */ for(i = 0,Current = End_IPC,Temp = End_IPC,SelIndex = NbreEntries - 1; i < ActHeight - 1 && Temp != (IPC_Status_t *)NULL; i++,Temp = Temp->Prec); } /* Affichage */ ClrZone(ActLig,ActCol,ActHeight,ActLength,IPCNorm); for(i = ActLig; i < (ActLig + ActHeight) && Temp != (IPC_Status_t *)NULL; i++,Temp = Temp->Suivant) WriteString(Temp->DispLine,IPCNorm,i,ActCol+1); InvLig = ActLig + ActHeight - 1; } PaintString(InvLig,InvCol,ActLength,IPCInv); } break; } } CloseWin(); /* Liberer la liste */ Current = IPC; Temp = (IPC_Status_t *)NULL; while(Current != (IPC_Status_t *)NULL) { Temp = Current; Current = Current->Suivant; free(Temp); } }