1 /* $Id$ */
2 /*
3  * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
4  * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20 
21 #include "pjsua_app_common.h"
22 
23 #define THIS_FILE	"pjsua_app_cli.c"
24 
25 #define CHECK_PJSUA_RUNNING() if (pjsua_get_state()!=PJSUA_STATE_RUNNING) \
26 				  return PJ_EINVALIDOP
27 
28 /* CLI command id */
29 /* level 1 command */
30 #define CMD_CALL		    100
31 #define CMD_PRESENCE		    200
32 #define CMD_ACCOUNT		    300
33 #define CMD_MEDIA		    400
34 #define CMD_CONFIG		    500
35 #define CMD_VIDEO		    600
36 #define CMD_SLEEP		    700
37 #define CMD_ECHO		    800
38 #define CMD_NETWORK		    900
39 #define CMD_QUIT		    110
40 #define CMD_RESTART		    120
41 #define CMD_HANDLE_IP_CHANGE	    130
42 
43 /* call level 2 command */
44 #define CMD_CALL_NEW		    ((CMD_CALL*10)+1)
45 #define CMD_CALL_MULTI		    ((CMD_CALL*10)+2)
46 #define CMD_CALL_ANSWER		    ((CMD_CALL*10)+3)
47 #define CMD_CALL_HANGUP		    ((CMD_CALL*10)+4)
48 #define CMD_CALL_HANGUP_ALL	    ((CMD_CALL*10)+5)
49 #define CMD_CALL_HOLD		    ((CMD_CALL*10)+6)
50 #define CMD_CALL_REINVITE	    ((CMD_CALL*10)+7)
51 #define CMD_CALL_UPDATE		    ((CMD_CALL*10)+8)
52 #define CMD_CALL_NEXT		    ((CMD_CALL*10)+9)
53 #define CMD_CALL_PREVIOUS	    ((CMD_CALL*10)+10)
54 #define CMD_CALL_TRANSFER	    ((CMD_CALL*10)+11)
55 #define CMD_CALL_TRANSFER_REPLACE   ((CMD_CALL*10)+12)
56 #define CMD_CALL_REDIRECT	    ((CMD_CALL*10)+13)
57 #define CMD_CALL_D2833		    ((CMD_CALL*10)+14)
58 #define CMD_CALL_INFO		    ((CMD_CALL*10)+15)
59 #define CMD_CALL_DUMP_Q		    ((CMD_CALL*10)+16)
60 #define CMD_CALL_SEND_ARB	    ((CMD_CALL*10)+17)
61 #define CMD_CALL_LIST		    ((CMD_CALL*10)+18)
62 
63 /* im & presence level 2 command */
64 #define CMD_PRESENCE_ADD_BUDDY	    ((CMD_PRESENCE*10)+1)
65 #define CMD_PRESENCE_DEL_BUDDY	    ((CMD_PRESENCE*10)+2)
66 #define CMD_PRESENCE_SEND_IM	    ((CMD_PRESENCE*10)+3)
67 #define CMD_PRESENCE_SUB	    ((CMD_PRESENCE*10)+4)
68 #define CMD_PRESENCE_UNSUB	    ((CMD_PRESENCE*10)+5)
69 #define CMD_PRESENCE_TOG_STATE	    ((CMD_PRESENCE*10)+6)
70 #define CMD_PRESENCE_TEXT	    ((CMD_PRESENCE*10)+7)
71 #define CMD_PRESENCE_LIST	    ((CMD_PRESENCE*10)+8)
72 
73 /* account level 2 command */
74 #define CMD_ACCOUNT_ADD		    ((CMD_ACCOUNT*10)+1)
75 #define CMD_ACCOUNT_DEL		    ((CMD_ACCOUNT*10)+2)
76 #define CMD_ACCOUNT_MOD		    ((CMD_ACCOUNT*10)+3)
77 #define CMD_ACCOUNT_REG		    ((CMD_ACCOUNT*10)+4)
78 #define CMD_ACCOUNT_UNREG	    ((CMD_ACCOUNT*10)+5)
79 #define CMD_ACCOUNT_NEXT	    ((CMD_ACCOUNT*10)+6)
80 #define CMD_ACCOUNT_PREV	    ((CMD_ACCOUNT*10)+7)
81 #define CMD_ACCOUNT_SHOW	    ((CMD_ACCOUNT*10)+8)
82 
83 /* conference & media level 2 command */
84 #define CMD_MEDIA_LIST		    ((CMD_MEDIA*10)+1)
85 #define CMD_MEDIA_CONF_CONNECT	    ((CMD_MEDIA*10)+2)
86 #define CMD_MEDIA_CONF_DISCONNECT   ((CMD_MEDIA*10)+3)
87 #define CMD_MEDIA_ADJUST_VOL	    ((CMD_MEDIA*10)+4)
88 #define CMD_MEDIA_CODEC_PRIO	    ((CMD_MEDIA*10)+5)
89 #define CMD_MEDIA_SPEAKER_TOGGLE    ((CMD_MEDIA*10)+6)
90 
91 /* status & config level 2 command */
92 #define CMD_CONFIG_DUMP_STAT	    ((CMD_CONFIG*10)+1)
93 #define CMD_CONFIG_DUMP_DETAIL	    ((CMD_CONFIG*10)+2)
94 #define CMD_CONFIG_DUMP_CONF	    ((CMD_CONFIG*10)+3)
95 #define CMD_CONFIG_WRITE_SETTING    ((CMD_CONFIG*10)+4)
96 
97 /* video level 2 command */
98 #define CMD_VIDEO_ENABLE	    ((CMD_VIDEO*10)+1)
99 #define CMD_VIDEO_DISABLE	    ((CMD_VIDEO*10)+2)
100 #define CMD_VIDEO_ACC		    ((CMD_VIDEO*10)+3)
101 #define CMD_VIDEO_CALL		    ((CMD_VIDEO*10)+4)
102 #define CMD_VIDEO_DEVICE	    ((CMD_VIDEO*10)+5)
103 #define CMD_VIDEO_CODEC		    ((CMD_VIDEO*10)+6)
104 #define CMD_VIDEO_WIN		    ((CMD_VIDEO*10)+7)
105 #define CMD_VIDEO_CONF		    ((CMD_VIDEO*10)+8)
106 
107 /* video level 3 command */
108 #define CMD_VIDEO_ACC_SHOW	    ((CMD_VIDEO_ACC*10)+1)
109 #define CMD_VIDEO_ACC_AUTORX	    ((CMD_VIDEO_ACC*10)+2)
110 #define CMD_VIDEO_ACC_AUTOTX	    ((CMD_VIDEO_ACC*10)+3)
111 #define CMD_VIDEO_ACC_CAP_ID	    ((CMD_VIDEO_ACC*10)+4)
112 #define CMD_VIDEO_ACC_REN_ID	    ((CMD_VIDEO_ACC*10)+5)
113 #define CMD_VIDEO_CALL_RX	    ((CMD_VIDEO_CALL*10)+1)
114 #define CMD_VIDEO_CALL_TX	    ((CMD_VIDEO_CALL*10)+2)
115 #define CMD_VIDEO_CALL_ADD	    ((CMD_VIDEO_CALL*10)+3)
116 #define CMD_VIDEO_CALL_ENABLE	    ((CMD_VIDEO_CALL*10)+4)
117 #define CMD_VIDEO_CALL_DISABLE	    ((CMD_VIDEO_CALL*10)+5)
118 #define CMD_VIDEO_CALL_CAP	    ((CMD_VIDEO_CALL*10)+6)
119 #define CMD_VIDEO_DEVICE_LIST	    ((CMD_VIDEO_DEVICE*10)+1)
120 #define CMD_VIDEO_DEVICE_REFRESH    ((CMD_VIDEO_DEVICE*10)+2)
121 #define CMD_VIDEO_DEVICE_PREVIEW    ((CMD_VIDEO_DEVICE*10)+3)
122 #define CMD_VIDEO_CODEC_LIST	    ((CMD_VIDEO_CODEC*10)+1)
123 #define CMD_VIDEO_CODEC_PRIO	    ((CMD_VIDEO_CODEC*10)+2)
124 #define CMD_VIDEO_CODEC_FPS	    ((CMD_VIDEO_CODEC*10)+3)
125 #define CMD_VIDEO_CODEC_BITRATE	    ((CMD_VIDEO_CODEC*10)+4)
126 #define CMD_VIDEO_CODEC_SIZE	    ((CMD_VIDEO_CODEC*10)+5)
127 #define CMD_VIDEO_WIN_LIST	    ((CMD_VIDEO_WIN*10)+1)
128 #define CMD_VIDEO_WIN_ARRANGE	    ((CMD_VIDEO_WIN*10)+2)
129 #define CMD_VIDEO_WIN_SHOW	    ((CMD_VIDEO_WIN*10)+3)
130 #define CMD_VIDEO_WIN_HIDE	    ((CMD_VIDEO_WIN*10)+4)
131 #define CMD_VIDEO_WIN_MOVE	    ((CMD_VIDEO_WIN*10)+5)
132 #define CMD_VIDEO_WIN_RESIZE	    ((CMD_VIDEO_WIN*10)+6)
133 #define CMD_VIDEO_CONF_LIST	    ((CMD_VIDEO_CONF*10)+1)
134 #define CMD_VIDEO_CONF_CONNECT	    ((CMD_VIDEO_CONF*10)+2)
135 #define CMD_VIDEO_CONF_DISCONNECT   ((CMD_VIDEO_CONF*10)+3)
136 
137 /* dynamic choice argument list */
138 #define DYN_CHOICE_START	    9900
139 #define DYN_CHOICE_BUDDY_ID	    (DYN_CHOICE_START)+1
140 #define DYN_CHOICE_ACCOUNT_ID	    (DYN_CHOICE_START)+2
141 #define DYN_CHOICE_MEDIA_PORT	    (DYN_CHOICE_START)+3
142 #define DYN_CHOICE_AUDIO_CODEC_ID   (DYN_CHOICE_START)+4
143 #define DYN_CHOICE_CAP_DEV_ID	    (DYN_CHOICE_START)+5
144 #define DYN_CHOICE_REN_DEV_ID	    (DYN_CHOICE_START)+6
145 #define DYN_CHOICE_VID_DEV_ID	    (DYN_CHOICE_START)+7
146 #define DYN_CHOICE_STREAM_ID	    (DYN_CHOICE_START)+8
147 #define DYN_CHOICE_VIDEO_CODEC_ID   (DYN_CHOICE_START)+9
148 #define DYN_CHOICE_WIN_ID	    (DYN_CHOICE_START)+10
149 #define DYN_CHOICE_CALL_ID	    (DYN_CHOICE_START)+11
150 #define DYN_CHOICE_ADDED_BUDDY_ID   (DYN_CHOICE_START)+12
151 
152 static pj_bool_t	   pj_inited = PJ_FALSE;
153 static pj_caching_pool	   cli_cp;
154 static pj_bool_t	   cli_cp_inited = PJ_FALSE;
155 static pj_cli_t		   *cli = NULL;
156 static pj_cli_sess	   *cli_cons_sess = NULL;
157 static pj_cli_front_end	   *telnet_front_end = NULL;
158 
159 #ifdef USE_GUI
160 void displayLog(const char *msg, int len);
161 #endif
162 
163 /** Forward declaration **/
164 pj_status_t cli_setup_command(pj_cli_t *cli);
165 void cli_destroy(void);
166 
cli_get_info(char * info,pj_size_t size)167 PJ_DEF(void) cli_get_info(char *info, pj_size_t size)
168 {
169     pj_cli_telnet_info telnet_info;
170     pj_cli_telnet_get_info(telnet_front_end, &telnet_info);
171 
172     pj_ansi_snprintf(info, size, "Telnet to %.*s:%d",
173 		     (int)telnet_info.ip_address.slen,
174 		     telnet_info.ip_address.ptr,
175 		     telnet_info.port);
176 }
177 
cli_log_writer(int level,const char * buffer,int len)178 static void cli_log_writer(int level, const char *buffer, int len)
179 {
180     if (cli)
181         pj_cli_write_log(cli, level, buffer, len);
182 #ifdef USE_GUI
183     displayLog(buffer, len);
184 #endif
185 }
186 
cli_init(void)187 pj_status_t cli_init(void)
188 {
189     pj_status_t status;
190 
191     pj_cli_cfg *cfg = &app_config.cli_cfg.cfg;
192 
193     /* Init PJLIB */
194     status = pj_init();
195     if (status != PJ_SUCCESS)
196 	goto on_error;
197 
198     pj_inited = PJ_TRUE;
199 
200     /* Init PJLIB-UTIL */
201     status = pjlib_util_init();
202     if (status != PJ_SUCCESS)
203 	goto on_error;
204 
205     /* Init CLI */
206     pj_caching_pool_init(&cli_cp, NULL, 0);
207     cli_cp_inited = PJ_TRUE;
208     cfg->pf = &cli_cp.factory;
209     cfg->name = pj_str("pjsua_cli");
210     cfg->title = pj_str("Pjsua CLI Application");
211     status = pj_cli_create(cfg, &cli);
212     if (status != PJ_SUCCESS)
213 	goto on_error;
214 
215     status = cli_setup_command(cli);
216     if (status != PJ_SUCCESS)
217 	goto on_error;
218 
219     /* Init telnet frontend */
220     if (app_config.cli_cfg.cli_fe & CLI_FE_TELNET) {
221 	pj_cli_telnet_cfg *fe_cfg = &app_config.cli_cfg.telnet_cfg;
222 
223 	status = pj_cli_telnet_create(cli, fe_cfg, &telnet_front_end);
224 	if (status != PJ_SUCCESS)
225 	    goto on_error;
226     }
227 
228     /* Init console frontend */
229     if (app_config.cli_cfg.cli_fe & CLI_FE_CONSOLE) {
230 	pj_cli_console_cfg *fe_cfg = &app_config.cli_cfg.console_cfg;
231 
232 	fe_cfg->quit_command = pj_str("shutdown");
233 	status = pj_cli_console_create(cli, fe_cfg,
234 				       &cli_cons_sess, NULL);
235 	if (status != PJ_SUCCESS)
236 	    goto on_error;
237     }
238 
239     return PJ_SUCCESS;
240 
241 on_error:
242     cli_destroy();
243     return status;
244 }
245 
cli_main(pj_bool_t wait_telnet_cli)246 pj_status_t cli_main(pj_bool_t wait_telnet_cli)
247 {
248     char cmdline[PJ_CLI_MAX_CMDBUF];
249 
250     /* ReInit logging */
251     app_config.log_cfg.cb = &cli_log_writer;
252     pjsua_reconfigure_logging(&app_config.log_cfg);
253 
254     if (app_config.cli_cfg.cli_fe & CLI_FE_CONSOLE) {
255 	/* Main loop for CLI FE console */
256 	while (!pj_cli_is_quitting(cli)) {
257 	    pj_cli_console_process(cli_cons_sess, cmdline, sizeof(cmdline));
258 	}
259     } else if (wait_telnet_cli) {
260 	/* Just wait for CLI quit */
261 	while (!pj_cli_is_quitting(cli)) {
262 	    pj_thread_sleep(200);
263 	}
264     }
265 
266     return PJ_SUCCESS;
267 }
268 
cli_destroy(void)269 void cli_destroy(void)
270 {
271     /* Destroy CLI, it will automatically destroy any FEs */
272     if (cli) {
273 	pj_cli_destroy(cli);
274 	cli = NULL;
275     }
276 
277     /* Destroy CLI caching pool factory */
278     if (cli_cp_inited) {
279 	pj_caching_pool_destroy(&cli_cp);
280 	cli_cp_inited = PJ_FALSE;
281     }
282 
283     /* Shutdown PJLIB */
284     if (pj_inited) {
285 	pj_shutdown();
286 	pj_inited = PJ_FALSE;
287     }
288 }
289 
290 /* Get input URL */
get_input_url(char * buf,pj_size_t len,pj_cli_cmd_val * cval,struct input_result * result)291 static void get_input_url(char *buf,
292 			  pj_size_t len,
293 			  pj_cli_cmd_val *cval,
294 			  struct input_result *result)
295 {
296     static const pj_str_t err_invalid_input = {"Invalid input\n", 15};
297     result->nb_result = PJSUA_APP_NO_NB;
298     result->uri_result = NULL;
299 
300     len = strlen(buf);
301 
302     /* Left trim */
303     while (pj_isspace(*buf)) {
304 	++buf;
305 	--len;
306     }
307 
308     /* Remove trailing newlines */
309     while (len && (buf[len-1] == '\r' || buf[len-1] == '\n'))
310 	buf[--len] = '\0';
311 
312     if (len == 0 || buf[0]=='q')
313 	return;
314 
315     if (pj_isdigit(*buf) || *buf=='-') {
316 
317 	unsigned i;
318 
319 	if (*buf=='-')
320 	    i = 1;
321 	else
322 	    i = 0;
323 
324 	for (; i<len; ++i) {
325 	    if (!pj_isdigit(buf[i])) {
326 		pj_cli_sess_write_msg(cval->sess, err_invalid_input.ptr,
327 				      (int)err_invalid_input.slen);
328 		return;
329 	    }
330 	}
331 
332 	result->nb_result = my_atoi(buf);
333 
334 	if (result->nb_result >= 0 &&
335 	    result->nb_result <= (int)pjsua_get_buddy_count())
336 	{
337 	    return;
338 	}
339 	if (result->nb_result == -1)
340 	    return;
341 
342 	pj_cli_sess_write_msg(cval->sess, err_invalid_input.ptr,
343 			      (int)err_invalid_input.slen);
344 	result->nb_result = PJSUA_APP_NO_NB;
345 	return;
346 
347     } else {
348 	pj_status_t status;
349 
350 	if ((status=pjsua_verify_url(buf)) != PJ_SUCCESS) {
351 	    pjsua_perror(THIS_FILE, "Invalid URL", status);
352 	    return;
353 	}
354 
355 	result->uri_result = buf;
356     }
357 }
358 
359 /* CLI dynamic choice handler */
360 /* Get buddy id */
get_buddy_id(pj_cli_dyn_choice_param * param)361 static void get_buddy_id(pj_cli_dyn_choice_param *param)
362 {
363     if (param->cnt < param->max_cnt) {
364 	pjsua_buddy_id ids[64];
365 	int i = 0;
366 	unsigned count = PJ_ARRAY_SIZE(ids);
367 	char data_out[64];
368 
369 	pjsua_enum_buddies(ids, &count);
370 
371 	if (count > 0) {
372 	    for (i=0; i<(int)count; ++i) {
373 		pjsua_buddy_info info;
374 
375 		if (pjsua_buddy_get_info(ids[i], &info) != PJ_SUCCESS)
376 		    continue;
377 
378 		/* Fill buddy id */
379 		pj_ansi_snprintf(data_out, sizeof(data_out), "%d", ids[i]+1);
380 		pj_strdup2(param->pool, &param->choice[param->cnt].value,
381 			   data_out);
382 		pj_bzero(data_out, PJ_ARRAY_SIZE(data_out));
383 
384 		/* Format & fill description */
385 		pj_ansi_snprintf(data_out,
386 				sizeof(data_out),
387 				"<%.*s>  %.*s",
388 				(int)info.status_text.slen,
389 				info.status_text.ptr,
390 				(int)info.uri.slen,
391 				info.uri.ptr);
392 
393 		pj_strdup2(param->pool, &param->choice[param->cnt].desc,
394 			   data_out);
395 		if (++param->cnt >= (param->max_cnt-1))
396 		    break;
397 	    }
398 	}
399 	if (param->arg_id == DYN_CHOICE_BUDDY_ID) {
400 	    /* Add URL input option */
401 	    pj_ansi_snprintf(data_out, sizeof(data_out), "URL");
402 	    pj_strdup2(param->pool, &param->choice[param->cnt].value, data_out);
403 	    pj_ansi_snprintf(data_out, sizeof(data_out), "An URL");
404 	    pj_strdup2(param->pool, &param->choice[param->cnt].desc, data_out);
405 	    ++param->cnt;
406 	}
407     }
408 }
409 
get_account_id(pj_cli_dyn_choice_param * param)410 static void get_account_id(pj_cli_dyn_choice_param *param)
411 {
412     if (param->cnt < param->max_cnt) {
413 	char buf[8];
414 	char buf_out[80];
415 	pjsua_acc_info info;
416 
417 	pjsua_acc_id acc_ids[16];
418 	unsigned count = PJ_ARRAY_SIZE(acc_ids);
419 	int i;
420 
421 	pjsua_enum_accs(acc_ids, &count);
422 
423 	for (i=0; i<(int)count; ++i) {
424 	    pj_bzero(&buf_out[0], PJ_ARRAY_SIZE(buf_out));
425 
426 	    pjsua_acc_get_info(acc_ids[i], &info);
427 
428 	    pj_ansi_snprintf(buf_out,
429 			     sizeof(buf_out),
430 			     "%c%.*s",
431 			     (acc_ids[i]==current_acc?'*':' '),
432 			     (int)info.acc_uri.slen,
433 			     info.acc_uri.ptr);
434 
435 	    pj_bzero(buf, sizeof(buf));
436 	    pj_ansi_snprintf(buf, sizeof(buf), "%d", acc_ids[i]);
437 	    pj_strdup2(param->pool, &param->choice[param->cnt].value, buf);
438 	    pj_strdup2(param->pool, &param->choice[param->cnt].desc, buf_out);
439 	    if (++param->cnt >= param->max_cnt)
440 		break;
441 	}
442     }
443 }
444 
get_media_port(pj_cli_dyn_choice_param * param)445 static void get_media_port(pj_cli_dyn_choice_param *param)
446 {
447     unsigned i, count;
448     pjsua_conf_port_id id[PJSUA_MAX_CONF_PORTS];
449 
450     count = PJ_ARRAY_SIZE(id);
451     pjsua_enum_conf_ports(id, &count);
452 
453     for (i=0; i<count; ++i) {
454 	char slot_id[8];
455 	char desc[512];
456 	char txlist[256];
457 	unsigned j;
458 	int len;
459 	pjsua_conf_port_info info;
460 
461 	pjsua_conf_get_port_info(id[i], &info);
462 
463 	pj_ansi_snprintf(slot_id, sizeof(slot_id),
464 			 "%d", info.slot_id);
465 	pj_strdup2(param->pool, &param->choice[param->cnt].value, slot_id);
466 
467 	txlist[0] = '\0';
468 	for (j=0; j<info.listener_cnt; ++j) {
469 	    char s[10];
470 	    pj_ansi_snprintf(s, sizeof(s), "#%d ", info.listeners[j]);
471 	    pj_ansi_strcat(txlist, s);
472 	}
473 
474 	len = pj_ansi_snprintf(desc,
475 	       sizeof(desc),
476 	       "[%2dKHz/%dms/%d] %20.*s  transmitting to: %s",
477 	       info.clock_rate/1000,
478 	       info.samples_per_frame*1000/info.channel_count/info.clock_rate,
479 	       info.channel_count,
480 	       (int)info.name.slen,
481 	       info.name.ptr,
482 	       txlist);
483 	PJ_CHECK_TRUNC_STR(len, desc, sizeof(desc));
484 
485 	pj_strdup2(param->pool, &param->choice[param->cnt].desc, desc);
486 	if (++param->cnt >= param->max_cnt)
487 	    break;
488     }
489 }
490 
get_audio_codec_id(pj_cli_dyn_choice_param * param)491 static void get_audio_codec_id(pj_cli_dyn_choice_param *param)
492 {
493     if (param->cnt < param->max_cnt) {
494 	pjsua_codec_info c[32];
495 	unsigned i, count = PJ_ARRAY_SIZE(c);
496 	char codec_id[64];
497 	char desc[128];
498 	pj_str_t all_codec_id = pj_str("*");
499 
500 	pjsua_enum_codecs(c, &count);
501 	for (i=0; i<=count; ++i) {
502 	    pj_str_t cid;
503 	    cid = (i==count?all_codec_id : c[i].codec_id);
504 
505 	    pj_ansi_snprintf(codec_id, sizeof(codec_id),
506 			     "%.*s", (int)cid.slen, cid.ptr);
507 
508 	    if (i < count) {
509 		pj_ansi_snprintf(desc, sizeof(desc),
510 				 "Audio, prio: %d%s%.*s",
511 				 c[i].priority,
512 				 c[i].desc.slen ? " - " : "",
513 				 (int)c[i].desc.slen,
514 				 c[i].desc.ptr);
515 	    } else {
516 		pj_ansi_snprintf(desc, sizeof(desc), "Audio (All)");
517 	    }
518 	    pj_strdup2(param->pool, &param->choice[param->cnt].value,
519 		       codec_id);
520 	    pj_strdup2(param->pool, &param->choice[param->cnt].desc, desc);
521 	    if (++param->cnt >= param->max_cnt)
522 		break;
523 	}
524     }
525 }
526 
527 #if PJSUA_HAS_VIDEO
get_video_stream_id(pj_cli_dyn_choice_param * param)528 static void get_video_stream_id(pj_cli_dyn_choice_param *param)
529 {
530     if (param->cnt < param->max_cnt) {
531 	pjsua_call_info call_info;
532 
533 	if (current_call != PJSUA_INVALID_ID) {
534 	    unsigned i;
535 	    pjsua_call_get_info(current_call, &call_info);
536 	    for (i=0; i<call_info.media_cnt; ++i) {
537 		if (call_info.media[i].type == PJMEDIA_TYPE_VIDEO) {
538 		    char med_idx[8];
539 		    pj_ansi_snprintf(med_idx, sizeof(med_idx), "%d",
540 				     call_info.media[i].index);
541 		    pj_strdup2(param->pool, &param->choice[param->cnt].value,
542 			       med_idx);
543 
544 		    switch (call_info.media[i].status) {
545 		    case PJSUA_CALL_MEDIA_NONE:
546 			pj_strdup2(param->pool, &param->choice[param->cnt].desc,
547 				   "Status:None");
548 			break;
549 		    case PJSUA_CALL_MEDIA_ACTIVE:
550 			pj_strdup2(param->pool, &param->choice[param->cnt].desc,
551 				   "Status:Active");
552 			break;
553 		    case PJSUA_CALL_MEDIA_LOCAL_HOLD:
554 			pj_strdup2(param->pool, &param->choice[param->cnt].desc,
555 				   "Status:Local Hold");
556 			break;
557 		    case PJSUA_CALL_MEDIA_REMOTE_HOLD:
558 			pj_strdup2(param->pool, &param->choice[param->cnt].desc,
559 				   "Status:Remote Hold");
560 			break;
561 		    case PJSUA_CALL_MEDIA_ERROR:
562 			pj_strdup2(param->pool, &param->choice[param->cnt].desc,
563 				   "Status:Media Error");
564 			break;
565 		    }
566 		    if (++param->cnt >= param->max_cnt)
567 			break;
568 		}
569 	    }
570 	}
571     }
572 }
573 
get_video_dev_hint(pj_cli_dyn_choice_param * param,pjmedia_vid_dev_info * vdi,unsigned vid_dev_id)574 static void get_video_dev_hint(pj_cli_dyn_choice_param *param,
575 			       pjmedia_vid_dev_info *vdi,
576 			       unsigned vid_dev_id)
577 {
578     char desc[128];
579     char dev_id[8];
580     pj_ansi_snprintf(dev_id, sizeof(dev_id),
581 	"%d", vid_dev_id);
582     pj_ansi_snprintf(desc, sizeof(desc), "%s [%s]",
583 	vdi->name, vdi->driver);
584 
585     pj_strdup2(param->pool, &param->choice[param->cnt].value,
586 	       dev_id);
587     pj_strdup2(param->pool, &param->choice[param->cnt].desc,
588 	       desc);
589 }
590 
get_video_dev_id(pj_cli_dyn_choice_param * param,pj_bool_t all,pj_bool_t capture)591 static void get_video_dev_id(pj_cli_dyn_choice_param *param,
592 			     pj_bool_t all,
593 			     pj_bool_t capture)
594 {
595     if (param->cnt < param->max_cnt) {
596 	unsigned i, count;
597 	pjmedia_vid_dev_info vdi;
598 	pj_status_t status;
599 
600 	count = pjsua_vid_dev_count();
601 	if (count == 0) {
602 	    return;
603 	}
604 
605 	for (i=0; i<count; ++i) {
606 	    status = pjsua_vid_dev_get_info(i, &vdi);
607 	    if (status == PJ_SUCCESS) {
608 		if ((all) ||
609 		    ((capture) && (vdi.dir == PJMEDIA_DIR_CAPTURE)) ||
610 		    ((!capture) && (vdi.dir == PJMEDIA_DIR_RENDER)))
611 		{
612 		    get_video_dev_hint(param, &vdi, i);
613 		    if (++param->cnt >= param->max_cnt)
614 			break;
615 		}
616 	    }
617 	}
618     }
619 }
620 
get_video_codec_id(pj_cli_dyn_choice_param * param)621 static void get_video_codec_id(pj_cli_dyn_choice_param *param)
622 {
623     if (param->cnt < param->max_cnt) {
624 	pjsua_codec_info ci[32];
625 	unsigned i, count = PJ_ARRAY_SIZE(ci);
626 	char codec_id[64];
627 	char desc[128];
628 	pj_str_t all_codec_id = pj_str("*");
629 
630 	pjsua_vid_enum_codecs(ci, &count);
631 	for (i = 0; i <= count; ++i) {
632 	    pjmedia_vid_codec_param cp;
633 	    pjmedia_video_format_detail *vfd;
634 	    pj_status_t status = PJ_SUCCESS;
635 	    pj_str_t cur_ci;
636 
637 	    pj_bzero(&cur_ci, sizeof(cur_ci));
638 	    if (i < count) {
639 		status = pjsua_vid_codec_get_param(&ci[i].codec_id, &cp);
640 		if (status != PJ_SUCCESS)
641 		    continue;
642 
643 		cur_ci = ci[i].codec_id;
644 
645 	    } else {
646 		cur_ci = all_codec_id;
647 	    }
648 	    vfd = pjmedia_format_get_video_format_detail(&cp.enc_fmt, PJ_TRUE);
649 
650 	    pj_ansi_snprintf(codec_id, sizeof(codec_id),
651 			     "%.*s", (int)cur_ci.slen,
652 			     cur_ci.ptr);
653 
654 	    if (i < count) {
655 		pj_ansi_snprintf(desc, sizeof(desc),
656 				 "Video, p[%d], f[%.2f], b[%d/%d], s[%dx%d]",
657 				 ci[i].priority,
658 				 (vfd->fps.num*1.0 / vfd->fps.denum),
659 				 vfd->avg_bps / 1000, vfd->max_bps / 1000,
660 				 vfd->size.w, vfd->size.h);
661 	    } else {
662 		pj_ansi_snprintf(desc, sizeof(desc), "Video (All)");
663 	    }
664 
665 	    pj_strdup2(param->pool, &param->choice[param->cnt].value,
666 		       codec_id);
667 	    pj_strdup2(param->pool, &param->choice[param->cnt].desc, desc);
668 	    if (++param->cnt >= param->max_cnt)
669 		break;
670 	}
671     }
672 }
673 
get_video_window_id(pj_cli_dyn_choice_param * param)674 static void get_video_window_id(pj_cli_dyn_choice_param *param)
675 {
676     if (param->cnt < param->max_cnt) {
677 	pjsua_vid_win_id wids[PJSUA_MAX_VID_WINS];
678 	unsigned i, cnt = PJ_ARRAY_SIZE(wids);
679 	char win_id[64];
680 	char desc[128];
681 
682 	pjsua_vid_enum_wins(wids, &cnt);
683 
684 	for (i = 0; i < cnt; ++i) {
685 	    pjsua_vid_win_info wi;
686 
687 	    pjsua_vid_win_get_info(wids[i], &wi);
688 	    pj_ansi_snprintf(win_id, sizeof(win_id), "%d", wids[i]);
689 	    pj_strdup2(param->pool, &param->choice[param->cnt].value, win_id);
690 
691 	    pj_ansi_snprintf(desc, sizeof(desc),
692 			     "Show:%c Pos(%d,%d)  Size(%dx%d)",
693 			     (wi.show?'Y':'N'), wi.pos.x, wi.pos.y,
694 			     wi.size.w, wi.size.h);
695 
696 	    pj_strdup2(param->pool, &param->choice[param->cnt].desc, desc);
697 	    if (++param->cnt >= param->max_cnt)
698 		break;
699 	}
700     }
701 }
702 
get_call_id(pj_cli_dyn_choice_param * param)703 static void get_call_id(pj_cli_dyn_choice_param *param)
704 {
705     if (param->cnt < param->max_cnt) {
706 	char call_id[64];
707 	char desc[128];
708 	unsigned i, count;
709 	pjsua_call_id ids[PJSUA_MAX_CALLS];
710 	int call = current_call;
711 
712 	count = PJ_ARRAY_SIZE(ids);
713 	pjsua_enum_calls(ids, &count);
714 
715 	if (count > 1) {
716 	    for (i=0; i<count; ++i) {
717 		pjsua_call_info call_info;
718 
719 		if (ids[i] == call)
720 		    return;
721 
722 		pjsua_call_get_info(ids[i], &call_info);
723 		pj_ansi_snprintf(call_id, sizeof(call_id), "%d", ids[i]);
724 		pj_strdup2(param->pool, &param->choice[param->cnt].value,
725 			   call_id);
726 		pj_ansi_snprintf(desc, sizeof(desc), "%.*s [%.*s]",
727 		    (int)call_info.remote_info.slen,
728 		    call_info.remote_info.ptr,
729 		    (int)call_info.state_text.slen,
730 		    call_info.state_text.ptr);
731 		pj_strdup2(param->pool, &param->choice[param->cnt].desc, desc);
732 		if (++param->cnt >= param->max_cnt)
733 		    break;
734 
735 	    }
736 	}
737     }
738 }
739 
740 #endif
741 
get_choice_value(pj_cli_dyn_choice_param * param)742 static void get_choice_value(pj_cli_dyn_choice_param *param)
743 {
744     switch (param->arg_id) {
745     case DYN_CHOICE_BUDDY_ID:
746     case DYN_CHOICE_ADDED_BUDDY_ID:
747 	get_buddy_id(param);
748 	break;
749     case DYN_CHOICE_ACCOUNT_ID:
750 	get_account_id(param);
751 	break;
752     case DYN_CHOICE_MEDIA_PORT:
753 	get_media_port(param);
754 	break;
755     case DYN_CHOICE_AUDIO_CODEC_ID:
756 	get_audio_codec_id(param);
757 	break;
758 #if PJSUA_HAS_VIDEO
759     case DYN_CHOICE_CAP_DEV_ID:
760     case DYN_CHOICE_REN_DEV_ID:
761     case DYN_CHOICE_VID_DEV_ID:
762 	get_video_dev_id(param,
763 			 (param->arg_id==DYN_CHOICE_VID_DEV_ID),
764 			 (param->arg_id==DYN_CHOICE_CAP_DEV_ID));
765 	break;
766     case DYN_CHOICE_STREAM_ID:
767 	get_video_stream_id(param);
768 	break;
769     case DYN_CHOICE_VIDEO_CODEC_ID:
770 	get_video_codec_id(param);
771 	break;
772     case DYN_CHOICE_WIN_ID:
773 	get_video_window_id(param);
774 	break;
775     case DYN_CHOICE_CALL_ID:
776 	get_call_id(param);
777 	break;
778 #endif
779     default:
780 	param->cnt = 0;
781 	break;
782     }
783 }
784 
785 /*
786  * CLI command handler
787  */
788 
789 /* Add account */
cmd_add_account(pj_cli_cmd_val * cval)790 static pj_status_t cmd_add_account(pj_cli_cmd_val *cval)
791 {
792     pjsua_acc_config acc_cfg;
793     pj_status_t status;
794 
795     pjsua_acc_config_default(&acc_cfg);
796     acc_cfg.id = cval->argv[1];
797     acc_cfg.reg_uri = cval->argv[2];
798     acc_cfg.cred_count = 1;
799     acc_cfg.cred_info[0].scheme = pj_str("Digest");
800     acc_cfg.cred_info[0].realm = cval->argv[3];
801     acc_cfg.cred_info[0].username = cval->argv[4];
802     acc_cfg.cred_info[0].data_type = 0;
803     acc_cfg.cred_info[0].data = cval->argv[5];
804 
805     acc_cfg.rtp_cfg = app_config.rtp_cfg;
806     app_config_init_video(&acc_cfg);
807 
808     status = pjsua_acc_add(&acc_cfg, PJ_TRUE, NULL);
809     if (status != PJ_SUCCESS) {
810 	pjsua_perror(THIS_FILE, "Error adding new account", status);
811     }
812 
813     return status;
814 }
815 
816 /* Delete account */
cmd_del_account(pj_cli_cmd_val * cval)817 static pj_status_t cmd_del_account(pj_cli_cmd_val *cval)
818 {
819     char out_str[64];
820     unsigned str_len;
821 
822     int i = my_atoi2(&cval->argv[1]);
823 
824     if (!pjsua_acc_is_valid(i)) {
825 	pj_ansi_snprintf(out_str, sizeof(out_str),
826 		        "Invalid account id %d\n", i);
827 	str_len = (unsigned)pj_ansi_strlen(out_str);
828 	pj_cli_sess_write_msg(cval->sess, out_str, str_len);
829     } else {
830 	pjsua_acc_del(i);
831 	pj_ansi_snprintf(out_str, sizeof(out_str),
832 			 "Account %d deleted\n", i);
833 	str_len = (unsigned)pj_ansi_strlen(out_str);
834 	pj_cli_sess_write_msg(cval->sess, out_str, str_len);
835     }
836     return PJ_SUCCESS;
837 }
838 
839 /* Modify account */
cmd_mod_account(pj_cli_cmd_val * cval)840 static pj_status_t cmd_mod_account(pj_cli_cmd_val *cval)
841 {
842     PJ_UNUSED_ARG(cval);
843     return PJ_SUCCESS;
844 }
845 
846 /* Register account */
cmd_reg_account()847 static pj_status_t cmd_reg_account()
848 {
849     pjsua_acc_set_registration(current_acc, PJ_TRUE);
850     return PJ_SUCCESS;
851 }
852 
853 /* Unregister account */
cmd_unreg_account()854 static pj_status_t cmd_unreg_account()
855 {
856     pjsua_acc_set_registration(current_acc, PJ_FALSE);
857     return PJ_SUCCESS;
858 }
859 
860 /* Select account to be used for sending outgoing request */
cmd_next_account(pj_cli_cmd_val * cval)861 static pj_status_t cmd_next_account(pj_cli_cmd_val *cval)
862 {
863     int i = my_atoi2(&cval->argv[1]);
864     if (pjsua_acc_is_valid(i)) {
865 	pjsua_acc_set_default(i);
866 	PJ_LOG(3,(THIS_FILE, "Current account changed to %d", i));
867     } else {
868 	PJ_LOG(3,(THIS_FILE, "Invalid account id %d", i));
869     }
870     return PJ_SUCCESS;
871 }
872 
873 /* Show account list */
cmd_show_account(pj_cli_cmd_val * cval)874 static pj_status_t cmd_show_account(pj_cli_cmd_val *cval)
875 {
876     pjsua_acc_id acc_ids[16];
877     unsigned count = PJ_ARRAY_SIZE(acc_ids);
878     int i;
879     static const pj_str_t header = {"Account list:\n", 15};
880 
881     pjsua_enum_accs(acc_ids, &count);
882 
883     pj_cli_sess_write_msg(cval->sess, header.ptr, header.slen);
884     for (i=0; i<(int)count; ++i) {
885 	char acc_info[80];
886 	char out_str[160];
887 	pjsua_acc_info info;
888 
889 	pjsua_acc_get_info(acc_ids[i], &info);
890 
891 	if (!info.has_registration) {
892 	    pj_ansi_snprintf(acc_info, sizeof(acc_info), "%.*s",
893 			     (int)info.status_text.slen,
894 			     info.status_text.ptr);
895 
896 	} else {
897 	    pj_ansi_snprintf(acc_info, sizeof(acc_info),
898 			     "%d/%.*s (expires=%d)",
899 			     info.status,
900 			     (int)info.status_text.slen,
901 			     info.status_text.ptr,
902 			     info.expires);
903 
904 	}
905 
906 	pj_ansi_snprintf(out_str, sizeof(out_str),
907 			 " %c[%2d] %.*s: %s\n",
908 			 (acc_ids[i]==current_acc?'*':' '), acc_ids[i],
909 			 (int)info.acc_uri.slen, info.acc_uri.ptr,
910 			 acc_info);
911 	pj_cli_sess_write_msg(cval->sess, out_str, pj_ansi_strlen(out_str));
912 
913 	pj_bzero(out_str, sizeof(out_str));
914 	pj_ansi_snprintf(out_str, sizeof(out_str),
915 			 "       Online status: %.*s\n",
916 			 (int)info.online_status_text.slen,
917 			 info.online_status_text.ptr);
918 
919 	pj_cli_sess_write_msg(cval->sess, out_str, pj_ansi_strlen(out_str));
920     }
921 
922     return PJ_SUCCESS;
923 }
924 
925 /* Account command handler */
cmd_account_handler(pj_cli_cmd_val * cval)926 pj_status_t cmd_account_handler(pj_cli_cmd_val *cval)
927 {
928     pj_status_t status = PJ_SUCCESS;
929 
930     CHECK_PJSUA_RUNNING();
931 
932     switch(pj_cli_get_cmd_id(cval->cmd)) {
933     case CMD_ACCOUNT_ADD:
934 	status = cmd_add_account(cval);
935 	break;
936     case CMD_ACCOUNT_DEL:
937 	status = cmd_del_account(cval);
938 	break;
939     case CMD_ACCOUNT_MOD:
940 	status = cmd_mod_account(cval);
941 	break;
942     case CMD_ACCOUNT_REG:
943 	status = cmd_reg_account();
944 	break;
945     case CMD_ACCOUNT_UNREG:
946 	status = cmd_unreg_account();
947 	break;
948     case CMD_ACCOUNT_NEXT:
949     case CMD_ACCOUNT_PREV:
950 	status = cmd_next_account(cval);
951 	break;
952     case CMD_ACCOUNT_SHOW:
953 	status = cmd_show_account(cval);
954 	break;
955     }
956     return status;
957 }
958 
959 /* Add buddy */
cmd_add_buddy(pj_cli_cmd_val * cval)960 static pj_status_t cmd_add_buddy(pj_cli_cmd_val *cval)
961 {
962     char out_str[80];
963     pjsua_buddy_config buddy_cfg;
964     pjsua_buddy_id buddy_id;
965     pj_status_t status = PJ_SUCCESS;
966     cval->argv[1].ptr[cval->argv[1].slen] = 0;
967 
968     if (pjsua_verify_url(cval->argv[1].ptr) != PJ_SUCCESS) {
969 	pj_ansi_snprintf(out_str, sizeof(out_str),
970 			 "Invalid URI '%s'\n", cval->argv[1].ptr);
971     } else {
972 	pj_bzero(&buddy_cfg, sizeof(pjsua_buddy_config));
973 
974 	buddy_cfg.uri = pj_str(cval->argv[1].ptr);
975 	buddy_cfg.subscribe = PJ_TRUE;
976 
977 	status = pjsua_buddy_add(&buddy_cfg, &buddy_id);
978 	if (status == PJ_SUCCESS) {
979 	    pj_ansi_snprintf(out_str, sizeof(out_str),
980 			      "New buddy '%s' added at index %d\n",
981 			      cval->argv[1].ptr, buddy_id+1);
982 	}
983     }
984     pj_cli_sess_write_msg(cval->sess, out_str, pj_ansi_strlen(out_str));
985     return status;
986 }
987 
988 /* Delete buddy */
cmd_del_buddy(pj_cli_cmd_val * cval)989 static pj_status_t cmd_del_buddy(pj_cli_cmd_val *cval)
990 {
991     int i = my_atoi2(&cval->argv[1]) - 1;
992     char out_str[80];
993 
994     if (!pjsua_buddy_is_valid(i)) {
995 	pj_ansi_snprintf(out_str, sizeof(out_str),
996 			 "Invalid buddy id %d\n", i);
997     } else {
998 	pjsua_buddy_del(i);
999 	pj_ansi_snprintf(out_str, sizeof(out_str),
1000 			 "Buddy %d deleted\n", i);
1001     }
1002     pj_cli_sess_write_msg(cval->sess, out_str, pj_ansi_strlen(out_str));
1003     return PJ_SUCCESS;
1004 }
1005 
1006 /* Send IM */
cmd_send_im(pj_cli_cmd_val * cval)1007 static pj_status_t cmd_send_im(pj_cli_cmd_val *cval)
1008 {
1009     int i = -1;
1010     struct input_result result;
1011     char dest[64];
1012     pj_str_t tmp = pj_str(dest);
1013 
1014     /* make compiler happy. */
1015     char *uri = NULL;
1016 
1017     pj_strncpy_with_null(&tmp, &cval->argv[1], sizeof(dest));
1018 
1019     /* input destination. */
1020     get_input_url(tmp.ptr, tmp.slen, cval, &result);
1021     if (result.nb_result != PJSUA_APP_NO_NB) {
1022 
1023 	if (result.nb_result == -1) {
1024 	    static const pj_str_t err_msg = {"you can't send broadcast im "
1025 					     "like that!\n", 40 };
1026 	    pj_cli_sess_write_msg(cval->sess, err_msg.ptr, err_msg.slen);
1027 	    return PJ_SUCCESS;
1028 	} else if (result.nb_result == 0) {
1029 	    i = current_call;
1030 	} else {
1031 	    pjsua_buddy_info binfo;
1032 	    pjsua_buddy_get_info(result.nb_result-1, &binfo);
1033 	    pj_strncpy_with_null(&tmp, &binfo.uri, sizeof(dest));
1034 	    uri = tmp.ptr;
1035 	}
1036 
1037     } else if (result.uri_result) {
1038 	uri = result.uri_result;
1039     }
1040 
1041     /* send typing indication. */
1042     if (i != -1)
1043 	pjsua_call_send_typing_ind(i, PJ_TRUE, NULL);
1044     else {
1045 	pj_str_t tmp_uri = pj_str(uri);
1046 	pjsua_im_typing(current_acc, &tmp_uri, PJ_TRUE, NULL);
1047     }
1048 
1049     /* send the im */
1050     if (i != -1)
1051 	pjsua_call_send_im(i, NULL, &cval->argv[2], NULL, NULL);
1052     else {
1053 	pj_str_t tmp_uri = pj_str(uri);
1054 	pjsua_im_send(current_acc, &tmp_uri, NULL, &cval->argv[2], NULL, NULL);
1055     }
1056     return PJ_SUCCESS;
1057 }
1058 
1059 /* Subscribe/unsubscribe presence */
cmd_subs_pres(pj_cli_cmd_val * cval,pj_bool_t subscribe)1060 static pj_status_t cmd_subs_pres(pj_cli_cmd_val *cval, pj_bool_t subscribe)
1061 {
1062     struct input_result result;
1063     char dest[64] = {0};
1064     pj_str_t tmp = pj_str(dest);
1065 
1066     pj_strncpy_with_null(&tmp, &cval->argv[1], sizeof(dest));
1067     get_input_url(tmp.ptr, tmp.slen, cval, &result);
1068     if (result.nb_result != PJSUA_APP_NO_NB) {
1069 	if (result.nb_result == -1) {
1070 	    int i, count;
1071 	    count = pjsua_get_buddy_count();
1072 	    for (i=0; i<count; ++i)
1073 		pjsua_buddy_subscribe_pres(i, subscribe);
1074 	} else if (result.nb_result == 0) {
1075 	    static const pj_str_t err_msg = {"Sorry, can only subscribe to "
1076 					     "buddy's presence, not from "
1077 					     "existing call\n", 71};
1078 	    pj_cli_sess_write_msg(cval->sess, err_msg.ptr, err_msg.slen);
1079 	} else {
1080 	    pjsua_buddy_subscribe_pres(result.nb_result-1, subscribe);
1081 	}
1082 
1083     } else if (result.uri_result) {
1084 	static const pj_str_t err_msg = {"Sorry, can only subscribe to "
1085 					 "buddy's presence, not arbitrary "
1086 					 "URL (for now)\n", 76};
1087 	pj_cli_sess_write_msg(cval->sess, err_msg.ptr, err_msg.slen);
1088     }
1089     return PJ_SUCCESS;
1090 }
1091 
1092 /* Toggle online state */
cmd_toggle_state(pj_cli_cmd_val * cval)1093 static pj_status_t cmd_toggle_state(pj_cli_cmd_val *cval)
1094 {
1095     char out_str[128];
1096     pjsua_acc_info acc_info;
1097 
1098     pjsua_acc_get_info(current_acc, &acc_info);
1099     acc_info.online_status = !acc_info.online_status;
1100     pjsua_acc_set_online_status(current_acc, acc_info.online_status);
1101     pj_ansi_snprintf(out_str, sizeof(out_str),
1102 		     "Setting %s online status to %s\n",
1103 		     acc_info.acc_uri.ptr,
1104 		    (acc_info.online_status?"online":"offline"));
1105     pj_cli_sess_write_msg(cval->sess, out_str, pj_ansi_strlen(out_str));
1106     return PJ_SUCCESS;
1107 }
1108 
1109 /* Set presence text */
cmd_set_presence_text(pj_cli_cmd_val * cval)1110 static pj_status_t cmd_set_presence_text(pj_cli_cmd_val *cval)
1111 {
1112     pjrpid_element elem;
1113     int choice;
1114     pj_bool_t online_status;
1115 
1116     enum {
1117 	AVAILABLE, BUSY, OTP, IDLE, AWAY, BRB, OFFLINE, OPT_MAX
1118     };
1119 
1120     choice = (int)pj_strtol(&cval->argv[1]) - 1;
1121 
1122     pj_bzero(&elem, sizeof(elem));
1123     elem.type = PJRPID_ELEMENT_TYPE_PERSON;
1124 
1125     online_status = PJ_TRUE;
1126 
1127     switch (choice) {
1128     case AVAILABLE:
1129 	break;
1130     case BUSY:
1131 	elem.activity = PJRPID_ACTIVITY_BUSY;
1132 	elem.note = pj_str("Busy");
1133 	break;
1134     case OTP:
1135 	elem.activity = PJRPID_ACTIVITY_BUSY;
1136 	elem.note = pj_str("On the phone");
1137 	break;
1138     case IDLE:
1139 	elem.activity = PJRPID_ACTIVITY_UNKNOWN;
1140 	elem.note = pj_str("Idle");
1141 	break;
1142     case AWAY:
1143 	elem.activity = PJRPID_ACTIVITY_AWAY;
1144 	elem.note = pj_str("Away");
1145 	break;
1146     case BRB:
1147 	elem.activity = PJRPID_ACTIVITY_UNKNOWN;
1148 	elem.note = pj_str("Be right back");
1149 	break;
1150     case OFFLINE:
1151 	online_status = PJ_FALSE;
1152 	break;
1153     }
1154     pjsua_acc_set_online_status2(current_acc, online_status, &elem);
1155     return PJ_SUCCESS;
1156 }
1157 
1158 /* Show buddy list */
cmd_show_buddy(pj_cli_cmd_val * cval)1159 static pj_status_t cmd_show_buddy(pj_cli_cmd_val *cval)
1160 {
1161     pjsua_buddy_id ids[64];
1162     int i;
1163     unsigned count = PJ_ARRAY_SIZE(ids);
1164     static const pj_str_t header = {"Buddy list:\n", 13};
1165     char out_str[64];
1166 
1167     pj_cli_sess_write_msg(cval->sess, header.ptr, header.slen);
1168 
1169     pjsua_enum_buddies(ids, &count);
1170 
1171     if (count == 0) {
1172 	pj_ansi_snprintf(out_str, sizeof(out_str), " -none-\n");
1173 	pj_cli_sess_write_msg(cval->sess, out_str, pj_ansi_strlen(out_str));
1174     } else {
1175 	for (i=0; i<(int)count; ++i) {
1176 	    pjsua_buddy_info info;
1177 	    pj_bzero(out_str, sizeof(out_str));
1178 
1179 	    if (pjsua_buddy_get_info(ids[i], &info) != PJ_SUCCESS)
1180 		continue;
1181 
1182 	    pj_ansi_snprintf(out_str, sizeof(out_str),
1183 		    " [%2d] <%.*s>  %.*s\n",
1184 		    ids[i]+1,
1185 		    (int)info.status_text.slen,
1186 		    info.status_text.ptr,
1187 		    (int)info.uri.slen,
1188 		    info.uri.ptr);
1189 
1190 	    pj_cli_sess_write_msg(cval->sess, out_str, pj_ansi_strlen(out_str));
1191 	}
1192     }
1193     return PJ_SUCCESS;
1194 }
1195 
1196 /* Presence/buddy command handler */
cmd_presence_handler(pj_cli_cmd_val * cval)1197 pj_status_t cmd_presence_handler(pj_cli_cmd_val *cval)
1198 {
1199     pj_status_t status = PJ_SUCCESS;
1200 
1201     CHECK_PJSUA_RUNNING();
1202 
1203     switch(pj_cli_get_cmd_id(cval->cmd)) {
1204     case CMD_PRESENCE_ADD_BUDDY:
1205 	status = cmd_add_buddy(cval);
1206 	break;
1207     case CMD_PRESENCE_DEL_BUDDY:
1208 	status = cmd_del_buddy(cval);
1209 	break;
1210     case CMD_PRESENCE_SEND_IM:
1211 	status = cmd_send_im(cval);
1212 	break;
1213     case CMD_PRESENCE_SUB:
1214     case CMD_PRESENCE_UNSUB:
1215 	status = cmd_subs_pres(cval,
1216 		               pj_cli_get_cmd_id(cval->cmd)==CMD_PRESENCE_SUB);
1217 	break;
1218     case CMD_PRESENCE_TOG_STATE:
1219 	status = cmd_toggle_state(cval);
1220 	break;
1221     case CMD_PRESENCE_TEXT:
1222 	status = cmd_set_presence_text(cval);
1223 	break;
1224     case CMD_PRESENCE_LIST:
1225 	status = cmd_show_buddy(cval);
1226 	break;
1227     }
1228 
1229     return status;
1230 }
1231 
1232 /* Show conference list */
cmd_media_list(pj_cli_cmd_val * cval)1233 static pj_status_t cmd_media_list(pj_cli_cmd_val *cval)
1234 {
1235     unsigned i, count;
1236     pjsua_conf_port_id id[PJSUA_MAX_CONF_PORTS];
1237     static const pj_str_t header = {"Conference ports:\n", 19};
1238 
1239     pj_cli_sess_write_msg(cval->sess, header.ptr, header.slen);
1240 
1241     count = PJ_ARRAY_SIZE(id);
1242     pjsua_enum_conf_ports(id, &count);
1243 
1244     for (i=0; i<count; ++i) {
1245 	char out_str[128];
1246 	char txlist[16];
1247 	unsigned j;
1248 	pjsua_conf_port_info info;
1249 
1250 	pjsua_conf_get_port_info(id[i], &info);
1251 
1252 	pj_bzero(txlist, sizeof(txlist));
1253 	for (j=0; j<info.listener_cnt; ++j) {
1254 	    char s[10];
1255 	    pj_ansi_snprintf(s, sizeof(s), "#%d ", info.listeners[j]);
1256 	    pj_ansi_strcat(txlist, s);
1257 	}
1258 	pj_ansi_snprintf(out_str,
1259 	       sizeof(out_str),
1260 	       "Port #%02d[%2dKHz/%dms/%d] %20.*s  transmitting to: %s\n",
1261 	       info.slot_id,
1262 	       info.clock_rate/1000,
1263 	       info.samples_per_frame*1000/info.channel_count/info.clock_rate,
1264 	       info.channel_count,
1265 	       (int)info.name.slen,
1266 	       info.name.ptr,
1267 	       txlist);
1268 	pj_cli_sess_write_msg(cval->sess, out_str, pj_ansi_strlen(out_str));
1269     }
1270     return PJ_SUCCESS;
1271 }
1272 
1273 /* Conference connect/disconnect */
cmd_media_connect(pj_cli_cmd_val * cval,pj_bool_t connect)1274 static pj_status_t cmd_media_connect(pj_cli_cmd_val *cval, pj_bool_t connect)
1275 {
1276     pj_status_t status;
1277 
1278     if (connect)
1279 	status = pjsua_conf_connect((int)pj_strtol(&cval->argv[1]),
1280 				    (int)pj_strtol(&cval->argv[2]));
1281     else
1282 	status = pjsua_conf_disconnect((int)pj_strtol(&cval->argv[1]),
1283 				       (int)pj_strtol(&cval->argv[2]));
1284 
1285     if (status == PJ_SUCCESS) {
1286 	static const pj_str_t success_msg = {"Success\n", 9};
1287 	pj_cli_sess_write_msg(cval->sess, success_msg.ptr, success_msg.slen);
1288     } else {
1289 	static const pj_str_t err_msg = {"ERROR!!\n", 9};
1290 	pj_cli_sess_write_msg(cval->sess, err_msg.ptr, err_msg.slen);
1291     }
1292     return status;
1293 }
1294 
1295 /* Adjust audio volume */
cmd_adjust_vol(pj_cli_cmd_val * cval)1296 static pj_status_t cmd_adjust_vol(pj_cli_cmd_val *cval)
1297 {
1298     char buf[80];
1299     float orig_level;
1300     char *err;
1301     char level_val[16] = {0};
1302     pj_str_t tmp = pj_str(level_val);
1303 
1304     /* Adjust mic level */
1305     orig_level = app_config.mic_level;
1306     pj_strncpy_with_null(&tmp, &cval->argv[1], sizeof(level_val));
1307     app_config.mic_level = (float)strtod(level_val, &err);
1308     pjsua_conf_adjust_rx_level(0, app_config.mic_level);
1309 
1310     pj_ansi_snprintf(buf, sizeof(buf),
1311 		     "Adjust mic level: [%4.1fx] -> [%4.1fx]\n",
1312 		     orig_level, app_config.mic_level);
1313 
1314     pj_cli_sess_write_msg(cval->sess, buf, pj_ansi_strlen(buf));
1315 
1316     /* Adjust speaker level */
1317     orig_level = app_config.speaker_level;
1318     pj_strncpy_with_null(&tmp, &cval->argv[2], sizeof(level_val));
1319     app_config.speaker_level = (float)strtod(level_val, &err);
1320     pjsua_conf_adjust_tx_level(0, app_config.speaker_level);
1321 
1322     pj_ansi_snprintf(buf, sizeof(buf),
1323 		      "Adjust speaker level: [%4.1fx] -> [%4.1fx]\n",
1324 		      orig_level, app_config.speaker_level);
1325 
1326     pj_cli_sess_write_msg(cval->sess, buf, pj_ansi_strlen(buf));
1327 
1328     return PJ_SUCCESS;
1329 }
1330 
1331 /* Set codec priority */
cmd_set_codec_prio(pj_cli_cmd_val * cval)1332 static pj_status_t cmd_set_codec_prio(pj_cli_cmd_val *cval)
1333 {
1334     int new_prio;
1335     pj_status_t status;
1336 
1337     new_prio = (int)pj_strtol(&cval->argv[2]);
1338     if (new_prio < 0)
1339 	new_prio = 0;
1340     else if (new_prio > PJMEDIA_CODEC_PRIO_HIGHEST)
1341 	new_prio = PJMEDIA_CODEC_PRIO_HIGHEST;
1342 
1343     status = pjsua_codec_set_priority(&cval->argv[1],
1344 				      (pj_uint8_t)new_prio);
1345 #if PJSUA_HAS_VIDEO
1346     if (status != PJ_SUCCESS) {
1347 	status = pjsua_vid_codec_set_priority(&cval->argv[1],
1348 					      (pj_uint8_t)new_prio);
1349     }
1350 #endif
1351     if (status != PJ_SUCCESS)
1352 	pjsua_perror(THIS_FILE, "Error setting codec priority", status);
1353 
1354     return status;
1355 }
1356 
1357 /* Conference/media command handler */
cmd_media_handler(pj_cli_cmd_val * cval)1358 pj_status_t cmd_media_handler(pj_cli_cmd_val *cval)
1359 {
1360     pj_status_t status = PJ_SUCCESS;
1361 
1362     CHECK_PJSUA_RUNNING();
1363 
1364     switch(pj_cli_get_cmd_id(cval->cmd)) {
1365     case CMD_MEDIA_LIST:
1366 	status = cmd_media_list(cval);
1367 	break;
1368     case CMD_MEDIA_CONF_CONNECT:
1369     case CMD_MEDIA_CONF_DISCONNECT:
1370 	status = cmd_media_connect(cval,
1371 		          pj_cli_get_cmd_id(cval->cmd)==CMD_MEDIA_CONF_CONNECT);
1372 	break;
1373     case CMD_MEDIA_ADJUST_VOL:
1374 	status = cmd_adjust_vol(cval);
1375 	break;
1376     case CMD_MEDIA_CODEC_PRIO:
1377 	status = cmd_set_codec_prio(cval);
1378 	break;
1379     case CMD_MEDIA_SPEAKER_TOGGLE:
1380 	{
1381 	    static int route = PJMEDIA_AUD_DEV_ROUTE_DEFAULT;
1382 	    status = pjsua_snd_get_setting(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE,
1383 					   &route);
1384 	    if (status != PJ_SUCCESS) {
1385 		PJ_PERROR(2, (THIS_FILE, status,
1386 			      "Warning: unable to retrieve route setting"));
1387 	    }
1388 
1389 	    if (route == PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER)
1390 		route = PJMEDIA_AUD_DEV_ROUTE_DEFAULT;
1391 	    else
1392 		route = PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER;
1393 
1394 	    PJ_LOG(4,(THIS_FILE, "Setting output route to %s %s",
1395 		      (route==PJMEDIA_AUD_DEV_ROUTE_DEFAULT?
1396 				      "default" : "loudspeaker"),
1397 		      (status? "anyway" : "")));
1398 
1399 	    status = pjsua_snd_set_setting(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE,
1400 					   &route, PJ_TRUE);
1401 	    PJ_PERROR(4,(THIS_FILE, status, "Result"));
1402 	}
1403 	break;
1404     }
1405 
1406     return status;
1407 }
1408 
1409 /* Dump status */
cmd_stat_dump(pj_bool_t detail)1410 static pj_status_t cmd_stat_dump(pj_bool_t detail)
1411 {
1412     pjsua_dump(detail);
1413     return PJ_SUCCESS;
1414 }
1415 
cmd_show_config()1416 static pj_status_t cmd_show_config()
1417 {
1418     char settings[2000];
1419     int len;
1420 
1421     len = write_settings(&app_config, settings, sizeof(settings));
1422     if (len < 1)
1423 	PJ_LOG(1,(THIS_FILE, "Error: not enough buffer"));
1424     else
1425 	PJ_LOG(3,(THIS_FILE,
1426 		  "Dumping configuration (%d bytes):\n%s\n",
1427 		  len, settings));
1428 
1429     return PJ_SUCCESS;
1430 }
1431 
cmd_write_config(pj_cli_cmd_val * cval)1432 static pj_status_t cmd_write_config(pj_cli_cmd_val *cval)
1433 {
1434     char settings[2000];
1435     char buf[128] = {0};
1436     int len;
1437     pj_str_t tmp = pj_str(buf);
1438 
1439     pj_strncpy_with_null(&tmp, &cval->argv[1], sizeof(buf));
1440 
1441     len = write_settings(&app_config, settings, sizeof(settings));
1442     if (len < 1)
1443 	PJ_LOG(1,(THIS_FILE, "Error: not enough buffer"));
1444     else {
1445 	pj_oshandle_t fd;
1446 	pj_status_t status;
1447 
1448 	status = pj_file_open(app_config.pool, buf, PJ_O_WRONLY, &fd);
1449 	if (status != PJ_SUCCESS) {
1450 	    pjsua_perror(THIS_FILE, "Unable to open file", status);
1451 	} else {
1452 	    char out_str[256];
1453 	    pj_ssize_t size = len;
1454 	    pj_file_write(fd, settings, &size);
1455 	    pj_file_close(fd);
1456 
1457 	    pj_ansi_snprintf(out_str, sizeof(out_str),
1458 			     "Settings successfully written to '%s'\n", buf);
1459 
1460 	    pj_cli_sess_write_msg(cval->sess, out_str, pj_ansi_strlen(out_str));
1461 	}
1462     }
1463 
1464     return PJ_SUCCESS;
1465 }
1466 
1467 /* Status and config command handler */
cmd_config_handler(pj_cli_cmd_val * cval)1468 pj_status_t cmd_config_handler(pj_cli_cmd_val *cval)
1469 {
1470     pj_status_t status = PJ_SUCCESS;
1471 
1472     CHECK_PJSUA_RUNNING();
1473 
1474     switch(pj_cli_get_cmd_id(cval->cmd)) {
1475     case CMD_CONFIG_DUMP_STAT:
1476 	status = cmd_stat_dump(PJ_FALSE);
1477 	break;
1478     case CMD_CONFIG_DUMP_DETAIL:
1479 	status = cmd_stat_dump(PJ_TRUE);
1480 	break;
1481     case CMD_CONFIG_DUMP_CONF:
1482 	status = cmd_show_config();
1483 	break;
1484     case CMD_CONFIG_WRITE_SETTING:
1485 	status = cmd_write_config(cval);
1486 	break;
1487     }
1488 
1489     return status;
1490 }
1491 
1492 /* Make single call */
cmd_make_single_call(pj_cli_cmd_val * cval)1493 static pj_status_t cmd_make_single_call(pj_cli_cmd_val *cval)
1494 {
1495     struct input_result result;
1496     char dest[64] = {0};
1497     char out_str[128];
1498     pj_str_t tmp = pj_str(dest);
1499 
1500     pj_strncpy_with_null(&tmp, &cval->argv[1], sizeof(dest));
1501 
1502     pj_ansi_snprintf(out_str,
1503 		     sizeof(out_str),
1504 		     "(You currently have %d calls)\n",
1505 		     pjsua_call_get_count());
1506 
1507     pj_cli_sess_write_msg(cval->sess, out_str, pj_ansi_strlen(out_str));
1508 
1509     /* input destination. */
1510     get_input_url(tmp.ptr, tmp.slen, cval, &result);
1511     if (result.nb_result != PJSUA_APP_NO_NB) {
1512 	pjsua_buddy_info binfo;
1513 	if (result.nb_result == -1 || result.nb_result == 0) {
1514 	    static const pj_str_t err_msg =
1515 		    {"You can't do that with make call!\n", 35};
1516 	    pj_cli_sess_write_msg(cval->sess, err_msg.ptr, err_msg.slen);
1517 	    return PJ_SUCCESS;
1518 	}
1519 	pjsua_buddy_get_info(result.nb_result-1, &binfo);
1520 	pj_strncpy(&tmp, &binfo.uri, sizeof(dest));
1521     } else if (result.uri_result) {
1522 	tmp = pj_str(result.uri_result);
1523     } else {
1524 	tmp.slen = 0;
1525     }
1526 
1527     pjsua_msg_data_init(&msg_data);
1528     TEST_MULTIPART(&msg_data);
1529     pjsua_call_make_call(current_acc, &tmp, &call_opt, NULL,
1530 			 &msg_data, &current_call);
1531     return PJ_SUCCESS;
1532 }
1533 
1534 /* Make multi call */
cmd_make_multi_call(pj_cli_cmd_val * cval)1535 static pj_status_t cmd_make_multi_call(pj_cli_cmd_val *cval)
1536 {
1537     struct input_result result;
1538     char dest[64] = {0};
1539     char out_str[128];
1540     int i, count;
1541     pj_str_t tmp = pj_str(dest);
1542 
1543     pj_ansi_snprintf(out_str,
1544 		     sizeof(out_str),
1545 		     "(You currently have %d calls)\n",
1546 		     pjsua_call_get_count());
1547 
1548     count = (int)pj_strtol(&cval->argv[1]);
1549     if (count < 1)
1550 	return PJ_SUCCESS;
1551 
1552     pj_strncpy_with_null(&tmp, &cval->argv[2], sizeof(dest));
1553 
1554     /* input destination. */
1555     get_input_url(tmp.ptr, tmp.slen, cval, &result);
1556     if (result.nb_result != PJSUA_APP_NO_NB) {
1557 	pjsua_buddy_info binfo;
1558 	if (result.nb_result == -1 || result.nb_result == 0) {
1559 	    static const pj_str_t err_msg =
1560 			    {"You can't do that with make call!\n", 35};
1561 	    pj_cli_sess_write_msg(cval->sess, err_msg.ptr, err_msg.slen);
1562 	    return PJ_SUCCESS;
1563 	}
1564 	pjsua_buddy_get_info(result.nb_result-1, &binfo);
1565 	pj_strncpy(&tmp, &binfo.uri, sizeof(dest));
1566     } else {
1567 	tmp = pj_str(result.uri_result);
1568     }
1569 
1570     for (i=0; i<count; ++i) {
1571 	pj_status_t status;
1572 
1573 	status = pjsua_call_make_call(current_acc, &tmp, &call_opt, NULL,
1574 	    NULL, NULL);
1575 	if (status != PJ_SUCCESS)
1576 	    break;
1577     }
1578     return PJ_SUCCESS;
1579 }
1580 
1581 /* Answer call */
cmd_answer_call(pj_cli_cmd_val * cval)1582 static pj_status_t cmd_answer_call(pj_cli_cmd_val *cval)
1583 {
1584     pjsua_call_info call_info;
1585 
1586     if (current_call != PJSUA_INVALID_ID) {
1587 	pjsua_call_get_info(current_call, &call_info);
1588     } else {
1589 	/* Make compiler happy */
1590 	call_info.role = PJSIP_ROLE_UAC;
1591 	call_info.state = PJSIP_INV_STATE_DISCONNECTED;
1592     }
1593 
1594     if (current_call == PJSUA_INVALID_ID ||
1595 	call_info.role != PJSIP_ROLE_UAS ||
1596 	call_info.state >= PJSIP_INV_STATE_CONNECTING)
1597     {
1598 	static const pj_str_t err_msg = {"No pending incoming call\n", 26};
1599 	pj_cli_sess_write_msg(cval->sess, err_msg.ptr, err_msg.slen);
1600 
1601     } else {
1602 	int st_code;
1603 	char contact[120];
1604 	pj_str_t hname = { "Contact", 7 };
1605 	pj_str_t hvalue;
1606 	pjsip_generic_string_hdr hcontact;
1607 
1608 	st_code = (int)pj_strtol(&cval->argv[1]);
1609 	if ((st_code < 100) || (st_code > 699))
1610 	    return PJ_SUCCESS;
1611 
1612 	pjsua_msg_data_init(&msg_data);
1613 
1614 	if (st_code/100 == 3) {
1615 	    if (cval->argc < 3) {
1616 		static const pj_str_t err_msg = {"Enter URL to be put "
1617 						 "in Contact\n",  32};
1618 		pj_cli_sess_write_msg(cval->sess, err_msg.ptr, err_msg.slen);
1619 		return PJ_SUCCESS;
1620 	    }
1621 
1622 	    hvalue = pj_str(contact);
1623 	    pjsip_generic_string_hdr_init2(&hcontact, &hname, &hvalue);
1624 
1625 	    pj_list_push_back(&msg_data.hdr_list, &hcontact);
1626 	}
1627 
1628 	/*
1629 	* Must check again!
1630 	* Call may have been disconnected while we're waiting for
1631 	* keyboard input.
1632 	*/
1633 	if (current_call == PJSUA_INVALID_ID) {
1634 	    static const pj_str_t err_msg = {"Call has been disconnected\n",
1635 					     28};
1636 	    pj_cli_sess_write_msg(cval->sess, err_msg.ptr, err_msg.slen);
1637 	}
1638 
1639 	pjsua_call_answer2(current_call, &call_opt, st_code, NULL, &msg_data);
1640     }
1641     return PJ_SUCCESS;
1642 }
1643 
1644 /* Hangup call */
cmd_hangup_call(pj_cli_cmd_val * cval,pj_bool_t all)1645 static pj_status_t cmd_hangup_call(pj_cli_cmd_val *cval, pj_bool_t all)
1646 {
1647     if (current_call == PJSUA_INVALID_ID) {
1648 	static const pj_str_t err_msg = {"No current call\n", 17};
1649 	pj_cli_sess_write_msg(cval->sess, err_msg.ptr, err_msg.slen);
1650     } else {
1651 	if (all)
1652 	    pjsua_call_hangup_all();
1653 	else
1654 	    pjsua_call_hangup(current_call, 0, NULL, NULL);
1655     }
1656     return PJ_SUCCESS;
1657 }
1658 
1659 /* Hold call */
cmd_hold_call()1660 static pj_status_t cmd_hold_call()
1661 {
1662     if (current_call != PJSUA_INVALID_ID) {
1663 	pjsua_call_set_hold(current_call, NULL);
1664 
1665     } else {
1666 	PJ_LOG(3,(THIS_FILE, "No current call"));
1667     }
1668     return PJ_SUCCESS;
1669 }
1670 
1671 /* Call reinvite */
cmd_call_reinvite()1672 static pj_status_t cmd_call_reinvite()
1673 {
1674     if (current_call != PJSUA_INVALID_ID) {
1675 	/*
1676 	 * re-INVITE
1677 	 */
1678 	call_opt.flag |= PJSUA_CALL_UNHOLD;
1679 	pjsua_call_reinvite2(current_call, &call_opt, NULL);
1680 
1681     } else {
1682 	PJ_LOG(3,(THIS_FILE, "No current call"));
1683     }
1684     return PJ_SUCCESS;
1685 }
1686 
1687 /* Send update */
cmd_call_update()1688 static pj_status_t cmd_call_update()
1689 {
1690     if (current_call != PJSUA_INVALID_ID) {
1691 	pjsua_call_update2(current_call, &call_opt, NULL);
1692     } else {
1693 	PJ_LOG(3,(THIS_FILE, "No current call"));
1694     }
1695     return PJ_SUCCESS;
1696 }
1697 
1698 /* Select next call */
cmd_next_call(pj_bool_t next)1699 static pj_status_t cmd_next_call(pj_bool_t next)
1700 {
1701     /*
1702      * Cycle next/prev dialog.
1703      */
1704     if (next) {
1705 	find_next_call();
1706     } else {
1707 	find_prev_call();
1708     }
1709 
1710     if (current_call != PJSUA_INVALID_ID) {
1711 	pjsua_call_info call_info;
1712 
1713 	pjsua_call_get_info(current_call, &call_info);
1714 	PJ_LOG(3,(THIS_FILE,"Current dialog: %.*s",
1715 		  (int)call_info.remote_info.slen,
1716 		  call_info.remote_info.ptr));
1717 
1718     } else {
1719 	PJ_LOG(3,(THIS_FILE,"No current dialog"));
1720     }
1721     return PJ_SUCCESS;
1722 }
1723 
1724 /* Transfer call */
cmd_transfer_call(pj_cli_cmd_val * cval)1725 static pj_status_t cmd_transfer_call(pj_cli_cmd_val *cval)
1726 {
1727     if (current_call == PJSUA_INVALID_ID) {
1728 
1729 	PJ_LOG(3,(THIS_FILE, "No current call"));
1730 
1731     } else {
1732 	char out_str[64];
1733 	int call = current_call;
1734 	char dest[64] = {0};
1735 	pj_str_t tmp = pj_str(dest);
1736 	struct input_result result;
1737 	pjsip_generic_string_hdr refer_sub;
1738 	pj_str_t STR_REFER_SUB = { "Refer-Sub", 9 };
1739 	pj_str_t STR_FALSE = { "false", 5 };
1740 	pjsua_call_info ci;
1741 
1742 	pj_strncpy_with_null(&tmp, &cval->argv[1], sizeof(dest));
1743 
1744 	pjsua_call_get_info(current_call, &ci);
1745 	pj_ansi_snprintf(out_str,
1746 			 sizeof(out_str),
1747 			 "Transferring current call [%d] %.*s\n",
1748 			 current_call,
1749 			 (int)ci.remote_info.slen,
1750 			 ci.remote_info.ptr);
1751 
1752 	get_input_url(tmp.ptr, tmp.slen, cval, &result);
1753 
1754 	/* Check if call is still there. */
1755 
1756 	if (call != current_call) {
1757 	    puts("Call has been disconnected");
1758 	    return PJ_SUCCESS;
1759 	}
1760 
1761 	pjsua_msg_data_init(&msg_data);
1762 	if (app_config.no_refersub) {
1763 	    /* Add Refer-Sub: false in outgoing REFER request */
1764 	    pjsip_generic_string_hdr_init2(&refer_sub, &STR_REFER_SUB,
1765 		&STR_FALSE);
1766 	    pj_list_push_back(&msg_data.hdr_list, &refer_sub);
1767 	}
1768 	if (result.nb_result != PJSUA_APP_NO_NB) {
1769 	    if (result.nb_result == -1 || result.nb_result == 0) {
1770 		static const pj_str_t err_msg = {"You can't do that with "
1771 						 "transfer call!\n", 39};
1772 
1773 		pj_cli_sess_write_msg(cval->sess, err_msg.ptr, err_msg.slen);
1774 	    } else {
1775 		pjsua_buddy_info binfo;
1776 		pjsua_buddy_get_info(result.nb_result-1, &binfo);
1777 		pjsua_call_xfer( current_call, &binfo.uri, &msg_data);
1778 	    }
1779 	} else if (result.uri_result) {
1780 	    pj_str_t tmp2;
1781 	    tmp2 = pj_str(result.uri_result);
1782 	    pjsua_call_xfer( current_call, &tmp2, &msg_data);
1783 	}
1784     }
1785     return PJ_SUCCESS;
1786 }
1787 
1788 /* Transfer call */
cmd_transfer_replace_call(pj_cli_cmd_val * cval)1789 static pj_status_t cmd_transfer_replace_call(pj_cli_cmd_val *cval)
1790 {
1791     if (current_call == -1) {
1792 	PJ_LOG(3,(THIS_FILE, "No current call"));
1793     } else {
1794 	int call = current_call;
1795 	int dst_call;
1796 	pjsip_generic_string_hdr refer_sub;
1797 	pj_str_t STR_REFER_SUB = { "Refer-Sub", 9 };
1798 	pj_str_t STR_FALSE = { "false", 5 };
1799 	pjsua_call_id ids[PJSUA_MAX_CALLS];
1800 	pjsua_msg_data msg_data_;
1801 	unsigned count;
1802 	static const pj_str_t err_invalid_num =
1803 				    {"Invalid destination call number\n", 32 };
1804 	count = PJ_ARRAY_SIZE(ids);
1805 	pjsua_enum_calls(ids, &count);
1806 
1807 	if (count <= 1) {
1808 	    static const pj_str_t err_no_other_call =
1809 				    {"There are no other calls\n", 25};
1810 
1811 	    pj_cli_sess_write_msg(cval->sess, err_no_other_call.ptr,
1812 				  err_no_other_call.slen);
1813 	    return PJ_SUCCESS;
1814 	}
1815 
1816 	dst_call = my_atoi2(&cval->argv[1]);
1817 
1818 	/* Check if call is still there. */
1819 	if (call != current_call) {
1820 	    static pj_str_t err_call_dc =
1821 				    {"Call has been disconnected\n", 27};
1822 
1823 	    pj_cli_sess_write_msg(cval->sess, err_call_dc.ptr,
1824 				  err_call_dc.slen);
1825 	    return PJ_SUCCESS;
1826 	}
1827 
1828 	/* Check that destination call is valid. */
1829 	if (dst_call == call) {
1830 	    static pj_str_t err_same_num =
1831 				    {"Destination call number must not be the "
1832 				     "same as the call being transferred\n", 74};
1833 
1834 	    pj_cli_sess_write_msg(cval->sess, err_same_num.ptr,
1835 				  err_same_num.slen);
1836 	    return PJ_SUCCESS;
1837 	}
1838 
1839 	if (dst_call >= PJSUA_MAX_CALLS) {
1840 	    pj_cli_sess_write_msg(cval->sess, err_invalid_num.ptr,
1841 				  err_invalid_num.slen);
1842 	    return PJ_SUCCESS;
1843 	}
1844 
1845 	if (!pjsua_call_is_active(dst_call)) {
1846 	    pj_cli_sess_write_msg(cval->sess, err_invalid_num.ptr,
1847 				  err_invalid_num.slen);
1848 	    return PJ_SUCCESS;
1849 	}
1850 
1851 	pjsua_msg_data_init(&msg_data_);
1852 	if (app_config.no_refersub) {
1853 	    /* Add Refer-Sub: false in outgoing REFER request */
1854 	    pjsip_generic_string_hdr_init2(&refer_sub, &STR_REFER_SUB,
1855 					   &STR_FALSE);
1856 	    pj_list_push_back(&msg_data_.hdr_list, &refer_sub);
1857 	}
1858 
1859 	pjsua_call_xfer_replaces(call, dst_call,
1860 				 PJSUA_XFER_NO_REQUIRE_REPLACES,
1861 				 &msg_data_);
1862     }
1863     return PJ_SUCCESS;
1864 }
1865 
cmd_redirect_call(pj_cli_cmd_val * cval)1866 static pj_status_t cmd_redirect_call(pj_cli_cmd_val *cval)
1867 {
1868     if (current_call == PJSUA_INVALID_ID) {
1869 	PJ_LOG(3,(THIS_FILE, "No current call"));
1870 	return PJ_SUCCESS;
1871     }
1872     if (!pjsua_call_is_active(current_call)) {
1873 	PJ_LOG(1,(THIS_FILE, "Call %d has gone", current_call));
1874     } else {
1875 	enum {
1876 	    ACCEPT_REPLACE, ACCEPT, REJECT, STOP
1877 	};
1878 	int choice = (int)pj_strtol(&cval->argv[1]);
1879 
1880 	switch (choice) {
1881 	case ACCEPT_REPLACE:
1882 	    pjsua_call_process_redirect(current_call,
1883 	    				PJSIP_REDIRECT_ACCEPT_REPLACE);
1884 	    break;
1885 	case ACCEPT:
1886 	    pjsua_call_process_redirect(current_call, PJSIP_REDIRECT_ACCEPT);
1887 	    break;
1888 	case REJECT:
1889 	    pjsua_call_process_redirect(current_call, PJSIP_REDIRECT_REJECT);
1890 	    break;
1891 	default:
1892 	    pjsua_call_process_redirect(current_call, PJSIP_REDIRECT_STOP);
1893 	    break;
1894 	}
1895     }
1896     return PJ_SUCCESS;
1897 }
1898 
1899 /* Send DTMF (RFC2833) */
cmd_dtmf_2833(pj_cli_cmd_val * cval)1900 static pj_status_t cmd_dtmf_2833(pj_cli_cmd_val *cval)
1901 {
1902     if (current_call == PJSUA_INVALID_ID) {
1903 
1904 	PJ_LOG(3,(THIS_FILE, "No current call"));
1905 
1906     } else if (!pjsua_call_has_media(current_call)) {
1907 
1908 	PJ_LOG(3,(THIS_FILE, "Media is not established yet!"));
1909 
1910     } else {
1911 	int call = current_call;
1912 	pj_status_t status;
1913 
1914 	if (call != current_call) {
1915 	    static const pj_str_t err_msg = {"Call has been disconnected\n",
1916 					     28};
1917 	    pj_cli_sess_write_msg(cval->sess, err_msg.ptr, err_msg.slen);
1918 	    return PJ_SUCCESS;;
1919 	}
1920 
1921 	status = pjsua_call_dial_dtmf(current_call, &cval->argv[1]);
1922 	if (status != PJ_SUCCESS) {
1923 	    pjsua_perror(THIS_FILE, "Unable to send DTMF", status);
1924 	} else {
1925 	    static const pj_str_t msg = {"DTMF digits enqueued "
1926 					 "for transmission\n", 39};
1927 	    pj_cli_sess_write_msg(cval->sess, msg.ptr, msg.slen);
1928 	}
1929     }
1930     return PJ_SUCCESS;
1931 }
1932 
1933 /* Send DTMF with SIP Info */
cmd_call_info(pj_cli_cmd_val * cval)1934 static pj_status_t cmd_call_info(pj_cli_cmd_val *cval)
1935 {
1936     if (current_call == PJSUA_INVALID_ID) {
1937 
1938 	PJ_LOG(3,(THIS_FILE, "No current call"));
1939 
1940     } else {
1941 	const pj_str_t SIP_INFO = pj_str("INFO");
1942 	int call = current_call;
1943 	int i;
1944 	pj_status_t status;
1945 
1946 	if (call != current_call) {
1947 	    static const pj_str_t err_msg = {"Call has been disconnected\n",
1948 					     28};
1949 	    pj_cli_sess_write_msg(cval->sess, err_msg.ptr, err_msg.slen);
1950 	    return PJ_SUCCESS;;
1951 	}
1952 
1953 	for (i=0; i<cval->argv[1].slen; ++i) {
1954 	    char body[64];
1955 
1956 	    pjsua_msg_data_init(&msg_data);
1957 	    msg_data.content_type = pj_str("application/dtmf-relay");
1958 
1959 	    pj_ansi_snprintf(body,
1960 			     sizeof(body),
1961 			     "Signal=%c\n"
1962 			     "Duration=160",
1963 			     cval->argv[1].ptr[i]);
1964 
1965 	    msg_data.msg_body = pj_str(body);
1966 
1967 	    status = pjsua_call_send_request(current_call, &SIP_INFO,
1968 		&msg_data);
1969 	    if (status != PJ_SUCCESS) {
1970 		break;
1971 	    }
1972 	}
1973     }
1974     return PJ_SUCCESS;
1975 }
1976 
1977 /* Dump call quality */
cmd_call_quality()1978 static pj_status_t cmd_call_quality()
1979 {
1980     if (current_call != PJSUA_INVALID_ID) {
1981 	log_call_dump(current_call);
1982     } else {
1983 	PJ_LOG(3,(THIS_FILE, "No current call"));
1984     }
1985     return PJ_SUCCESS;
1986 }
1987 
1988 /* Send arbitrary request */
cmd_send_arbitrary(pj_cli_cmd_val * cval)1989 static pj_status_t cmd_send_arbitrary(pj_cli_cmd_val *cval)
1990 {
1991     if (pjsua_acc_get_count() == 0) {
1992 	static const pj_str_t err_msg = {"Sorry, need at least one "
1993 					 "account configured\n", 45};
1994 	pj_cli_sess_write_msg(cval->sess, err_msg.ptr, err_msg.slen);
1995     } else {
1996 	char *uri;
1997 	char dest[64] = {0};
1998 	pj_str_t tmp = pj_str(dest);
1999 	struct input_result result;
2000 	static const pj_str_t header = {"Send arbitrary request to "
2001 					"remote host\n", 39};
2002 
2003 	pj_cli_sess_write_msg(cval->sess, header.ptr, header.slen);
2004 
2005 	pj_strncpy_with_null(&tmp, &cval->argv[2], sizeof(dest));
2006 	/* Input destination URI */
2007 	uri = NULL;
2008 	get_input_url(tmp.ptr, tmp.slen, cval, &result);
2009 	if (result.nb_result != PJSUA_APP_NO_NB) {
2010 	    if (result.nb_result == -1) {
2011 		static const pj_str_t err_msg = {"Sorry you can't do that!\n",
2012 						 26};
2013 		pj_cli_sess_write_msg(cval->sess, err_msg.ptr, err_msg.slen);
2014 		return PJ_SUCCESS;
2015 	    } else if (result.nb_result == 0) {
2016 		uri = NULL;
2017 		if (current_call == PJSUA_INVALID_ID) {
2018 		    static const pj_str_t err_msg = {"No current call\n",
2019 						     17};
2020 		    pj_cli_sess_write_msg(cval->sess, err_msg.ptr,
2021 				          err_msg.slen);
2022 
2023 		    return PJ_SUCCESS;
2024 		}
2025 	    } else {
2026 		pjsua_buddy_info binfo;
2027 		pjsua_buddy_get_info(result.nb_result-1, &binfo);
2028 		pj_strncpy_with_null(&tmp, &binfo.uri, sizeof(dest));
2029 		uri = tmp.ptr;
2030 	    }
2031 	} else if (result.uri_result) {
2032 	    uri = result.uri_result;
2033 	} else {
2034 	    return PJ_SUCCESS;;
2035 	}
2036 
2037 	if (uri) {
2038 	    char method[64] = {0};
2039 	    pj_str_t tmp_method = pj_str(method);
2040 	    pj_strncpy_with_null(&tmp_method, &cval->argv[1], sizeof(method));
2041 	    tmp = pj_str(uri);
2042 	    send_request(method, &tmp);
2043 	} else {
2044 	    /* If you send call control request using this method
2045 	    * (such requests includes BYE, CANCEL, etc.), it will
2046 	    * not go well with the call state, so don't do it
2047 	    * unless it's for testing.
2048 	    */
2049 	    pjsua_call_send_request(current_call, &cval->argv[1], NULL);
2050 	}
2051     }
2052     return PJ_SUCCESS;
2053 }
2054 
cmd_show_current_call(pj_cli_cmd_val * cval)2055 static pj_status_t cmd_show_current_call(pj_cli_cmd_val *cval)
2056 {
2057     char out_str[128];
2058     int i = pjsua_call_get_count();
2059     pj_ansi_snprintf(out_str, sizeof(out_str),
2060 		     "You have %d active call%s\n", i, (i>1?"s":""));
2061 
2062     pj_cli_sess_write_msg(cval->sess, out_str,
2063 	pj_ansi_strlen(out_str));
2064 
2065     if (current_call != PJSUA_INVALID_ID) {
2066 	pjsua_call_info ci;
2067 	if (pjsua_call_get_info(current_call, &ci)==PJ_SUCCESS) {
2068 	    pj_ansi_snprintf(out_str, sizeof(out_str),
2069 		   "Current call id=%d to %.*s [%.*s]\n", current_call,
2070 		   (int)ci.remote_info.slen, ci.remote_info.ptr,
2071 		   (int)ci.state_text.slen, ci.state_text.ptr);
2072 
2073 	    pj_cli_sess_write_msg(cval->sess, out_str,
2074 				  pj_ansi_strlen(out_str));
2075 	}
2076     }
2077     return PJ_SUCCESS;
2078 }
2079 
2080 /* Call handler */
cmd_call_handler(pj_cli_cmd_val * cval)2081 pj_status_t cmd_call_handler(pj_cli_cmd_val *cval)
2082 {
2083     pj_status_t status = PJ_SUCCESS;
2084     pj_cli_cmd_id cmd_id = pj_cli_get_cmd_id(cval->cmd);
2085 
2086     CHECK_PJSUA_RUNNING();
2087 
2088     switch(cmd_id) {
2089     case CMD_CALL_NEW:
2090 	status = cmd_make_single_call(cval);
2091 	break;
2092     case CMD_CALL_MULTI:
2093 	status = cmd_make_multi_call(cval);
2094 	break;
2095     case CMD_CALL_ANSWER:
2096 	status = cmd_answer_call(cval);
2097 	break;
2098     case CMD_CALL_HANGUP:
2099     case CMD_CALL_HANGUP_ALL:
2100 	status = cmd_hangup_call(cval, (cmd_id==CMD_CALL_HANGUP_ALL));
2101 	break;
2102     case CMD_CALL_HOLD:
2103 	status = cmd_hold_call();
2104 	break;
2105     case CMD_CALL_REINVITE:
2106 	status = cmd_call_reinvite();
2107 	break;
2108     case CMD_CALL_UPDATE:
2109 	status = cmd_call_update();
2110 	break;
2111     case CMD_CALL_NEXT:
2112     case CMD_CALL_PREVIOUS:
2113 	status = cmd_next_call(cmd_id==CMD_CALL_NEXT);
2114 	break;
2115     case CMD_CALL_TRANSFER:
2116 	status = cmd_transfer_call(cval);
2117 	break;
2118     case CMD_CALL_TRANSFER_REPLACE:
2119 	status = cmd_transfer_replace_call(cval);
2120 	break;
2121     case CMD_CALL_REDIRECT:
2122 	status = cmd_redirect_call(cval);
2123 	break;
2124     case CMD_CALL_D2833:
2125 	status = cmd_dtmf_2833(cval);
2126 	break;
2127     case CMD_CALL_INFO:
2128 	status = cmd_call_info(cval);
2129 	break;
2130     case CMD_CALL_DUMP_Q:
2131 	status = cmd_call_quality();
2132 	break;
2133     case CMD_CALL_SEND_ARB:
2134 	status = cmd_send_arbitrary(cval);
2135 	break;
2136     case CMD_CALL_LIST:
2137 	status = cmd_show_current_call(cval);
2138 	break;
2139     }
2140 
2141     return status;
2142 }
2143 
2144 #if PJSUA_HAS_VIDEO
cmd_set_video_enable(pj_bool_t enabled)2145 static pj_status_t cmd_set_video_enable(pj_bool_t enabled)
2146 {
2147     app_config.vid.vid_cnt = (enabled ? 1 : 0);
2148     PJ_LOG(3,(THIS_FILE, "Video will be %s in next offer/answer",
2149 	(enabled?"enabled":"disabled")));
2150 
2151     return PJ_SUCCESS;
2152 }
2153 
modify_video_account(pjsua_acc_config * acc_cfg)2154 static pj_status_t modify_video_account(pjsua_acc_config *acc_cfg)
2155 {
2156     pj_status_t status = pjsua_acc_modify(current_acc, acc_cfg);
2157     if (status != PJ_SUCCESS)
2158 	PJ_PERROR(1,(THIS_FILE, status, "Error modifying account %d",
2159 		     current_acc));
2160 
2161     return status;
2162 }
2163 
cmd_show_account_video()2164 static pj_status_t cmd_show_account_video()
2165 {
2166     pjsua_acc_config acc_cfg;
2167     pj_pool_t *pool = pjsua_pool_create("tmp-pjsua", 1000, 1000);
2168 
2169     pjsua_acc_get_config(current_acc, pool, &acc_cfg);
2170     app_config_show_video(current_acc, &acc_cfg);
2171     pj_pool_release(pool);
2172     return PJ_SUCCESS;
2173 }
2174 
cmd_video_acc_handler(pj_cli_cmd_val * cval)2175 static pj_status_t cmd_video_acc_handler(pj_cli_cmd_val *cval)
2176 {
2177     pjsua_acc_config acc_cfg;
2178     pj_cli_cmd_id cmd_id = pj_cli_get_cmd_id(cval->cmd);
2179     pj_pool_t *pool = pjsua_pool_create("tmp-pjsua", 1000, 1000);
2180 
2181     CHECK_PJSUA_RUNNING();
2182 
2183     pjsua_acc_get_config(current_acc, pool, &acc_cfg);
2184 
2185     switch(cmd_id) {
2186     case CMD_VIDEO_ACC_AUTORX:
2187     case CMD_VIDEO_ACC_AUTOTX:
2188 	{
2189 	    int on = (pj_ansi_strnicmp(cval->argv[1].ptr, "On", 2)==0);
2190 
2191 	    if (cmd_id == CMD_VIDEO_ACC_AUTORX)
2192 		acc_cfg.vid_in_auto_show = on;
2193 	    else
2194 		acc_cfg.vid_out_auto_transmit = on;
2195 	}
2196 	break;
2197     case CMD_VIDEO_ACC_CAP_ID:
2198     case CMD_VIDEO_ACC_REN_ID:
2199 	{
2200 	    int dev = (int)pj_strtol(&cval->argv[1]);
2201 
2202 	    if (cmd_id == CMD_VIDEO_ACC_CAP_ID)
2203 		acc_cfg.vid_cap_dev = dev;
2204 	    else
2205 		acc_cfg.vid_rend_dev = dev;
2206 	}
2207 	break;
2208     }
2209     modify_video_account(&acc_cfg);
2210     pj_pool_release(pool);
2211     return PJ_SUCCESS;
2212 }
2213 
cmd_add_vid_strm()2214 static pj_status_t cmd_add_vid_strm()
2215 {
2216     return pjsua_call_set_vid_strm(current_call,
2217 				   PJSUA_CALL_VID_STRM_ADD, NULL);
2218 }
2219 
cmd_enable_vid_rx(pj_cli_cmd_val * cval)2220 static pj_status_t cmd_enable_vid_rx(pj_cli_cmd_val *cval)
2221 {
2222     pjsua_call_vid_strm_op_param param;
2223     pjsua_stream_info si;
2224     pj_status_t status = PJ_SUCCESS;
2225     pj_bool_t on = (pj_ansi_strnicmp(cval->argv[1].ptr, "On", 2) == 0);
2226 
2227     pjsua_call_vid_strm_op_param_default(&param);
2228 
2229     param.med_idx = (int)pj_strtol(&cval->argv[2]);
2230     if (pjsua_call_get_stream_info(current_call, param.med_idx, &si) ||
2231 	si.type != PJMEDIA_TYPE_VIDEO)
2232     {
2233 	PJ_PERROR(1,(THIS_FILE, PJ_EINVAL, "Invalid stream"));
2234 	return status;
2235     }
2236 
2237     if (on) param.dir = (si.info.vid.dir | PJMEDIA_DIR_DECODING);
2238     else param.dir = (si.info.vid.dir & PJMEDIA_DIR_ENCODING);
2239 
2240     status = pjsua_call_set_vid_strm(current_call,
2241 				     PJSUA_CALL_VID_STRM_CHANGE_DIR,
2242 				     &param);
2243     return status;
2244 }
2245 
cmd_enable_vid_tx(pj_cli_cmd_val * cval)2246 static pj_status_t cmd_enable_vid_tx(pj_cli_cmd_val *cval)
2247 {
2248     pjsua_call_vid_strm_op_param param;
2249     pj_status_t status = PJ_SUCCESS;
2250     pj_bool_t on = (pj_ansi_strnicmp(cval->argv[1].ptr, "On", 2) == 0);
2251 
2252     pjsua_call_vid_strm_op op = on? PJSUA_CALL_VID_STRM_START_TRANSMIT :
2253 				PJSUA_CALL_VID_STRM_STOP_TRANSMIT;
2254 
2255     pjsua_call_vid_strm_op_param_default(&param);
2256 
2257     param.med_idx = (int)pj_strtol(&cval->argv[2]);
2258 
2259     status = pjsua_call_set_vid_strm(current_call, op, &param);
2260     return status;
2261 }
2262 
cmd_enable_vid_stream(pj_cli_cmd_val * cval,pj_bool_t enable)2263 static pj_status_t cmd_enable_vid_stream(pj_cli_cmd_val *cval,
2264 					   pj_bool_t enable)
2265 {
2266     pjsua_call_vid_strm_op_param param;
2267     pjsua_call_vid_strm_op op = enable? PJSUA_CALL_VID_STRM_CHANGE_DIR :
2268 				PJSUA_CALL_VID_STRM_REMOVE;
2269 
2270     pjsua_call_vid_strm_op_param_default(&param);
2271 
2272     param.med_idx = cval->argc > 1 ?
2273                     (int)pj_strtol(&cval->argv[1]) : -1;
2274     param.dir = PJMEDIA_DIR_ENCODING_DECODING;
2275     return pjsua_call_set_vid_strm(current_call, op, &param);
2276 }
2277 
cmd_set_cap_dev_id(pj_cli_cmd_val * cval)2278 static pj_status_t cmd_set_cap_dev_id(pj_cli_cmd_val *cval)
2279 {
2280     pjsua_call_vid_strm_op_param param;
2281 
2282     pjsua_call_vid_strm_op_param_default(&param);
2283     param.med_idx = cval->argc > 1?
2284                     (int)pj_strtol(&cval->argv[1]) : -1;
2285     param.cap_dev = cval->argc > 2?
2286                     (int)pj_strtol(&cval->argv[2]) :
2287                     PJMEDIA_VID_DEFAULT_CAPTURE_DEV;
2288 
2289     return pjsua_call_set_vid_strm(current_call,
2290 				   PJSUA_CALL_VID_STRM_CHANGE_CAP_DEV,
2291 				   &param);
2292 }
2293 
cmd_list_vid_dev()2294 static pj_status_t cmd_list_vid_dev()
2295 {
2296     vid_list_devs();
2297     return PJ_SUCCESS;
2298 }
2299 
cmd_vid_device_refresh()2300 static pj_status_t cmd_vid_device_refresh()
2301 {
2302     pjmedia_vid_dev_refresh();
2303     return PJ_SUCCESS;
2304 }
2305 
cmd_vid_device_preview(pj_cli_cmd_val * cval)2306 static pj_status_t cmd_vid_device_preview(pj_cli_cmd_val *cval)
2307 {
2308     int dev_id = (int)pj_strtol(&cval->argv[2]);
2309     pj_bool_t on = (pj_ansi_strnicmp(cval->argv[1].ptr, "On", 2) == 0);
2310 
2311     if (on) {
2312 	pjsua_vid_preview_param param;
2313 
2314 	pjsua_vid_preview_param_default(&param);
2315 	param.wnd_flags = PJMEDIA_VID_DEV_WND_BORDER |
2316 	    PJMEDIA_VID_DEV_WND_RESIZABLE;
2317 	pjsua_vid_preview_start(dev_id, &param);
2318 	arrange_window(pjsua_vid_preview_get_win(dev_id));
2319     } else {
2320 	pjsua_vid_win_id wid;
2321 	wid = pjsua_vid_preview_get_win(dev_id);
2322 	if (wid != PJSUA_INVALID_ID) {
2323 	    /* Preview window hiding once it is stopped is
2324 	    * responsibility of app */
2325 	    pjsua_vid_win_set_show(wid, PJ_FALSE);
2326 	    pjsua_vid_preview_stop(dev_id);
2327 	}
2328     }
2329     return PJ_SUCCESS;
2330 }
2331 
cmd_vid_codec_list()2332 static pj_status_t cmd_vid_codec_list()
2333 {
2334     pjsua_codec_info ci[PJMEDIA_CODEC_MGR_MAX_CODECS];
2335     unsigned count = PJ_ARRAY_SIZE(ci);
2336     pj_status_t status = pjsua_vid_enum_codecs(ci, &count);
2337     if (status != PJ_SUCCESS) {
2338 	PJ_PERROR(1,(THIS_FILE, status, "Error enumerating codecs"));
2339     } else {
2340 	unsigned i;
2341 	PJ_LOG(3,(THIS_FILE, "Found %d video codecs:", count));
2342 	PJ_LOG(3,(THIS_FILE, "codec id      prio  fps    bw(kbps)   size"));
2343 	PJ_LOG(3,(THIS_FILE, "------------------------------------------"));
2344 	for (i=0; i<count; ++i) {
2345 	    pjmedia_vid_codec_param cp;
2346 	    pjmedia_video_format_detail *vfd;
2347 
2348 	    status = pjsua_vid_codec_get_param(&ci[i].codec_id, &cp);
2349 	    if (status != PJ_SUCCESS)
2350 		continue;
2351 
2352 	    vfd = pjmedia_format_get_video_format_detail(&cp.enc_fmt,
2353 		PJ_TRUE);
2354 	    PJ_LOG(3,(THIS_FILE, "%.*s%.*s %3d %7.2f  %4d/%4d  %dx%d",
2355 		(int)ci[i].codec_id.slen, ci[i].codec_id.ptr,
2356 		13-(int)ci[i].codec_id.slen, "                ",
2357 		ci[i].priority,
2358 		(vfd->fps.num*1.0/vfd->fps.denum),
2359 		vfd->avg_bps/1000, vfd->max_bps/1000,
2360 		vfd->size.w, vfd->size.h));
2361 	}
2362     }
2363     return PJ_SUCCESS;
2364 }
2365 
cmd_set_vid_codec_prio(pj_cli_cmd_val * cval)2366 static pj_status_t cmd_set_vid_codec_prio(pj_cli_cmd_val *cval)
2367 {
2368     int prio = (int)pj_strtol(&cval->argv[2]);
2369     pj_status_t status;
2370 
2371     status = pjsua_vid_codec_set_priority(&cval->argv[1], (pj_uint8_t)prio);
2372     if (status != PJ_SUCCESS)
2373 	PJ_PERROR(1,(THIS_FILE, status, "Set codec priority error"));
2374 
2375     return PJ_SUCCESS;
2376 }
2377 
cmd_set_vid_codec_fps(pj_cli_cmd_val * cval)2378 static pj_status_t cmd_set_vid_codec_fps(pj_cli_cmd_val *cval)
2379 {
2380     pjmedia_vid_codec_param cp;
2381     int M, N;
2382     pj_status_t status;
2383 
2384     M = (int)pj_strtol(&cval->argv[2]);
2385     N = (int)pj_strtol(&cval->argv[3]);
2386     status = pjsua_vid_codec_get_param(&cval->argv[1], &cp);
2387     if (status == PJ_SUCCESS) {
2388 	cp.enc_fmt.det.vid.fps.num = M;
2389 	cp.enc_fmt.det.vid.fps.denum = N;
2390 	status = pjsua_vid_codec_set_param(&cval->argv[1], &cp);
2391     }
2392     if (status != PJ_SUCCESS)
2393 	PJ_PERROR(1,(THIS_FILE, status, "Set codec framerate error"));
2394 
2395     return PJ_SUCCESS;
2396 }
2397 
cmd_set_vid_codec_bitrate(pj_cli_cmd_val * cval)2398 static pj_status_t cmd_set_vid_codec_bitrate(pj_cli_cmd_val *cval)
2399 {
2400     pjmedia_vid_codec_param cp;
2401     int M, N;
2402     pj_status_t status;
2403 
2404     M = (int)pj_strtol(&cval->argv[2]);
2405     N = (int)pj_strtol(&cval->argv[3]);
2406     status = pjsua_vid_codec_get_param(&cval->argv[1], &cp);
2407     if (status == PJ_SUCCESS) {
2408 	cp.enc_fmt.det.vid.avg_bps = M * 1000;
2409 	cp.enc_fmt.det.vid.max_bps = N * 1000;
2410 	status = pjsua_vid_codec_set_param(&cval->argv[1], &cp);
2411     }
2412     if (status != PJ_SUCCESS)
2413 	PJ_PERROR(1,(THIS_FILE, status, "Set codec bitrate error"));
2414 
2415     return status;
2416 }
2417 
cmd_set_vid_codec_size(pj_cli_cmd_val * cval)2418 static pj_status_t cmd_set_vid_codec_size(pj_cli_cmd_val *cval)
2419 {
2420     pjmedia_vid_codec_param cp;
2421     int M, N;
2422     pj_status_t status;
2423 
2424     M = (int)pj_strtol(&cval->argv[2]);
2425     N = (int)pj_strtol(&cval->argv[3]);
2426     status = pjsua_vid_codec_get_param(&cval->argv[1], &cp);
2427     if (status == PJ_SUCCESS) {
2428 	cp.enc_fmt.det.vid.size.w = M;
2429 	cp.enc_fmt.det.vid.size.h = N;
2430 	status = pjsua_vid_codec_set_param(&cval->argv[1], &cp);
2431     }
2432     if (status != PJ_SUCCESS)
2433 	PJ_PERROR(1,(THIS_FILE, status, "Set codec size error"));
2434 
2435     return status;
2436 }
2437 
cmd_vid_win_list()2438 static pj_status_t cmd_vid_win_list()
2439 {
2440     pjsua_vid_win_id wids[PJSUA_MAX_VID_WINS];
2441     unsigned i, cnt = PJ_ARRAY_SIZE(wids);
2442 
2443     pjsua_vid_enum_wins(wids, &cnt);
2444 
2445     PJ_LOG(3,(THIS_FILE, "Found %d video windows:", cnt));
2446     PJ_LOG(3,(THIS_FILE, "WID show    pos       size"));
2447     PJ_LOG(3,(THIS_FILE, "------------------------------"));
2448     for (i = 0; i < cnt; ++i) {
2449 	pjsua_vid_win_info wi;
2450 	pjsua_vid_win_get_info(wids[i], &wi);
2451 	PJ_LOG(3,(THIS_FILE, "%3d   %c  (%d,%d)  %dx%d",
2452 	    wids[i], (wi.show?'Y':'N'), wi.pos.x, wi.pos.y,
2453 	    wi.size.w, wi.size.h));
2454     }
2455     return PJ_SUCCESS;
2456 }
2457 
cmd_arrange_vid_win()2458 static pj_status_t cmd_arrange_vid_win()
2459 {
2460     arrange_window(PJSUA_INVALID_ID);
2461     return PJ_SUCCESS;
2462 }
2463 
cmd_show_vid_win(pj_cli_cmd_val * cval,pj_bool_t show)2464 static pj_status_t cmd_show_vid_win(pj_cli_cmd_val *cval, pj_bool_t show)
2465 {
2466     pjsua_vid_win_id wid = (int)pj_strtol(&cval->argv[1]);
2467     return pjsua_vid_win_set_show(wid, show);
2468 }
2469 
cmd_move_vid_win(pj_cli_cmd_val * cval)2470 static pj_status_t cmd_move_vid_win(pj_cli_cmd_val *cval)
2471 {
2472     pjsua_vid_win_id wid = (int)pj_strtol(&cval->argv[1]);
2473     pjmedia_coord pos;
2474 
2475     pos.x = (int)pj_strtol(&cval->argv[2]);
2476     pos.y = (int)pj_strtol(&cval->argv[3]);
2477     return pjsua_vid_win_set_pos(wid, &pos);
2478 }
2479 
cmd_resize_vid_win(pj_cli_cmd_val * cval)2480 static pj_status_t cmd_resize_vid_win(pj_cli_cmd_val *cval)
2481 {
2482     pjsua_vid_win_id wid = (int)pj_strtol(&cval->argv[1]);
2483     pjmedia_rect_size size;
2484 
2485     size.w = (int)pj_strtol(&cval->argv[2]);
2486     size.h = (int)pj_strtol(&cval->argv[3]);
2487     return pjsua_vid_win_set_size(wid, &size);
2488 }
2489 
cmd_vid_conf_list()2490 static pj_status_t cmd_vid_conf_list()
2491 {
2492     pjsua_conf_port_id id[100];
2493     unsigned count = PJ_ARRAY_SIZE(id);
2494     unsigned i;
2495     pj_status_t status;
2496 
2497     status = pjsua_vid_conf_enum_ports(id, &count);
2498     if (status != PJ_SUCCESS) {
2499 	PJ_PERROR(1,(THIS_FILE, status,
2500 		     "Failed enumerating video conf bridge ports"));
2501 	return status;
2502     }
2503 
2504     PJ_LOG(3,(THIS_FILE," Video conference has %d ports:\n", count));
2505     PJ_LOG(3,(THIS_FILE," id name                   format               rx           tx    \n"));
2506     PJ_LOG(3,(THIS_FILE," ------------------------------------------------------------------\n"));
2507     for (i=0; i<count; ++i) {
2508 	char li_list[PJSUA_MAX_CALLS*4];
2509 	char tr_list[PJSUA_MAX_CALLS*4];
2510 	char s[32];
2511 	unsigned j;
2512 	pjsua_vid_conf_port_info info;
2513 	pjmedia_rect_size *size;
2514 	pjmedia_ratio *fps;
2515 
2516 	pjsua_vid_conf_get_port_info(id[i], &info);
2517 	size = &info.format.det.vid.size;
2518 	fps = &info.format.det.vid.fps;
2519 
2520 	li_list[0] = '\0';
2521 	for (j=0; j<info.listener_cnt; ++j) {
2522 	    char str_info[10];
2523 	    pj_ansi_snprintf(str_info, sizeof(str_info), "%d%s",
2524 			     info.listeners[j],
2525 			     (j==info.listener_cnt-1)?"":",");
2526 	    pj_ansi_strcat(li_list, str_info);
2527 	}
2528 	tr_list[0] = '\0';
2529 	for (j=0; j<info.transmitter_cnt; ++j) {
2530 	    char str_info[10];
2531 	    pj_ansi_snprintf(str_info, sizeof(str_info), "%d%s", info.transmitters[j],
2532 			     (j==info.transmitter_cnt-1)?"":",");
2533 	    pj_ansi_strcat(tr_list, str_info);
2534 	}
2535 	pjmedia_fourcc_name(info.format.id, s);
2536 	s[4] = ' ';
2537 	pj_ansi_snprintf(s+5, sizeof(s)-5, "%dx%d@%.1f",
2538 			 size->w, size->h, (float)(fps->num*1.0/fps->denum));
2539 	PJ_LOG(3,(THIS_FILE,"%3d %.*s%.*s %s%.*s %s%.*s %s\n",
2540 			    id[i],
2541 			    (int)info.name.slen, info.name.ptr,
2542 			    22-(int)info.name.slen, "                   ",
2543 			    s,
2544 			    20-pj_ansi_strlen(s), "                    ",
2545 			    tr_list,
2546 			    12-pj_ansi_strlen(tr_list), "            ",
2547 			    li_list));
2548     }
2549     return PJ_SUCCESS;
2550 }
2551 
cmd_vid_conf_connect(pj_cli_cmd_val * cval,pj_bool_t connect)2552 static pj_status_t cmd_vid_conf_connect(pj_cli_cmd_val *cval, pj_bool_t connect)
2553 {
2554     int P, Q;
2555 
2556     P = (int)pj_strtol(&cval->argv[1]);
2557     Q = (int)pj_strtol(&cval->argv[2]);
2558     if (connect)
2559 	return pjsua_vid_conf_connect(P, Q, NULL);
2560     else
2561 	return pjsua_vid_conf_disconnect(P, Q);
2562 }
2563 
2564 
2565 /* Video handler */
cmd_video_handler(pj_cli_cmd_val * cval)2566 static pj_status_t cmd_video_handler(pj_cli_cmd_val *cval)
2567 {
2568     pj_status_t status = PJ_SUCCESS;
2569     pj_cli_cmd_id cmd_id = pj_cli_get_cmd_id(cval->cmd);
2570 
2571     CHECK_PJSUA_RUNNING();
2572 
2573     switch(cmd_id) {
2574     case CMD_VIDEO_ENABLE:
2575 	status = cmd_set_video_enable(PJ_TRUE);
2576 	break;
2577     case CMD_VIDEO_DISABLE:
2578 	status = cmd_set_video_enable(PJ_FALSE);
2579 	break;
2580     case CMD_VIDEO_ACC_SHOW:
2581 	status = cmd_show_account_video();
2582 	break;
2583     case CMD_VIDEO_ACC_AUTORX:
2584     case CMD_VIDEO_ACC_AUTOTX:
2585     case CMD_VIDEO_ACC_CAP_ID:
2586     case CMD_VIDEO_ACC_REN_ID:
2587 	status = cmd_video_acc_handler(cval);
2588 	break;
2589     case CMD_VIDEO_CALL_ADD:
2590 	status = cmd_add_vid_strm();
2591 	break;
2592     case CMD_VIDEO_CALL_RX:
2593 	status = cmd_enable_vid_rx(cval);
2594 	break;
2595     case CMD_VIDEO_CALL_TX:
2596 	status = cmd_enable_vid_tx(cval);
2597 	break;
2598     case CMD_VIDEO_CALL_ENABLE:
2599     case CMD_VIDEO_CALL_DISABLE:
2600 	status = cmd_enable_vid_stream(cval, (cmd_id==CMD_VIDEO_CALL_ENABLE));
2601 	break;
2602     case CMD_VIDEO_CALL_CAP:
2603 	status = cmd_set_cap_dev_id(cval);
2604 	break;
2605     case CMD_VIDEO_DEVICE_LIST:
2606 	status = cmd_list_vid_dev();
2607 	break;
2608     case CMD_VIDEO_DEVICE_REFRESH:
2609 	status = cmd_vid_device_refresh();
2610 	break;
2611     case CMD_VIDEO_DEVICE_PREVIEW:
2612 	status = cmd_vid_device_preview(cval);
2613 	break;
2614     case CMD_VIDEO_CODEC_LIST:
2615 	status = cmd_vid_codec_list();
2616 	break;
2617     case CMD_VIDEO_CODEC_PRIO:
2618 	status = cmd_set_vid_codec_prio(cval);
2619 	break;
2620     case CMD_VIDEO_CODEC_FPS:
2621 	status = cmd_set_vid_codec_fps(cval);
2622 	break;
2623     case CMD_VIDEO_CODEC_BITRATE:
2624 	status = cmd_set_vid_codec_bitrate(cval);
2625 	break;
2626     case CMD_VIDEO_CODEC_SIZE:
2627 	status = cmd_set_vid_codec_size(cval);
2628 	break;
2629     case CMD_VIDEO_WIN_LIST:
2630 	status = cmd_vid_win_list();
2631 	break;
2632     case CMD_VIDEO_WIN_ARRANGE:
2633 	status = cmd_arrange_vid_win();
2634 	break;
2635     case CMD_VIDEO_WIN_SHOW:
2636     case CMD_VIDEO_WIN_HIDE:
2637 	status = cmd_show_vid_win(cval, (cmd_id==CMD_VIDEO_WIN_SHOW));
2638 	break;
2639     case CMD_VIDEO_WIN_MOVE:
2640 	status = cmd_move_vid_win(cval);
2641 	break;
2642     case CMD_VIDEO_WIN_RESIZE:
2643 	status = cmd_resize_vid_win(cval);
2644 	break;
2645     case CMD_VIDEO_CONF_LIST:
2646 	status = cmd_vid_conf_list();
2647 	break;
2648     case CMD_VIDEO_CONF_CONNECT:
2649     case CMD_VIDEO_CONF_DISCONNECT:
2650 	status = cmd_vid_conf_connect(cval, (cmd_id==CMD_VIDEO_CONF_CONNECT));
2651 	break;
2652     }
2653 
2654     return status;
2655 }
2656 #endif
2657 
2658 /* Other command handler */
cmd_sleep_handler(pj_cli_cmd_val * cval)2659 static pj_status_t cmd_sleep_handler(pj_cli_cmd_val *cval)
2660 {
2661     int delay;
2662 
2663     delay = (int)pj_strtoul(&cval->argv[1]);
2664     if (delay < 0) delay = 0;
2665     pj_thread_sleep(delay);
2666 
2667     return PJ_SUCCESS;
2668 }
2669 
cmd_network_handler(pj_cli_cmd_val * cval)2670 static pj_status_t cmd_network_handler(pj_cli_cmd_val *cval)
2671 {
2672     pj_status_t status = PJ_SUCCESS;
2673     PJ_UNUSED_ARG(cval);
2674 
2675     CHECK_PJSUA_RUNNING();
2676 
2677     status = pjsua_detect_nat_type();
2678     if (status != PJ_SUCCESS)
2679 	pjsua_perror(THIS_FILE, "Error", status);
2680 
2681     return status;
2682 }
2683 
cmd_quit_handler(pj_cli_cmd_val * cval)2684 static pj_status_t cmd_quit_handler(pj_cli_cmd_val *cval)
2685 {
2686     PJ_LOG(3,(THIS_FILE, "Quitting app.."));
2687     pj_cli_quit(cval->sess->fe->cli, cval->sess, PJ_FALSE);
2688 
2689     /* Invoke CLI stop callback (defined in pjsua_app.c) */
2690     cli_on_stopped(PJ_FALSE, 0, NULL);
2691 
2692     return PJ_SUCCESS;
2693 }
2694 
cmd_ip_change_handler(pj_cli_cmd_val * cval)2695 static pj_status_t cmd_ip_change_handler(pj_cli_cmd_val *cval)
2696 {
2697     pjsua_ip_change_param param;
2698     PJ_UNUSED_ARG(cval);
2699 
2700     pjsua_ip_change_param_default(&param);
2701     pjsua_handle_ip_change(&param);
2702 
2703     return PJ_SUCCESS;
2704 }
2705 
2706 /*
2707  * Syntax error handler for parser.
2708  */
on_syntax_error(pj_scanner * scanner)2709 static void on_syntax_error(pj_scanner *scanner)
2710 {
2711     PJ_UNUSED_ARG(scanner);
2712     PJ_THROW(PJ_EINVAL);
2713 }
2714 
2715 /*
2716  * This method will parse buffer string info array of argument string
2717  * @argc On input, maximum array size of argument. On output, number of argument
2718  * parsed
2719  * @argv Array of argument string
2720  */
get_options(pj_str_t * options,unsigned * argc,pj_str_t argv[])2721 static pj_status_t get_options(pj_str_t *options, unsigned *argc,
2722 			       pj_str_t argv[])
2723 {
2724     pj_scanner scanner;
2725     unsigned max_argc = *argc;
2726 
2727     PJ_USE_EXCEPTION;
2728 
2729     if (!options)
2730 	return PJ_SUCCESS;
2731 
2732     pj_scan_init(&scanner, options->ptr, options->slen, PJ_SCAN_AUTOSKIP_WS,
2733 		 &on_syntax_error);
2734     PJ_TRY {
2735 	*argc = 0;
2736 	while (!pj_scan_is_eof(&scanner) && (max_argc > *argc)) {
2737 	    pj_str_t str;
2738 
2739 	    pj_scan_get_until_chr(&scanner, " \t\r\n", &str);
2740 	    argv[*argc] = str;
2741 	    ++(*argc);
2742 	}
2743     }
2744     PJ_CATCH_ANY {
2745 	pj_scan_fini(&scanner);
2746 	return PJ_GET_EXCEPTION();
2747     }
2748     PJ_END;
2749     return PJ_SUCCESS;
2750 }
2751 
cmd_restart_handler(pj_cli_cmd_val * cval)2752 static pj_status_t cmd_restart_handler(pj_cli_cmd_val *cval)
2753 {
2754     enum { MAX_ARGC = 64 };
2755     int i;
2756     unsigned argc = 1;
2757     static char argv_buffer[PJ_CLI_MAX_CMDBUF];
2758     static char *argv[MAX_ARGC] = {NULL};
2759     char *pbuf = argv_buffer;
2760 
2761     PJ_LOG(3,(THIS_FILE, "Restarting app.."));
2762     pj_cli_quit(cval->sess->fe->cli, cval->sess, PJ_TRUE);
2763 
2764     /** Get the pjsua option **/
2765     for (i=1; i < cval->argc; i++) {
2766 	pj_str_t argvst[MAX_ARGC];
2767 	unsigned j, ac;
2768 
2769 	ac = MAX_ARGC - argc;
2770 	get_options(&cval->argv[i], &ac, argvst);
2771 	for (j = 0; j < ac; j++) {
2772 	    pj_ansi_strncpy(pbuf, argvst[j].ptr, argvst[j].slen);
2773 	    pbuf[argvst[j].slen] = '\0';
2774 	    argv[argc + j] = pbuf;
2775 	    pbuf += argvst[j].slen + 1;
2776 	}
2777 	argc += ac;
2778     }
2779 
2780     /* Invoke CLI stop callback (defined in pjsua_app.c) */
2781     cli_on_stopped(PJ_TRUE, argc, (char**)argv);
2782 
2783     return PJ_SUCCESS;
2784 }
2785 
add_call_command(pj_cli_t * c)2786 static pj_status_t add_call_command(pj_cli_t *c)
2787 {
2788     char* call_command =
2789 	"<CMD name='call' id='100' desc='Call related commands'>"
2790 	"  <CMD name='new' id='1001' desc='Make a new call/INVITE'>"
2791 	"    <ARG name='buddy_id' type='choice' id='9901' validate='0' "
2792 	"     desc='Buddy Id'>"
2793 	"      <CHOICE value='-1' desc='All buddies'/>"
2794 	"      <CHOICE value='0' desc='Current dialog'/>"
2795 	"    </ARG>"
2796 	"  </CMD>"
2797 	"  <CMD name='multi' id='1002' desc='Make multiple calls'>"
2798 	"    <ARG name='number_of_calls' type='int' desc='Number of calls'/>"
2799 	"    <ARG name='buddy_id' type='choice' id='9901' validate='0' "
2800 	"     desc='Buddy Id'>"
2801 	"      <CHOICE value='-1' desc='All buddies'/>"
2802 	"      <CHOICE value='0' desc='Current dialog'/>"
2803 	"    </ARG>"
2804 	"  </CMD>"
2805 	"  <CMD name='answer' id='1003' desc='Answer call'>"
2806 	"    <ARG name='code' type='int' desc='Answer code'/>"
2807 	"    <ARG name='new_url' type='string' optional='1' "
2808 	"     desc='New URL(for 3xx resp)'/>"
2809 	"  </CMD>"
2810 	"  <CMD name='hangup' id='1004' sc='g' desc='Hangup call'/>"
2811 	"  <CMD name='hangup_all' id='1005' sc='hA' desc='Hangup all call'/>"
2812 	"  <CMD name='hold' id='1006' sc='H' desc='Hold call'/>"
2813 	"  <CMD name='reinvite' id='1007' sc='v' "
2814 	"   desc='Re-invite (release hold)'/>"
2815 	"  <CMD name='update' id='1008' sc='U' desc='Send Update request'/>"
2816 	"  <CMD name='next' id='1009' sc=']' desc='Select next call'/>"
2817 	"  <CMD name='previous' id='1010' sc='[' desc='Select previous call'/>"
2818 	"  <CMD name='transfer' id='1011' sc='x' desc='Transfer call'>"
2819 	"    <ARG name='buddy_id' type='choice' id='9901' validate='0' "
2820 	"     desc='Buddy Id'>"
2821 	"      <CHOICE value='-1' desc='All buddies'/>"
2822 	"      <CHOICE value='0' desc='Current dialog'/>"
2823 	"    </ARG>"
2824 	"  </CMD>"
2825 	"  <CMD name='transfer_replaces' id='1012' sc='X' "
2826 	"   desc='Transfer replace call'>"
2827 	"    <ARG name='call_id' type='choice' id='9911' desc='Call Id'/>"
2828 	"  </CMD>"
2829 	"  <CMD name='redirect' id='1013' sc='R' desc='Redirect call'>"
2830 	"    <ARG name='redirect_option' type='choice' desc='Redirect option'>"
2831 	"      <CHOICE value='0' desc='Redirect accept replace'/>"
2832 	"      <CHOICE value='1' desc='Redirect accept'/>"
2833 	"      <CHOICE value='2' desc='Redirect reject'/>"
2834 	"      <CHOICE value='3' desc='Redirect stop'/>"
2835 	"    </ARG>"
2836 	"  </CMD>"
2837 	"  <CMD name='d_2833' id='1014' sc='#' desc='Send DTMF (RFC 2833)'>"
2838 	"    <ARG name='dtmf_to_send' type='string' "
2839 	"     desc='DTMF String to send'/>"
2840 	"  </CMD>"
2841 	"  <CMD name='d_info' id='1015' sc='*' desc='Send DTMF with SIP INFO'>"
2842 	"    <ARG name='dtmf_to_send' type='string' "
2843 	"     desc='DTMF String to send'/>"
2844 	"  </CMD>"
2845 	"  <CMD name='dump_q' id='1016' sc='dq' desc='Dump (call) quality'/>"
2846 	"  <CMD name='send_arb' id='1017' sc='S' desc='Send arbitrary request'>"
2847 	"    <ARG name='request_method' type='string' desc='Request method'/>"
2848 	"    <ARG name='buddy_id' type='choice' id='9901' validate='0' "
2849 	"     desc='Buddy Id'>"
2850 	"      <CHOICE value='-1' desc='All buddies'/>"
2851 	"      <CHOICE value='0' desc='Current dialog'/>"
2852 	"    </ARG>"
2853 	"  </CMD>"
2854 	"  <CMD name='list' id='1018' desc='Show current call'/>"
2855 	"</CMD>";
2856 
2857     pj_str_t xml = pj_str(call_command);
2858     return pj_cli_add_cmd_from_xml(c, NULL,
2859 				   &xml, cmd_call_handler,
2860 				   NULL, get_choice_value);
2861 }
2862 
add_presence_command(pj_cli_t * c)2863 static pj_status_t add_presence_command(pj_cli_t *c)
2864 {
2865     char* presence_command =
2866 	"<CMD name='im' id='200' desc='IM and Presence Commands'>"
2867 	"  <CMD name='add_b' id='2001' sc='+b' desc='Add buddy'>"
2868 	"    <ARG name='buddy_uri' type='string' desc='Buddy URI'/>"
2869 	"  </CMD>"
2870 	"  <CMD name='del_b' id='2002' sc='-b' desc='Delete buddy'>"
2871 	"    <ARG name='added_buddy_id' type='choice' id='9912' "
2872 	"     desc='Buddy ID'/>"
2873 	"  </CMD>"
2874 	"  <CMD name='send_im' id='2003' sc='i' desc='Send IM'>"
2875 	"    <ARG name='buddy_id' type='choice' id='9901' validate='0' "
2876 	"     desc='Buddy Id'>"
2877 	"      <CHOICE value='-1' desc='All buddies'/>"
2878 	"      <CHOICE value='0' desc='Current dialog'/>"
2879 	"    </ARG>"
2880 	"    <ARG name='message_content' type='string' desc='Message Content'/>"
2881 	"  </CMD>"
2882 	"  <CMD name='sub_pre' id='2004' desc='Subscribe presence'>"
2883 	"    <ARG name='buddy_id' type='choice' id='9901' validate='0' "
2884 	"     desc='Buddy Id'>"
2885 	"      <CHOICE value='-1' desc='All buddies'/>"
2886 	"      <CHOICE value='0' desc='Current dialog'/>"
2887 	"    </ARG>"
2888 	"  </CMD>"
2889 	"  <CMD name='unsub_pre' id='2005' desc='Unsubscribe Presence'>"
2890 	"    <ARG name='buddy_id' type='choice' id='9901' validate='0' "
2891 	"     desc='Buddy Id'>"
2892 	"      <CHOICE value='-1' desc='All buddies'/>"
2893 	"      <CHOICE value='0' desc='Current dialog'/>"
2894 	"    </ARG>"
2895 	"  </CMD>"
2896 	"  <CMD name='tog_state' id='2006' desc='Toggle online state'/>"
2897 	"  <CMD name='pre_text' id='2007' sc='T' "
2898 	"   desc='Specify custom presence text'>"
2899 	"    <ARG name='online_state' type='choice' desc='Online state'>"
2900 	"      <CHOICE value='1' desc='Available'/>"
2901 	"      <CHOICE value='2' desc='Busy'/>"
2902 	"      <CHOICE value='3' desc='On The Phone'/>"
2903 	"      <CHOICE value='4' desc='Idle'/>"
2904 	"      <CHOICE value='5' desc='Away'/>"
2905 	"      <CHOICE value='6' desc='Be Right Back'/>"
2906 	"      <CHOICE value='7' desc='Offline'/>"
2907 	"    </ARG>"
2908 	"  </CMD>"
2909 	"  <CMD name='bud_list' id='2008' sc='bl' desc='Show buddy list'/>"
2910 	"</CMD>";
2911 
2912     pj_str_t xml = pj_str(presence_command);
2913 
2914     return pj_cli_add_cmd_from_xml(c, NULL,
2915 				   &xml, cmd_presence_handler,
2916 				   NULL, get_choice_value);
2917 }
2918 
add_account_command(pj_cli_t * c)2919 static pj_status_t add_account_command(pj_cli_t *c)
2920 {
2921     char* account_command =
2922 	"<CMD name='acc' id='300' desc='Account commands'>"
2923 	"  <CMD name='add' id='3001' sc='+a' desc='Add new account'>"
2924 	"    <ARG name='sip_url' type='string' desc='Your SIP URL'/>"
2925 	"    <ARG name='registrar_url' type='string' "
2926 	"     desc='URL of the registrar'/>"
2927 	"    <ARG name='auth_realm' type='string' desc='Auth realm'/>"
2928 	"    <ARG name='auth_username' type='string' desc='Auth username'/>"
2929 	"    <ARG name='auth_password' type='string' desc='Auth password'/>"
2930 	"  </CMD>"
2931 	"  <CMD name='del' id='3002' sc='-a' desc='Delete account'>"
2932 	"    <ARG name='account_id' type='choice' id='9902' desc='Account Id'/>"
2933 	"  </CMD>"
2934 	"  <CMD name='mod' id='3003' sc='!a' desc='Modify account'>"
2935 	"    <ARG name='account_id' type='choice' id='9902' desc='Account Id'/>"
2936 	"    <ARG name='sip_url' type='string' desc='Your SIP URL'/>"
2937 	"    <ARG name='registrar_url' type='string' "
2938 	"     desc='URL of the registrar'/>"
2939 	"    <ARG name='auth_realm' type='string' desc='Auth realm'/>"
2940 	"    <ARG name='auth_username' type='string' desc='Auth username'/>"
2941 	"    <ARG name='auth_password' type='string' desc='Auth password'/>"
2942 	"  </CMD>"
2943 	"  <CMD name='reg' id='3004' sc='rr' "
2944 	"   desc='Send (Refresh) Register request to register'/>"
2945 	"  <CMD name='unreg' id='3005' sc='ru' "
2946 	"   desc='Send Register request to unregister'/>"
2947 	"  <CMD name='next' id='3006' sc='<' "
2948 	"   desc='Select the next account for sending outgoing requests'>"
2949 	"    <ARG name='account_id' type='choice' id='9902' desc='Account Id'/>"
2950 	"  </CMD>"
2951 	"  <CMD name='prev' id='3007' sc='>' "
2952 	"   desc='Select the previous account for sending outgoing requests'>"
2953 	"    <ARG name='account_id' type='choice' id='9902' desc='Account Id'/>"
2954 	"  </CMD>"
2955 	"  <CMD name='show' id='3008' sc='l' desc='Show account list'/>"
2956 	"</CMD>";
2957 
2958     pj_str_t xml = pj_str(account_command);
2959     return pj_cli_add_cmd_from_xml(c, NULL,
2960 				   &xml, cmd_account_handler,
2961 				   NULL, get_choice_value);
2962 }
2963 
add_media_command(pj_cli_t * c)2964 static pj_status_t add_media_command(pj_cli_t *c)
2965 {
2966     char* media_command =
2967 	"<CMD name='audio' id='400' desc='Conference and Media commands'>"
2968 	"  <CMD name='list' id='4001' sc='cl' desc='Show conference list'/>"
2969 	"  <CMD name='conf_con' id='4002' sc='cc' desc='Conference connect'>"
2970 	"    <ARG name='source_port' type='choice' id='9903' "
2971 	"     desc='Source Port'/>"
2972 	"    <ARG name='destination_port' type='choice' id='9903' "
2973 	"     desc='Destination Port'/>"
2974 	"  </CMD>"
2975 	"  <CMD name='conf_dis' id='4003' sc='cd' desc='Conference disconnect'>"
2976 	"    <ARG name='source_port' type='choice' id='9903' "
2977 	"     desc='Source Port'/>"
2978 	"    <ARG name='destination_port' type='choice' id='9903' "
2979 	"     desc='Destination Port'/>"
2980 	"  </CMD>"
2981 	"  <CMD name='adjust_vol' id='4004' sc='V' desc='Adjust volume'>"
2982 	"    <ARG name='mic_level' type='int' desc='Mic Level'/>"
2983 	"    <ARG name='speaker_port' type='int' desc='Speaker Level'/>"
2984 	"  </CMD>"
2985 	"  <CMD name='speakertog' id='4006' desc='Toggle audio output route' />"
2986 	"  <CMD name='codec_prio' id='4005' sc='Cp' "
2987 	"   desc='Arrange codec priorities'>"
2988 	"    <ARG name='codec_id' type='choice' id='9904' desc='Codec Id'/>"
2989 	"    <ARG name='priority' type='int' desc='Codec Priority'/>"
2990 	"  </CMD>"
2991 	"</CMD>";
2992 
2993     pj_str_t xml = pj_str(media_command);
2994     return pj_cli_add_cmd_from_xml(c, NULL,
2995 				   &xml, cmd_media_handler,
2996 				   NULL, get_choice_value);
2997 }
2998 
add_config_command(pj_cli_t * c)2999 static pj_status_t add_config_command(pj_cli_t *c)
3000 {
3001     char* config_command =
3002 	"<CMD name='stat' id='500' desc='Status and config commands'>"
3003 	"  <CMD name='dump_stat' id='5001' sc='ds' desc='Dump status'/>"
3004 	"  <CMD name='dump_detail' id='5002' sc='dd' "
3005 	"   desc='Dump detail status'/>"
3006 	"  <CMD name='dump_conf' id='5003' sc='dc' "
3007 	"   desc='Dump configuration to screen'/>"
3008 	"  <CMD name='write_setting' id='5004' sc='f' "
3009 	"   desc='Write current configuration file'>"
3010 	"    <ARG name='output_file' type='string' desc='Output filename'/>"
3011 	"  </CMD>"
3012 	"</CMD>";
3013 
3014     pj_str_t xml = pj_str(config_command);
3015     return pj_cli_add_cmd_from_xml(c, NULL,
3016 				   &xml, cmd_config_handler,
3017 				   NULL, get_choice_value);
3018 }
3019 
3020 #if PJSUA_HAS_VIDEO
add_video_command(pj_cli_t * c)3021 static pj_status_t add_video_command(pj_cli_t *c)
3022 {
3023     char* video_command =
3024 	"<CMD name='video' id='600' desc='Video commands'>"
3025 	"  <CMD name='enable' id='6001' desc='Enable video'/>"
3026 	"  <CMD name='disable' id='6002' desc='Disable video'/>"
3027 	"  <CMD name='acc' id='6003' desc='Video setting for current account'>"
3028 	"    <CMD name='show' id='60031' "
3029 	"     desc='Show video setting for current account'/>"
3030 	"    <CMD name='autorx' id='60032' sc='ar' "
3031 	"     desc='Automatically show incoming video'>"
3032 	"      <ARG name='enable_option' type='choice' "
3033 	"       desc='Enable/Disable option'>"
3034 	"        <CHOICE value='On' desc='Enable'/>"
3035 	"        <CHOICE value='Off' desc='Disable'/>"
3036 	"      </ARG>"
3037 	"    </CMD>"
3038 	"    <CMD name='autotx' id='60033' sc='at' "
3039 	"     desc='Automatically offer video'>"
3040 	"      <ARG name='enable_option' type='choice' "
3041 	"       desc='Enable/Disable option'>"
3042 	"        <CHOICE value='On' desc='Enable'/>"
3043 	"        <CHOICE value='Off' desc='Disable'/>"
3044 	"      </ARG>"
3045 	"    </CMD>"
3046 	"    <CMD name='cap_id' id='60034' sc='ci' "
3047 	"     desc='Set default capture device for current account'>"
3048 	"      <ARG name='cap_dev_id' type='choice' id='9905' "
3049 	"       desc='Capture device Id'/>"
3050 	"    </CMD>"
3051 	"    <CMD name='ren_id' id='60035' sc='ri' "
3052 	"     desc='Set default renderer device for current account'>"
3053 	"      <ARG name='ren_dev_id' type='choice' id='9906' "
3054 	"       desc='Renderer device Id'/>"
3055 	"    </CMD>"
3056 	"  </CMD>"
3057 	"  <CMD name='call' id='6004' sc='vcl' "
3058 	"   desc='Video call commands/settings'>"
3059 	"    <CMD name='rx' id='60041' "
3060 	"     desc='Enable/disable video RX for stream in curr call'>"
3061 	"      <ARG name='enable_option' type='choice' "
3062 	"       desc='Enable/Disable option'>"
3063 	"        <CHOICE value='On' desc='Enable'/>"
3064 	"        <CHOICE value='Off' desc='Disable'/>"
3065 	"      </ARG>"
3066 	"      <ARG name='stream_id' type='choice' id='9908' desc='Stream Id'/>"
3067 	"    </CMD>"
3068 	"    <CMD name='tx' id='60042' "
3069 	"     desc='Enable/disable video TX for stream in curr call'>"
3070 	"      <ARG name='enable_option' type='choice' "
3071 	"       desc='Enable/Disable option'>"
3072 	"        <CHOICE value='On' desc='Enable'/>"
3073 	"        <CHOICE value='Off' desc='Disable'/>"
3074 	"      </ARG>"
3075 	"      <ARG name='stream_id' type='choice' id='9908' desc='Stream Id'/>"
3076 	"    </CMD>"
3077 	"    <CMD name='add' id='60043' "
3078 	"     desc='Add video stream for current call'/>"
3079 	"    <CMD name='enable' id='60044' "
3080 	"     desc='Enable stream #N in current call'>"
3081 	"      <ARG name='stream_id' type='choice' id='9908' optional='1' "
3082 	"       desc='Stream Id'/>"
3083 	"    </CMD>"
3084 	"    <CMD name='disable' id='60045' "
3085 	"     desc='Disable stream #N in current call'>"
3086 	"      <ARG name='stream_id' type='choice' id='9908' optional='1' "
3087 	"       desc='Stream Id'/>"
3088 	"    </CMD>"
3089 	"    <CMD name='cap' id='60046' "
3090 	"     desc='Set capture dev ID for stream #N in current call'>"
3091 	"      <ARG name='stream_id' type='choice' id='9908' desc='Stream Id'/>"
3092 	"      <ARG name='cap_device_id' type='choice' id='9905' "
3093 	"       desc='Device Id'/>"
3094 	"    </CMD>"
3095 	"  </CMD>"
3096 	"  <CMD name='device' id='6005' sc='vv' desc='Video device commands'>"
3097 	"    <CMD name='list' id='60051' desc='Show all video devices'/>"
3098 	"    <CMD name='refresh' id='60052' desc='Refresh video device list'/>"
3099 	"    <CMD name='prev' id='60053' "
3100 	"     desc='Enable/disable preview for specified device ID'>"
3101 	"      <ARG name='enable_option' type='choice' "
3102 	"       desc='Enable/Disable option'>"
3103 	"        <CHOICE value='On' desc='Enable'/>"
3104 	"        <CHOICE value='Off' desc='Disable'/>"
3105 	"      </ARG>"
3106 	"      <ARG name='device_id' type='choice' id='9907' "
3107 	"       desc='Video Device Id'/>"
3108 	"    </CMD>"
3109 	"  </CMD>"
3110 	"  <CMD name='codec' id='6006' desc='Video codec commands'>"
3111 	"    <CMD name='list' id='60061' desc='Show video codec list'/>"
3112 	"    <CMD name='prio' id='60062' desc='Set video codec priority'>"
3113 	"      <ARG name='codec_id' type='choice' id='9909' desc='Codec Id'/>"
3114 	"      <ARG name='priority' type='int' desc='Priority'/>"
3115 	"    </CMD>"
3116 	"    <CMD name='fps' id='60063' desc='Set video codec framerate'>"
3117 	"      <ARG name='codec_id' type='choice' id='9909' desc='Codec Id'/>"
3118 	"      <ARG name='num' type='int' desc='Numerator'/>"
3119 	"      <ARG name='denum' type='int' desc='Denumerator'/>"
3120 	"    </CMD>"
3121 	"    <CMD name='bitrate' id='60064' desc='Set video codec bitrate'>"
3122 	"      <ARG name='codec_id' type='choice' id='9909' desc='Codec Id'/>"
3123 	"      <ARG name='avg' type='int' desc='Average bps'/>"
3124 	"      <ARG name='max' type='int' desc='Maximum bps'/>"
3125 	"    </CMD>"
3126 	"    <CMD name='size' id='60065' desc='Set codec ID size/resolution'>"
3127 	"      <ARG name='codec_id' type='choice' id='9909' desc='Codec Id'/>"
3128 	"      <ARG name='width' type='int' desc='Width'/>"
3129 	"      <ARG name='height' type='int' desc='Height'/>"
3130 	"    </CMD>"
3131 	"  </CMD>"
3132 	"  <CMD name='win' id='6007' desc='Video windows settings/commands'>"
3133 	"    <CMD name='list' id='60071' desc='List all active video windows'/>"
3134 	"    <CMD name='arrange' id='60072' desc='Auto arrange windows'/>"
3135 	"    <CMD name='show' id='60073' desc='Show specific windows'>"
3136 	"      <ARG name='window_id' type='choice' id='9910' "
3137 	"       desc='Windows Id'/>"
3138 	"    </CMD>"
3139 	"    <CMD name='hide' id='60074' desc='Hide specific windows'>"
3140 	"      <ARG name='window_id' type='choice' id='9910' "
3141 	"       desc='Windows Id'/>"
3142 	"    </CMD>"
3143 	"    <CMD name='move' id='60075' desc='Move window position'>"
3144 	"      <ARG name='window_id' type='choice' id='9910' "
3145 	"       desc='Windows Id'/>"
3146 	"      <ARG name='x' type='int' desc='Horizontal position'/>"
3147 	"      <ARG name='y' type='int' desc='Vertical position'/>"
3148 	"    </CMD>"
3149 	"    <CMD name='resize' id='60076' "
3150 	"     desc='Resize window to specific width/height'>"
3151 	"      <ARG name='window_id' type='choice' id='9910' "
3152 	"       desc='Windows Id'/>"
3153 	"      <ARG name='width' type='int' desc='Width'/>"
3154 	"      <ARG name='height' type='int' desc='Height'/>"
3155 	"    </CMD>"
3156 	"  </CMD>"
3157 	"  <CMD name='conf' id='6008' desc='Video conference commands'>"
3158 	"    <CMD name='list' id='60081' desc='List all ports in video conference'/>"
3159 	"    <CMD name='cc' id='60082' desc='Connect ports in video conference'>"
3160 	"      <ARG name='source' type='int' desc='Source port ID'/>"
3161 	"      <ARG name='sink' type='int' desc='Sink port ID'/>"
3162 	"    </CMD>"
3163 	"    <CMD name='cd' id='60083' desc='Disconnect ports in video conference'>"
3164 	"      <ARG name='source' type='int' desc='Source port ID'/>"
3165 	"      <ARG name='sink' type='int' desc='Sink port ID'/>"
3166 	"    </CMD>"
3167 	"  </CMD>"
3168 	"</CMD>";
3169 
3170     pj_str_t xml = pj_str(video_command);
3171     return pj_cli_add_cmd_from_xml(c, NULL,
3172 				   &xml, cmd_video_handler,
3173 				   NULL, get_choice_value);
3174 }
3175 #endif
3176 
add_other_command(pj_cli_t * c)3177 static pj_status_t add_other_command(pj_cli_t *c)
3178 {
3179     char* sleep_command =
3180 	"<CMD name='sleep' id='700' desc='Suspend keyboard input'>"
3181 	"  <ARG name='msec' type='int' desc='Millisecond'/>"
3182 	"</CMD>";
3183 
3184     char* network_command =
3185 	"<CMD name='network' id='900' desc='Detect network type'/>";
3186 
3187     char* shutdown_command =
3188 	"<CMD name='shutdown' id='110' desc='Shutdown application'/>";
3189 
3190     char* restart_command =
3191 	"<CMD name='restart' id='120' desc='Restart application'>"
3192 	"  <ARG name='options1' type='string' desc='Options' optional='1'/>"
3193 	"  <ARG name='options2' type='string' desc='Options' optional='1'/>"
3194 	"  <ARG name='options3' type='string' desc='Options' optional='1'/>"
3195 	"  <ARG name='options4' type='string' desc='Options' optional='1'/>"
3196 	"</CMD>";
3197 
3198     char* ip_change_command =
3199 	"<CMD name='ip_change' id='130' desc='Handle IP change'/>";
3200 
3201     pj_status_t status;
3202     pj_str_t sleep_xml = pj_str(sleep_command);
3203     pj_str_t network_xml = pj_str(network_command);
3204     pj_str_t shutdown_xml = pj_str(shutdown_command);
3205     pj_str_t restart_xml = pj_str(restart_command);
3206     pj_str_t ip_change_xml = pj_str(ip_change_command);
3207 
3208     status = pj_cli_add_cmd_from_xml(c, NULL,
3209 				     &sleep_xml, cmd_sleep_handler,
3210 				     NULL, NULL);
3211     if (status != PJ_SUCCESS)
3212 	return status;
3213 
3214     status = pj_cli_add_cmd_from_xml(c, NULL,
3215 				     &network_xml, cmd_network_handler,
3216 				     NULL, NULL);
3217     if (status != PJ_SUCCESS)
3218 	return status;
3219 
3220     status = pj_cli_add_cmd_from_xml(c, NULL,
3221 				     &shutdown_xml, cmd_quit_handler,
3222 				     NULL, NULL);
3223 
3224     if (status != PJ_SUCCESS)
3225 	return status;
3226 
3227     status = pj_cli_add_cmd_from_xml(c, NULL,
3228 				     &restart_xml, cmd_restart_handler,
3229 				     NULL, NULL);
3230 
3231     if (status != PJ_SUCCESS)
3232 	return status;
3233 
3234     status = pj_cli_add_cmd_from_xml(c, NULL,
3235 				     &ip_change_xml, cmd_ip_change_handler,
3236 				     NULL, NULL);
3237 
3238     return status;
3239 }
3240 
cli_setup_command(pj_cli_t * c)3241 pj_status_t cli_setup_command(pj_cli_t *c)
3242 {
3243     pj_status_t status;
3244 
3245     status = add_call_command(c);
3246     if (status != PJ_SUCCESS)
3247 	return status;
3248 
3249     status = add_presence_command(c);
3250     if (status != PJ_SUCCESS)
3251 	return status;
3252 
3253     status = add_account_command(c);
3254     if (status != PJ_SUCCESS)
3255 	return status;
3256 
3257     status = add_media_command(c);
3258     if (status != PJ_SUCCESS)
3259 	return status;
3260 
3261     status = add_config_command(c);
3262     if (status != PJ_SUCCESS)
3263 	return status;
3264 
3265 #if PJSUA_HAS_VIDEO
3266     status = add_video_command(c);
3267     if (status != PJ_SUCCESS)
3268 	return status;
3269 #endif
3270 
3271     status = add_other_command(c);
3272 
3273     return status;
3274 }
3275