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, ¶m->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, ¶m->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, ¶m->choice[param->cnt].value, data_out);
403 pj_ansi_snprintf(data_out, sizeof(data_out), "An URL");
404 pj_strdup2(param->pool, ¶m->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, ¶m->choice[param->cnt].value, buf);
438 pj_strdup2(param->pool, ¶m->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, ¶m->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, ¶m->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, ¶m->choice[param->cnt].value,
519 codec_id);
520 pj_strdup2(param->pool, ¶m->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, ¶m->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, ¶m->choice[param->cnt].desc,
547 "Status:None");
548 break;
549 case PJSUA_CALL_MEDIA_ACTIVE:
550 pj_strdup2(param->pool, ¶m->choice[param->cnt].desc,
551 "Status:Active");
552 break;
553 case PJSUA_CALL_MEDIA_LOCAL_HOLD:
554 pj_strdup2(param->pool, ¶m->choice[param->cnt].desc,
555 "Status:Local Hold");
556 break;
557 case PJSUA_CALL_MEDIA_REMOTE_HOLD:
558 pj_strdup2(param->pool, ¶m->choice[param->cnt].desc,
559 "Status:Remote Hold");
560 break;
561 case PJSUA_CALL_MEDIA_ERROR:
562 pj_strdup2(param->pool, ¶m->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, ¶m->choice[param->cnt].value,
586 dev_id);
587 pj_strdup2(param->pool, ¶m->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, ¶m->choice[param->cnt].value,
666 codec_id);
667 pj_strdup2(param->pool, ¶m->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, ¶m->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, ¶m->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, ¶m->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, ¶m->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, ¤t_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(¶m);
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 ¶m);
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(¶m);
2256
2257 param.med_idx = (int)pj_strtol(&cval->argv[2]);
2258
2259 status = pjsua_call_set_vid_strm(current_call, op, ¶m);
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(¶m);
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, ¶m);
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(¶m);
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 ¶m);
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(¶m);
2315 param.wnd_flags = PJMEDIA_VID_DEV_WND_BORDER |
2316 PJMEDIA_VID_DEV_WND_RESIZABLE;
2317 pjsua_vid_preview_start(dev_id, ¶m);
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(¶m);
2701 pjsua_handle_ip_change(¶m);
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