]> sjero.net Git - linphone/blob - console/linphonec.c
fix some bugs
[linphone] / console / linphonec.c
1 /****************************************************************************
2  *
3  *  $Id: linphonec.c,v 1.57 2007/11/14 13:40:27 smorlat Exp $
4  *
5  *  Copyright (C) 2006  Sandro Santilli <strk@keybit.net>
6  *  Copyright (C) 2002  Florian Winterstein <flox@gmx.net>
7  *  Copyright (C) 2000  Simon MORLAT <simon.morlat@free.fr>
8  *
9 ****************************************************************************
10  *
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public License
13  * as published by the Free Software Foundation; either version 2
14  * of the License, or (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
24  *
25  ****************************************************************************/
26 #include <string.h>
27 #ifndef _WIN32_WCE
28 #include <sys/time.h>
29 #include <sys/types.h>
30 #include <unistd.h>
31 #include <errno.h>
32 #include <signal.h>
33 #include "private.h" /*coreapi/private.h, needed for LINPHONE_VERSION */
34 #endif /*_WIN32_WCE*/
35 #include <limits.h>
36 #include <ctype.h>
37
38 #include <linphonecore.h>
39
40 #include "linphonec.h"
41
42 #ifdef WIN32
43 #include <ws2tcpip.h>
44 #include <ctype.h>
45 #ifndef _WIN32_WCE
46 #include <conio.h>
47 #endif /*_WIN32_WCE*/
48 #else
49 #include <sys/socket.h>
50 #include <netdb.h>
51 #include <sys/un.h>
52 #include <sys/stat.h>
53 #endif
54
55 #if defined(_WIN32_WCE)
56
57 #if !defined(PATH_MAX)
58 #define PATH_MAX 256
59 #endif /*PATH_MAX*/
60
61 #if !defined(strdup)
62 #define strdup _strdup
63 #endif /*strdup*/
64
65 #endif /*_WIN32_WCE*/
66
67 #ifdef HAVE_GETTEXT
68 #include <libintl.h>
69 #ifndef _
70 #define _(String) gettext(String)
71 #endif
72 #else
73 #define _(something)    (something)
74 #endif
75
76 #ifndef PACKAGE_DIR
77 #define PACKAGE_DIR ""
78 #endif
79
80 #ifdef HAVE_X11_XLIB_H
81 #include <X11/Xlib.h>
82 #include <SDL/SDL_syswm.h>
83 #endif
84
85 /***************************************************************************
86  *
87  *  Types
88  *
89  ***************************************************************************/
90
91 typedef struct {
92         LinphoneAuthInfo *elem[MAX_PENDING_AUTH];
93         int nitems;
94 } LPC_AUTH_STACK;
95
96 /***************************************************************************
97  *
98  *  Forward declarations
99  *
100  ***************************************************************************/
101
102 char *lpc_strip_blanks(char *input);
103
104 static int handle_configfile_migration(void);
105 #if !defined(_WIN32_WCE)
106 static int copy_file(const char *from, const char *to);
107 #endif /*_WIN32_WCE*/
108 static int linphonec_parse_cmdline(int argc, char **argv);
109 static int linphonec_init(int argc, char **argv);
110 static int linphonec_main_loop (LinphoneCore * opm, char * sipAddr);
111 static int linphonec_idle_call (void);
112 #ifdef HAVE_READLINE
113 static int linphonec_initialize_readline(void);
114 static int linphonec_finish_readline();
115 static char **linephonec_readline_completion(const char *text,
116         int start, int end);
117 #endif
118
119 /* These are callback for linphone core */
120 static void linphonec_prompt_for_auth(LinphoneCore *lc, const char *realm,
121         const char *username);
122 static void linphonec_display_refer (LinphoneCore * lc, const char *refer_to);
123 static void linphonec_display_something (LinphoneCore * lc, const char *something);
124 static void linphonec_display_url (LinphoneCore * lc, const char *something, const char *url);
125 static void linphonec_display_warning (LinphoneCore * lc, const char *something);
126 static void linphonec_notify_received(LinphoneCore *lc, LinphoneCall *call, const char *from,const char *event);
127
128 static void linphonec_notify_presence_received(LinphoneCore *lc,LinphoneFriend *fid);
129 static void linphonec_new_unknown_subscriber(LinphoneCore *lc,
130                 LinphoneFriend *lf, const char *url);
131
132 static void linphonec_text_received(LinphoneCore *lc, LinphoneChatRoom *cr,
133                 const char *from, const char *msg);
134 static void linphonec_display_status (LinphoneCore * lc, const char *something);
135 static void linphonec_dtmf_received(LinphoneCore *lc, LinphoneCall *call, int dtmf);
136 static void print_prompt(LinphoneCore *opm);
137 void linphonec_out(const char *fmt,...);
138 /***************************************************************************
139  *
140  * Global variables
141  *
142  ***************************************************************************/
143
144 LinphoneCore *linphonec;
145 FILE *mylogfile;
146 #ifdef HAVE_READLINE
147 static char *histfile_name=NULL;
148 static char last_in_history[256];
149 #endif
150 //auto answer (-a) option
151 static bool_t auto_answer=FALSE;
152 static bool_t answer_call=FALSE;
153 static bool_t vcap_enabled=FALSE;
154 static bool_t display_enabled=FALSE;
155 static bool_t preview_enabled=FALSE;
156 static bool_t show_general_state=FALSE;
157 static bool_t unix_socket=FALSE;
158 static bool_t linphonec_running=TRUE;
159 LPC_AUTH_STACK auth_stack;
160 static int trace_level = 0;
161 static char *logfile_name = NULL;
162 static char configfile_name[PATH_MAX];
163 static char *sipAddr = NULL; /* for autocall */
164 #if !defined(_WIN32_WCE)
165 static ortp_pipe_t client_sock=ORTP_PIPE_INVALID;
166 #endif /*_WIN32_WCE*/
167 char prompt[PROMPT_MAX_LEN];
168 #if !defined(_WIN32_WCE)
169 static ortp_thread_t pipe_reader_th;
170 static bool_t pipe_reader_run=FALSE;
171 #endif /*_WIN32_WCE*/
172 #if !defined(_WIN32_WCE)
173 static ortp_pipe_t server_sock;
174 #endif /*_WIN32_WCE*/
175
176
177 extern VideoParams lpc_video_params;
178
179 void linphonec_call_identify(LinphoneCall* call){
180         static long callid=1;
181         linphone_call_set_user_pointer (call,(void*)callid);
182         callid++;
183 }
184
185 LinphoneCall *linphonec_get_call(long id){
186         const MSList *elem=linphone_core_get_calls(linphonec);
187         for (;elem!=NULL;elem=elem->next){
188                 LinphoneCall *call=(LinphoneCall*)elem->data;
189                 if (linphone_call_get_user_pointer (call)==(void*)id){
190                         return call;
191                 }
192         }
193         linphonec_out("Sorry, no call with id %i exists at this time.",id);
194         return NULL;
195 }
196
197 /***************************************************************************
198  *
199  * Linphone core callbacks
200  *
201  ***************************************************************************/
202
203 /*
204  * Linphone core callback
205  */
206 static void
207 linphonec_display_refer (LinphoneCore * lc, const char *refer_to)
208 {
209         linphonec_out("Receiving out of call refer to %s", refer_to);
210 }
211
212 /*
213  * Linphone core callback
214  */
215 static void
216 linphonec_display_something (LinphoneCore * lc, const char *something)
217 {
218         fprintf (stdout, "%s\n%s", something,prompt);
219         fflush(stdout);
220 }
221
222 /*
223  * Linphone core callback
224  */
225 static void
226 linphonec_display_status (LinphoneCore * lc, const char *something)
227 {
228         fprintf (stdout, "%s\n%s", something,prompt);
229         fflush(stdout);
230 }
231
232 /*
233  * Linphone core callback
234  */
235 static void
236 linphonec_display_warning (LinphoneCore * lc, const char *something)
237 {
238         fprintf (stdout, "Warning: %s\n%s", something,prompt);
239         fflush(stdout);
240 }
241
242 /*
243  * Linphone core callback
244  */
245 static void
246 linphonec_display_url (LinphoneCore * lc, const char *something, const char *url)
247 {
248         fprintf (stdout, "%s : %s\n", something, url);
249 }
250
251 /*
252  * Linphone core callback
253  */
254 static void
255 linphonec_prompt_for_auth(LinphoneCore *lc, const char *realm, const char *username)
256 {
257         /* no prompt possible when using pipes or tcp mode*/
258         if (unix_socket){
259                 linphone_core_abort_authentication(lc,NULL);
260         }else{
261                 LinphoneAuthInfo *pending_auth;
262
263                 if ( auth_stack.nitems+1 > MAX_PENDING_AUTH )
264                 {
265                         fprintf(stderr,
266                                 "Can't accept another authentication request.\n"
267                                 "Consider incrementing MAX_PENDING_AUTH macro.\n");
268                         return;
269                 }
270
271                 pending_auth=linphone_auth_info_new(username,NULL,NULL,NULL,realm);
272                 auth_stack.elem[auth_stack.nitems++]=pending_auth;
273         }
274 }
275
276 /*
277  * Linphone core callback
278  */
279 static void
280 linphonec_notify_received(LinphoneCore *lc, LinphoneCall *call, const char *from,const char *event)
281 {
282         if(!strcmp(event,"refer"))
283         {
284                 linphonec_out("The distand endpoint %s of call %li has been transfered, you can safely close the call.\n",
285                               from,(long)linphone_call_get_user_pointer (call));
286         }
287 }
288
289
290 /*
291  * Linphone core callback
292  */
293 static void
294 linphonec_notify_presence_received(LinphoneCore *lc,LinphoneFriend *fid)
295 {
296         char *tmp=linphone_address_as_string(linphone_friend_get_address(fid));
297         printf("Friend %s is %s\n", tmp, linphone_online_status_to_string(linphone_friend_get_status(fid)));
298         ms_free(tmp);
299         // todo: update Friend list state (unimplemented)
300 }
301
302 /*
303  * Linphone core callback
304  */
305 static void
306 linphonec_new_unknown_subscriber(LinphoneCore *lc, LinphoneFriend *lf,
307                 const char *url)
308 {
309         printf("Friend %s requested subscription "
310                 "(accept/deny is not implemented yet)\n", url);
311         // This means that this person wishes to be notified
312         // of your presence information (online, busy, away...).
313
314 }
315
316 static void linphonec_call_state_changed(LinphoneCore *lc, LinphoneCall *call, LinphoneCallState st, const char *msg){
317         char *from=linphone_call_get_remote_address_as_string(call);
318         long id=(long)linphone_call_get_user_pointer (call);
319         switch(st){
320                 case LinphoneCallEnd:
321                         linphonec_out("Call %i with %s ended.\n", id, from);
322                 break;
323                 case LinphoneCallResuming:
324                         linphonec_out("Resuming call %i with %s.\n", id, from);
325                 break;
326                 case LinphoneCallStreamsRunning:
327                         linphonec_out("Media streams established with %s for call %i.\n", from,id);
328                 break;
329                 case LinphoneCallPausing:
330                         linphonec_out("Pausing call %i with %s.\n", id, from);
331                 break;
332                 case LinphoneCallPaused:
333                         linphonec_out("Call %i with %s is now paused.\n", id, from);
334                 break;
335                 case LinphoneCallPausedByRemote:
336                         linphonec_out("Call %i has been paused by %s.\n",id,from);
337                 break;
338                 case LinphoneCallIncomingReceived:
339                         linphonec_call_identify(call);
340                         id=(long)linphone_call_get_user_pointer (call);
341                         linphonec_set_caller(from);
342                         if ( auto_answer)  {
343                                 answer_call=TRUE;
344                         }
345                         linphonec_out("Receiving new incoming call from %s, assigned id %i", from,id);
346                 break;
347                 case LinphoneCallOutgoingInit:
348                         linphonec_call_identify(call);
349                 break;
350                 default:
351                 break;
352         }
353         ms_free(from);
354 }
355
356 /*
357  * Linphone core callback
358  */
359 static void
360 linphonec_text_received(LinphoneCore *lc, LinphoneChatRoom *cr,
361                 const char *from, const char *msg)
362 {
363         printf("%s: %s\n", from, msg);
364         // TODO: provide mechanism for answering.. ('say' command?)
365 }
366
367
368 static void linphonec_dtmf_received(LinphoneCore *lc, LinphoneCall *call, int dtmf){
369         char *from=linphone_call_get_remote_address_as_string(call);
370         fprintf(stdout,"Receiving tone %c from %s\n",dtmf,from);
371         fflush(stdout);
372         ms_free(from);
373 }
374
375 static char received_prompt[PROMPT_MAX_LEN];
376 static ms_mutex_t prompt_mutex;
377 static bool_t have_prompt=FALSE;
378
379 static void *prompt_reader_thread(void *arg){
380         char *ret;
381         char tmp[PROMPT_MAX_LEN];
382         while ((ret=fgets(tmp,sizeof(tmp),stdin))!=NULL){
383                 ms_mutex_lock(&prompt_mutex);
384                 strcpy(received_prompt,ret);
385                 have_prompt=TRUE;
386                 ms_mutex_unlock(&prompt_mutex);
387         }
388         return NULL;
389 }
390
391 static void start_prompt_reader(void){
392         ortp_thread_t th;
393         ms_mutex_init(&prompt_mutex,NULL);
394         ortp_thread_create(&th,NULL,prompt_reader_thread,NULL);
395 }
396 #if !defined(_WIN32_WCE)
397 static ortp_pipe_t create_server_socket(void){
398         char path[128];
399 #ifndef WIN32
400         snprintf(path,sizeof(path)-1,"linphonec-%i",getuid());
401 #else
402         {
403                 TCHAR username[128];
404                 DWORD size=sizeof(username)-1;
405                 GetUserName(username,&size);
406                 snprintf(path,sizeof(path)-1,"linphonec-%s",username);
407         }
408 #endif
409         return ortp_server_pipe_create(path);
410 }
411
412
413 static void *pipe_thread(void*p){
414         char tmp[250];
415         server_sock=create_server_socket();
416         if (server_sock==ORTP_PIPE_INVALID) return NULL;
417         while(pipe_reader_run){
418                 while(client_sock!=ORTP_PIPE_INVALID){ /*sleep until the last command is finished*/
419 #ifndef WIN32
420                         usleep(20000);
421 #else
422                         Sleep(20);
423 #endif
424                 }
425                 client_sock=ortp_server_pipe_accept_client(server_sock);
426                 if (client_sock!=ORTP_PIPE_INVALID){
427                         int len;
428                         /*now read from the client */
429                         if ((len=ortp_pipe_read(client_sock,(uint8_t*)tmp,sizeof(tmp)-1))>0){
430                                 ortp_mutex_lock(&prompt_mutex);
431                                 tmp[len]='\0';
432                                 strcpy(received_prompt,tmp);
433                                 printf("Receiving command '%s'\n",received_prompt);fflush(stdout);
434                                 have_prompt=TRUE;
435                                 ortp_mutex_unlock(&prompt_mutex);
436                         }else{
437                                 printf("read nothing\n");fflush(stdout);
438                                 ortp_server_pipe_close_client(client_sock);
439                                 client_sock=ORTP_PIPE_INVALID;
440                         }
441
442                 }else{
443                         if (pipe_reader_run) fprintf(stderr,"accept() failed: %s\n",strerror(errno));
444                 }
445         }
446         ms_message("Exiting pipe_reader_thread.");
447         fflush(stdout);
448         return NULL;
449 }
450
451 static void start_pipe_reader(void){
452         ms_mutex_init(&prompt_mutex,NULL);
453         pipe_reader_run=TRUE;
454         ortp_thread_create(&pipe_reader_th,NULL,pipe_thread,NULL);
455 }
456
457 static void stop_pipe_reader(void){
458         pipe_reader_run=FALSE;
459         linphonec_command_finished();
460         ortp_server_pipe_close(server_sock);
461         ortp_thread_join(pipe_reader_th,NULL);
462 }
463 #endif /*_WIN32_WCE*/
464
465 #ifdef HAVE_READLINE
466 #define BOOL_HAVE_READLINE 1
467 #else
468 #define BOOL_HAVE_READLINE 0
469 #endif
470
471 char *linphonec_readline(char *prompt){
472         if (unix_socket || !BOOL_HAVE_READLINE ){
473                 static bool_t prompt_reader_started=FALSE;
474                 static bool_t pipe_reader_started=FALSE;
475                 if (!prompt_reader_started){
476                         start_prompt_reader();
477                         prompt_reader_started=TRUE;
478                 }
479                 if (unix_socket && !pipe_reader_started){
480 #if !defined(_WIN32_WCE)
481                         start_pipe_reader();
482                         pipe_reader_started=TRUE;
483 #endif /*_WIN32_WCE*/
484                 }
485                 fprintf(stdout,"%s",prompt);
486                 fflush(stdout);
487                 while(1){
488                         ms_mutex_lock(&prompt_mutex);
489                         if (have_prompt){
490                                 char *ret=strdup(received_prompt);
491                                 have_prompt=FALSE;
492                                 ms_mutex_unlock(&prompt_mutex);
493                                 return ret;
494                         }
495                         ms_mutex_unlock(&prompt_mutex);
496                         linphonec_idle_call();
497 #ifdef WIN32
498                         Sleep(20);
499                         /* Following is to get the video window going as it
500                                  should. Maybe should we only have this on when the option -V
501                                  or -D is on? */
502                         MSG msg;
503         
504                         if (PeekMessage(&msg, NULL, 0, 0,1)) {
505                                 TranslateMessage(&msg);
506                                 DispatchMessage(&msg);
507                         }
508 #else
509                         usleep(20000);
510 #endif
511                 }
512         }else{
513 #ifdef HAVE_READLINE
514                 return readline(prompt);
515 #endif
516         }
517 }
518
519 void linphonec_out(const char *fmt,...){
520         char *res;
521         va_list args;
522         va_start (args, fmt);
523         res=ortp_strdup_vprintf(fmt,args);
524         va_end (args);
525         printf("%s",res);
526         fflush(stdout);
527 #if !defined(_WIN32_WCE)
528         if (client_sock!=ORTP_PIPE_INVALID){
529                 if (ortp_pipe_write(client_sock,(uint8_t*)res,strlen(res))==-1){
530                         fprintf(stderr,"Fail to send output via pipe: %s",strerror(errno));
531                 }
532         }
533 #endif /*_WIN32_WCE*/
534         ortp_free(res);
535 }
536
537 void linphonec_command_finished(void){
538 #if !defined(_WIN32_WCE)
539         if (client_sock!=ORTP_PIPE_INVALID){
540                 ortp_server_pipe_close_client(client_sock);
541                 client_sock=ORTP_PIPE_INVALID;
542         }
543 #endif /*_WIN32_WCE*/
544 }
545
546 void linphonec_set_autoanswer(bool_t enabled){
547         auto_answer=enabled;
548 }
549
550 bool_t linphonec_get_autoanswer(){
551         return auto_answer;
552 }
553
554 LinphoneCoreVTable linphonec_vtable={0};
555
556 /***************************************************************************/
557 /*
558  * Main
559  *
560  * Use globals:
561  *
562  *      - char *histfile_name
563  *      - FILE *mylogfile
564  */
565 #if defined (_WIN32_WCE)
566
567 char **convert_args_to_ascii(int argc, _TCHAR **wargv){
568         int i;
569         char **result=malloc(argc*sizeof(char*));
570         char argtmp[128];
571         for(i=0;i<argc;++i){
572                 wcstombs(argtmp,wargv[i],sizeof(argtmp));
573                 result[i]=strdup(argtmp);
574         }
575         return result;
576 }
577
578 int _tmain(int argc, _TCHAR* wargv[]) {
579         char **argv=convert_args_to_ascii(argc,wargv);
580         trace_level=6;
581
582 #else
583 int
584 main (int argc, char *argv[]) {
585 #endif
586         linphonec_vtable.call_state_changed=linphonec_call_state_changed;
587         linphonec_vtable.notify_presence_recv = linphonec_notify_presence_received;
588         linphonec_vtable.new_unknown_subscriber = linphonec_new_unknown_subscriber;
589         linphonec_vtable.auth_info_requested = linphonec_prompt_for_auth;
590         linphonec_vtable.display_status = linphonec_display_status;
591         linphonec_vtable.display_message=linphonec_display_something;
592         linphonec_vtable.display_warning=linphonec_display_warning;
593         linphonec_vtable.display_url=linphonec_display_url;
594         linphonec_vtable.text_received=linphonec_text_received;
595         linphonec_vtable.dtmf_received=linphonec_dtmf_received;
596         linphonec_vtable.refer_received=linphonec_display_refer;
597         linphonec_vtable.notify_recv=linphonec_notify_received;
598         
599         if (! linphonec_init(argc, argv) ) exit(EXIT_FAILURE);
600
601         linphonec_main_loop (linphonec, sipAddr);
602
603         linphonec_finish(EXIT_SUCCESS);
604
605         exit(EXIT_SUCCESS); /* should never reach here */
606 }
607
608 /*
609  * Initialize linphonec
610  */
611 static int
612 linphonec_init(int argc, char **argv)
613 {
614
615         //g_mem_set_vtable(&dbgtable);
616
617         /*
618          * Set initial values for global variables
619          */
620         mylogfile = NULL;
621         
622         
623 #ifndef _WIN32
624         snprintf(configfile_name, PATH_MAX, "%s/.linphonerc",
625                         getenv("HOME"));
626 #elif defined(_WIN32_WCE)
627         strncpy(configfile_name,PACKAGE_DIR "\\linphonerc",PATH_MAX);
628         mylogfile=fopen(PACKAGE_DIR "\\" "linphonec.log","w");
629         printf("Logs are redirected in" PACKAGE_DIR "\\linphonec.log");
630 #else
631         snprintf(configfile_name, PATH_MAX, "%s/Linphone/linphonerc",
632                         getenv("APPDATA"));
633 #endif
634         /* Handle configuration filename changes */
635         switch (handle_configfile_migration())
636         {
637                 case -1: /* error during file copies */
638                         fprintf(stderr,
639                                 "Error in configuration file migration\n");
640                         break;
641
642                 case 0: /* nothing done */
643                 case 1: /* migrated */
644                 default:
645                         break;
646         }
647
648 #ifdef ENABLE_NLS
649         if (NULL == bindtextdomain (GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR))
650                 perror ("bindtextdomain failed");
651 #ifndef __ARM__
652         bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
653 #endif
654         textdomain (GETTEXT_PACKAGE);
655 #else
656         printf ("NLS disabled.\n");
657 #endif
658
659         linphonec_parse_cmdline(argc, argv);
660
661         if (trace_level > 0)
662         {
663                 if (logfile_name != NULL)
664                         mylogfile = fopen (logfile_name, "w+");
665
666                 if (mylogfile == NULL)
667                 {
668                         mylogfile = stdout;
669                         fprintf (stderr,
670                                  "INFO: no logfile, logging to stdout\n");
671                 }
672                 linphone_core_enable_logs(mylogfile);
673         }
674         else
675         {
676                 linphone_core_disable_logs();
677         }
678         /*
679          * Initialize auth stack
680          */
681         auth_stack.nitems=0;
682
683         /*
684          * Initialize linphone core
685          */
686         linphonec=linphone_core_new (&linphonec_vtable, configfile_name, NULL,
687                             NULL);
688         linphone_core_enable_video(linphonec,vcap_enabled,display_enabled);
689         linphone_core_enable_video_preview(linphonec,preview_enabled);
690         if (!(vcap_enabled || display_enabled)) printf("Warning: video is disabled in linphonec, use -V or -C or -D to enable.\n");
691 #ifdef HAVE_READLINE
692         /*
693          * Initialize readline
694          */
695         linphonec_initialize_readline();
696 #endif
697 #if !defined(_WIN32_WCE)
698         /*
699          * Initialize signal handlers
700          */
701         signal(SIGTERM, linphonec_finish);
702         signal(SIGINT, linphonec_finish);
703 #endif /*_WIN32_WCE*/
704         return 1;
705 }
706
707
708 void linphonec_main_loop_exit(void){
709         linphonec_running=FALSE;
710 }
711
712 /*
713  * Close linphonec, cleanly terminating
714  * any pending call
715  */
716 void
717 linphonec_finish(int exit_status)
718 {
719         linphonec_out("Terminating...\n");
720
721         /* Terminate any pending call */
722         linphone_core_terminate_all_calls(linphonec);
723 #ifdef HAVE_READLINE
724         linphonec_finish_readline();
725 #endif
726 #if !defined(_WIN32_WCE)
727         if (pipe_reader_run)
728                 stop_pipe_reader();
729 #endif /*_WIN32_WCE*/
730
731         linphone_core_destroy (linphonec);
732
733         if (mylogfile != NULL && mylogfile != stdout)
734         {
735                 fclose (mylogfile);
736         }
737
738         exit(exit_status);
739
740 }
741
742 /*
743  * This is called from idle_call() whenever
744  * pending_auth != NULL.
745  *
746  * It prompts user for a password.
747  * Hitting ^D (EOF) would make this function
748  * return 0 (Cancel).
749  * Any other input would try to set linphone core
750  * auth_password for the pending_auth, add the auth_info
751  * and return 1.
752  */
753 int
754 linphonec_prompt_for_auth_final(LinphoneCore *lc)
755 {
756         char *input, *iptr;
757         char auth_prompt[256];
758 #ifdef HAVE_READLINE
759         rl_hook_func_t *old_event_hook;
760 #endif
761         LinphoneAuthInfo *pending_auth=auth_stack.elem[auth_stack.nitems-1];
762
763         snprintf(auth_prompt, 256, "Password for %s on %s: ",
764                 pending_auth->username, pending_auth->realm);
765
766         printf("\n");
767 #ifdef HAVE_READLINE
768         /*
769          * Disable event hook to avoid entering an
770          * infinite loop. This would prevent idle_call
771          * from being called during authentication reads.
772          * Note that it might be undesiderable...
773          */
774         old_event_hook=rl_event_hook;
775         rl_event_hook=NULL;
776 #endif
777
778         while (1)
779         {
780                 input=linphonec_readline(auth_prompt);
781
782                 /*
783                  * If EOF (^D) is sent you probably don't want
784                  * to provide an auth password... should give up
785                  * the operation, but there's no mechanism to
786                  * send this info back to caller currently...
787                  */
788                 if ( ! input )
789                 {
790                         printf("Cancel requested, but not implemented.\n");
791                         continue;
792                 }
793
794                 /* Strip blanks */
795                 iptr=lpc_strip_blanks(input);
796
797                 /*
798                  * Only blanks, continue asking
799                  */
800                 if ( ! *iptr )
801                 {
802                         free(input);
803                         continue;
804                 }
805
806                 /* Something typed, let's try */
807                 break;
808         }
809
810         /*
811          * No check is done here to ensure password is correct.
812          * I guess password will be asked again later.
813          */
814         linphone_auth_info_set_passwd(pending_auth, input);
815         linphone_core_add_auth_info(lc, pending_auth);
816         linphone_auth_info_destroy(pending_auth);
817         auth_stack.elem[auth_stack.nitems-1]=0;
818         --(auth_stack.nitems);
819 #ifdef HAVE_READLINE
820         /*
821          * Reset line_buffer, to avoid the password
822          * to be used again from outer readline
823          */
824         rl_line_buffer[0]='\0';
825         rl_event_hook=old_event_hook;
826 #endif
827         return 1;
828 }
829
830 void
831 print_usage (int exit_status)
832 {
833         fprintf (stdout, "\n\
834 usage: linphonec [-c file] [-s sipaddr] [-a] [-V] [-d level ] [-l logfile]\n\
835        linphonec -v\n\
836 \n\
837   -c  file             specify path of configuration file.\n\
838   -d  level            be verbose. 0 is no output. 6 is all output\n\
839   -l  logfile          specify the log file for your SIP phone\n\
840   -s  sipaddress       specify the sip call to do at startup\n\
841   -a                   enable auto answering for incoming calls\n\
842   -V                   enable video features globally (disabled by default)\n\
843   -C                   enable video capture only (disabled by default)\n\
844   -D                   enable video display only (disabled by default)\n\
845   -S                   show general state messages (disabled by default)\n\
846   -v or --version      display version and exits.\n");
847
848         exit(exit_status);
849 }
850
851 #ifdef VIDEO_ENABLED
852
853 #ifdef HAVE_X11_XLIB_H
854 static void sdl_x11_apply_video_params(){
855         static SDL_SysWMinfo info;
856         bool_t wminfo_ready=FALSE;
857         
858         
859         SDL_VERSION(&info.version);
860         if ( SDL_GetWMInfo(&info) ) {
861                 if ( info.subsystem == SDL_SYSWM_X11 ) {
862                         wminfo_ready=TRUE;
863                 }
864         }
865         
866         if ( !wminfo_ready) return;
867         
868         {
869                 XWindowChanges wc;
870                 unsigned int flags=0;
871                 Display *display = info.info.x11.display;
872                 Window window = info.info.x11.wmwindow;
873                 
874                 memset(&wc,0,sizeof(wc));
875                 wc.x=lpc_video_params.x;
876                 wc.y=lpc_video_params.y;
877                 wc.width=lpc_video_params.w;
878                 wc.height=lpc_video_params.h;
879                 if (lpc_video_params.x!=-1 ){
880                         flags|=CWX|CWY;
881                 }
882                 if (lpc_video_params.w!=-1){
883                         flags|=CWWidth|CWHeight;
884                 }
885                 info.info.x11.lock_func();
886                 XConfigureWindow(display,window,flags,&wc);
887                 if (lpc_video_params.show)
888                         XMapWindow(display,window);
889                 else
890                         XUnmapWindow(display,window);
891                 info.info.x11.unlock_func();
892         }
893 }
894 #endif
895
896
897 static void lpc_apply_video_params(){
898         static unsigned long prev_wid=0;
899
900         unsigned long wid=linphone_core_get_native_video_window_id (linphonec);
901
902         if (wid!=0 && (lpc_video_params.refresh || prev_wid!=wid)){
903                 lpc_video_params.refresh=FALSE;
904 #ifdef HAVE_X11_XLIB_H
905                 sdl_x11_apply_video_params();
906 #endif
907         }
908         prev_wid=wid;
909 }
910
911 #endif
912
913
914 /*
915  *
916  * Called every second from main read loop.
917  *
918  * Will use the following globals:
919  *
920  *  - LinphoneCore linphonec
921  *  - LPC_AUTH_STACK auth_stack;
922  *
923  */
924 static int
925 linphonec_idle_call ()
926 {
927         LinphoneCore *opm=linphonec;
928
929         /* Uncomment the following to verify being called */
930         /* printf(".\n"); */
931
932         linphone_core_iterate(opm);
933         if (answer_call){
934                 fprintf (stdout, "-------auto answering to call-------\n" );
935                 linphone_core_accept_call(opm,NULL);
936                 answer_call=FALSE;
937         }
938
939         if ( auth_stack.nitems )
940         {
941                 /*
942                  * Inhibit command completion
943                  * during password prompts
944                  */
945 #ifdef HAVE_READLINE
946                 rl_inhibit_completion=1;
947 #endif
948                 linphonec_prompt_for_auth_final(opm);
949 #ifdef HAVE_READLINE
950                 rl_inhibit_completion=0;
951 #endif
952         }
953
954 #ifdef VIDEO_ENABLED
955         lpc_apply_video_params();
956 #endif
957
958         return 0;
959 }
960
961 #ifdef HAVE_READLINE
962 /*
963  * Use globals:
964  *
965  *      - char *histfile_name (also sets this)
966  *      - char *last_in_history (allocates it)
967  */
968 static int
969 linphonec_initialize_readline()
970 {
971         /*rl_bind_key('\t', rl_insert);*/
972
973         /* Allow conditional parsing of ~/.inputrc */
974         rl_readline_name = "linphonec";
975
976         /* Call idle_call() every second */
977         rl_set_keyboard_input_timeout(LPC_READLINE_TIMEOUT);
978         rl_event_hook=linphonec_idle_call;
979
980         /* Set history file and read it */
981         histfile_name = ms_strdup_printf ("%s/.linphonec_history",
982                 getenv("HOME"));
983         read_history(histfile_name);
984
985         /* Initialized last_in_history cache*/
986         last_in_history[0] = '\0';
987
988         /* Register a completion function */
989         rl_attempted_completion_function = linephonec_readline_completion;
990
991         /* printf("Readline initialized.\n"); */
992         setlinebuf(stdout);
993         return 0;
994 }
995
996 /*
997  * Uses globals:
998  *
999  *      - char *histfile_name (writes history to file and frees it)
1000  *      - char *last_in_history (frees it)
1001  *
1002  */
1003 static int
1004 linphonec_finish_readline()
1005 {
1006
1007         stifle_history(HISTSIZE);
1008         write_history(histfile_name);
1009         free(histfile_name);
1010         histfile_name=NULL;
1011         return 0;
1012 }
1013
1014 #endif
1015
1016 static void print_prompt(LinphoneCore *opm){
1017 #ifdef IDENTITY_AS_PROMPT
1018         snprintf(prompt, PROMPT_MAX_LEN, "%s> ",
1019                 linphone_core_get_primary_contact(opm));
1020 #else
1021         snprintf(prompt, PROMPT_MAX_LEN, "linphonec> ");
1022 #endif
1023 }
1024
1025 static int
1026 linphonec_main_loop (LinphoneCore * opm, char * sipAddr)
1027 {
1028         char buf[LINE_MAX_LEN]; /* auto call handling */
1029         char *input;
1030
1031         print_prompt(opm);
1032
1033
1034         /* auto call handling */
1035         if (sipAddr != NULL )
1036         {
1037                 snprintf (buf, sizeof(buf),"call %s", sipAddr);
1038                 linphonec_parse_command_line(linphonec, buf);
1039         }
1040
1041         while (linphonec_running && (input=linphonec_readline(prompt)))
1042         {
1043                 char *iptr; /* input and input pointer */
1044                 size_t input_len;
1045
1046                 /* Strip blanks */
1047                 iptr=lpc_strip_blanks(input);
1048
1049                 input_len = strlen(iptr);
1050
1051                 /*
1052                  * Do nothing but release memory
1053                  * if only blanks are read
1054                  */
1055                 if ( ! input_len )
1056                 {
1057                         free(input);
1058                         continue;
1059                 }
1060
1061 #ifdef HAVE_READLINE
1062                 /*
1063                  * Only add to history if not already
1064                  * last item in it, and only if the command
1065                  * doesn't start with a space (to allow for
1066                  * hiding passwords)
1067                  */
1068                 if ( iptr == input && strcmp(last_in_history, iptr) )
1069                 {
1070                         strncpy(last_in_history,iptr,sizeof(last_in_history));
1071                         last_in_history[sizeof(last_in_history)-1]='\0';
1072                         add_history(iptr);
1073                 }
1074 #endif
1075
1076                 linphonec_parse_command_line(linphonec, iptr);
1077                 linphonec_command_finished();
1078                 free(input);
1079         }
1080
1081         return 0;
1082 }
1083
1084 /*
1085  *  Parse command line switches
1086  *
1087  *  Use globals:
1088  *
1089  *      - int trace_level
1090  *      - char *logfile_name
1091  *      - char *configfile_name
1092  *      - char *sipAddr
1093  */
1094 static int
1095 linphonec_parse_cmdline(int argc, char **argv)
1096 {
1097         int arg_num=1;
1098
1099         while (arg_num < argc)
1100         {
1101                 int old_arg_num = arg_num;
1102                 if (strncmp ("-d", argv[arg_num], 2) == 0)
1103                 {
1104                         arg_num++;
1105                         if (arg_num < argc)
1106                                 trace_level = atoi (argv[arg_num]);
1107                         else
1108                                 trace_level = 1;
1109                 }
1110                 else if (strncmp ("-l", argv[arg_num], 2) == 0)
1111                 {
1112                         arg_num++;
1113                         if (arg_num < argc)
1114                                 logfile_name = argv[arg_num];
1115                 }
1116                 else if (strncmp ("-c", argv[arg_num], 2) == 0)
1117                 {
1118                         if ( ++arg_num >= argc ) print_usage(EXIT_FAILURE);
1119 #if !defined(_WIN32_WCE)
1120                         if (access(argv[arg_num],F_OK)!=0 )
1121                         {
1122                                 fprintf (stderr,
1123                                         "Cannot open config file %s.\n",
1124                                          argv[arg_num]);
1125                                 exit(EXIT_FAILURE);
1126                         }
1127 #endif /*_WIN32_WCE*/
1128                         snprintf(configfile_name, PATH_MAX, "%s", argv[arg_num]);
1129                 }
1130                 else if (strncmp ("-s", argv[arg_num], 2) == 0)
1131                 {
1132                         arg_num++;
1133                         if (arg_num < argc)
1134                                 sipAddr = argv[arg_num];
1135                 }
1136                 else if (strncmp ("-a", argv[arg_num], 2) == 0)
1137                 {
1138                         auto_answer = TRUE;
1139                 }
1140                 else if (strncmp ("-C", argv[arg_num], 2) == 0)
1141                 {
1142                         vcap_enabled = TRUE;
1143                 }
1144                 else if (strncmp ("-D", argv[arg_num], 2) == 0)
1145                 {
1146                         display_enabled = TRUE;
1147                 }
1148                 else if (strncmp ("-V", argv[arg_num], 2) == 0)
1149                 {
1150                         display_enabled = TRUE;
1151                         vcap_enabled = TRUE;
1152                         preview_enabled=TRUE;
1153                 }
1154                 else if ((strncmp ("-v", argv[arg_num], 2) == 0)
1155                          ||
1156                          (strncmp
1157                           ("--version", argv[arg_num],
1158                            strlen ("--version")) == 0))
1159                 {
1160 #if !defined(_WIN32_WCE)
1161                         printf ("version: " LINPHONE_VERSION "\n");
1162 #endif
1163                         exit (EXIT_SUCCESS);
1164                 }
1165                 else if (strncmp ("-S", argv[arg_num], 2) == 0)
1166                 {
1167                         show_general_state = TRUE;
1168                 }
1169                 else if (strncmp ("--pipe", argv[arg_num], 6) == 0)
1170                 {
1171                         unix_socket=1;
1172                 }
1173                 else if (old_arg_num == arg_num)
1174                 {
1175                         fprintf (stderr, "ERROR: bad arguments\n");
1176                         print_usage (EXIT_FAILURE);
1177                 }
1178                 arg_num++;
1179         }
1180
1181         return 1;
1182 }
1183
1184 /*
1185  * Up to version 1.2.1 linphone used ~/.linphonec for
1186  * CLI and ~/.gnome2/linphone for GUI as configuration file.
1187  * In newer version both interfaces will use ~/.linphonerc.
1188  *
1189  * This function helps transparently migrating from one
1190  * to the other layout using the following heuristic:
1191  *
1192  *      IF new_config EXISTS => do nothing
1193  *      ELSE IF old_cli_config EXISTS => copy to new_config
1194  *      ELSE IF old_gui_config EXISTS => copy to new_config
1195  *
1196  * Returns:
1197  *       0 if it did nothing
1198  *       1 if it migrated successfully
1199  *      -1 on error
1200  */
1201 static int
1202 handle_configfile_migration()
1203 {
1204 #if !defined(_WIN32_WCE)
1205         char *old_cfg_gui;
1206         char *old_cfg_cli;
1207         char *new_cfg;
1208 #if !defined(_WIN32_WCE)
1209         const char *home = getenv("HOME");
1210 #else
1211         const char *home = ".";
1212 #endif /*_WIN32_WCE*/
1213         new_cfg = ms_strdup_printf("%s/.linphonerc", home);
1214
1215         /*
1216          * If the *NEW* configuration already exists
1217          * do nothing.
1218          */
1219         if (access(new_cfg,F_OK)==0)
1220         {
1221                 free(new_cfg);
1222                 return 0;
1223         }
1224
1225         old_cfg_cli = ms_strdup_printf("%s/.linphonec", home);
1226
1227         /*
1228          * If the *OLD* CLI configurations exist copy it to
1229          * the new file and make it a symlink.
1230          */
1231         if (access(old_cfg_cli, F_OK)==0)
1232         {
1233                 if ( ! copy_file(old_cfg_cli, new_cfg) )
1234                 {
1235                         free(old_cfg_cli);
1236                         free(new_cfg);
1237                         return -1;
1238                 }
1239                 printf("%s copied to %s\n", old_cfg_cli, new_cfg);
1240                 free(old_cfg_cli);
1241                 free(new_cfg);
1242                 return 1;
1243         }
1244
1245         free(old_cfg_cli);
1246         old_cfg_gui = ms_strdup_printf("%s/.gnome2/linphone", home);
1247
1248         /*
1249          * If the *OLD* GUI configurations exist copy it to
1250          * the new file and make it a symlink.
1251          */
1252         if (access(old_cfg_gui, F_OK)==0)
1253         {
1254                 if ( ! copy_file(old_cfg_gui, new_cfg) )
1255                 {
1256                         exit(EXIT_FAILURE);
1257                         free(old_cfg_gui);
1258                         free(new_cfg);
1259                         return -1;
1260                 }
1261                 printf("%s copied to %s\n", old_cfg_gui, new_cfg);
1262                 free(old_cfg_gui);
1263                 free(new_cfg);
1264                 return 1;
1265         }
1266
1267         free(old_cfg_gui);
1268         free(new_cfg);
1269 #endif /*_WIN32_WCE*/
1270         return 0;
1271 }
1272 #if !defined(_WIN32_WCE)
1273 /*
1274  * Copy file "from" to file "to".
1275  * Destination file is truncated if existing.
1276  * Return 1 on success, 0 on error (printing an error).
1277  */
1278 static int
1279 copy_file(const char *from, const char *to)
1280 {
1281         char message[256];
1282         FILE *in, *out;
1283         char buf[256];
1284         size_t n;
1285
1286         /* Open "from" file for reading */
1287         in=fopen(from, "r");
1288         if ( in == NULL )
1289         {
1290                 snprintf(message, 255, "Can't open %s for reading: %s\n",
1291                         from, strerror(errno));
1292                 fprintf(stderr, "%s", message);
1293                 return 0;
1294         }
1295
1296         /* Open "to" file for writing (will truncate existing files) */
1297         out=fopen(to, "w");
1298         if ( out == NULL )
1299         {
1300                 snprintf(message, 255, "Can't open %s for writing: %s\n",
1301                         to, strerror(errno));
1302                 fprintf(stderr, "%s", message);
1303                 return 0;
1304         }
1305
1306         /* Copy data from "in" to "out" */
1307         while ( (n=fread(buf, 1, sizeof buf, in)) > 0 )
1308         {
1309                 if ( ! fwrite(buf, 1, n, out) )
1310                 {
1311                         return 0;
1312                 }
1313         }
1314
1315         fclose(in);
1316         fclose(out);
1317
1318         return 1;
1319 }
1320 #endif /*_WIN32_WCE*/
1321
1322 #ifdef HAVE_READLINE
1323 static char **
1324 linephonec_readline_completion(const char *text, int start, int end)
1325 {
1326         char **matches = NULL;
1327
1328         /*
1329          * Prevent readline from falling
1330          * back to filename-completion
1331          */
1332         rl_attempted_completion_over=1;
1333
1334         /*
1335          * If this is the start of line we complete with commands
1336          */
1337         if ( ! start )
1338         {
1339                 return rl_completion_matches(text, linphonec_command_generator);
1340         }
1341
1342         /*
1343          * Otherwise, we should peek at command name
1344          * or context to implement a smart completion.
1345          * For example: "call .." could return
1346          * friends' sip-uri as matches
1347          */
1348
1349         return matches;
1350 }
1351
1352 #endif
1353
1354 /*
1355  * Strip blanks from a string.
1356  * Return a pointer into the provided string.
1357  * Modifies input adding a NULL at first
1358  * of trailing blanks.
1359  */
1360 char *
1361 lpc_strip_blanks(char *input)
1362 {
1363         char *iptr;
1364
1365         /* Find first non-blank */
1366         while(*input && isspace(*input)) ++input;
1367
1368         /* Find last non-blank */
1369         iptr=input+strlen(input);
1370         if (iptr > input) {
1371                 while(isspace(*--iptr));
1372                 *(iptr+1)='\0';
1373         }
1374
1375         return input;
1376 }
1377
1378 /****************************************************************************
1379  *
1380  * $Log: linphonec.c,v $
1381  * Revision 1.57  2007/11/14 13:40:27  smorlat
1382  * fix --disable-video build.
1383  *
1384  * Revision 1.56  2007/09/26 14:07:27  fixkowalski
1385  * - ANSI/C++ compilation issues with non-GCC compilers
1386  * - Faster epm-based packaging
1387  * - Ability to build & run on FC6's eXosip/osip
1388  *
1389  * Revision 1.55  2007/09/24 16:01:58  smorlat
1390  * fix bugs.
1391  *
1392  * Revision 1.54  2007/08/22 14:06:11  smorlat
1393  * authentication bugs fixed.
1394  *
1395  * Revision 1.53  2007/02/13 21:31:01  smorlat
1396  * added patch for general state.
1397  * new doxygen for oRTP
1398  * gtk-doc removed.
1399  *
1400  * Revision 1.52  2007/01/10 14:11:24  smorlat
1401  * add --video to linphonec.
1402  *
1403  * Revision 1.51  2006/08/21 12:49:59  smorlat
1404  * merged several little patches.
1405  *
1406  * Revision 1.50  2006/07/26 08:17:28  smorlat
1407  * fix bugs.
1408  *
1409  * Revision 1.49  2006/07/17 18:45:00  smorlat
1410  * support for several event queues in ortp.
1411  * glib dependency removed from coreapi/ and console/
1412  *
1413  * Revision 1.48  2006/04/09 12:45:32  smorlat
1414  * linphonec improvements.
1415  *
1416  * Revision 1.47  2006/04/04 08:04:34  smorlat
1417  * switched to mediastreamer2, most bugs fixed.
1418  *
1419  * Revision 1.46  2006/03/16 17:17:40  smorlat
1420  * fix various bugs.
1421  *
1422  * Revision 1.45  2006/03/12 21:48:31  smorlat
1423  * gcc-2.95 compile error fixed.
1424  * mediastreamer2 in progress
1425  *
1426  * Revision 1.44  2006/03/04 11:17:10  smorlat
1427  * mediastreamer2 in progress.
1428  *
1429  * Revision 1.43  2006/02/13 09:50:50  strk
1430  * fixed unused variable warning.
1431  *
1432  * Revision 1.42  2006/02/02 15:39:18  strk
1433  * - Added 'friend list' and 'friend call' commands
1434  * - Allowed for multiple DTFM send in a single line
1435  * - Added status-specific callback (bare version)
1436  *
1437  * Revision 1.41  2006/02/02 13:30:05  strk
1438  * - Padded vtable with missing callbacks
1439  *   (fixing a segfault on friends subscription)
1440  * - Handled friends notify (bare version)
1441  * - Handled text messages receive (bare version)
1442  * - Printed message on subscription request (bare version)
1443  *
1444  * Revision 1.40  2006/01/26 09:48:05  strk
1445  * Added limits.h include
1446  *
1447  * Revision 1.39  2006/01/26 02:11:01  strk
1448  * Removed unused variables, fixed copyright date
1449  *
1450  * Revision 1.38  2006/01/25 18:33:02  strk
1451  * Removed the -t swich, terminate_on_close made the default behaviour
1452  *
1453  * Revision 1.37  2006/01/20 14:12:34  strk
1454  * Added linphonec_init() and linphonec_finish() functions.
1455  * Handled SIGINT and SIGTERM to invoke linphonec_finish().
1456  * Handling of auto-termination (-t) moved to linphonec_finish().
1457  * Reworked main (input read) loop to not rely on 'terminate'
1458  * and 'run' variable (dropped). configfile_name allocated on stack
1459  * using PATH_MAX limit. Changed print_usage signature to allow
1460  * for an exit_status specification.
1461  *
1462  * Revision 1.36  2006/01/18 09:25:32  strk
1463  * Command completion inhibited in proxy addition and auth request prompts.
1464  * Avoided use of readline's internal filename completion.
1465  *
1466  * Revision 1.35  2006/01/14 13:29:32  strk
1467  * Reworked commands interface to use a table structure,
1468  * used by command line parser and help function.
1469  * Implemented first level of completion (commands).
1470  * Added notification of invalid "answer" and "terminate"
1471  * commands (no incoming call, no active call).
1472  * Forbidden "call" intialization when a call is already active.
1473  * Cleaned up all commands, adding more feedback and error checks.
1474  *
1475  * Revision 1.34  2006/01/13 13:00:29  strk
1476  * Added linphonec.h. Code layout change (added comments, forward decl,
1477  * globals on top, copyright notices and Logs). Handled out-of-memory
1478  * condition on history management. Removed assumption on sizeof(char).
1479  * Fixed bug in authentication prompt (introduced by readline).
1480  * Added support for multiple authentication requests (up to MAX_PENDING_AUTH).
1481  *
1482  *
1483  ****************************************************************************/
1484