1 (* Copyright 2001, 2002 b8_bavard, b8_fee_carabine, INRIA *)
3 This file is part of mldonkey.
5 mldonkey is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 mldonkey is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with mldonkey; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
30 (* permet de manipuler le terminal *)
38 let in_update = ref false
41 if not
!in_update then begin
45 Printf2.lprintf
"\027[%d;%dH" (y
+1) (x
+1)
47 let stdvid () = print_string
"\027[m" (* "]" *);;
49 let revvid () = print_string
"\027[7m"
51 let clrscr () = print_string
"\027[2J"
53 let vtflush () = flush stdout
55 (* begin_update / end_update : *)
56 (* Sert uniquement à sauvegarder la position du curseur. *)
57 (* N'assure pas d'exclusion mutuelle pour l'accès à l'écran. *)
59 let begin_update () = in_update := true
60 let end_update () = gotoxy !cx !cy; in_update := false
62 let beep () = print_char '
\007'
; flush stdout
70 mutable durty_line
: bool ; (* la ligne courante est-elle a jour ? *)
71 mutable durty_info
: bool ; (* le bandeau d'info est-il a jour ? *)
72 mutable ligne
: string ; (* ligne courante *)
73 mutable pos
: int ; (* position dans cette ligne *)
74 mutable trailer
: string list
;
75 mutable header
: string list
;
78 (* initialisatino de l'etat *)
79 let etat = { ligne
= ""; pos
= 0; trailer
= [];
81 durty_line
= true ; durty_info
= true }
83 (* ajoute les espaces a la fin de la chaine s pour atteindre la taille w *)
85 let l = String.length s
in
86 if l < w
then s^
String.make
(w
-l) ' '
else String.sub s
0 w
89 let off = ref (String.length
!prompt)
93 | CHANGE_LINE
of string * int
94 (* Commande générique *)
96 (* message en entree *)
98 (* message recu d'un canal *)
99 | MES_OUT
of string (* canal * message *)
100 (* message d'information (resultat d'une commande...) *)
103 let reader = ref (fun _
-> ())
104 let set_reader f
= reader := f
106 (* met a jour la ligne *)
108 gotoxy 0 (!height - 1);
109 print_string
(!prompt^
align (!width - !off) etat.ligne
);
110 gotoxy (!off + etat.pos
) (!height - 1);
112 etat.durty_line
<- false
114 (* meet a jour le bandeau d'info *)
117 let rec iter i list
=
122 print_string
(align !width line
);
126 let rec iter i list
=
130 gotoxy 0 (!height - i
- 1);
131 print_string
(align !width line
);
134 iter (List.length
etat.trailer
) etat.trailer
;
136 etat.durty_info
<- false
138 (* ajoute une ligne au-dessus du bandeau *)
140 gotoxy 0 (!height - (List.length
etat.trailer
) -1) ;
141 print_string
(align !width s
);
142 gotoxy 0 (!height-1) ;
144 etat.durty_line
<- true ;
145 etat.durty_info
<- true
148 let set_prompt p
= prompt := p
; off := String.length p
150 let set_header lines
=
151 etat.header
<- lines
;
152 etat.durty_info
<- true;
155 let set_trailer lines
=
156 etat.trailer
<- lines
;
157 etat.durty_info
<- true;
163 module Output
= struct
166 (* recoit les commandes l'affichage, il y a un mutex pour qu'il
167 n'y ait qu'une seule instance a la fois, c'est un mutex sur l'ecran *)
169 let info m
= control (INFO m
) in begin
171 | CHANGE_LINE
(s
,i
) ->
174 etat.durty_line
<- true
175 (* une commande est recue *)
176 | COMMAND
(cmd
) -> !reader (Command cmd
)
178 let lexbuf = Lexing.from_string cmd in
181 match Parser.cmd
Lexer.cmd
lexbuf with
182 | Help
-> info ("ceci est l'aide")
185 let canal = List.assoc chan
!chanmap
in
186 etat.chan
<- Some
(chan
, canal);
187 info (sprintf
"connecte au canal '%s'" chan
);
189 with Not_found
-> (* pas encore de recepteur pour ce canal *)
190 try (* on en cree un *)
191 let serveur = nsrecord
.get_loc chan
in
192 etat.chan
<- Some
(chan
, create_channel chan
serveur control) ;
193 info (sprintf
"connecte au canal '%s'" chan
)
195 info (sprintf
"canal '%s' inexistant" chan
)
197 | Create chan
-> begin
198 let chan = String.sub cmd
7 (String.length cmd
- 7) in
200 let _ = nsrecord
.get_loc
chan in
201 info (sprintf
"canal '%s' deja existant" chan)
203 let serveur = create_server
chan in
204 nsrecord
.add_channel
(chan,serveur);
205 info (sprintf
"canal '%s' cree" chan);
206 control (COMMAND
("join "^
chan)) (* on join le canal *)
209 List.iter (fun s
-> info s
) (nsrecord
.get_all_chan
())
210 with Parsing.Parse_error
->
211 info ("mauvaise commande, taper aide pour la "^
212 "liste des commandes");
214 (* un message est recu depuis le clavier *)
217 (* un message est recu depuis le gestionnaire du canal *)
219 (* c'est bien le canal actif, on l'affiche *)
223 (* un message d'info a afficher *)
224 | INFO m
-> print_line ("$ "^m
)
226 (* on met a jour ce qu'il faut *)
227 if etat.durty_info
then change_info ();
228 if etat.durty_line
then change_line ()
233 let output c
= Output.control c
235 let print m
= output (MES_OUT m
)
238 module Input
= struct
247 mutable history
: string list
; (* l'historique des commandes *)
248 mutable curline
: int ; (* la ligne visible *)
249 mutable nbline
: int ; (* le nombre de lignes en tout *)
250 mutable line
: string ; (* la ligne courante *)
251 mutable pos
: int ; (* la position courante sur cette ligne *)
252 mutable seq
: seq_state
; (* l'etat pour les sequences de caracteres *)
255 (* initialisation de l'etat *)
256 let s = { history
= []; curline
= 0; nbline
= 0 ;
257 line
= ""; pos
= 0; seq
= NORMAL
}
262 let sock = TcpBufferedSocket.create
"stdin" Unix.stdin
265 TcpBufferedSocket.set_reader sock (
267 let change () = output (CHANGE_LINE
(s.line
,s.pos
)) in
268 (* on lit un paquet de touches au clavier *)
269 let b = TcpBufferedSocket.buf
sock in
270 let buf = String.sub
b.TcpBufferedSocket.buf b.TcpBufferedSocket.pos nr
in
271 TcpBufferedSocket.buf_used
sock nr
;
273 for i
= 0 to nr
- 1 do
280 (* est-ce une commande (debut par /) *)
281 if length
line >= 2 && line.[0] = '
/'
&& line.[1] <> '
/'
then
283 (sub
(lowercase
line) 1 (length
line - 1)))
284 else if line <> "" then
285 output (MES_IN
line);
286 if line <> "" && ((s.nbline
> 0 && List.hd
s.history
<> line)
289 (* on ajoute la ligne a l'historique si ce n'est pas la meme
290 que la precedente et qu'elle n'est pas vide *)
291 s.history
<- (s.line :: s.history
);
292 s.nbline
<- s.nbline
+ 1;
298 (* | c -> print_string (Char.escaped c); flush stdout*)
300 | '
\008'
| '
\127'
when s.pos
> 0 ->
301 let l = length
s.line in
302 s.line <- sub
s.line 0 (s.pos
-1) ^ sub
s.line s.pos
(l-s.pos
) ;
305 | '
\008'
| '
\127'
| '
\004'
-> ()
307 | '
\001'
-> s.pos
<- 0
309 | '
\005'
-> s.pos
<- String.length
s.line
310 (* debut d'une sequence *)
311 | '
\027'
-> s.seq
<- ESC
312 (* on ajoute le caractere a la ligne la ou l'on est *)
314 s.line <- sub
s.line 0 s.pos ^
(make
1 c) ^
315 sub
s.line s.pos
(length
s.line - s.pos
) ;
321 | '
['
-> s.seq
<- SQUARE
322 | _ -> s.seq
<- NORMAL
326 printf "%d %d" s.curline s.nbline; *)
329 | 'C'
when s.pos
< String.length
s.line ->
332 | 'D'
when s.pos
> 0 ->
335 | 'A'
when s.curline
= 0 && s.line <> "" && s.nbline
> 0
336 && List.hd
s.history
<> s.line ->
337 (* on doit ajouter la ligne courante et il y en a
338 deja au moins une dans l'historique *)
339 s.history
<- s.line :: s.history
;
340 s.nbline
<- s.nbline
+ 1;
341 s.line <- List.nth
s.history
1;
343 s.pos
<- length
s.line
344 | 'A'
when s.curline
= 0 && s.line <> "" && s.nbline
> 1 ->
345 (* on ne doit pas ajouter la ligne courante car c'est la
346 meme que la premiere ligne de l'historique et il y en a
347 deja au moins une dans l'historique *)
348 s.line <- List.nth
s.history
1;
350 s.pos
<- length
s.line
351 | 'A'
when s.curline
= 0 && s.nbline
> 0 ->
352 (* on ne doit pas ajouter la ligne courante *)
353 s.line <- List.nth
s.history
0;
354 s.pos
<- length
s.line
355 | 'A'
when s.curline
< s.nbline
- 1 ->
356 (* cas normal : on est en train de parcourir la liste *)
357 s.curline
<- s.curline
+ 1;
358 s.line <- List.nth
s.history
s.curline
;
359 s.pos
<- length
s.line
360 | 'A'
when s.curline
= s.nbline
- 1 ->
361 (* on arrive en bout de liste *)
362 s.line <- List.nth
s.history
s.curline
;
363 s.pos
<- length
s.line
365 | 'B'
when s.curline
> 0 ->
366 (* on parcourt la liste dans l'autre sens *)
367 s.curline
<- s.curline
- 1;
368 s.line <- List.nth
s.history
s.curline
;
369 s.pos
<- length
s.line
370 | 'B'
(* curline = 0 *) ->
376 if c = '
1'
then s.seq
<- TILDE
379 s.pos
<- length
s.line;
380 if c = '
4'
then s.seq
<- TILDE
383 let l = length
s.line in
385 s.line <- sub
s.line 0 s.pos ^
386 sub
s.line (s.pos
+1) (l-s.pos
-1) ;
390 if s.seq
= SQUARE
then s.seq
<- NORMAL
;
392 (* les sequences se terniment souvent pas un ~ qu'il faut faire
403 width := int_of_string
(Sys.getenv
"COLUMNS");
404 height := int_of_string
(Sys.getenv
"LINES");
406 print_endline
"$LINES/$COLUMNS not defined.";
407 print_endline
"try 'export LINES COLUMNS'"
411 (* mise en place des paramettres du terminal *)
412 let attr_save = Unix.tcgetattr
Unix.stdin
in
413 let restore () = Unix.tcsetattr
Unix.stdin
Unix.TCSANOW
attr_save in
415 let attr = Unix.tcgetattr
Unix.stdin
in
416 attr.Unix.c_icanon
<- false;
417 attr.Unix.c_echo
<- false;
418 attr.Unix.c_vmin
<- 1;
419 Unix.tcsetattr
Unix.stdin
Unix.TCSANOW
attr;
423 let update () = change_info (); flush stdout
428 set_reader (fun cmd ->
431 | Command cmd -> print (sprintf "COMMAND %s" cmd)
433 set_trailer ["TRAILER"];
434 set_header ["HEADER"];
443 let esc = "\027" (* 033 *)
444 let esc_CHAR = '
\027'
446 (* Puts everything back to normal *)
448 let ansi_NORMAL = esc ^
"[2;37;0m"
450 let ansi_CLRSCR = esc ^
"[2J"
451 let ansi_CLREOL = esc ^
"[2K"
453 (* Non-color based font changes *)
455 let ansi_BOLD = esc ^
"[1m" (* Turn on bold mode *)
456 let ansi_BLINK = esc ^
"[5m"
457 (* Initialize blink mode *)
458 let ansi_UNDERLINE = esc ^
"[4m"
459 (* Initialize underscore mode *)
460 let ansi_REVERSE = esc ^
"[7m" (* Turns reverse video mode on *)
461 let ansi_HIGH_REVERSE = esc ^
"[1,7m" (* Hi intensity reverse video *)
463 (* Foreground Colors *)
465 let ansi_BLACK = esc ^
"[30m"
466 let ansi_RED = esc ^
"[31m"
467 let ansi_GREEN = esc ^
"[32m"
468 let ansi_YELLOW = esc ^
"[33m"
469 let ansi_BLUE = esc ^
"[34m"
470 let ansi_MAGENTA = esc ^
"[35m"
471 let ansi_CYAN = esc ^
"[36m"
472 let ansi_WHITE = esc ^
"[37m"
474 (* Hi Intensity Foreground Colors *)
476 let ansi_HIGH_RED = esc ^
"[1;31m"
477 let ansi_HIGH_GREEN = esc ^
"[1;32m"
478 let ansi_HIGH_YELLOW = esc ^
"[1;33m"
479 let ansi_HIGH_BLUE = esc ^
"[1;34m"
480 let ansi_HIGH_MAGENTA = esc ^
"[1;35m"
481 let ansi_HIGH_CYAN = esc ^
"[1;36m"
482 let ansi_HIGH_WHITE = esc ^
"[1;37m"
484 (* Background Colors *)
486 let ansi_BACKGROUND_BLACK = esc ^
"[40m"
487 let ansi_BACKGROUND_RED = esc ^
"[41m"
488 let ansi_BACKGROUND_GREEN = esc ^
"[42m"
489 let ansi_BACKGROUND_YELLOW = esc ^
"[43m"
490 let ansi_BACKGROUND_BLUE = esc ^
"[44m"
491 let ansi_BACKGROUND_MAGENTA = esc ^
"[45m"
492 let ansi_BACKGROUND_CYAN = esc ^
"[46m"
493 let ansi_BACKGROUND_WHITE = esc ^
"[47m"
495 (* High Intensity Background Colors *)
497 let ansi_HIGH_BACKGROUND_RED = esc ^
"[41;1m"
498 let ansi_HIGH_BACKGROUND_GREEN = esc ^
"[42;1m"
499 let ansi_HIGH_BACKGROUND_YELLOW = esc ^
"[43;1m"
500 let ansi_HIGH_BACKGROUND_BLUE = esc ^
"[44;1m"
501 let ansi_HIGH_BACKGROUND_MAGENTA = esc ^
"[45;1m"
502 let ansi_HIGH_BACKGROUND_CYAN = esc ^
"[46;1m"
503 let ansi_HIGH_BACKGROUND_WHITE = esc ^
"[47;1m"
509 external get_screen_size : Unix.file_descr -> bool = "ml_get_screen_size"
510 external screen_width : unit -> int = "ml_screen_width"
511 external screen_height : unit -> int = "ml_screen_height"
514 (* How do you get the size associated with a socket ? I have no book here,
515 I think we have to associate a pseudo-tty with the socket, but I don't
516 remember exactly... so, keep it simple. *)
519 let gotoxy x y
= Printf.sprintf
"\027[%d;%dH" (y
+1) (x
+1)