Index: /pjproject/trunk/pjlib-util/build/Makefile
===================================================================
--- /pjproject/trunk/pjlib-util/build/Makefile	(revision 4439)
+++ /pjproject/trunk/pjlib-util/build/Makefile	(revision 4440)
@@ -29,7 +29,8 @@
 export PJLIB_UTIL_SRCDIR = ../src/pjlib-util
 export PJLIB_UTIL_OBJS += $(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \
-		base64.o crc32.o errno.o dns.o dns_dump.o dns_server.o \
-		getopt.o hmac_md5.o hmac_sha1.o http_client.o md5.o pcap.o resolver.o \
-		scanner.o sha1.o srv_resolver.o string.o stun_simple.o \
+		base64.o cli.o cli_console.o cli_telnet.o crc32.o errno.o dns.o \
+		dns_dump.o dns_server.o getopt.o hmac_md5.o hmac_sha1.o \
+		http_client.o md5.o pcap.o resolver.o scanner.o sha1.o \
+		srv_resolver.o string.o stun_simple.o \
 		stun_simple_client.o xml.o
 export PJLIB_UTIL_CFLAGS += $(_CFLAGS)
Index: /pjproject/trunk/pjlib-util/build/pjlib_util.vcproj
===================================================================
--- /pjproject/trunk/pjlib-util/build/pjlib_util.vcproj	(revision 4439)
+++ /pjproject/trunk/pjlib-util/build/pjlib_util.vcproj	(revision 4440)
@@ -2770,4 +2770,16 @@
 			</File>
 			<File
+				RelativePath="..\src\pjlib-util\cli.c"
+				>
+			</File>
+			<File
+				RelativePath="..\src\pjlib-util\cli_console.c"
+				>
+			</File>
+			<File
+				RelativePath="..\src\pjlib-util\cli_telnet.c"
+				>
+			</File>
+			<File
 				RelativePath="..\src\pjlib-util\crc32.c"
 				>
@@ -4715,4 +4727,20 @@
 			</File>
 			<File
+				RelativePath="..\include\pjlib-util\cli.h"
+				>
+			</File>
+			<File
+				RelativePath="..\include\pjlib-util\cli_console.h"
+				>
+			</File>
+			<File
+				RelativePath="..\include\pjlib-util\cli_imp.h"
+				>
+			</File>
+			<File
+				RelativePath="..\include\pjlib-util\cli_telnet.h"
+				>
+			</File>
+			<File
 				RelativePath="..\include\pjlib-util\config.h"
 				>
Index: /pjproject/trunk/pjlib-util/include/pjlib-util/cli.h
===================================================================
--- /pjproject/trunk/pjlib-util/include/pjlib-util/cli.h	(revision 4440)
+++ /pjproject/trunk/pjlib-util/include/pjlib-util/cli.h	(revision 4440)
@@ -0,0 +1,529 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2010 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#ifndef __PJLIB_UTIL_CLI_H__
+#define __PJLIB_UTIL_CLI_H__
+
+/**
+ * @file cli.h
+ * @brief Command Line Interface
+ */
+
+#include <pjlib-util/types.h>
+#include <pj/list.h>
+
+
+PJ_BEGIN_DECL
+
+/**
+ * @defgroup PJLIB_UTIL_CLI Command Line Interface Framework
+ * @{
+ * A CLI framework features an interface for defining command specification, 
+ * parsing, and executing a command. 
+ * It also features an interface to communicate with various front-ends, 
+ * such as console, telnet.
+ * Application normally needs only one CLI instance to be created. 
+ * On special cases, application could also create multiple CLI 
+ * instances, with each instance has specific command structure.
+ *
+\verbatim
+| vid help                  Show this help screen                             |
+| vid enable|disable        Enable or disable video in next offer/answer      |
+| vid call add              Add video stream for current call                 |
+| vid call cap N ID         Set capture dev ID for stream #N in current call  |
+| disable_codec g711|g722   Show this help screen                             |
+<CMD name='vid' id='0' desc="">
+       <CMD name='help' id='0' desc='' />
+       <CMD name='enable' id='0' desc='' />
+       <CMD name='disable' id='0' desc='' />
+       <CMD name='call' id='0' desc='' >
+                <CMD name='add' id='101' desc='...' />
+                <CMD name='cap' id='102' desc='...' >
+                   <ARG name='streamno' type='int' desc='...' id='1'/>
+                   <ARG name='devid' type='int' optional='1' id='2'/>
+                </CMD>
+       </CMD>
+</CMD>
+<CMD name='disable_codec' id=0 desc="">
+	<ARG name='codec_list' type='choice' id='3'>
+	    <CHOICE value='g711'/>
+	    <CHOICE value='g722'/>
+	</ARG>
+</CMD>
+\endverbatim 
+ */
+
+/**
+ * This opaque structure represents a CLI application. A CLI application is
+ * the root placeholder of other CLI objects. In an application, one (and
+ * normally only one) CLI application instance needs to be created.
+ */
+typedef struct pj_cli_t pj_cli_t;
+
+/**
+ * Type of command id.
+ */
+typedef int pj_cli_cmd_id;
+
+/**
+ * This describes the parameters to be specified when creating a CLI
+ * application with pj_cli_create(). Application MUST initialize this
+ * structure by calling pj_cli_cfg_default() before using it.
+ */
+typedef struct pj_cli_cfg
+{
+    /**
+     * The application name, which will be used in places such as logs.
+     * This field is mandatory.
+     */
+    pj_str_t name;
+
+    /**
+     * Optional application title, which will be used in places such as
+     * window title. If not specified, the application name will be used
+     * as the title.
+     */
+    pj_str_t title;
+
+    /**
+     * The pool factory where all memory allocations will be taken from.
+     * This field is mandatory.
+     */
+    pj_pool_factory *pf;
+
+} pj_cli_cfg;
+
+/**
+ * Type of argument id.
+ */
+typedef int pj_cli_arg_id;
+
+/**
+ * Forward declaration of pj_cli_cmd_spec structure.
+ */
+typedef struct pj_cli_cmd_spec pj_cli_cmd_spec;
+
+/**
+ * Forward declaration for pj_cli_sess, which will be declared in cli_imp.h.
+ */
+typedef struct pj_cli_sess pj_cli_sess;
+
+/**
+ * Forward declaration for CLI front-end.
+ */
+typedef struct pj_cli_front_end pj_cli_front_end;
+
+/**
+ * Forward declaration for CLI argument spec structure.
+ */
+typedef struct pj_cli_arg_spec pj_cli_arg_spec;
+
+/**
+ * This structure contains the command to be executed by command handler.
+ */
+typedef struct pj_cli_cmd_val
+{
+    /** The session on which the command was executed on. */
+    pj_cli_sess *sess;
+
+    /** The command specification being executed. */
+    const pj_cli_cmd_spec *cmd;
+
+    /** Number of argvs. */
+    int argc;
+
+    /** Array of args, with argv[0] specifies the  name of the cmd. */
+    pj_str_t argv[PJ_CLI_MAX_ARGS];
+
+} pj_cli_cmd_val;
+
+/**
+ * This structure contains the hints information for the end user. 
+ * This structure could contain either command or argument information.
+ * The front-end will format the information and present it to the user.
+ */
+typedef struct pj_cli_hint_info
+{
+    /**
+     * The hint value.
+     */
+    pj_str_t name;
+
+    /**
+     * The hint type.
+     */
+    pj_str_t type;
+
+    /**
+     * Helpful description of the hint value. 
+     */
+    pj_str_t desc;
+
+} pj_cli_hint_info;
+
+/**
+ * This structure contains extra information returned by pj_cli_sess_exec()/
+ * pj_cli_sess_parse().
+ * Upon return from the function, various other fields in this structure will
+ * be set by the function.
+ */
+typedef struct pj_cli_exec_info
+{
+    /**
+     * If command parsing failed, on return this will point to the location
+     * where the failure occurs, otherwise the value will be set to -1.
+     */
+    int err_pos;
+
+    /**
+     * If a command matching the command in the cmdline was found, on return
+     * this will be set to the command id of the command, otherwise it will be
+     * set to PJ_CLI_INVALID_CMD_ID.
+     */
+    pj_cli_cmd_id cmd_id;
+
+    /**
+     * If a command was executed, on return this will be set to the return
+     * value of the command, otherwise it will contain PJ_SUCCESS.
+     */
+    pj_status_t cmd_ret;
+
+    /**
+     * The number of hint elements
+     **/
+    unsigned hint_cnt;
+
+    /**
+     * If pj_cli_sess_parse() fails because of a missing argument or ambigous 
+     * command/argument, the function returned PJ_CLI_EMISSINGARG or 
+     * PJ_CLI_EAMBIGUOUS error. 
+     * This field will contain the hint information. This is useful to give 
+     * helpful information to the end_user.
+     */
+    pj_cli_hint_info hint[PJ_CLI_MAX_HINTS];
+
+} pj_cli_exec_info;
+
+/**
+ * This structure contains the information returned from the dynamic 
+ * argument callback.
+ */
+typedef struct pj_cli_arg_choice_val
+{
+    /**
+     * The argument choice value
+     */
+    pj_str_t value;
+
+    /**
+     * Helpful description of the choice value. This text will be used when
+     * displaying the help texts for the choice value
+     */
+    pj_str_t desc;
+
+} pj_cli_arg_choice_val;
+
+/**
+ * This structure contains the parameters for pj_cli_get_dyn_choice
+ */
+typedef struct pj_cli_dyn_choice_param
+{
+    /**
+     * The session on which the command was executed on.
+     */
+    pj_cli_sess *sess;
+
+    /**
+     * The command being processed.
+     */
+    pj_cli_cmd_spec *cmd;
+
+    /**
+     * The argument id.
+     */
+    pj_cli_arg_id arg_id;
+
+    /**
+     * The maximum number of values that the choice can hold.
+     */
+    unsigned max_cnt;
+
+    /**
+     * The pool to allocate memory from.
+     */
+    pj_pool_t *pool;
+
+    /**
+     * The choice values count.
+     */
+    unsigned cnt;
+
+    /**
+     * Array containing the valid choice values.
+     */
+    pj_cli_arg_choice_val choice[PJ_CLI_MAX_CHOICE_VAL];
+} pj_cli_dyn_choice_param;
+
+/**
+ * This specifies the callback type for argument handlers, which will be
+ * called to get the valid values of the choice type arguments.
+ */
+typedef void (*pj_cli_get_dyn_choice) (pj_cli_dyn_choice_param *param);
+
+/**
+ * This specifies the callback type for command handlers, which will be
+ * executed when the specified command is invoked.
+ *
+ * @param cmd_val   The command that is specified by the user.
+ *
+ * @return          Return the status of the command execution.
+ */
+typedef pj_status_t (*pj_cli_cmd_handler)(pj_cli_cmd_val *cval);
+
+/**
+ * Write a log message to the CLI application. The CLI application
+ * will send the log message to all the registered front-ends.
+ *
+ * @param cli		The CLI application instance.
+ * @param level	        Verbosity level of this message message.
+ * @param buffer        The message itself.
+ * @param len 	        Length of this message.
+ */
+PJ_DECL(void) pj_cli_write_log(pj_cli_t *cli,
+                               int level,
+                               const char *buffer,
+                               int len);
+
+/**
+ * Create a new CLI application instance.
+ *
+ * @param cfg		CLI application creation parameters.
+ * @param p_cli		Pointer to receive the returned instance.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_cli_create(pj_cli_cfg *cfg,
+                                   pj_cli_t **p_cli);
+
+/**
+ * This specifies the function to get the id of the specified command
+ * 
+ * @param cmd		The specified command.
+ *
+ * @return		The command id
+ */
+PJ_DECL(pj_cli_cmd_id) pj_cli_get_cmd_id(const pj_cli_cmd_spec *cmd);
+
+/**
+ * Get the internal parameter of the CLI instance.
+ *
+ * @param cli		The CLI application instance.
+ *
+ * @return		CLI parameter instance.
+ */
+PJ_DECL(pj_cli_cfg*) pj_cli_get_param(pj_cli_t *cli);
+
+/**
+ * Call this to signal application shutdown. Typically application would
+ * call this from it's "Quit" menu or similar command to quit the
+ * application.
+ *
+ * See also pj_cli_sess_end_session() to end a session instead of quitting the
+ * whole application.
+ *
+ * @param cli		The CLI application instance.
+ * @param req		The session on which the shutdown request is
+ * 			received.
+ * @param restart	Indicate whether application restart is wanted.
+ */
+PJ_DECL(void) pj_cli_quit(pj_cli_t *cli, pj_cli_sess *req,
+			  pj_bool_t restart);
+/**
+ * Check if application shutdown or restart has been requested.
+ *
+ * @param cli		The CLI application instance.
+ *
+ * @return		PJ_TRUE if pj_cli_quit() has been called.
+ */
+PJ_DECL(pj_bool_t) pj_cli_is_quitting(pj_cli_t *cli);
+
+/**
+ * Check if application restart has been requested.
+ *
+ * @param cli		The CLI application instance.
+ *
+ * @return		PJ_TRUE if pj_cli_quit() has been called with
+ * 			restart parameter set.
+ */
+PJ_DECL(pj_bool_t) pj_cli_is_restarting(pj_cli_t *cli);
+
+/**
+ * Destroy a CLI application instance. This would also close all sessions
+ * currently running for this CLI application.
+ *
+ * @param cli		The CLI application.
+ */
+PJ_DECL(void) pj_cli_destroy(pj_cli_t *cli);
+
+/**
+ * Initialize a pj_cli_cfg with its default values.
+ *
+ * @param param  The instance to be initialized.
+ */
+PJ_DECL(void) pj_cli_cfg_default(pj_cli_cfg *param);
+
+/**
+ * Register a front end to the CLI application.
+ *
+ * @param CLI		The CLI application.
+ * @param fe		The CLI front end to be registered.
+ */
+PJ_DECL(void) pj_cli_register_front_end(pj_cli_t *cli,
+                                        pj_cli_front_end *fe);
+
+/**
+ * Create a new complete command specification from an XML node text and
+ * register it to the CLI application.
+ *
+ * @param cli		The CLI application.
+ * @param group		Optional group to which this command will be added
+ * 			to, or specify NULL if this command is a root
+ * 			command.
+ * @param xml		Input string containing XML node text for the
+ * 			command.
+ * @param handler	Function handler for the command. This must be NULL
+ * 			if the command specifies a command group.
+ * @param p_cmd		Optional pointer to store the newly created
+ * 			specification.
+ * @param get_choice	Function handler for the argument. Specify this for 
+ *			dynamic choice type arguments.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_cli_add_cmd_from_xml(pj_cli_t *cli,
+					     pj_cli_cmd_spec *group,
+                                             const pj_str_t *xml,
+                                             pj_cli_cmd_handler handler,
+                                             pj_cli_cmd_spec **p_cmd, 
+					     pj_cli_get_dyn_choice get_choice);
+/**
+ * Initialize pj_cli_exec_info with its default values.
+ *
+ * @param param		The param to be initialized.
+ */
+PJ_DECL(void) pj_cli_exec_info_default(pj_cli_exec_info *param);
+
+/**
+ * Write a log message to the specific CLI session. 
+ *
+ * @param sess		The CLI active session.
+ * @param buffer        The message itself.
+ * @param len 	        Length of this message.
+ */
+PJ_DECL(void) pj_cli_sess_write_msg(pj_cli_sess *sess,                               
+				    const char *buffer,
+				    int len);
+
+/**
+ * Parse an input cmdline string. The first word of the command line is the
+ * command itself, which will be matched against command  specifications
+ * registered in the CLI application.
+ *
+ * Zero or more arguments follow the command name. Arguments are separated by
+ * one or more whitespaces. Argument may be placed inside a pair of quotes,
+ * double quotes, '{' and '}', or '[' and ']' pairs. This is useful when the
+ * argument itself contains whitespaces or other restricted characters. If
+ * the quote character itself is to appear in the argument, the argument then
+ * must be quoted with different quote characters. There is no character
+ * escaping facility provided by this function (such as the use of backslash
+ * '\' character).
+ *
+ * The cmdline may be followed by an extra newline (LF or CR-LF characters),
+ * which will be removed by the function. However any more characters
+ * following this newline will cause an error to be returned.
+ *
+ * @param sess		The CLI session.
+ * @param cmdline	The command line string to be parsed.
+ * @param val		Structure to store the parsing result.
+ * @param pool		The pool to allocate memory from.
+ * @param info		Additional info to be returned regarding the parsing.
+ *
+ * @return		This function returns the status of the parsing,
+ * 			which can be one of the following :
+ *			  - PJ_SUCCESS: a command was executed successfully.
+ *			  - PJ_EINVAL: invalid parameter to this function.
+ *			  - PJ_ENOTFOUND: command is not found.
+ *			  - PJ_CLI_EAMBIGUOUS: command/argument is ambiguous.
+ *			  - PJ_CLI_EMISSINGARG: missing argument.
+ *			  - PJ_CLI_EINVARG: invalid command argument.
+ *			  - PJ_CLI_EEXIT: "exit" has been called to end
+ *			      the current session. This is a signal for the
+ *			      application to end it's main loop.
+ */
+PJ_DECL(pj_status_t) pj_cli_sess_parse(pj_cli_sess *sess,
+				       char *cmdline,
+				       pj_cli_cmd_val *val,
+				       pj_pool_t *pool,
+				       pj_cli_exec_info *info);
+
+/**
+ * End the specified session, and destroy it to release all resources used
+ * by the session.
+ *
+ * See also pj_cli_sess and pj_cli_front_end for more info regarding the 
+ * creation process.
+ * See also pj_cli_quit() to quit the whole application instead.
+ *
+ * @param sess		The CLI session to be destroyed.
+ */
+PJ_DECL(void) pj_cli_sess_end_session(pj_cli_sess *sess);
+
+/**
+ * Execute a command line. This function will parse the input string to find
+ * the appropriate command and verify whether the string matches the command
+ * specifications. If matches, the command will be executed, and the return
+ * value of the command will be set in the \a cmd_ret field of the \a info
+ * argument, if specified.
+ *
+ * See also pj_cli_sess_parse() for more info regarding the cmdline format.
+ *
+ * @param sess		The CLI session.
+ * @param cmdline	The command line string to be executed. 
+ * @param pool		The pool to allocate memory from.
+ * @param info		Optional pointer to receive additional information
+ * 			related to the execution of the command (such as
+ * 			the command return value).
+ *
+ * @return		This function returns the status of the command
+ * 			parsing and execution (note that the return value
+ * 			of the handler itself will be returned in \a info
+ * 			argument, if specified). Please see the return value
+ * 			of pj_cli_sess_parse() for possible return values.
+ */
+PJ_DECL(pj_status_t) pj_cli_sess_exec(pj_cli_sess *sess,
+				      char *cmdline,
+				      pj_pool_t *pool,
+				      pj_cli_exec_info *info);
+
+/**
+ * @}
+ */
+
+PJ_END_DECL
+
+#endif /* __PJLIB_UTIL_CLI_H__ */
Index: /pjproject/trunk/pjlib-util/include/pjlib-util/cli_console.h
===================================================================
--- /pjproject/trunk/pjlib-util/include/pjlib-util/cli_console.h	(revision 4440)
+++ /pjproject/trunk/pjlib-util/include/pjlib-util/cli_console.h	(revision 4440)
@@ -0,0 +1,117 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2010 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+#ifndef __PJLIB_UTIL_CLI_CONSOLE_H__
+#define __PJLIB_UTIL_CLI_CONSOLE_H__
+
+/**
+ * @file cli_console.h
+ * @brief Command Line Interface Console Front End API
+ */
+
+#include <pjlib-util/cli_imp.h>
+
+
+PJ_BEGIN_DECL
+
+/**
+ * @ingroup PJLIB_UTIL_CLI_IMP
+ * @{
+ *
+ */
+
+
+/**
+ * This structure contains various options for CLI console front-end.
+ * Application must call pj_cli_console_cfg_default() to initialize
+ * this structure with its default values.
+ */
+typedef struct pj_cli_console_cfg
+{
+    /**
+     * Default log verbosity level for the session.
+     *
+     * Default value: PJ_CLI_CONSOLE_LOG_LEVEL
+     */
+    int log_level;
+
+    /**
+     * Specify text message as a prompt string to user.
+     *
+     * Default: empty
+     */
+    pj_str_t prompt_str;
+
+    /**
+     * Specify the command to quit the application.
+     *
+     * Default: empty
+     */
+    pj_str_t quit_command;
+
+} pj_cli_console_cfg;
+
+
+/**
+ * Initialize pj_cli_console_cfg with its default values.
+ *
+ * @param param		The structure to be initialized.
+ */
+PJ_DECL(void) pj_cli_console_cfg_default(pj_cli_console_cfg *param);
+
+
+/**
+ * Create a console front-end for the specified CLI application, and return
+ * the only session instance for the console front end. On Windows operating
+ * system, this may open a new console window.
+ *
+ * @param cli		The CLI application instance.
+ * @param param		Optional console CLI parameters. If this value is
+ * 			NULL, default parameters will be used.
+ * @param p_sess	Pointer to receive the session instance for the
+ * 			console front end.
+ * @param p_fe		Optional pointer to receive the front-end instance
+ * 			of the console front-end just created.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_cli_console_create(pj_cli_t *cli,
+					   const pj_cli_console_cfg *param,
+					   pj_cli_sess **p_sess,
+					   pj_cli_front_end **p_fe);
+
+/**
+ * Retrieve a cmdline from console stdin and process the input accordingly.
+ *
+ * @param sess		The CLI session.
+ * @param buf		Pointer to receive the buffer.
+ * @param maxlen	Maximum length to read.
+ *
+ * @return		PJ_SUCCESS if an input was read
+ */
+PJ_DECL(pj_status_t) pj_cli_console_process(pj_cli_sess *sess, 
+					    char *buf,
+					    unsigned maxlen);
+
+/**
+ * @}
+ */
+
+PJ_END_DECL
+
+#endif /* __PJLIB_UTIL_CLI_CONSOLE_H__ */
Index: /pjproject/trunk/pjlib-util/include/pjlib-util/cli_imp.h
===================================================================
--- /pjproject/trunk/pjlib-util/include/pjlib-util/cli_imp.h	(revision 4440)
+++ /pjproject/trunk/pjlib-util/include/pjlib-util/cli_imp.h	(revision 4440)
@@ -0,0 +1,208 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2010 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+#ifndef __PJLIB_UTIL_CLI_IMP_H__
+#define __PJLIB_UTIL_CLI_IMP_H__
+
+/**
+ * @file cli_imp.h
+ * @brief Command Line Interface Implementor's API
+ */
+
+#include <pjlib-util/cli.h>
+
+
+PJ_BEGIN_DECL
+
+/**
+ * @defgroup PJLIB_UTIL_CLI_IMP Command Line Interface Implementor's API
+ * @ingroup PJLIB_UTIL_CLI
+ * @{
+ *
+ */
+
+/**
+ * Default log level for console sessions.
+ */
+#ifndef PJ_CLI_CONSOLE_LOG_LEVEL
+#   define PJ_CLI_CONSOLE_LOG_LEVEL	PJ_LOG_MAX_LEVEL
+#endif
+
+/**
+ * Default log level for telnet sessions.
+ */
+#ifndef PJ_CLI_TELNET_LOG_LEVEL
+#   define PJ_CLI_TELNET_LOG_LEVEL	3
+#endif
+
+/**
+ * Default port number for telnet daemon.
+ */
+#ifndef PJ_CLI_TELNET_PORT
+#   define PJ_CLI_TELNET_PORT		0
+#endif
+
+/**
+ * This enumeration specifies front end types.
+ */
+typedef enum pj_cli_front_end_type
+{
+    PJ_CLI_CONSOLE_FRONT_END,	/**< Console front end.	*/
+    PJ_CLI_TELNET_FRONT_END,	/**< Telnet front end.	*/
+    PJ_CLI_HTTP_FRONT_END,	/**< HTTP front end.	*/
+    PJ_CLI_GUI_FRONT_END	/**< GUI front end.	*/
+} pj_cli_front_end_type;
+
+
+/**
+ * Front end operations. Only the CLI should call these functions
+ * directly.
+ */
+typedef struct pj_cli_front_end_op
+{
+    /**
+     * Callback to write a log message to the appropriate sessions belonging
+     * to this front end. The front end would only send the log message to
+     * the session if the session's log verbosity level is greater than the
+     * level of this log message.
+     *
+     * @param fe	The front end.
+     * @param level	Verbosity level of this message message.
+     * @param data	The message itself.
+     * @param len 	Length of this message.
+     */
+    void (*on_write_log)(pj_cli_front_end *fe, int level,
+		         const char *data, int len);
+
+    /**
+     * Callback to be called when the application is quitting, to signal the
+     * front-end to end its main loop or any currently blocking functions,
+     * if any.
+     *
+     * @param fe	The front end.
+     * @param req	The session which requested the application quit.
+     */
+    void (*on_quit)(pj_cli_front_end *fe, pj_cli_sess *req);
+
+    /**
+     * Callback to be called to close and self destroy the front-end. This
+     * must also close any active sessions created by this front-ends.
+     *
+     * @param fe	The front end.
+     */
+    void (*on_destroy)(pj_cli_front_end *fe);
+
+} pj_cli_front_end_op;
+
+
+/**
+ * This structure describes common properties of CLI front-ends. A front-
+ * end is a mean to interact with end user, for example the CLI application
+ * may interact with console, telnet, web, or even a GUI.
+ *
+ * Each end user's interaction will create an instance of pj_cli_sess.
+ *
+ * Application instantiates the front end by calling the appropriate
+ * function to instantiate them.
+ */
+struct pj_cli_front_end
+{
+    /**
+     * Linked list members
+     */
+    PJ_DECL_LIST_MEMBER(struct pj_cli_front_end);
+
+    /**
+     * Front end type.
+     */
+    pj_cli_front_end_type type;
+
+    /**
+     * The CLI application.
+     */
+    pj_cli_t *cli;
+
+    /**
+     * Front end operations.
+     */
+    pj_cli_front_end_op *op;
+};
+
+
+/**
+ * Session operations.
+ */
+typedef struct pj_cli_sess_op
+{
+    /**
+     * Callback to be called to close and self destroy the session.
+     *
+     * @param sess	The session to destroy.
+     */
+    void (*destroy)(pj_cli_sess *sess);
+
+} pj_cli_sess_op;
+
+
+/**
+ * This structure describes common properties of a CLI session. A CLI session
+ * is the interaction of an end user to the CLI application via a specific
+ * renderer, where the renderer can be console, telnet, web, or a GUI app for
+ * mobile. A session is created by its renderer, and it's creation procedures
+ * vary among renderer (for example, a telnet session is created when the
+ * end user connects to the application, while a console session is always
+ * instantiated once the program is run).
+ */
+struct pj_cli_sess
+{
+    /**
+     * Linked list members
+     */
+    PJ_DECL_LIST_MEMBER(struct pj_cli_sess);
+
+    /**
+     * Pointer to the front-end instance which created this session.
+     */
+    pj_cli_front_end *fe;
+
+    /**
+     * Session operations.
+     */
+    pj_cli_sess_op *op;
+
+    /**
+     * Text containing session info, which is filled by the renderer when
+     * the session is created.
+     */
+    pj_str_t info;
+
+    /**
+     * Log verbosity of this session.
+     */
+    int log_level;
+
+};
+
+/**
+ * @}
+ */
+
+
+PJ_END_DECL
+
+#endif	/* __PJLIB_UTIL_CLI_IMP_H__ */
Index: /pjproject/trunk/pjlib-util/include/pjlib-util/cli_telnet.h
===================================================================
--- /pjproject/trunk/pjlib-util/include/pjlib-util/cli_telnet.h	(revision 4440)
+++ /pjproject/trunk/pjlib-util/include/pjlib-util/cli_telnet.h	(revision 4440)
@@ -0,0 +1,120 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2010 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+#ifndef __PJLIB_UTIL_CLI_TELNET_H__
+#define __PJLIB_UTIL_CLI_TELNET_H__
+
+/**
+ * @file cli_telnet.h
+ * @brief Command Line Interface Telnet Front End API
+ */
+
+#include <pjlib-util/cli_imp.h>
+
+PJ_BEGIN_DECL
+
+/**
+ * @ingroup PJLIB_UTIL_CLI_IMP
+ * @{
+ *
+ */
+
+
+/**
+ * This structure contains various options to instantiate the telnet daemon.
+ * Application must call pj_cli_telnet_cfg_default() to initialize
+ * this structure with its default values.
+ */
+typedef struct pj_cli_telnet_cfg
+{
+    /**
+     * Listening port number. The value may be 0 to let the system choose
+     * the first available port.
+     *
+     * Default value: PJ_CLI_TELNET_PORT
+     */
+    pj_uint16_t port;
+
+    /**
+     * Ioqueue instance to be used. If this field is NULL, an internal
+     * ioqueue and worker thread will be created.
+     */
+    pj_ioqueue_t *ioqueue;
+
+    /**
+     * Default log verbosity level for the session.
+     *
+     * Default value: PJ_CLI_TELNET_LOG_LEVEL
+     */
+    int log_level;
+
+    /**
+     * Specify a password to be asked to the end user to access the
+     * application.
+     *
+     * Default: empty (no password)
+     */
+    pj_str_t passwd;
+
+    /**
+     * Specify text message to be displayed to newly connected users.
+     *
+     * Default: empty
+     */
+    pj_str_t welcome_msg;
+
+    /**
+     * Specify text message as a prompt string to user.
+     *
+     * Default: empty
+     */
+    pj_str_t prompt_str;
+
+} pj_cli_telnet_cfg;
+
+
+/**
+ * Initialize pj_cli_telnet_cfg with its default values.
+ *
+ * @param param		The structure to be initialized.
+ */
+PJ_DECL(void) pj_cli_telnet_cfg_default(pj_cli_telnet_cfg *param);
+
+
+/**
+ * Create, initialize, and start a telnet daemon for the application.
+ *
+ * @param cli		The CLI application instance.
+ * @param param		Optional parameters for creating the telnet daemon.
+ * 			If this value is NULL, default parameters will be used.
+ * @param p_fe		Optional pointer to receive the front-end instance
+ * 			of the telnet front-end just created.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_cli_telnet_create(pj_cli_t *cli,
+					  pj_cli_telnet_cfg *param,
+					  pj_cli_front_end **p_fe);
+
+/**
+ * @}
+ */
+
+PJ_END_DECL
+
+#endif /* __PJLIB_UTIL_CLI_TELNET_H__ */
Index: /pjproject/trunk/pjlib-util/include/pjlib-util/config.h
===================================================================
--- /pjproject/trunk/pjlib-util/include/pjlib-util/config.h	(revision 4439)
+++ /pjproject/trunk/pjlib-util/include/pjlib-util/config.h	(revision 4440)
@@ -267,4 +267,104 @@
 #endif
 
+/* **************************************************************************
+ * CLI configuration
+ */
+
+/**
+ * Initial pool size for CLI.
+ * Default: 1024 bytes
+ */
+#ifndef PJ_CLI_POOL_SIZE
+#   define PJ_CLI_POOL_SIZE    1024
+#endif
+
+/**
+ * Pool increment size for CLI.
+ * Default: 512 bytes
+ */
+#ifndef PJ_CLI_POOL_INC
+#   define PJ_CLI_POOL_INC     512
+#endif
+
+/**
+ * Maximum length of command buffer.
+ * Default: 120
+ */
+#ifndef PJ_CLI_MAX_CMDBUF
+#   define PJ_CLI_MAX_CMDBUF		120
+#endif
+
+/**
+ * Maximum command arguments.
+ * Default: 8
+ */
+#ifndef PJ_CLI_MAX_ARGS
+#   define PJ_CLI_MAX_ARGS		8
+#endif
+
+/**
+ * Maximum number of hints.
+ * Default: 32
+ */
+#ifndef PJ_CLI_MAX_HINTS
+#   define PJ_CLI_MAX_HINTS		32
+#endif
+
+/**
+ * Maximum short name version (shortcuts) for a command.
+ * Default: 4
+ */
+#ifndef PJ_CLI_MAX_SHORTCUTS
+#   define PJ_CLI_MAX_SHORTCUTS		4
+#endif
+
+/**
+ * Initial pool size for console CLI.
+ * Default: 256 bytes
+ */
+#ifndef PJ_CLI_CONSOLE_POOL_SIZE
+#   define PJ_CLI_CONSOLE_POOL_SIZE    256
+#endif
+
+/**
+ * Pool increment size for console CLI.
+ * Default: 256 bytes
+ */
+#ifndef PJ_CLI_CONSOLE_POOL_INC
+#   define PJ_CLI_CONSOLE_POOL_INC     256
+#endif
+
+/**
+ * Initial pool size for telnet CLI.
+ * Default: 1024 bytes
+ */
+#ifndef PJ_CLI_TELNET_POOL_SIZE
+#   define PJ_CLI_TELNET_POOL_SIZE 1024
+#endif
+
+/**
+ * Pool increment size for telnet CLI.
+ * Default: 512 bytes
+ */
+#ifndef PJ_CLI_TELNET_POOL_INC
+#   define PJ_CLI_TELNET_POOL_INC  512
+#endif
+
+/**
+ * Maximum number of argument values of choice type.
+ * Default: 16
+ */
+#ifndef PJ_CLI_MAX_CHOICE_VAL
+#   define PJ_CLI_MAX_CHOICE_VAL  16
+#endif
+
+/**
+ * Maximum number of command history.
+ * Default: 16
+ */
+#ifndef PJ_CLI_MAX_CMD_HISTORY
+#   define PJ_CLI_MAX_CMD_HISTORY  16
+#endif
+
 /**
  * @}
Index: /pjproject/trunk/pjlib-util/include/pjlib-util/errno.h
===================================================================
--- /pjproject/trunk/pjlib-util/include/pjlib-util/errno.h	(revision 4439)
+++ /pjproject/trunk/pjlib-util/include/pjlib-util/errno.h	(revision 4440)
@@ -387,4 +387,58 @@
 #define PJLIB_UTIL_EHTTPLOST	    (PJLIB_UTIL_ERRNO_START+155)/* 320155 */
 
+/************************************************************
+ * CLI ERROR
+ ***********************************************************/
+
+/**
+ * @hideinitializer
+ * End the current session. This is a special error code returned by
+ * pj_cli_sess_exec() to indicate that "exit" or equivalent command has been
+ * called to end the current session.
+ */
+#define PJ_CLI_EEXIT        	    (PJLIB_UTIL_ERRNO_START+201)/* 320201 */
+/**
+ * @hideinitializer
+ * A required CLI argument is not specified.
+ */
+#define PJ_CLI_EMISSINGARG    	    (PJLIB_UTIL_ERRNO_START+202)/* 320202 */
+ /**
+ * @hideinitializer
+ * Too many CLI arguments.
+ */
+#define PJ_CLI_ETOOMANYARGS    	    (PJLIB_UTIL_ERRNO_START+203)/* 320203 */
+/**
+ * @hideinitializer
+ * Invalid CLI argument. Typically this is caused by extra characters
+ * specified in the command line which does not match any arguments.
+ */
+#define PJ_CLI_EINVARG        	    (PJLIB_UTIL_ERRNO_START+204)/* 320204 */
+/**
+ * @hideinitializer
+ * CLI command with the specified name already exist.
+ */
+#define PJ_CLI_EBADNAME        	    (PJLIB_UTIL_ERRNO_START+205)/* 320205 */
+/**
+ * @hideinitializer
+ * CLI command with the specified id already exist.
+ */
+#define PJ_CLI_EBADID        	    (PJLIB_UTIL_ERRNO_START+206)/* 320206 */
+/**
+ * @hideinitializer
+ * Invalid XML format for CLI command specification.
+ */
+#define PJ_CLI_EBADXML        	    (PJLIB_UTIL_ERRNO_START+207)/* 320207 */
+/**
+ * @hideinitializer
+ * CLI command entered by user match with more than one command/argument 
+ * specification.
+ */
+#define PJ_CLI_EAMBIGUOUS	    (PJLIB_UTIL_ERRNO_START+208)/* 320208 */
+/**
+ * @hideinitializer
+ * Telnet connection lost.
+ */
+#define PJ_CLI_ETELNETLOST          (PJLIB_UTIL_ERRNO_START+211)/* 320211 */
+
 /**
  * @}
Index: /pjproject/trunk/pjlib-util/src/pjlib-util/cli.c
===================================================================
--- /pjproject/trunk/pjlib-util/src/pjlib-util/cli.c	(revision 4440)
+++ /pjproject/trunk/pjlib-util/src/pjlib-util/cli.c	(revision 4440)
@@ -0,0 +1,1206 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2010 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <pjlib-util/cli_imp.h>
+#include <pj/assert.h>
+#include <pj/errno.h>
+#include <pj/except.h>
+#include <pj/hash.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pjlib-util/errno.h>
+#include <pjlib-util/scanner.h>
+#include <pjlib-util/xml.h>
+
+#define CMD_HASH_TABLE_SIZE 63	/* Hash table size */
+
+#define CLI_CMD_CHANGE_LOG  30000
+#define CLI_CMD_EXIT        30001
+
+#if 1
+    /* Enable some tracing */
+    #define THIS_FILE   "cli.c"
+    #define TRACE_(arg)	PJ_LOG(3,arg)
+#else
+    #define TRACE_(arg)
+#endif
+
+/**
+ * This structure describes the full specification of a CLI command. A CLI
+ * command mainly consists of the name of the command, zero or more arguments,
+ * and a callback function to be called to execute the command.
+ *
+ * Application can create this specification by forming an XML document and
+ * calling pj_cli_add_cmd_from_xml() to instantiate the spec. A sample XML
+ * document containing a command spec is as follows:
+ *
+ \verbatim
+  <CMD name='makecall' id='101' sc='m,mc' desc='Make outgoing call'>
+      <ARGS>
+	  <ARG name='target' type='text' desc='The destination'/>
+      </ARGS>
+  </CMD>
+ \endverbatim
+ */
+struct pj_cli_cmd_spec
+{
+    /**
+     * To make list of child cmds.
+     */
+    PJ_DECL_LIST_MEMBER(struct pj_cli_cmd_spec);
+
+    /**
+     * Command ID assigned to this command by the application during command
+     * creation. If this value is PJ_CLI_CMD_ID_GROUP (-2), then this is
+     * a command group and it can't be executed.
+     */
+    pj_cli_cmd_id id;
+
+    /**
+     * The command name.
+     */
+    pj_str_t name;
+
+    /**
+     * The full description of the command.
+     */
+    pj_str_t desc;
+
+    /**
+     * Number of optional shortcuts
+     */
+    unsigned sc_cnt;
+
+    /**
+     * Optional array of shortcuts, if any. Shortcut is a short name version
+     * of the command. If the command doesn't have any shortcuts, this
+     * will be initialized to NULL.
+     */
+    pj_str_t *sc;
+
+    /**
+     * The command handler, to be executed when a command matching this command
+     * specification is invoked by the end user. The value may be NULL if this
+     * is a command group.
+     */
+    pj_cli_cmd_handler handler;
+
+    /**
+     * Number of arguments.
+     */
+    unsigned arg_cnt;
+
+    /**
+     * Array of arguments.
+     */
+    pj_cli_arg_spec *arg;
+
+    /**
+     * Child commands, if any. A command will only have subcommands if it is
+     * a group. If the command doesn't have subcommands, this field will be
+     * initialized with NULL.
+     */
+    pj_cli_cmd_spec *sub_cmd;
+};
+
+struct pj_cli_t
+{
+    pj_pool_t	       *pool;           /* Pool to allocate memory from */
+    pj_cli_cfg          cfg;            /* CLI configuration */
+    pj_cli_cmd_spec     root;           /* Root of command tree structure */
+    pj_cli_front_end    fe_head;        /* List of front-ends */
+    pj_hash_table_t    *cmd_name_hash;  /* Command name hash table */
+    pj_hash_table_t    *cmd_id_hash;    /* Command id hash table */
+
+    pj_bool_t           is_quitting;
+    pj_bool_t           is_restarting;
+};
+
+/**
+ * Reserved command id constants.
+ */
+typedef enum pj_cli_std_cmd_id
+{
+    /**
+     * Constant to indicate an invalid command id.
+     */
+    PJ_CLI_INVALID_CMD_ID = -1,
+
+    /**
+     * A special command id to indicate that a command id denotes
+     * a command group.
+     */
+    PJ_CLI_CMD_ID_GROUP = -2
+
+} pj_cli_std_cmd_id;
+
+/**
+ * This describes the type of an argument (pj_cli_arg_spec).
+ */
+typedef enum pj_cli_arg_type
+{
+    /**
+     * Unformatted string.
+     */
+    PJ_CLI_ARG_TEXT,
+
+    /**
+     * An integral number.
+     */
+    PJ_CLI_ARG_INT,
+
+    /**
+     * Choice type
+    */
+    PJ_CLI_ARG_CHOICE
+
+} pj_cli_arg_type;
+
+struct arg_type
+{
+    const pj_str_t msg;
+} arg_type[3] = 
+{
+    {{"Text", 4}},
+    {{"Int", 3}},
+    {{"Choice", 6}}
+};
+
+/**
+ * This structure describe the specification of a command argument.
+ */
+struct pj_cli_arg_spec
+{
+    /**
+     * Argument id
+     */
+    pj_cli_arg_id id;
+
+    /**
+     * Argument name.
+     */
+    pj_str_t name;
+
+    /**
+     * Helpful description of the argument. This text will be used when
+     * displaying help texts for the command/argument.
+     */
+    pj_str_t desc;
+
+    /**
+     * Argument type, which will be used for rendering the argument and
+     * to perform basic validation against an input value.
+     */
+    pj_cli_arg_type type;
+
+    /**
+     * Argument status
+     */
+    pj_bool_t optional;
+
+    /**
+     * Validate choice values
+     */
+    pj_bool_t validate;
+
+    /**
+     * Static Choice Values count
+     */
+    unsigned stat_choice_cnt; 
+
+    /**
+     * Static Choice Values
+     */
+    pj_cli_arg_choice_val *stat_choice_val; 
+
+    /**
+     * Argument callback to get the valid values
+     */
+    pj_cli_get_dyn_choice get_dyn_choice;
+
+};
+
+/**
+ * This describe the parse mode of the command line
+ */
+typedef enum pj_cli_parse_mode {
+    PARSE_NONE,
+    PARSE_COMPLETION,	/* Complete the command line */
+    PARSE_NEXT_AVAIL,   /* Find the next available command line */
+    PARSE_EXEC		/* Exec the command line */
+} pj_cli_parse_mode;
+
+/** 
+ * This is used to get the matched command/argument from the 
+ * command/argument structure.
+ * 
+ * @param sess		The session on which the command is execute on.
+ * @param cmd		The active command.
+ * @param cmd_val	The command value to match.
+ * @param argc		The number of argument that the 
+ *			current active command have.
+ * @param pool		The memory pool to allocate memory.
+ * @param get_cmd	Set true to search matching command from sub command.
+ * @param parse_mode	The parse mode.
+ * @param p_cmd		The command that mathes the command value.
+ * @param info		The output information containing any hints for 
+ *			matching command/arg.
+ * @return		This function return the status of the 
+ *			matching process.Please see the return value
+ * 			of pj_cli_sess_parse() for possible return values.
+ */
+static pj_status_t get_available_cmds(pj_cli_sess *sess,
+				      pj_cli_cmd_spec *cmd, 
+				      pj_str_t *cmd_val,
+				      unsigned argc,
+				      pj_pool_t *pool,
+				      pj_bool_t get_cmd,
+				      pj_cli_parse_mode parse_mode,
+				      pj_cli_cmd_spec **p_cmd,
+				      pj_cli_exec_info *info);
+
+PJ_DEF(pj_cli_cmd_id) pj_cli_get_cmd_id(const pj_cli_cmd_spec *cmd)
+{
+    return cmd->id;
+}
+
+PJ_DEF(void) pj_cli_cfg_default(pj_cli_cfg *param)
+{
+    pj_assert(param);
+    pj_bzero(param, sizeof(*param));
+    pj_strset2(&param->name, "");
+}
+
+PJ_DEF(void) pj_cli_exec_info_default(pj_cli_exec_info *param)
+{
+    pj_assert(param);
+    pj_bzero(param, sizeof(*param));
+    param->err_pos = -1;
+    param->cmd_id = PJ_CLI_INVALID_CMD_ID;
+    param->cmd_ret = PJ_SUCCESS;
+}
+
+PJ_DEF(void) pj_cli_write_log(pj_cli_t *cli,
+                              int level,
+                              const char *buffer,
+                              int len)
+{
+    struct pj_cli_front_end *fe;
+
+    pj_assert(cli);
+
+    fe = cli->fe_head.next;
+    while (fe != &cli->fe_head) {
+        if (fe->op && fe->op->on_write_log)
+            (*fe->op->on_write_log)(fe, level, buffer, len);
+        fe = fe->next;
+    }
+}
+
+PJ_DECL(void) pj_cli_sess_write_msg(pj_cli_sess *sess,                               
+				    const char *buffer,
+				    int len)
+{
+    struct pj_cli_front_end *fe;
+
+    pj_assert(sess);
+
+    fe = sess->fe;
+    if (fe->op && fe->op->on_write_log)
+        (*fe->op->on_write_log)(fe, 0, buffer, len);
+}
+
+/* Command handler */
+static pj_status_t cmd_handler(pj_cli_cmd_val *cval)
+{
+    unsigned level;
+
+    switch(cval->cmd->id) {
+    case CLI_CMD_CHANGE_LOG:
+        level = pj_strtoul(&cval->argv[1]);
+        if (!level && cval->argv[1].slen > 0 && (cval->argv[1].ptr[0] < '0' ||
+            cval->argv[1].ptr[0] > '9'))
+	{
+            return PJ_CLI_EINVARG;
+	}
+        cval->sess->log_level = level;
+        return PJ_SUCCESS;
+    case CLI_CMD_EXIT:
+        pj_cli_sess_end_session(cval->sess);
+        return PJ_CLI_EEXIT;
+    default:
+        return PJ_SUCCESS;
+    }
+}
+
+PJ_DEF(pj_status_t) pj_cli_create(pj_cli_cfg *cfg,
+                                  pj_cli_t **p_cli)
+{
+    pj_pool_t *pool;
+    pj_cli_t *cli;    
+    unsigned i;
+
+    /* This is an example of the command structure */
+    char* cmd_xmls[] = {
+     "<CMD name='log' id='30000' sc='' desc='Change log level'>"
+     "    <ARG name='level' type='int' optional='0' desc='Log level'/>"
+     "</CMD>",     
+     "<CMD name='exit' id='30001' sc='' desc='Exit session'>"     
+     "</CMD>",
+    };
+
+    PJ_ASSERT_RETURN(cfg && cfg->pf && p_cli, PJ_EINVAL);
+
+    pool = pj_pool_create(cfg->pf, "cli", PJ_CLI_POOL_SIZE, 
+                          PJ_CLI_POOL_INC, NULL);
+    if (!pool)
+        return PJ_ENOMEM;
+    cli = PJ_POOL_ZALLOC_T(pool, struct pj_cli_t);
+
+    pj_memcpy(&cli->cfg, cfg, sizeof(*cfg));
+    cli->pool = pool;
+    pj_list_init(&cli->fe_head);
+
+    cli->cmd_name_hash = pj_hash_create(pool, CMD_HASH_TABLE_SIZE);
+    cli->cmd_id_hash = pj_hash_create(pool, CMD_HASH_TABLE_SIZE);
+
+    cli->root.sub_cmd = PJ_POOL_ZALLOC_T(pool, pj_cli_cmd_spec);
+    pj_list_init(cli->root.sub_cmd);
+
+    /* Register some standard commands. */
+    for (i = 0; i < sizeof(cmd_xmls)/sizeof(cmd_xmls[0]); i++) {
+        pj_str_t xml = pj_str(cmd_xmls[i]);
+
+        if (pj_cli_add_cmd_from_xml(cli, NULL, &xml, 
+				    &cmd_handler, NULL, NULL) != PJ_SUCCESS) 
+	{
+            TRACE_((THIS_FILE, "Failed to add command #%d", i));
+	}
+    }
+
+    *p_cli = cli;
+
+    return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_cli_cfg*) pj_cli_get_param(pj_cli_t *cli)
+{
+    PJ_ASSERT_RETURN(cli, NULL);
+
+    return &cli->cfg;
+}
+
+PJ_DEF(void) pj_cli_register_front_end(pj_cli_t *cli,
+                                       pj_cli_front_end *fe)
+{
+    pj_list_push_back(&cli->fe_head, fe);
+}
+
+PJ_DEF(void) pj_cli_quit(pj_cli_t *cli, pj_cli_sess *req,
+			 pj_bool_t restart)
+{
+    pj_cli_front_end *fe;
+
+    pj_assert(cli);
+    if (cli->is_quitting)
+	return;
+
+    cli->is_quitting = PJ_TRUE;
+    cli->is_restarting = restart;
+
+    fe = cli->fe_head.next;
+    while (fe != &cli->fe_head) {
+        if (fe->op && fe->op->on_quit)
+            (*fe->op->on_quit)(fe, req);
+        fe = fe->next;
+    }
+}
+
+PJ_DEF(pj_bool_t) pj_cli_is_quitting(pj_cli_t *cli)
+{
+    PJ_ASSERT_RETURN(cli, PJ_FALSE);
+
+    return cli->is_quitting;
+}
+
+PJ_DEF(pj_bool_t) pj_cli_is_restarting(pj_cli_t *cli)
+{
+    PJ_ASSERT_RETURN(cli, PJ_FALSE);
+
+    return cli->is_restarting;
+}
+
+PJ_DEF(void) pj_cli_destroy(pj_cli_t *cli)
+{
+    pj_cli_front_end *fe;
+
+    if (!cli)
+	return;
+
+    if (!pj_cli_is_quitting(cli))
+        pj_cli_quit(cli, NULL, PJ_FALSE);
+
+    fe = cli->fe_head.next;
+    while (fe != &cli->fe_head) {
+        pj_list_erase(fe);
+	if (fe->op && fe->op->on_destroy)
+	    (*fe->op->on_destroy)(fe);
+
+        fe = cli->fe_head.next;
+    }
+    cli->is_quitting = PJ_FALSE;
+    pj_pool_release(cli->pool);
+}
+
+PJ_DEF(void) pj_cli_sess_end_session(pj_cli_sess *sess)
+{
+    pj_assert(sess);
+
+    if (sess->op && sess->op->destroy)
+        (*sess->op->destroy)(sess);
+}
+
+/* Syntax error handler for parser. */
+static void on_syntax_error(pj_scanner *scanner)
+{
+    PJ_UNUSED_ARG(scanner);
+    PJ_THROW(PJ_EINVAL);
+}
+
+/* Check if command already added to command hash */
+static pj_bool_t cmd_name_exists(pj_cli_t *cli, pj_cli_cmd_spec *group, 
+				 pj_str_t *cmd)
+{
+    pj_str_t cmd_val;
+    char cmd_ptr[64];
+
+    cmd_val.ptr = &cmd_ptr[0];
+    cmd_val.slen = 0;
+
+    if (group) {
+	char cmd_str[16];
+	pj_ansi_sprintf(&cmd_str[0], "%d", group->id);
+	pj_strcat2(&cmd_val, &cmd_str[0]);	
+    }
+    pj_strcat(&cmd_val, cmd);
+    return (pj_hash_get(cli->cmd_name_hash, 
+	                cmd_val.ptr, cmd_val.slen, NULL) != 0);
+}
+
+/* Add command to the command hash */
+static void add_cmd_name(pj_cli_t *cli, pj_cli_cmd_spec *group, 
+			 pj_cli_cmd_spec *cmd, pj_str_t *cmd_name)
+{
+    pj_str_t cmd_val;
+    pj_str_t add_cmd;
+    char cmd_ptr[64];
+
+    cmd_val.ptr = &cmd_ptr[0];
+    cmd_val.slen = 0;
+
+    if (group) {
+	pj_strcat(&cmd_val, &group->name);
+    }
+    pj_strcat(&cmd_val, cmd_name);
+    pj_strdup(cli->pool, &add_cmd, &cmd_val);
+    
+    pj_hash_set(cli->pool, cli->cmd_name_hash, 
+		cmd_val.ptr, cmd_val.slen, 0, cmd);
+}
+
+/**
+ * This method is to parse and add the choice type 
+ * argument values to command structure.
+ **/
+static pj_status_t add_choice_node(pj_cli_t *cli,
+				   pj_xml_node *xml_node,
+				   pj_cli_arg_spec *arg,
+				   pj_cli_get_dyn_choice get_choice)
+{
+    pj_xml_node *choice_node;
+    pj_xml_node *sub_node;
+    pj_cli_arg_choice_val choice_values[PJ_CLI_MAX_CHOICE_VAL];
+    pj_status_t status = PJ_SUCCESS;
+
+    sub_node = xml_node;
+    arg->type = PJ_CLI_ARG_CHOICE;
+    arg->get_dyn_choice = get_choice;						
+
+    choice_node = sub_node->node_head.next;
+    while (choice_node != (pj_xml_node*)&sub_node->node_head) {
+	pj_xml_attr *choice_attr;
+	unsigned *stat_cnt = &arg->stat_choice_cnt;
+	pj_cli_arg_choice_val *choice_val = &choice_values[*stat_cnt];		     
+	pj_bzero(choice_val, sizeof(*choice_val));
+
+	choice_attr = choice_node->attr_head.next;
+	while (choice_attr != &choice_node->attr_head) {
+	    if (!pj_stricmp2(&choice_attr->name, "value")) {
+		pj_strassign(&choice_val->value, &choice_attr->value);
+	    } else if (!pj_stricmp2(&choice_attr->name, "desc")) {
+		pj_strassign(&choice_val->desc, &choice_attr->value);
+	    }
+	    choice_attr = choice_attr->next;
+	}		
+	if (++(*stat_cnt) >= PJ_CLI_MAX_CHOICE_VAL)
+	    break;
+	choice_node = choice_node->next;
+    }    
+    if (arg->stat_choice_cnt > 0) {
+        unsigned i;
+
+	arg->stat_choice_val = (pj_cli_arg_choice_val *)
+				pj_pool_zalloc(cli->pool, 
+					       arg->stat_choice_cnt *
+					       sizeof(pj_cli_arg_choice_val));
+
+        for (i = 0; i < arg->stat_choice_cnt; i++) {
+	    pj_strdup(cli->pool, &arg->stat_choice_val[i].value, 
+		      &choice_values[i].value);
+            pj_strdup(cli->pool, &arg->stat_choice_val[i].desc, 
+		      &choice_values[i].desc);            
+        }
+    }
+    return status;
+}
+
+/**
+ * This method is to parse and add the argument attribute to command structure.
+ **/
+static pj_status_t add_arg_node(pj_cli_t *cli,
+				pj_xml_node *xml_node,
+				pj_cli_cmd_spec *cmd,
+				pj_cli_arg_spec *arg,
+				pj_cli_get_dyn_choice get_choice)
+{    
+    pj_xml_attr *attr;
+    pj_status_t status = PJ_SUCCESS;
+    pj_xml_node *sub_node = xml_node;
+
+    if (cmd->arg_cnt >= PJ_CLI_MAX_ARGS)
+	return PJ_CLI_ETOOMANYARGS;
+    
+    pj_bzero(arg, sizeof(*arg));
+    attr = sub_node->attr_head.next;
+    arg->optional = PJ_FALSE;
+    arg->validate = PJ_TRUE;
+    while (attr != &sub_node->attr_head) {	
+	if (!pj_stricmp2(&attr->name, "name")) {
+	    pj_strassign(&arg->name, &attr->value);
+	} else if (!pj_stricmp2(&attr->name, "id")) {
+	    arg->id = pj_strtol(&attr->value);
+	} else if (!pj_stricmp2(&attr->name, "type")) {
+	    if (!pj_stricmp2(&attr->value, "text")) {
+		arg->type = PJ_CLI_ARG_TEXT;
+	    } else if (!pj_stricmp2(&attr->value, "int")) {
+		arg->type = PJ_CLI_ARG_INT;
+	    } else if (!pj_stricmp2(&attr->value, "choice")) {
+		/* Get choice value */
+		add_choice_node(cli, xml_node, arg, get_choice);
+	    } 
+	} else if (!pj_stricmp2(&attr->name, "desc")) {
+	    pj_strassign(&arg->desc, &attr->value);
+	} else if (!pj_stricmp2(&attr->name, "optional")) {
+	    if (!pj_strcmp2(&attr->value, "1")) {
+		arg->optional = PJ_TRUE;
+	    }
+	} else if (!pj_stricmp2(&attr->name, "validate")) {
+	    if (!pj_strcmp2(&attr->value, "1")) {
+		arg->validate = PJ_TRUE;
+	    } else {
+		arg->validate = PJ_FALSE;
+	    }	
+	} 
+	attr = attr->next;
+    }
+    cmd->arg_cnt++;
+    return status;
+}
+
+/**
+ * This method is to parse and add the command attribute to command structure.
+ **/
+static pj_status_t add_cmd_node(pj_cli_t *cli,				  
+				pj_cli_cmd_spec *group,					 
+				pj_xml_node *xml_node,
+				pj_cli_cmd_handler handler,
+				pj_cli_cmd_spec **p_cmd,
+				pj_cli_get_dyn_choice get_choice)
+{
+    pj_xml_node *root = xml_node;
+    pj_xml_attr *attr;
+    pj_xml_node *sub_node;
+    pj_cli_cmd_spec *cmd;
+    pj_cli_arg_spec args[PJ_CLI_MAX_ARGS];
+    pj_str_t sc[PJ_CLI_MAX_SHORTCUTS];
+    pj_status_t status = PJ_SUCCESS;
+
+    if (pj_stricmp2(&root->name, "CMD"))
+        return PJ_EINVAL;
+
+    /* Initialize the command spec */
+    cmd = PJ_POOL_ZALLOC_T(cli->pool, struct pj_cli_cmd_spec);
+    
+    /* Get the command attributes */
+    attr = root->attr_head.next;
+    while (attr != &root->attr_head) {
+        if (!pj_stricmp2(&attr->name, "name")) {
+            pj_strltrim(&attr->value);
+            if (!attr->value.slen || 
+		cmd_name_exists(cli, group, &attr->value))                
+            {
+                return PJ_CLI_EBADNAME;
+            }
+            pj_strdup(cli->pool, &cmd->name, &attr->value);
+        } else if (!pj_stricmp2(&attr->name, "id")) {	    
+	    pj_bool_t is_valid = PJ_FALSE;
+            if (attr->value.slen) {		
+		pj_cli_cmd_id cmd_id = pj_strtol(&attr->value);
+		if (!pj_hash_get(cli->cmd_id_hash, &cmd_id, 
+		                 sizeof(pj_cli_cmd_id), NULL))
+		    is_valid = PJ_TRUE;
+	    } 
+	    if (!is_valid)
+		return PJ_CLI_EBADID;
+            cmd->id = (pj_cli_cmd_id)pj_strtol(&attr->value);
+        } else if (!pj_stricmp2(&attr->name, "sc")) {
+            pj_scanner scanner;
+            pj_str_t str;
+
+            PJ_USE_EXCEPTION;
+
+            pj_scan_init(&scanner, attr->value.ptr, attr->value.slen,
+                         PJ_SCAN_AUTOSKIP_WS, &on_syntax_error);
+
+            PJ_TRY {
+                while (!pj_scan_is_eof(&scanner)) {
+                    pj_scan_get_until_ch(&scanner, ',', &str);
+                    pj_strrtrim(&str);
+                    if (!pj_scan_is_eof(&scanner))
+                        pj_scan_advance_n(&scanner, 1, PJ_TRUE);
+                    if (!str.slen)
+                        continue;
+
+                    if (cmd->sc_cnt >= PJ_CLI_MAX_SHORTCUTS) {
+                        PJ_THROW(PJ_CLI_ETOOMANYARGS);
+                    }
+                    /* Check whether the shortcuts are already used */
+                    if (cmd_name_exists(cli, group, &str)) {
+                        PJ_THROW(PJ_CLI_EBADNAME);
+                    }
+
+                    pj_strassign(&sc[cmd->sc_cnt++], &str);
+                }
+            }
+            PJ_CATCH_ANY {
+                pj_scan_fini(&scanner);
+                return (PJ_GET_EXCEPTION());
+            }
+            PJ_END;
+            
+        } else if (!pj_stricmp2(&attr->name, "desc")) {
+            pj_strdup(cli->pool, &cmd->desc, &attr->value);
+        }
+        attr = attr->next;
+    }
+
+    /* Get the command childs/arguments */
+    sub_node = root->node_head.next;
+    while (sub_node != (pj_xml_node*)&root->node_head) {
+	if (!pj_stricmp2(&sub_node->name, "CMD")) {
+	    status = add_cmd_node(cli, cmd, sub_node, handler, NULL, 
+				  get_choice);
+	    if (status != PJ_SUCCESS)
+		return status;
+	} else if (!pj_stricmp2(&sub_node->name, "ARG")) {
+	    /* Get argument attribute */
+	    status = add_arg_node(cli, sub_node, 
+			          cmd, &args[cmd->arg_cnt], 
+		                  get_choice);
+
+	    if (status != PJ_SUCCESS)
+		return status;
+        }
+        sub_node = sub_node->next;
+    }
+
+    if (!cmd->name.slen)
+        return PJ_CLI_EBADNAME;
+
+    if (!cmd->id)
+        return PJ_CLI_EBADID;
+
+    if (cmd->arg_cnt) {
+        unsigned i;
+
+        cmd->arg = (pj_cli_arg_spec *)pj_pool_zalloc(cli->pool, cmd->arg_cnt *
+                                                     sizeof(pj_cli_arg_spec));
+	
+        for (i = 0; i < cmd->arg_cnt; i++) {
+            pj_strdup(cli->pool, &cmd->arg[i].name, &args[i].name);
+            pj_strdup(cli->pool, &cmd->arg[i].desc, &args[i].desc);
+	    cmd->arg[i].id = args[i].id;
+            cmd->arg[i].type = args[i].type;
+	    cmd->arg[i].optional = args[i].optional;
+	    cmd->arg[i].validate = args[i].validate;
+	    cmd->arg[i].get_dyn_choice = args[i].get_dyn_choice;
+	    cmd->arg[i].stat_choice_cnt = args[i].stat_choice_cnt;
+	    cmd->arg[i].stat_choice_val = args[i].stat_choice_val;
+        }
+    }
+
+    if (cmd->sc_cnt) {
+        unsigned i;
+
+        cmd->sc = (pj_str_t *)pj_pool_zalloc(cli->pool, cmd->sc_cnt *
+                                             sizeof(pj_str_t));
+        for (i = 0; i < cmd->sc_cnt; i++) {
+            pj_strdup(cli->pool, &cmd->sc[i], &sc[i]);
+	    add_cmd_name(cli, group, cmd, &sc[i]);
+        }
+    }
+    
+    add_cmd_name(cli, group, cmd, &cmd->name);    
+    pj_hash_set(cli->pool, cli->cmd_id_hash, 
+		&cmd->id, sizeof(pj_cli_cmd_id), 0, cmd);
+
+    cmd->handler = handler;
+
+    if (group) {
+	if (!group->sub_cmd) {
+	    group->sub_cmd = PJ_POOL_ALLOC_T(cli->pool, struct pj_cli_cmd_spec);
+	    pj_list_init(group->sub_cmd);
+	}
+        pj_list_push_back(group->sub_cmd, cmd);
+    } else {
+        pj_list_push_back(cli->root.sub_cmd, cmd);
+    }
+
+    if (p_cmd)
+        *p_cmd = cmd;
+
+    return status;
+}
+
+PJ_DEF(pj_status_t) pj_cli_add_cmd_from_xml(pj_cli_t *cli,
+					    pj_cli_cmd_spec *group,
+                                            const pj_str_t *xml,
+                                            pj_cli_cmd_handler handler,
+                                            pj_cli_cmd_spec **p_cmd, 
+					    pj_cli_get_dyn_choice get_choice)
+{ 
+    pj_pool_t *pool;
+    pj_xml_node *root;
+    pj_status_t status = PJ_SUCCESS;
+    
+    PJ_ASSERT_RETURN(cli && xml, PJ_EINVAL);
+
+    /* Parse the xml */
+    pool = pj_pool_create(cli->cfg.pf, "xml", 1024, 1024, NULL);
+    if (!pool)
+        return PJ_ENOMEM;
+
+    root = pj_xml_parse(pool, xml->ptr, xml->slen);
+    if (!root) {
+	TRACE_((THIS_FILE, "Error: unable to parse XML"));
+	pj_pool_release(pool);
+	return PJ_CLI_EBADXML;
+    }    
+    status = add_cmd_node(cli, group, root, handler, p_cmd, get_choice);
+    pj_pool_release(pool);
+    return status;
+}
+
+PJ_DEF(pj_status_t) pj_cli_sess_parse(pj_cli_sess *sess,
+				      char *cmdline,
+				      pj_cli_cmd_val *val,
+				      pj_pool_t *pool,
+				      pj_cli_exec_info *info)
+{    
+    pj_scanner scanner;
+    pj_str_t str;
+    int len;    
+    pj_cli_cmd_spec *cmd;
+    pj_cli_cmd_spec *next_cmd;
+    pj_status_t status = PJ_SUCCESS;
+    pj_cli_parse_mode parse_mode = PARSE_NONE;    
+
+    PJ_USE_EXCEPTION;
+
+    PJ_ASSERT_RETURN(sess && cmdline && val, PJ_EINVAL);
+
+    PJ_UNUSED_ARG(pool);
+
+    str.slen = 0;
+    pj_cli_exec_info_default(info);
+
+    /* Set the parse mode based on the latest char. */
+    len = pj_ansi_strlen(cmdline);
+    if (len > 0 && ((cmdline[len - 1] == '\r')||(cmdline[len - 1] == '\n'))) {
+        cmdline[--len] = 0;
+	parse_mode = PARSE_EXEC;
+    } else if (len > 0 && 
+	       (cmdline[len - 1] == '\t' || cmdline[len - 1] == '?')) 
+    {
+	cmdline[--len] = 0;
+	if (len == 0) {
+	    parse_mode = PARSE_NEXT_AVAIL;
+	} else {
+	    if (cmdline[len - 1] == ' ') 
+		parse_mode = PARSE_NEXT_AVAIL;
+	    else 
+		parse_mode = PARSE_COMPLETION;
+	}
+    }
+    val->argc = 0;
+    info->err_pos = 0;
+    cmd = &sess->fe->cli->root;
+    if (len > 0) {
+	pj_scan_init(&scanner, cmdline, len, PJ_SCAN_AUTOSKIP_WS, 
+		     &on_syntax_error);
+	PJ_TRY {
+	    val->argc = 0;	    
+	    while (!pj_scan_is_eof(&scanner)) {
+		info->err_pos = scanner.curptr - scanner.begin;
+		if (*scanner.curptr == '\'' || *scanner.curptr == '"' ||
+		    *scanner.curptr == '[' || *scanner.curptr == '{')
+		{
+		    pj_scan_get_quotes(&scanner, "'\"[{", "'\"]}", 4, &str);
+		    /* Remove the quotes */
+		    str.ptr++;
+		    str.slen -= 2;
+		} else {
+		    pj_scan_get_until_chr(&scanner, " \t\r\n", &str);
+		}
+		++val->argc;
+		if (val->argc == PJ_CLI_MAX_ARGS)
+		    PJ_THROW(PJ_CLI_ETOOMANYARGS);		
+		
+		status = get_available_cmds(sess, cmd, &str, val->argc-1, 
+					    pool, PJ_TRUE, parse_mode, 
+					    &next_cmd, info);
+
+		if (status != PJ_SUCCESS)
+		    PJ_THROW(status);
+		
+		if (cmd != next_cmd) {
+		    /* Found new command, set it as the active command */
+		    cmd = next_cmd;
+		    val->argc = 1;
+		    val->cmd = cmd;
+		}
+		if (parse_mode == PARSE_EXEC) 
+		    pj_strassign(&val->argv[val->argc-1], &info->hint->name);
+		else 
+		    pj_strassign(&val->argv[val->argc-1], &str);
+
+	    }            
+	    if (!pj_scan_is_eof(&scanner)) 
+		PJ_THROW(PJ_CLI_EINVARG);
+
+	}
+	PJ_CATCH_ANY {
+	    pj_scan_fini(&scanner);
+	    return PJ_GET_EXCEPTION();
+	}
+	PJ_END;
+    } 
+    
+    if ((parse_mode == PARSE_NEXT_AVAIL) || (parse_mode == PARSE_EXEC)) {
+	/* If exec mode, just get the matching argument */
+	status = get_available_cmds(sess, cmd, NULL, val->argc, pool, 
+				    (parse_mode==PARSE_NEXT_AVAIL), 
+				    parse_mode,
+				    NULL, info);
+	if ((status != PJ_SUCCESS) && (status != PJ_CLI_EINVARG)) {
+	    pj_str_t data = pj_str(cmdline);
+	    pj_strrtrim(&data);
+	    data.ptr[data.slen] = ' ';
+	    data.ptr[data.slen+1] = 0;
+
+	    info->err_pos = pj_ansi_strlen(cmdline);
+	}
+    } 
+   
+    val->sess = sess;
+    return status;
+}
+
+PJ_DECL(pj_status_t) pj_cli_sess_exec(pj_cli_sess *sess,
+				      char *cmdline,
+				      pj_pool_t *pool,
+				      pj_cli_exec_info *info)
+{
+    pj_cli_cmd_val val;
+    pj_status_t status;
+    pj_cli_exec_info einfo;
+    pj_str_t cmd;
+
+    PJ_ASSERT_RETURN(sess && cmdline, PJ_EINVAL);
+
+    PJ_UNUSED_ARG(pool);
+
+    cmd.ptr = cmdline;
+    cmd.slen = pj_ansi_strlen(cmdline);
+
+    if (pj_strtrim(&cmd)->slen == 0)
+	return PJ_SUCCESS;
+
+    if (!info)
+        info = &einfo;
+	
+    status = pj_cli_sess_parse(sess, cmdline, &val, pool, info);
+    if (status != PJ_SUCCESS)
+        return status;
+
+    if ((val.argc > 0) && (val.cmd->handler)) {
+        info->cmd_ret = (*val.cmd->handler)(&val);
+        if (info->cmd_ret == PJ_CLI_EINVARG ||
+            info->cmd_ret == PJ_CLI_EEXIT)
+	{
+            return info->cmd_ret;
+	}
+    }
+
+    return PJ_SUCCESS;
+}
+
+static pj_status_t insert_new_hint(pj_pool_t *pool, 
+				   const pj_str_t *name, 
+				   const pj_str_t *desc, 
+				   const pj_str_t *type, 
+				   pj_cli_exec_info *info)
+{        
+    pj_cli_hint_info *hint;
+    PJ_ASSERT_RETURN(pool && info, PJ_EINVAL);
+    PJ_ASSERT_RETURN((info->hint_cnt < PJ_CLI_MAX_HINTS), PJ_EINVAL);
+    hint = &info->hint[info->hint_cnt];
+
+    pj_strdup(pool, &hint->name, name);
+
+    if (desc && (desc->slen > 0))  {
+	pj_strdup(pool, &hint->desc, desc);
+    } else {
+	hint->desc.slen = 0;
+    }
+    
+    if (type && (type->slen > 0)) {
+	pj_strdup(pool, &hint->type, type);
+    } else {
+	hint->type.slen = 0;
+    }
+
+    ++info->hint_cnt;    
+    return PJ_SUCCESS;
+}
+
+static pj_status_t get_match_cmds(pj_cli_cmd_spec *cmd, 
+				  const pj_str_t *cmd_val,				      
+				  pj_pool_t *pool, 
+				  pj_cli_cmd_spec **p_cmd, 			       
+				  pj_cli_exec_info *info)
+{
+    pj_status_t status = PJ_SUCCESS;
+    PJ_ASSERT_RETURN(cmd && pool && info && cmd_val, PJ_EINVAL);    
+
+    if (p_cmd)
+	*p_cmd = cmd;
+
+    /* Get matching command */
+    if (cmd->sub_cmd) {
+	pj_cli_cmd_spec *child_cmd = cmd->sub_cmd->next;
+	while (child_cmd != cmd->sub_cmd) {
+	    unsigned i;	   
+	    pj_bool_t found = PJ_FALSE;
+	    if (!pj_strncmp(&child_cmd->name, cmd_val, cmd_val->slen)) {		
+		status = insert_new_hint(pool, &child_cmd->name, 
+					 &child_cmd->desc, NULL, info);
+		if (status != PJ_SUCCESS)
+		    return status;
+
+		found = PJ_TRUE;
+	    }
+	    for (i=0; i < child_cmd->sc_cnt; ++i) {
+		static const pj_str_t SHORTCUT = {"SC", 2};
+		pj_str_t *sc = &child_cmd->sc[i];
+		PJ_ASSERT_RETURN(sc, PJ_EINVAL);
+
+		if (!pj_strncmp(sc, cmd_val, cmd_val->slen)) {		
+		    status = insert_new_hint(pool, sc, &child_cmd->desc, 
+					     &SHORTCUT, info);
+		    if (status != PJ_SUCCESS)
+			return status;
+
+		    found = PJ_TRUE;
+		}		
+	    }
+	    if (found && p_cmd) 
+		*p_cmd = child_cmd;			    
+
+	    child_cmd = child_cmd->next;
+	}
+    }
+    return status;
+}
+
+static pj_status_t get_match_args(pj_cli_sess *sess,
+				  pj_cli_cmd_spec *cmd, 
+				  const pj_str_t *cmd_val,
+				  unsigned argc,
+				  pj_pool_t *pool, 
+				  pj_cli_parse_mode parse_mode,
+				  pj_cli_exec_info *info)
+{
+    pj_cli_arg_spec *arg;
+    pj_status_t status = PJ_SUCCESS;
+
+    PJ_ASSERT_RETURN(cmd && pool && cmd_val && info, PJ_EINVAL);
+
+    if ((argc > cmd->arg_cnt) && (!cmd->sub_cmd)) {
+	if (cmd_val->slen > 0)
+	    return PJ_CLI_ETOOMANYARGS;
+	else
+	    return PJ_SUCCESS;
+    }
+
+    if (cmd->arg_cnt > 0) {
+	arg = &cmd->arg[argc-1];
+	PJ_ASSERT_RETURN(arg, PJ_EINVAL);
+	if (arg->type == PJ_CLI_ARG_CHOICE) {	    
+	    unsigned j;	    	    
+
+	    if ((parse_mode == PARSE_EXEC) && (!arg->validate)) {
+		/* If no validation needed, then insert the values */
+		status = insert_new_hint(pool, 
+					 cmd_val, 
+			  	         NULL, 
+					 NULL,
+					 info);		
+		return status;
+	    }
+
+	    for (j=0; j < arg->stat_choice_cnt; ++j) {
+		pj_cli_arg_choice_val *choice_val = &arg->stat_choice_val[j];		
+    	    
+		PJ_ASSERT_RETURN(choice_val, PJ_EINVAL);		
+    	    
+		if (!pj_strncmp(&choice_val->value, cmd_val, cmd_val->slen)) {		    
+		    status = insert_new_hint(pool, 
+					     &choice_val->value, 
+					     &choice_val->desc, 
+					     &arg_type[PJ_CLI_ARG_CHOICE].msg, 
+					     info);
+		    if (status != PJ_SUCCESS)
+			return status;		    
+		}
+	    }
+	    if (arg->get_dyn_choice) {
+		pj_cli_dyn_choice_param dyn_choice_param;
+		static pj_str_t choice_str = {"choice", 6};
+
+		/* Get the dynamic choice values */	    
+		dyn_choice_param.sess = sess;
+		dyn_choice_param.cmd = cmd;
+		dyn_choice_param.arg_id = arg->id;
+		dyn_choice_param.max_cnt = PJ_CLI_MAX_CHOICE_VAL;
+		dyn_choice_param.pool = pool;
+		dyn_choice_param.cnt = 0;	    
+
+		(*arg->get_dyn_choice)(&dyn_choice_param);
+		for (j=0; j < dyn_choice_param.cnt; ++j) {
+		    pj_cli_arg_choice_val *choice = &dyn_choice_param.choice[j];
+		    if (!pj_strncmp(&choice->value, cmd_val, cmd_val->slen)) {
+			pj_strassign(&info->hint[info->hint_cnt].name, 
+				     &choice->value);
+			pj_strassign(&info->hint[info->hint_cnt].type, 
+				     &choice_str);
+			pj_strassign(&info->hint[info->hint_cnt].desc, 
+				     &choice->desc);
+			if ((++info->hint_cnt) >= PJ_CLI_MAX_HINTS)
+			    break;
+		    }
+		}
+		if ((info->hint_cnt == 0) && (!arg->optional))
+		    return PJ_CLI_EMISSINGARG;
+	    }
+	} else {
+	    if (cmd_val->slen == 0) {
+		if (info->hint_cnt == 0) {
+		    if (!((parse_mode == PARSE_EXEC) && (arg->optional))) {
+			/* For exec mode,no need to insert hint if optional */
+			status = insert_new_hint(pool, 
+						 &arg->name, 
+				  	         &arg->desc, 
+						 &arg_type[arg->type].msg,
+						 info);
+			if (status != PJ_SUCCESS)
+			    return status;
+		    }
+		    if (!arg->optional)
+			return PJ_CLI_EMISSINGARG;
+		} 
+	    } else {
+		return insert_new_hint(pool, cmd_val, NULL, NULL, info);
+	    }
+	}
+    } 
+    return status;
+}
+
+static pj_status_t get_available_cmds(pj_cli_sess *sess,
+				      pj_cli_cmd_spec *cmd, 
+				      pj_str_t *cmd_val,
+				      unsigned argc,
+				      pj_pool_t *pool,
+				      pj_bool_t get_cmd,
+				      pj_cli_parse_mode parse_mode,
+				      pj_cli_cmd_spec **p_cmd,
+				      pj_cli_exec_info *info)
+{
+    pj_status_t status = PJ_SUCCESS;
+    pj_str_t *prefix;
+    pj_str_t EMPTY_STR = {NULL, 0};
+
+    prefix = cmd_val?(pj_strtrim(cmd_val)):(&EMPTY_STR);
+
+    info->hint_cnt = 0;    
+
+    if (get_cmd)
+	status = get_match_cmds(cmd, prefix, pool, p_cmd, info);
+    if (argc > 0)
+	status = get_match_args(sess, cmd, prefix, argc, 
+			        pool, parse_mode, info);
+
+    if (status == PJ_SUCCESS) {	
+	if (prefix->slen > 0) {
+	    if (info->hint_cnt == 0) {
+		status = PJ_CLI_EINVARG;
+	    } else if (info->hint_cnt > 1) {
+		status = PJ_CLI_EAMBIGUOUS;
+	    }
+	} else {
+	    if (info->hint_cnt > 0)
+		status = PJ_CLI_EAMBIGUOUS;
+	}
+    } 
+
+    return status;
+}
+
Index: /pjproject/trunk/pjlib-util/src/pjlib-util/cli_console.c
===================================================================
--- /pjproject/trunk/pjlib-util/src/pjlib-util/cli_console.c	(revision 4440)
+++ /pjproject/trunk/pjlib-util/src/pjlib-util/cli_console.c	(revision 4440)
@@ -0,0 +1,540 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2010 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <pjlib-util/cli_imp.h>
+#include <pjlib-util/cli_console.h>
+#include <pj/assert.h>
+#include <pj/errno.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pjlib-util/errno.h>
+
+/**
+ * This specify the state of output character parsing.
+ */
+typedef enum out_parse_state
+{
+    OP_NORMAL,
+    OP_TYPE,
+    OP_SHORTCUT,
+    OP_CHOICE
+} out_parse_state;
+
+struct cli_console_fe
+{
+    pj_cli_front_end    base;
+    pj_pool_t          *pool;
+    pj_cli_sess        *sess;
+    pj_thread_t        *input_thread;
+    pj_bool_t           thread_quit;
+    pj_sem_t           *thread_sem;   
+    pj_cli_console_cfg  cfg;    
+
+    struct async_input_t
+    {        
+        char       *buf;
+        unsigned    maxlen;	
+        pj_sem_t   *sem;
+    } input;
+};
+
+static void console_write_log(pj_cli_front_end *fe, int level,
+		              const char *data, int len)
+{
+    struct cli_console_fe * cfe = (struct cli_console_fe *)fe;
+
+    if (cfe->sess->log_level > level)
+        printf("%.*s", len, data);
+}
+
+static void console_quit(pj_cli_front_end *fe, pj_cli_sess *req)
+{
+    struct cli_console_fe * cfe = (struct cli_console_fe *)fe;
+
+    PJ_UNUSED_ARG(req);
+
+    pj_assert(cfe);
+    if (cfe->input_thread) {
+        cfe->thread_quit = PJ_TRUE;
+        pj_sem_post(cfe->input.sem);
+        pj_sem_post(cfe->thread_sem);
+    }
+}
+
+static void console_destroy(pj_cli_front_end *fe)
+{
+    struct cli_console_fe * cfe = (struct cli_console_fe *)fe;
+
+    pj_assert(cfe);
+    console_quit(fe, NULL);
+
+    if (cfe->input_thread)
+        pj_thread_join(cfe->input_thread);
+
+    if (cfe->input_thread) {
+        pj_thread_destroy(cfe->input_thread);
+	cfe->input_thread = NULL;
+    }
+
+    pj_sem_destroy(cfe->thread_sem);
+    pj_sem_destroy(cfe->input.sem);
+    pj_pool_release(cfe->pool);
+}
+
+PJ_DEF(void) pj_cli_console_cfg_default(pj_cli_console_cfg *param)
+{
+    pj_assert(param);
+
+    param->log_level = PJ_CLI_CONSOLE_LOG_LEVEL;
+    param->prompt_str.slen = 0;
+    param->quit_command.slen = 0;
+}
+
+PJ_DEF(pj_status_t) pj_cli_console_create(pj_cli_t *cli,
+					  const pj_cli_console_cfg *param,
+					  pj_cli_sess **p_sess,
+					  pj_cli_front_end **p_fe)
+{
+    pj_cli_sess *sess;
+    struct cli_console_fe *fe;
+    pj_cli_console_cfg cfg;
+    pj_pool_t *pool;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(cli && p_sess, PJ_EINVAL);
+
+    pool = pj_pool_create(pj_cli_get_param(cli)->pf, "console_fe",
+                          PJ_CLI_CONSOLE_POOL_SIZE, PJ_CLI_CONSOLE_POOL_INC,
+                          NULL);
+    if (!pool)
+        return PJ_ENOMEM;
+	
+    sess = PJ_POOL_ZALLOC_T(pool, pj_cli_sess);
+    fe = PJ_POOL_ZALLOC_T(pool, struct cli_console_fe);
+
+    if (!param) {
+        pj_cli_console_cfg_default(&cfg);
+        param = &cfg;
+    }
+    sess->fe = &fe->base;
+    sess->log_level = param->log_level;    
+    sess->op = PJ_POOL_ZALLOC_T(pool, struct pj_cli_sess_op);
+    fe->base.op = PJ_POOL_ZALLOC_T(pool, struct pj_cli_front_end_op);
+    fe->base.cli = cli;
+    fe->base.type = PJ_CLI_CONSOLE_FRONT_END;
+    fe->base.op->on_write_log = &console_write_log;
+    fe->base.op->on_quit = &console_quit;
+    fe->base.op->on_destroy = &console_destroy;
+    fe->pool = pool;
+    fe->sess = sess;
+    status = pj_sem_create(pool, "console_fe", 0, 1, &fe->thread_sem);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    status = pj_sem_create(pool, "console_fe", 0, 1, &fe->input.sem);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    pj_cli_register_front_end(cli, &fe->base);
+    if (param->prompt_str.slen == 0) {	
+	pj_str_t prompt_sign = pj_str(">>> ");
+	fe->cfg.prompt_str.ptr = pj_pool_alloc(fe->pool, prompt_sign.slen+1);
+	pj_strcpy(&fe->cfg.prompt_str, &prompt_sign);	
+    } else {
+	fe->cfg.prompt_str.ptr = pj_pool_alloc(fe->pool, 
+					       param->prompt_str.slen+1);
+	pj_strcpy(&fe->cfg.prompt_str, &param->prompt_str);
+    }    
+    fe->cfg.prompt_str.ptr[fe->cfg.prompt_str.slen] = 0; 
+
+    if (param->quit_command.slen)
+	pj_strdup(fe->pool, &fe->cfg.quit_command, &param->quit_command);
+
+    *p_sess = sess;
+    if (p_fe)
+        *p_fe = &fe->base;
+
+    return PJ_SUCCESS;
+}
+
+static void send_prompt_str(pj_cli_sess *sess)
+{
+    pj_str_t send_data;
+    char data_str[128];
+    struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe;
+
+    send_data.ptr = &data_str[0];
+    send_data.slen = 0;
+    
+    pj_strcat(&send_data, &fe->cfg.prompt_str);
+    send_data.ptr[send_data.slen] = 0;
+
+    printf("%s", send_data.ptr);
+}
+
+static void send_err_arg(pj_cli_sess *sess, 
+			 const pj_cli_exec_info *info, 
+			 const pj_str_t *msg,
+			 pj_bool_t with_return)
+{
+    pj_str_t send_data;
+    char data_str[256];
+    unsigned len;
+    unsigned i;
+    struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe;
+
+    send_data.ptr = &data_str[0];
+    send_data.slen = 0;
+
+    if (with_return)
+	pj_strcat2(&send_data, "\r\n");
+
+    len = fe->cfg.prompt_str.slen + info->err_pos;
+
+    for (i=0;i<len;++i) {
+	pj_strcat2(&send_data, " ");
+    }
+    pj_strcat2(&send_data, "^");
+    pj_strcat2(&send_data, "\r\n");
+    pj_strcat(&send_data, msg);
+    pj_strcat(&send_data, &fe->cfg.prompt_str);
+
+    send_data.ptr[send_data.slen] = 0;
+    printf("%s", send_data.ptr);    
+}
+
+static void send_inv_arg(pj_cli_sess *sess, 
+			 const pj_cli_exec_info *info,
+			 pj_bool_t with_return)
+{
+    static const pj_str_t ERR_MSG = {"%Error : Invalid Arguments\r\n", 28};
+    send_err_arg(sess, info, &ERR_MSG, with_return);
+}
+
+static void send_too_many_arg(pj_cli_sess *sess, 
+			      const pj_cli_exec_info *info,
+			      pj_bool_t with_return)
+{
+    static const pj_str_t ERR_MSG = {"%Error : Too Many Arguments\r\n", 29};
+    send_err_arg(sess, info, &ERR_MSG, with_return);
+}
+
+static void send_hint_arg(pj_str_t *send_data, 
+			  const pj_str_t *desc,
+			  pj_ssize_t cmd_len,
+			  pj_ssize_t max_len)
+{
+    if ((desc) && (desc->slen > 0)) {
+	int j;
+
+	for (j=0;j<(max_len-cmd_len);++j) {
+	    pj_strcat2(send_data, " ");
+	}
+	pj_strcat2(send_data, "  ");
+	pj_strcat(send_data, desc);
+	send_data->ptr[send_data->slen] = 0;
+	printf("%s", send_data->ptr);
+	send_data->slen = 0;
+    }
+}
+
+static void send_ambi_arg(pj_cli_sess *sess, 
+			  const pj_cli_exec_info *info,
+			  pj_bool_t with_return)
+{
+    unsigned i;
+    unsigned len;
+    pj_str_t send_data;
+    char data[1028];
+    struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe;
+    const pj_cli_hint_info *hint = info->hint;
+    out_parse_state parse_state = OP_NORMAL;
+    pj_ssize_t max_length = 0;
+    pj_ssize_t cmd_length = 0;
+    const pj_str_t *cmd_desc = 0;
+    static const pj_str_t sc_type = {"sc", 2};
+    static const pj_str_t choice_type = {"choice", 6};
+    send_data.ptr = &data[0];
+    send_data.slen = 0;
+    
+    if (with_return)
+	pj_strcat2(&send_data, "\r\n");
+
+    len = fe->cfg.prompt_str.slen + info->err_pos;
+
+    for (i=0;i<len;++i) {
+	pj_strcat2(&send_data, " ");
+    }
+    pj_strcat2(&send_data, "^");        
+    /* Get the max length of the command name */
+    for (i=0;i<info->hint_cnt;++i) {
+	if ((&hint[i].type) && (hint[i].type.slen > 0)) {	    
+	    if (pj_stricmp(&hint[i].type, &sc_type) == 0) {
+		//Additional delimiter " | "
+		cmd_length += (hint[i].name.slen + 3);
+	    } else {
+		cmd_length = hint[i].name.slen;
+	    }
+	} else {	    	    
+	    cmd_length = hint[i].name.slen;
+	}
+
+	if (cmd_length > max_length) {
+	    max_length = cmd_length;
+	}
+    }
+
+    cmd_length = 0;
+    for (i=0;i<info->hint_cnt;++i) {	
+	if ((&hint[i].type) && (hint[i].type.slen > 0)) {	    
+	    if (pj_stricmp(&hint[i].type, &sc_type) == 0) {
+		parse_state = OP_SHORTCUT;
+	    } else if (pj_stricmp(&hint[i].type, &choice_type) == 0) {
+		parse_state = OP_CHOICE;
+	    } else {
+		parse_state = OP_TYPE;
+	    }
+	} else {	    	    
+	    parse_state = OP_NORMAL;
+	}
+
+	if ((parse_state != OP_SHORTCUT)) {
+	    if (cmd_desc) {
+		/* Print data if previous hint is shortcut */
+		send_hint_arg(&send_data, cmd_desc, cmd_length, max_length);
+		cmd_desc = 0;
+	    }
+	    pj_strcat2(&send_data, "\r\n  ");
+	    cmd_length = hint[i].name.slen;
+	}
+    
+	switch (parse_state) {
+	case OP_CHOICE:
+	    pj_strcat2(&send_data, "[");
+	    pj_strcat(&send_data, &hint[i].name);
+	    pj_strcat2(&send_data, "]");	
+	    break;
+	case OP_TYPE:
+	    pj_strcat2(&send_data, "<");
+	    pj_strcat(&send_data, &hint[i].type);
+	    pj_strcat2(&send_data, ">");	
+	    break;
+	case OP_SHORTCUT:	    
+	    pj_strcat2(&send_data, " | ");
+	    pj_strcat(&send_data, &hint[i].name);
+	    cmd_length += (hint[i].name.slen + 3);
+	    break;
+	default:
+	    pj_strcat(&send_data, &hint[i].name);
+	    cmd_desc = &hint[i].desc;
+	    break;
+	}
+    	
+	if ((parse_state == OP_TYPE) || (parse_state == OP_CHOICE) || 
+	    ((i+1) >= info->hint_cnt)) 
+	{
+	    /* Add description info */
+	    send_hint_arg(&send_data, &hint[i].desc, cmd_length, max_length);
+	}
+    }  
+    pj_strcat2(&send_data, "\r\n");
+    pj_strcat(&send_data, &fe->cfg.prompt_str);
+    send_data.ptr[send_data.slen] = 0;
+    printf("%s", send_data.ptr);    
+}
+
+static pj_bool_t handle_hint(pj_cli_sess *sess)
+{
+    pj_status_t status;
+    pj_bool_t retval = PJ_TRUE;
+    
+    pj_pool_t *pool;
+    pj_cli_cmd_val *cmd_val;
+    pj_cli_exec_info info;
+    struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe;
+    char *recv_buf = fe->input.buf;
+    pj_cli_t *cli = sess->fe->cli;
+
+    pool = pj_pool_create(pj_cli_get_param(cli)->pf, "handle_hint",
+                          PJ_CLI_CONSOLE_POOL_SIZE, PJ_CLI_CONSOLE_POOL_INC,
+                          NULL);
+
+    cmd_val = PJ_POOL_ZALLOC_T(pool, pj_cli_cmd_val);
+    
+    status = pj_cli_sess_parse(sess, recv_buf, cmd_val, 
+			       pool, &info);
+
+    switch (status) {
+    case PJ_CLI_EINVARG:
+	send_inv_arg(sess, &info, PJ_TRUE);		
+	break;
+    case PJ_CLI_ETOOMANYARGS:
+	send_too_many_arg(sess, &info, PJ_TRUE);
+	break;
+    case PJ_CLI_EMISSINGARG:
+    case PJ_CLI_EAMBIGUOUS:
+	send_ambi_arg(sess, &info, PJ_TRUE);
+	break;
+    case PJ_SUCCESS:	
+	if (info.hint_cnt > 0) {	
+	    /* Compelete command */	    
+	    send_ambi_arg(sess, &info, PJ_TRUE);	    		
+	} else {
+	    retval = PJ_FALSE;
+	}	
+	break;
+    }
+
+    pj_pool_release(pool);	
+    return retval; 
+}
+
+static pj_bool_t handle_exec(pj_cli_sess *sess)
+{
+    pj_status_t status;
+    pj_bool_t retval = PJ_TRUE;
+    
+    pj_pool_t *pool;    
+    pj_cli_exec_info info;
+    pj_cli_t *cli = sess->fe->cli;
+    struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe;
+    char *recv_buf = fe->input.buf;
+        
+    printf("\r\n");
+
+    pool = pj_pool_create(pj_cli_get_param(cli)->pf, "handle_exec",
+			  PJ_CLI_CONSOLE_POOL_SIZE, PJ_CLI_CONSOLE_POOL_INC,
+			  NULL);    
+    
+    status = pj_cli_sess_exec(sess, recv_buf, 
+			      pool, &info);
+
+    switch (status) {
+    case PJ_CLI_EINVARG:
+	send_inv_arg(sess, &info, PJ_FALSE);	
+	break;
+    case PJ_CLI_ETOOMANYARGS:
+	send_too_many_arg(sess, &info, PJ_FALSE);
+	break;
+    case PJ_CLI_EAMBIGUOUS:
+    case PJ_CLI_EMISSINGARG:
+	send_ambi_arg(sess, &info, PJ_FALSE);
+	break;
+    case PJ_CLI_EEXIT:
+	retval = PJ_FALSE;
+	break;
+    case PJ_SUCCESS:
+	send_prompt_str(sess);	
+	break;
+    }    
+
+    pj_pool_release(pool);	
+    return retval; 
+}
+
+static int readline_thread(void * p)
+{    
+    struct cli_console_fe * fe = (struct cli_console_fe *)p;
+
+    printf("%s", fe->cfg.prompt_str.ptr);
+
+    while (!fe->thread_quit) {
+	unsigned input_len = 0;
+	pj_str_t input_str;
+	char *recv_buf = fe->input.buf;
+	pj_bool_t is_valid = PJ_TRUE;
+
+	if (fgets(recv_buf, fe->input.maxlen, stdin) == NULL) {
+	    /* 
+	     * Be friendly to users who redirect commands into
+	     * program, when file ends, resume with kbd.
+	     * If exit is desired end script with q for quit
+	     */
+ 	    /* Reopen stdin/stdout/stderr to /dev/console */
+#if defined(PJ_WIN32) && PJ_WIN32!=0
+	    if (freopen ("CONIN$", "r", stdin) == NULL) {
+#else
+	    if (1) {
+#endif
+		puts("Cannot switch back to console from file redirection");
+		if (fe->cfg.quit_command.slen) {
+		    pj_memcpy(recv_buf, fe->cfg.quit_command.ptr, 
+			      fe->input.maxlen);
+		}
+		recv_buf[fe->cfg.quit_command.slen] = '\0';		
+	    } else {
+		puts("Switched back to console from file redirection");
+		continue;
+	    }
+	}
+
+	input_str.ptr = recv_buf;
+	input_str.slen = pj_ansi_strlen(recv_buf);
+	pj_strrtrim(&input_str);
+	recv_buf[input_str.slen] = '\n';
+	recv_buf[input_str.slen+1] = 0;
+	if (fe->thread_quit) {
+	    break;
+	}
+	input_len = pj_ansi_strlen(fe->input.buf);	
+	if ((input_len > 1) && (fe->input.buf[input_len-2] == '?')) {
+	    fe->input.buf[input_len-1] = 0;
+	    is_valid = handle_hint(fe->sess);
+	    if (!is_valid)
+		printf("%s", fe->cfg.prompt_str.ptr);
+	} else {
+	    is_valid = handle_exec(fe->sess);
+	}
+
+        pj_sem_post(fe->input.sem);        
+        pj_sem_wait(fe->thread_sem);
+    }    
+
+    return 0;
+}
+
+PJ_DEF(pj_status_t) pj_cli_console_process(pj_cli_sess *sess, 
+					   char *buf,
+					   unsigned maxlen)
+{
+    struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe;
+
+    PJ_ASSERT_RETURN(sess, PJ_EINVAL);
+
+    fe->input.buf = buf;
+    fe->input.maxlen = maxlen;
+
+    if (!fe->input_thread) {
+        pj_status_t status;
+
+        status = pj_thread_create(fe->pool, NULL, &readline_thread, fe,
+                                  0, 0, &fe->input_thread);
+        if (status != PJ_SUCCESS)
+            return status;
+    } else {
+        /* Wake up readline thread */
+        pj_sem_post(fe->thread_sem);
+    }
+
+    pj_sem_wait(fe->input.sem);
+
+    return (pj_cli_is_quitting(fe->base.cli)? PJ_CLI_EEXIT : PJ_SUCCESS);
+}
Index: /pjproject/trunk/pjlib-util/src/pjlib-util/cli_telnet.c
===================================================================
--- /pjproject/trunk/pjlib-util/src/pjlib-util/cli_telnet.c	(revision 4440)
+++ /pjproject/trunk/pjlib-util/src/pjlib-util/cli_telnet.c	(revision 4440)
@@ -0,0 +1,1786 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2010 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <pjlib-util/cli_imp.h>
+#include <pjlib-util/cli_telnet.h>
+#include <pj/activesock.h>
+#include <pj/assert.h>
+#include <pj/errno.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/except.h>
+#include <pjlib-util/errno.h>
+#include <pjlib-util/scanner.h>
+
+#define CLI_TELNET_BUF_SIZE 256
+
+#define CUT_MSG "<..data truncated..>\r\n"
+#define MAX_CUT_MSG_LEN 25
+
+#if 1
+    /* Enable some tracing */
+    #define THIS_FILE   "cli_telnet.c"
+    #define TRACE_(arg)	PJ_LOG(3,arg)
+#else
+    #define TRACE_(arg)
+#endif
+
+#define MAX_CLI_TELNET_OPTIONS 256
+
+/**
+ * This specify the state for the telnet option negotiation.
+ */
+enum cli_telnet_option_states 
+{    
+    OPT_DISABLE,		/* Option disable */
+    OPT_ENABLE,			/* Option enable */
+    OPT_EXPECT_DISABLE,		/* Already send disable req, expecting resp */
+    OPT_EXPECT_ENABLE,		/* Already send enable req, expecting resp */
+    OPT_EXPECT_DISABLE_REV,	/* Already send disable req, expecting resp, 
+				 * need to send enable req */
+    OPT_EXPECT_ENABLE_REV	/* Already send enable req, expecting resp,
+				 * need to send disable req */
+};
+
+/**
+ * This structure contains information for telnet session option negotiation.
+ * It contains the local/peer option config and the option negotiation state.
+ */
+typedef struct cli_telnet_sess_option
+{
+    /**
+     * Local setting for the option.
+     * Default: FALSE;
+     */
+    pj_bool_t local_is_enable;
+
+    /**
+     * Remote setting for the option.
+     * Default: FALSE;
+     */
+    pj_bool_t peer_is_enable;
+
+    /**
+     * Local state of the option negotiation.
+     */
+    enum cli_telnet_option_states local_state;
+
+    /**
+     * Remote state of the option negotiation.
+     */
+    enum cli_telnet_option_states peer_state;
+} cli_telnet_sess_option;
+
+/**
+ * This specify the state of input character parsing.
+ */
+typedef enum cmd_parse_state
+{
+    ST_NORMAL,
+    ST_CR,
+    ST_ESC,
+    ST_VT100,
+    ST_IAC,
+    ST_DO,
+    ST_DONT,
+    ST_WILL,
+    ST_WONT
+} cmd_parse_state;
+
+typedef enum cli_telnet_command
+{
+    SUBNEGO_END	    = 240,	/* End of subnegotiation parameters. */
+    NOP		    = 241,	/* No operation. */
+    DATA_MARK	    = 242,	/* Marker for NVT cleaning. */
+    BREAK	    = 243,	/* Indicates that the "break" key was hit. */
+    INT_PROCESS	    = 244,	/* Suspend, interrupt or abort the process. */
+    ABORT_OUTPUT    = 245,	/* Abort output, abort output stream. */
+    ARE_YOU_THERE   = 246,	/* Are you there. */
+    ERASE_CHAR	    = 247,	/* Erase character, erase the current char. */
+    ERASE_LINE	    = 248,	/* Erase line, erase the current line. */
+    GO_AHEAD	    = 249,	/* Go ahead, other end can transmit. */
+    SUBNEGO_BEGIN   = 250,	/* Subnegotiation begin. */
+    WILL	    = 251,	/* Accept the use of option. */
+    WONT	    = 252,	/* Refuse the use of option. */
+    DO		    = 253,	/* Request to use option. */
+    DONT	    = 254,	/* Request to not use option. */
+    IAC		    = 255	/* Interpret as command */
+} cli_telnet_command;
+
+enum cli_telnet_options
+{
+    TRANSMIT_BINARY	= 0,	/* Transmit Binary. */
+    ECHO		= 1,	/* Echo. */
+    RECONNECT		= 2,	/* Reconnection. */
+    SUPPRESS_GA		= 3,	/* Suppress Go Aheah. */
+    MESSAGE_SIZE_NEGO	= 4,	/* Approx Message Size Negotiation. */
+    STATUS		= 5,	/* Status. */
+    TIMING_MARK		= 6,	/* Timing Mark. */
+    RTCE_OPTION		= 7,	/* Remote Controlled Trans and Echo. */
+    OUTPUT_LINE_WIDTH	= 8,	/* Output Line Width. */
+    OUTPUT_PAGE_SIZE	= 9,	/* Output Page Size. */
+    CR_DISPOSITION	= 10,	/* Carriage-Return Disposition. */
+    HORI_TABSTOPS	= 11,	/* Horizontal Tabstops. */
+    HORI_TAB_DISPO	= 12,	/* Horizontal Tab Disposition. */
+    FF_DISP0		= 13,	/* Formfeed Disposition. */
+    VERT_TABSTOPS	= 14,	/* Vertical Tabstops. */
+    VERT_TAB_DISPO	= 15,	/* Vertical Tab Disposition. */
+    LF_DISP0		= 16,	/* Linefeed Disposition. */
+    EXT_ASCII		= 17, 	/* Extended ASCII. */
+    LOGOUT		= 18,	/* Logout. */
+    BYTE_MACRO		= 19,	/* Byte Macro. */
+    DE_TERMINAL		= 20,	/* Data Entry Terminal. */
+    SUPDUP_PROTO	= 21,	/* SUPDUP Protocol. */
+    SUPDUP_OUTPUT	= 22,	/* SUPDUP Output. */
+    SEND_LOC		= 23,	/* Send Location. */
+    TERM_TYPE		= 24,	/* Terminal Type. */
+    EOR			= 25,	/* End of Record. */
+    TACACS_UID		= 26,	/* TACACS User Identification. */
+    OUTPUT_MARKING	= 27,	/* Output Marking. */
+    TTYLOC		= 28,	/* Terminal Location Number. */
+    USE_3270_REGIME	= 29,	/* Telnet 3270 Regime. */
+    USE_X3_PAD		= 30,	/* X.3 PAD. */
+    WINDOW_SIZE		= 31,	/* Window Size. */
+    TERM_SPEED		= 32,	/* Terminal Speed. */
+    REM_FLOW_CONTROL	= 33,	/* Remote Flow Control. */
+    LINE_MODE		= 34,	/* Linemode. */
+    X_DISP_LOC		= 35,	/* X Display Location. */
+    ENVIRONMENT		= 36,	/* Environment. */
+    AUTH		= 37,	/* Authentication. */
+    ENCRYPTION		= 38, 	/* Encryption Option. */
+    NEW_ENVIRONMENT	= 39,	/* New Environment. */
+    TN_3270E		= 40,	/* TN3270E. */
+    XAUTH		= 41,	/* XAUTH. */
+    CHARSET		= 42,	/* CHARSET. */
+    REM_SERIAL_PORT	= 43,	/* Telnet Remote Serial Port. */
+    COM_PORT_CONTROL	= 44,	/* Com Port Control. */
+    SUPP_LOCAL_ECHO	= 45,	/* Telnet Suppress Local Echo. */
+    START_TLS		= 46,	/* Telnet Start TLS. */
+    KERMIT		= 47,	/* KERMIT. */
+    SEND_URL		= 48,	/* SEND-URL. */
+    FWD_X		= 49,	/* FORWARD_X. */
+    EXT_OPTIONS		= 255	/* Extended-Options-List */
+};
+
+enum terminal_cmd 
+{
+    TC_ESC		= 27,
+    TC_UP		= 65,
+    TC_DOWN		= 66,
+    TC_RIGHT		= 67,
+    TC_LEFT		= 68,
+    TC_END		= 70,
+    TC_HOME		= 72,    
+    TC_CTRL_C		= 3,
+    TC_CR		= 13,
+    TC_BS		= 8,
+    TC_TAB		= 9,
+    TC_QM		= 63,
+    TC_BELL		= 7,
+    TC_DEL		= 127
+};
+
+/**
+ * This specify the state of output character parsing.
+ */
+typedef enum out_parse_state
+{
+    OP_NORMAL,
+    OP_TYPE,
+    OP_SHORTCUT,
+    OP_CHOICE
+} out_parse_state;
+
+/**
+ * This structure contains the command line shown to the user. 
+ * The telnet also needs to maintain and manage command cursor position. 
+ * Due to that reason, the insert/delete character process from buffer will
+ * consider its current cursor position.
+ */
+typedef struct telnet_recv_buf {
+    /**
+     * Buffer containing the characters, NULL terminated.
+     */
+    unsigned char	    rbuf[PJ_CLI_MAX_CMDBUF];
+
+    /**
+     * Current length of the command line.
+     */
+    unsigned		    len;
+
+    /**
+     * Current cursor position.
+     */
+    unsigned		    cur_pos;
+} telnet_recv_buf;
+
+/**
+ * This structure contains the command history executed by user. 
+ * Besides storing the command history, it is necessary to be able
+ * to browse it.
+ */
+typedef struct cmd_history
+{
+    PJ_DECL_LIST_MEMBER(struct cmd_history);
+    pj_str_t command;
+} cmd_history;
+
+typedef struct cli_telnet_sess
+{
+    pj_cli_sess		    base;
+    pj_pool_t		    *pool;
+    pj_activesock_t	    *asock;
+    pj_bool_t		    authorized;
+    pj_ioqueue_op_key_t	    op_key;
+    pj_mutex_t		    *smutex;
+    cmd_parse_state	    parse_state;
+    cli_telnet_sess_option  telnet_option[MAX_CLI_TELNET_OPTIONS];
+    cmd_history		    *history;
+    cmd_history		    *active_history;
+
+    telnet_recv_buf	    *rcmd;
+    unsigned char	    buf[CLI_TELNET_BUF_SIZE + MAX_CUT_MSG_LEN];
+    unsigned		    buf_len;
+} cli_telnet_sess;
+
+struct cli_telnet_fe
+{
+    pj_cli_front_end        base;
+    pj_pool_t              *pool;
+    pj_cli_telnet_cfg       cfg;
+    pj_bool_t               own_ioqueue;
+    pj_cli_sess             sess_head;
+
+    pj_activesock_t	   *asock;
+    pj_thread_t            *worker_thread;
+    pj_bool_t               is_quitting;
+    pj_mutex_t             *mutex;
+};
+
+/* Forward Declaration */
+static pj_status_t telnet_sess_send2(cli_telnet_sess *sess,
+                                     const unsigned char *str, int len);
+
+static pj_status_t telnet_sess_send(cli_telnet_sess *sess,
+                                    const pj_str_t *str);
+
+/**
+ * Return the number of characters between the current cursor position 
+ * to the end of line.
+ */
+static unsigned recv_buf_right_len(telnet_recv_buf *recv_buf)
+{
+    return (recv_buf->len - recv_buf->cur_pos);
+}
+
+/**
+ * Insert character to the receive buffer. 
+ */
+static pj_bool_t recv_buf_insert(telnet_recv_buf *recv_buf, 
+				 unsigned char *data) 
+{    
+    if (recv_buf->len+1 >= PJ_CLI_MAX_CMDBUF) {
+	return PJ_FALSE;
+    } else {	
+	if (*data == '\t' || *data == '?' || *data == '\r') {
+	    /* Always insert to the end of line */
+	    recv_buf->rbuf[recv_buf->len] = *data;
+	} else {
+	    /* Insert based on the current cursor pos */
+	    unsigned cur_pos = recv_buf->cur_pos;
+	    unsigned rlen = recv_buf_right_len(recv_buf);	    
+	    if (rlen > 0) {		    
+		/* Shift right characters */
+		pj_memmove(&recv_buf->rbuf[cur_pos+1], 
+			   &recv_buf->rbuf[cur_pos], 
+			   rlen+1);
+	    } 
+	    recv_buf->rbuf[cur_pos] = *data;
+	}
+	++recv_buf->cur_pos;
+	++recv_buf->len;
+	recv_buf->rbuf[recv_buf->len] = 0;
+    }
+    return PJ_TRUE;
+}
+
+/**
+ * Delete character on the previous cursor position of the receive buffer.
+ */
+static pj_bool_t recv_buf_backspace(telnet_recv_buf *recv_buf)
+{
+    if ((recv_buf->cur_pos == 0) || (recv_buf->len == 0)) {
+	return PJ_FALSE;
+    } else {
+	unsigned rlen = recv_buf_right_len(recv_buf);
+	if (rlen) {
+	    unsigned cur_pos = recv_buf->cur_pos;
+	    /* Shift left characters */
+	    pj_memmove(&recv_buf->rbuf[cur_pos-1], &recv_buf->rbuf[cur_pos], 
+		       rlen);
+	}
+	--recv_buf->cur_pos;
+	--recv_buf->len;
+	recv_buf->rbuf[recv_buf->len] = 0;
+    }    
+    return PJ_TRUE;
+}
+
+static int compare_str(void *value, const pj_list_type *nd)
+{
+    cmd_history *node = (cmd_history*)nd;
+    return (pj_strcmp((pj_str_t *)value, &node->command));
+}
+
+/**
+ * Insert the command to history. If the entered command is not on the list, 
+ * a new entry will be created. All entered command will be moved to 
+ * the first entry of the history. 
+ */
+static pj_status_t insert_history(cli_telnet_sess *sess, 				 
+				  char *cmd_val)
+{
+    cmd_history *in_history;
+    pj_str_t cmd;
+    cmd.ptr = cmd_val;
+    cmd.slen = pj_ansi_strlen(cmd_val)-1;
+
+    if (cmd.slen == 0)
+	return PJ_SUCCESS;
+
+    PJ_ASSERT_RETURN(sess, PJ_EINVAL);
+
+    /* Find matching history */
+    in_history = pj_list_search(sess->history, (void*)&cmd, compare_str);
+    if (!in_history) {
+	if (pj_list_size(sess->history) < PJ_CLI_MAX_CMD_HISTORY) {	    
+	    char *data_history;
+	    in_history = PJ_POOL_ZALLOC_T(sess->pool, cmd_history);
+	    pj_list_init(in_history);
+	    data_history = (char *)pj_pool_calloc(sess->pool, 
+			   sizeof(char), PJ_CLI_MAX_CMDBUF);
+	    in_history->command.ptr = data_history;
+	    in_history->command.slen = 0;
+	} else {
+	    /* Get the oldest history */
+	    in_history = sess->history->prev;
+	}	    
+    } else {
+	pj_list_insert_nodes_after(in_history->prev, in_history->next);
+    }
+    pj_strcpy(&in_history->command, pj_strtrim(&cmd));
+    pj_list_push_front(sess->history, in_history);
+    sess->active_history = sess->history;
+
+    return PJ_SUCCESS;
+}
+
+/**
+ * Get the next or previous history of the shown/active history. 
+ */
+static pj_str_t* get_prev_history(cli_telnet_sess *sess, pj_bool_t is_forward)
+{
+    pj_str_t *retval;
+    pj_size_t history_size;
+    cmd_history *node;
+    cmd_history *root;
+
+    PJ_ASSERT_RETURN(sess, NULL);
+
+    node = sess->active_history;
+    root = sess->history;
+    history_size = pj_list_size(sess->history);
+
+    if (history_size == 0) {
+	return NULL;
+    } else {
+	if (is_forward) {	    
+	    node = (node->next==root)?node->next->next:node->next;	    
+	} else {
+	    node = (node->prev==root)?node->prev->prev:node->prev;	    
+	}
+	retval = &node->command;
+	sess->active_history = node;
+    }
+    return retval;
+}
+
+/*
+ * This method is used to send option negotiation command.
+ * The commands dealing with option negotiation are
+ * three byte sequences, the third byte being the code for the option
+ * referenced - (RFC-854).
+ */
+static pj_bool_t send_telnet_cmd(cli_telnet_sess *sess, 
+				 cli_telnet_command cmd, 
+				 unsigned char option)
+{       
+    unsigned char buf[3];
+    PJ_ASSERT_RETURN(sess, PJ_FALSE);
+
+    buf[0] = IAC;
+    buf[1] = cmd;
+    buf[2] = option;
+    telnet_sess_send2(sess, buf, 3);
+
+    return PJ_TRUE;
+}
+
+/**
+ * This method will handle sending telnet's ENABLE option negotiation.
+ * For local option: send WILL.
+ * For remote option: send DO.
+ * This method also handle the state transition of the ENABLE 
+ * negotiation process.
+ */
+static pj_bool_t send_enable_option(cli_telnet_sess *sess, 
+				    pj_bool_t is_local, 
+				    unsigned char option)
+{
+    cli_telnet_sess_option *sess_option;
+    enum cli_telnet_option_states *state;
+    PJ_ASSERT_RETURN(sess, PJ_FALSE);
+
+    sess_option = &sess->telnet_option[option];
+    state = is_local?(&sess_option->local_state):(&sess_option->peer_state);
+    switch (*state) {
+	case OPT_ENABLE:
+	    /* Ignore if already enabled */
+	    break;
+	case OPT_DISABLE:
+	    *state = OPT_EXPECT_ENABLE;
+	    send_telnet_cmd(sess, (is_local?WILL:DO), option);
+	    break;
+	case OPT_EXPECT_ENABLE:
+	    *state = OPT_DISABLE;
+	    break;
+	case OPT_EXPECT_DISABLE:
+	    *state = OPT_EXPECT_DISABLE_REV;
+	    break;
+	case OPT_EXPECT_ENABLE_REV:
+	    *state = OPT_EXPECT_ENABLE;
+	    break;
+	case OPT_EXPECT_DISABLE_REV:	    
+	    *state = OPT_DISABLE;
+	    break;
+	default:
+	    return PJ_FALSE;
+    }
+    return PJ_TRUE;
+}
+
+static pj_bool_t send_cmd_do(cli_telnet_sess *sess, 
+			     unsigned char option)
+{
+    return send_enable_option(sess, PJ_FALSE, option);
+}
+
+static pj_bool_t send_cmd_will(cli_telnet_sess *sess, 
+			       unsigned char option)
+{
+    return send_enable_option(sess, PJ_TRUE, option);
+}
+
+/**
+ * This method will handle receiving telnet's ENABLE option negotiation.
+ * This method also handle the state transition of the ENABLE 
+ * negotiation process. 
+ */
+static pj_bool_t receive_enable_option(cli_telnet_sess *sess, 
+				       pj_bool_t is_local, 
+				       unsigned char option)
+{
+    cli_telnet_sess_option *sess_opt;
+    enum cli_telnet_option_states *state;
+    pj_bool_t opt_ena;
+    PJ_ASSERT_RETURN(sess, PJ_FALSE);
+
+    sess_opt = &sess->telnet_option[option];
+    state = is_local?(&sess_opt->local_state):(&sess_opt->peer_state);
+    opt_ena = is_local?sess_opt->local_is_enable:sess_opt->peer_is_enable;
+    switch (*state) {
+	case OPT_ENABLE:
+	    /* Ignore if already enabled */
+    	    break;
+	case OPT_DISABLE:
+	    if (opt_ena) {
+		*state = OPT_ENABLE;
+		send_telnet_cmd(sess, is_local?WILL:DO, option);
+	    } else {
+		send_telnet_cmd(sess, is_local?WONT:DONT, option);
+	    }
+	    break;
+	case OPT_EXPECT_ENABLE:	    
+	    *state = OPT_ENABLE;
+	    break;
+	case OPT_EXPECT_DISABLE:	    
+	    *state = OPT_DISABLE;
+	    break;
+	case OPT_EXPECT_ENABLE_REV:
+	    *state = OPT_EXPECT_DISABLE;
+	    send_telnet_cmd(sess, is_local?WONT:DONT, option);	    
+	    break;
+	case OPT_EXPECT_DISABLE_REV:
+	    *state = OPT_EXPECT_DISABLE;
+	    break;
+	default:
+	    return PJ_FALSE;
+    }
+    return PJ_TRUE;        
+}
+
+/**
+ * This method will handle receiving telnet's DISABLE option negotiation.
+ * This method also handle the state transition of the DISABLE 
+ * negotiation process. 
+ */
+static pj_bool_t receive_disable_option(cli_telnet_sess *sess, 
+					pj_bool_t is_local, 
+					unsigned char option)
+{
+    cli_telnet_sess_option *sess_opt;
+    enum cli_telnet_option_states *state;
+
+    PJ_ASSERT_RETURN(sess, PJ_FALSE);
+
+    sess_opt = &sess->telnet_option[option];
+    state = is_local?(&sess_opt->local_state):(&sess_opt->peer_state);
+    
+    switch (*state) {
+	case OPT_ENABLE:
+	    /* Disabling option always need to be accepted */
+	    *state = OPT_DISABLE;
+	    send_telnet_cmd(sess, is_local?WONT:DONT, option);
+    	    break;
+	case OPT_DISABLE:
+	    /* Ignore if already enabled */
+	    break;
+	case OPT_EXPECT_ENABLE:	    
+	case OPT_EXPECT_DISABLE:
+	    *state = OPT_DISABLE;
+	    break;
+	case OPT_EXPECT_ENABLE_REV:
+	    *state = OPT_DISABLE;
+	    send_telnet_cmd(sess, is_local?WONT:DONT, option);	    
+	    break;
+	case OPT_EXPECT_DISABLE_REV:
+	    *state = OPT_EXPECT_ENABLE;
+	    send_telnet_cmd(sess, is_local?WILL:DO, option);
+	    break;
+	default:
+	    return PJ_FALSE;
+    }
+    return PJ_TRUE;       
+}
+
+static pj_bool_t receive_do(cli_telnet_sess *sess, unsigned char option)
+{
+    return receive_enable_option(sess, PJ_TRUE, option);
+}
+
+static pj_bool_t receive_dont(cli_telnet_sess *sess, unsigned char option)
+{
+    return receive_disable_option(sess, PJ_TRUE, option);
+}
+
+static pj_bool_t receive_will(cli_telnet_sess *sess, unsigned char option)
+{
+    return receive_enable_option(sess, PJ_FALSE, option);
+}
+
+static pj_bool_t receive_wont(cli_telnet_sess *sess, unsigned char option)
+{
+    return receive_disable_option(sess, PJ_FALSE, option);
+}
+
+static void set_local_option(cli_telnet_sess *sess, 
+			     unsigned char option, 
+			     pj_bool_t enable) 
+{
+    sess->telnet_option[option].local_is_enable = enable;
+}
+
+static void set_peer_option(cli_telnet_sess *sess, 
+			    unsigned char option, 
+			    pj_bool_t enable) 
+{
+    sess->telnet_option[option].peer_is_enable = enable;
+}
+
+static pj_bool_t is_local_option_state_ena(cli_telnet_sess *sess, 
+					   unsigned char option)
+{    
+    return (sess->telnet_option[option].local_state == OPT_ENABLE);
+}
+
+static void send_return_key(cli_telnet_sess *sess) 
+{    
+    telnet_sess_send2(sess, (unsigned char*)"\r\n", 2);
+}
+
+static void send_bell(cli_telnet_sess *sess) {
+    static const unsigned char bell = 0x07;
+    telnet_sess_send2(sess, &bell, 1);
+}
+
+static void send_prompt_str(cli_telnet_sess *sess)
+{
+    pj_str_t send_data;
+    char data_str[128];
+    struct cli_telnet_fe *fe = (struct cli_telnet_fe *)sess->base.fe;
+
+    send_data.ptr = &data_str[0];
+    send_data.slen = 0;
+    
+    pj_strcat(&send_data, &fe->cfg.prompt_str);
+
+    telnet_sess_send(sess, &send_data);
+}
+
+/*
+ * This method is used to send error message to client, including
+ * the error position of the source command.
+ */
+static void send_err_arg(cli_telnet_sess *sess, 
+			 const pj_cli_exec_info *info, 
+			 const pj_str_t *msg,
+			 pj_bool_t with_return,
+			 pj_bool_t with_last_cmd)
+{
+    pj_str_t send_data;
+    char data_str[256];
+    unsigned len;
+    unsigned i;
+    struct cli_telnet_fe *fe = (struct cli_telnet_fe *)sess->base.fe;
+
+    send_data.ptr = &data_str[0];
+    send_data.slen = 0;
+
+    if (with_return)
+	pj_strcat2(&send_data, "\r\n");
+
+    len = fe->cfg.prompt_str.slen + info->err_pos;
+
+    /* Set the error pointer mark */
+    for (i=0;i<len;++i) {
+	pj_strcat2(&send_data, " ");
+    }
+    pj_strcat2(&send_data, "^");
+    pj_strcat2(&send_data, "\r\n");
+    pj_strcat(&send_data, msg);
+    pj_strcat(&send_data, &fe->cfg.prompt_str);
+    if (with_last_cmd)
+	pj_strcat2(&send_data, (char *)&sess->rcmd->rbuf[0]);
+
+    telnet_sess_send(sess, &send_data);
+}
+
+static void send_inv_arg(cli_telnet_sess *sess, 
+			 const pj_cli_exec_info *info,
+			 pj_bool_t with_return,
+			 pj_bool_t with_last_cmd)
+{
+    static const pj_str_t ERR_MSG = {"%Error : Invalid Arguments\r\n", 28};
+    send_err_arg(sess, info, &ERR_MSG, with_return, with_last_cmd);
+}
+
+static void send_too_many_arg(cli_telnet_sess *sess, 
+			      const pj_cli_exec_info *info,
+			      pj_bool_t with_return,
+			      pj_bool_t with_last_cmd)
+{
+    static const pj_str_t ERR_MSG = {"%Error : Too Many Arguments\r\n", 29};
+    send_err_arg(sess, info, &ERR_MSG, with_return, with_last_cmd);
+}
+
+static void send_hint_arg(cli_telnet_sess *sess, 
+			  pj_str_t *send_data, 
+			  const pj_str_t *desc,
+			  pj_ssize_t cmd_len,
+			  pj_ssize_t max_len)
+{
+    if ((desc) && (desc->slen > 0)) {
+	int j;
+
+	for (j=0;j<(max_len-cmd_len);++j) {
+	    pj_strcat2(send_data, " ");
+	}
+	pj_strcat2(send_data, "  ");
+	pj_strcat(send_data, desc);
+	telnet_sess_send(sess, send_data);
+	send_data->slen = 0;
+    }
+}
+
+/*
+ * This method is used to notify to the client that the entered command
+ * is ambiguous. It will show the matching command as the hint information.
+ */
+static void send_ambi_arg(cli_telnet_sess *sess, 
+			  const pj_cli_exec_info *info,
+			  pj_bool_t with_return,
+			  pj_bool_t with_last_cmd)
+{
+    unsigned i;
+    unsigned len;
+    pj_str_t send_data;
+    char data[1028];
+    struct cli_telnet_fe *fe = (struct cli_telnet_fe *)sess->base.fe;
+    const pj_cli_hint_info *hint = info->hint;
+    out_parse_state parse_state = OP_NORMAL;
+    pj_ssize_t max_length = 0;
+    pj_ssize_t cmd_length = 0;
+    const pj_str_t *cmd_desc = 0;
+    static const pj_str_t sc_type = {"sc", 2};
+    static const pj_str_t choice_type = {"choice", 6};
+    send_data.ptr = &data[0];
+    send_data.slen = 0;
+    
+    if (with_return)
+	pj_strcat2(&send_data, "\r\n");
+
+    len = fe->cfg.prompt_str.slen + info->err_pos;
+
+    for (i=0;i<len;++i) {
+	pj_strcat2(&send_data, " ");
+    }
+    pj_strcat2(&send_data, "^");
+    /* Get the max length of the command name */
+    for (i=0;i<info->hint_cnt;++i) {
+	if ((&hint[i].type) && (hint[i].type.slen > 0)) {	    
+	    if (pj_stricmp(&hint[i].type, &sc_type) == 0) {
+		//Additional delimiter " | "
+		cmd_length += (hint[i].name.slen + 3);
+	    } else {
+		cmd_length = hint[i].name.slen;
+	    }
+	} else {	    	    
+	    cmd_length = hint[i].name.slen;
+	}
+
+	if (cmd_length > max_length) {
+	    max_length = cmd_length;
+	}
+    }
+    
+    cmd_length = 0;
+    /* Build hint information */
+    for (i=0;i<info->hint_cnt;++i) {			
+	if ((&hint[i].type) && (hint[i].type.slen > 0)) {	    
+	    if (pj_stricmp(&hint[i].type, &sc_type) == 0) {
+		parse_state = OP_SHORTCUT;
+	    } else if (pj_stricmp(&hint[i].type, &choice_type) == 0) {
+		parse_state = OP_CHOICE;
+	    } else {
+		parse_state = OP_TYPE;
+	    }
+	} else {	    	    
+	    parse_state = OP_NORMAL;
+	}
+	
+	if ((parse_state != OP_SHORTCUT)) {
+	    if (cmd_desc) {
+		/* Print data if previous hint is shortcut */
+		send_hint_arg(sess, &send_data, 
+			      cmd_desc, cmd_length, 
+			      max_length);
+		cmd_desc = 0;
+	    }
+	    pj_strcat2(&send_data, "\r\n  ");
+	    cmd_length = hint[i].name.slen;
+	}
+
+	switch (parse_state) {
+	case OP_CHOICE:
+	    /* Format : "[Choice Value]  description" */	    
+	    pj_strcat2(&send_data, "[");
+	    pj_strcat(&send_data, &hint[i].name);
+	    pj_strcat2(&send_data, "]");	
+	    break;
+	case OP_TYPE:
+	    /* Format : "<Argument Type>  description" */
+	    pj_strcat2(&send_data, "<");
+	    pj_strcat(&send_data, &hint[i].name);
+	    pj_strcat2(&send_data, ">");	
+	    break;
+	case OP_SHORTCUT:
+	    /* Format : "Command | sc |  description" */
+	    pj_strcat2(&send_data, " | ");
+	    pj_strcat(&send_data, &hint[i].name);
+	    cmd_length += (hint[i].name.slen + 3);
+	    break;
+	default:
+	    /* Command */
+	    pj_strcat(&send_data, &hint[i].name);
+	    cmd_desc = &hint[i].desc;	    
+	    break;
+	}
+    	
+	if ((parse_state == OP_TYPE) || (parse_state == OP_CHOICE) || 
+	    ((i+1) >= info->hint_cnt)) 
+	{
+	    /* Add description info */
+	    send_hint_arg(sess, &send_data, 
+			  &hint[i].desc, cmd_length, 
+			  max_length);
+	}	
+    }  
+    pj_strcat2(&send_data, "\r\n");       
+    pj_strcat(&send_data, &fe->cfg.prompt_str);
+    if (with_last_cmd)
+	pj_strcat2(&send_data, (char *)&sess->rcmd->rbuf[0]);
+
+    telnet_sess_send(sess, &send_data);    
+}
+
+/*
+ * This method is to send command completion of the entered command.
+ */
+static void send_comp_arg(cli_telnet_sess *sess, 
+			  pj_cli_exec_info *info)
+{
+    pj_str_t send_data;
+    char data[128];
+
+    pj_strcat2(&info->hint[0].name, " ");
+
+    send_data.ptr = &data[0];
+    send_data.slen = 0;
+
+    pj_strcat(&send_data, &info->hint[0].name);        
+
+    telnet_sess_send(sess, &send_data);
+}
+
+/*
+ * This method is to process the alfa numeric character sent by client.
+ */
+static pj_bool_t handle_alfa_num(cli_telnet_sess *sess, unsigned char *data)
+{        
+    if (is_local_option_state_ena(sess, ECHO)) {
+	if (recv_buf_right_len(sess->rcmd) > 0) {
+	    /* Cursor is not at EOL, insert character */	    
+	    unsigned char echo[5] = {0x1b, 0x5b, 0x31, 0x40, 0x00};
+	    echo[4] = *data;
+	    telnet_sess_send2(sess, &echo[0], 5);
+	} else {
+	    /* Append character */
+	    telnet_sess_send2(sess, data, 1);
+	}
+	return PJ_TRUE;
+    } 
+    return PJ_FALSE;
+}
+
+/*
+ * This method is to process the backspace character sent by client.
+ */
+static pj_bool_t handle_backspace(cli_telnet_sess *sess, unsigned char *data)
+{
+    unsigned rlen = recv_buf_right_len(sess->rcmd);
+    if (recv_buf_backspace(sess->rcmd)) {
+	if (rlen) {
+	    /* 
+	     * Cursor is not at the end of line, move the characters 
+	     * after the cursor to left
+	     */
+	    unsigned char echo[5] = {0x00, 0x1b, 0x5b, 0x31, 0x50};
+	    echo[0] = *data;
+	    telnet_sess_send2(sess, &echo[0], 5);
+	} else {
+	    const static unsigned char echo[3] = {0x08, 0x20, 0x08};	    
+	    telnet_sess_send2(sess, &echo[0], 3);
+	}
+	return PJ_TRUE;
+    }
+    return PJ_FALSE;
+}
+
+/* 
+ * Syntax error handler for parser. 
+ */
+static void on_syntax_error(pj_scanner *scanner)
+{
+    PJ_UNUSED_ARG(scanner);
+    PJ_THROW(PJ_EINVAL);
+}
+
+/*
+ * This method is to process the backspace character sent by client.
+ */
+static pj_status_t get_last_token(pj_str_t *cmd, pj_str_t *str)
+{
+    pj_scanner scanner;
+    PJ_USE_EXCEPTION;
+    pj_scan_init(&scanner, cmd->ptr, cmd->slen, PJ_SCAN_AUTOSKIP_WS, 
+		 &on_syntax_error);
+    PJ_TRY {
+	while (!pj_scan_is_eof(&scanner)) {
+	    pj_scan_get_until_chr(&scanner, " \t\r\n", str);
+	}
+    }
+    PJ_CATCH_ANY {
+	pj_scan_fini(&scanner);	
+	return PJ_GET_EXCEPTION();
+    }
+    PJ_END;
+    return PJ_SUCCESS;
+}
+
+/*
+ * This method is to process the tab character sent by client.
+ */
+static pj_bool_t handle_tab(cli_telnet_sess *sess)
+{
+    pj_status_t status;
+    pj_bool_t retval = PJ_TRUE;
+    unsigned len;
+    
+    pj_pool_t *pool;
+    pj_cli_cmd_val *cmd_val;
+    pj_cli_exec_info info;
+    pool = pj_pool_create(sess->pool->factory, "handle_tab",
+			  PJ_CLI_TELNET_POOL_SIZE, PJ_CLI_TELNET_POOL_INC,
+			  NULL);
+
+    cmd_val = PJ_POOL_ZALLOC_T(pool, pj_cli_cmd_val);
+    
+    status = pj_cli_sess_parse(&sess->base, (char *)&sess->rcmd->rbuf, cmd_val, 
+			       pool, &info);    
+
+    len = pj_ansi_strlen((char *)&sess->rcmd->rbuf[0]);
+
+    switch (status) {
+    case PJ_CLI_EINVARG:
+	send_inv_arg(sess, &info, PJ_TRUE, PJ_TRUE);		
+	break;
+    case PJ_CLI_ETOOMANYARGS:
+	send_too_many_arg(sess, &info, PJ_TRUE, PJ_TRUE);
+	break;
+    case PJ_CLI_EMISSINGARG:
+    case PJ_CLI_EAMBIGUOUS:
+	send_ambi_arg(sess, &info, PJ_TRUE, PJ_TRUE);
+	break;
+    case PJ_SUCCESS:
+	if (len > sess->rcmd->cur_pos)
+	{
+	    /* Send the cursor to EOL */
+	    unsigned rlen = len - sess->rcmd->cur_pos+1;
+	    unsigned char *data_sent = &sess->rcmd->rbuf[sess->rcmd->cur_pos-1];
+	    telnet_sess_send2(sess, data_sent, rlen);
+	}
+	if (info.hint_cnt > 0) {	
+	    /* Complete command */
+	    pj_str_t cmd = pj_str((char *)&sess->rcmd->rbuf[0]);
+	    pj_str_t last_token;
+
+	    if (get_last_token(&cmd, &last_token) == PJ_SUCCESS) {
+		/* Hint contains the match to the last command entered */
+		pj_str_t *hint_info = &info.hint[0].name;
+		pj_strtrim(&last_token);
+		if (hint_info->slen >= last_token.slen) {
+		    hint_info->slen -= last_token.slen;
+		    pj_memmove(&hint_info->ptr[0], 
+			       &hint_info->ptr[last_token.slen], 
+			       hint_info->slen);		    
+		} 
+		send_comp_arg(sess, &info);
+
+		pj_memcpy(&sess->rcmd->rbuf[len], &info.hint[0].name.ptr[0], 
+			  info.hint[0].name.slen);
+
+		len += info.hint[0].name.slen;
+		sess->rcmd->rbuf[len] = 0;		    
+	    }
+	} else {
+	    retval = PJ_FALSE;
+	}
+	break;
+    }
+    sess->rcmd->len = len;
+    sess->rcmd->cur_pos = sess->rcmd->len;
+
+    pj_pool_release(pool);	
+    return retval; 
+}
+
+/*
+ * This method is to process the return character sent by client.
+ */
+static pj_bool_t handle_return(cli_telnet_sess *sess)
+{
+    pj_status_t status;
+    pj_bool_t retval = PJ_TRUE;
+    
+    pj_pool_t *pool;    
+    pj_cli_exec_info info;    
+    
+    send_return_key(sess);
+    insert_history(sess, (char *)&sess->rcmd->rbuf);
+
+    pool = pj_pool_create(sess->pool->factory, "handle_return",
+			  PJ_CLI_TELNET_POOL_SIZE, PJ_CLI_TELNET_POOL_INC,
+			  NULL);    
+    
+    status = pj_cli_sess_exec(&sess->base, (char *)&sess->rcmd->rbuf, 
+			      pool, &info);    
+
+    switch (status) {
+    case PJ_CLI_EINVARG:
+	send_inv_arg(sess, &info, PJ_FALSE, PJ_FALSE);	
+	break;
+    case PJ_CLI_ETOOMANYARGS:
+	send_too_many_arg(sess, &info, PJ_FALSE, PJ_FALSE);
+	break;
+    case PJ_CLI_EAMBIGUOUS:
+    case PJ_CLI_EMISSINGARG:
+	send_ambi_arg(sess, &info, PJ_FALSE, PJ_FALSE);
+	break;
+    case PJ_CLI_EEXIT:
+	retval = PJ_FALSE;
+	break;
+    case PJ_SUCCESS:
+	send_prompt_str(sess);
+	break;
+    }    
+    if (retval) {
+	sess->rcmd->rbuf[0] = 0;
+	sess->rcmd->len = 0;
+	sess->rcmd->cur_pos = sess->rcmd->len;
+    }
+
+    pj_pool_release(pool);	
+    return retval; 
+}
+
+/*
+ * This method is to process the right key character sent by client.
+ */
+static pj_bool_t handle_right_key(cli_telnet_sess *sess)
+{
+    if (recv_buf_right_len(sess->rcmd)) {
+	unsigned char *data = &sess->rcmd->rbuf[sess->rcmd->cur_pos++];
+	telnet_sess_send2(sess, data, 1);		
+	return PJ_TRUE;
+    }
+    return PJ_FALSE;
+}
+
+/*
+ * This method is to process the left key character sent by client.
+ */
+static pj_bool_t handle_left_key(cli_telnet_sess *sess)
+{    
+    static const unsigned char move_cursor_left = 0x08;
+    if (sess->rcmd->cur_pos) {
+	telnet_sess_send2(sess, &move_cursor_left, 1);
+	--sess->rcmd->cur_pos;
+	return PJ_TRUE;
+    }
+    return PJ_FALSE;
+}
+
+/*
+ * This method is to process the up/down key character sent by client.
+ */
+static pj_bool_t handle_up_down(cli_telnet_sess *sess, pj_bool_t is_up)
+{
+    pj_str_t *history;
+
+    PJ_ASSERT_RETURN(sess, PJ_FALSE);
+
+    history = get_prev_history(sess, is_up);
+    if (history) {
+	pj_str_t send_data;
+	char str[PJ_CLI_MAX_CMDBUF];
+	enum {
+	    MOVE_CURSOR_LEFT = 0x08,
+	    CLEAR_CHAR = 0x20
+	};
+	send_data.ptr = &str[0];
+	send_data.slen = 0;
+
+	/* Move cursor position to the beginning of line */
+	if (sess->rcmd->cur_pos > 0) {
+	    pj_memset(send_data.ptr, MOVE_CURSOR_LEFT, sess->rcmd->cur_pos);
+	    send_data.slen = sess->rcmd->cur_pos;
+	}
+	
+	if (sess->rcmd->len > (unsigned)history->slen) {
+	    /* Clear the command currently shown*/
+	    unsigned buf_len = sess->rcmd->len;
+	    pj_memset(&send_data.ptr[send_data.slen], CLEAR_CHAR, buf_len);
+	    send_data.slen += buf_len;
+
+	    /* Move cursor position to the beginning of line */
+	    pj_memset(&send_data.ptr[send_data.slen], MOVE_CURSOR_LEFT, 
+		      buf_len);
+	    send_data.slen += buf_len;
+	} 
+	/* Send data */
+	pj_strcat(&send_data, history);
+	telnet_sess_send(sess, &send_data);
+	pj_ansi_strncpy((char*)&sess->rcmd->rbuf, history->ptr, history->slen);
+	sess->rcmd->rbuf[history->slen] = 0;
+	sess->rcmd->len = history->slen;
+	sess->rcmd->cur_pos = sess->rcmd->len;
+	return PJ_TRUE;
+    }
+    return PJ_FALSE;
+}
+
+static pj_status_t process_vt100_cmd(cli_telnet_sess *sess, 
+				     unsigned char *cmd)
+{    
+    pj_status_t status = PJ_TRUE;
+    switch (*cmd) {
+	case TC_ESC:
+	    break;
+	case TC_UP:
+	    status = handle_up_down(sess, PJ_TRUE);
+	    break;
+	case TC_DOWN:
+	    status = handle_up_down(sess, PJ_FALSE);
+	    break;
+	case TC_RIGHT:
+	    status = handle_right_key(sess);
+	    break;
+	case TC_LEFT:
+	    status = handle_left_key(sess);
+	    break;
+	case TC_END:
+	    break;
+	case TC_HOME:
+	    break;
+	case TC_CTRL_C:
+	    break;
+	case TC_CR:
+	    break;
+	case TC_BS:
+	    break;
+	case TC_TAB:
+	    break;
+	case TC_QM:
+	    break;
+	case TC_BELL:
+	    break;
+	case TC_DEL:
+	    break;
+    };
+    return status;
+}
+
+PJ_DEF(void) pj_cli_telnet_cfg_default(pj_cli_telnet_cfg *param)
+{
+    pj_assert(param);
+
+    pj_bzero(param, sizeof(*param));
+    param->port = PJ_CLI_TELNET_PORT;
+    param->log_level = PJ_CLI_TELNET_LOG_LEVEL;
+}
+
+/* 
+ * Send a message to a telnet session 
+ */
+static pj_status_t telnet_sess_send(cli_telnet_sess *sess,
+				    const pj_str_t *str)
+{
+    pj_ssize_t sz;
+    pj_status_t status = PJ_SUCCESS;
+
+    sz = str->slen;
+    if (!sz)
+        return PJ_SUCCESS;
+
+    pj_mutex_lock(sess->smutex);
+
+    if (sess->buf_len == 0)
+        status = pj_activesock_send(sess->asock, &sess->op_key, 
+                                    str->ptr, &sz, 0);
+    /* If we cannot send now, append it at the end of the buffer 
+     * to be sent later.
+     */
+    if (sess->buf_len > 0 || 
+        (status != PJ_SUCCESS && status != PJ_EPENDING))
+    {
+        int clen = (int)sz;
+
+        if (sess->buf_len + clen > CLI_TELNET_BUF_SIZE)
+            clen = CLI_TELNET_BUF_SIZE - sess->buf_len;
+        if (clen > 0)
+            pj_memmove(sess->buf + sess->buf_len, str->ptr, clen);
+        if (clen < sz) {
+            pj_ansi_snprintf((char *)sess->buf + CLI_TELNET_BUF_SIZE,
+                             MAX_CUT_MSG_LEN, CUT_MSG);
+            sess->buf_len = CLI_TELNET_BUF_SIZE +
+                            pj_ansi_strlen((char *)sess->buf+
+				           CLI_TELNET_BUF_SIZE);
+        } else
+            sess->buf_len += clen;
+    } else if (status == PJ_SUCCESS && sz < str->slen) {
+        pj_mutex_unlock(sess->smutex);
+        return PJ_CLI_ETELNETLOST;
+    }
+
+    pj_mutex_unlock(sess->smutex);
+
+    return PJ_SUCCESS;
+}
+
+/* 
+ * Send a message to a telnet session with formatted text 
+ * (add single linefeed character with carriage return)
+ */
+static pj_status_t telnet_sess_send_with_format(cli_telnet_sess *sess,
+						const pj_str_t *str)
+{
+    pj_scanner scanner;
+    pj_str_t out_str;
+    static const pj_str_t CR_LF = {("\r\n"), 2};
+    int str_len = 0;
+    char *str_begin = 0;
+
+    PJ_USE_EXCEPTION;
+
+    pj_scan_init(&scanner, str->ptr, str->slen, 
+	         PJ_SCAN_AUTOSKIP_WS, &on_syntax_error);
+    
+    str_begin = scanner.begin;
+
+    PJ_TRY {
+	while (!pj_scan_is_eof(&scanner)) {	    	    
+	    pj_scan_get_until_ch(&scanner, '\n', &out_str);	    
+	    str_len = scanner.curptr - str_begin;
+	    if (*scanner.curptr == '\n') {		
+		if ((str_len > 1) && (out_str.ptr[str_len-2] == '\r')) 
+		{	    		    
+		    continue;
+		} else {		    
+		    int str_pos = str_begin - scanner.begin;
+
+		    if (str_len > 0) {
+			pj_str_t s;
+			pj_strset(&s, &str->ptr[str_pos], str_len);
+			telnet_sess_send(sess, &s);
+		    }
+		    telnet_sess_send(sess, &CR_LF);
+
+		    if (!pj_scan_is_eof(&scanner)) {
+			pj_scan_advance_n(&scanner, 1, PJ_TRUE);
+			str_begin = scanner.curptr;
+		    }		    
+		}
+	    } else {
+		pj_str_t s;
+		int str_pos = str_begin - scanner.begin;
+
+		pj_strset(&s, &str->ptr[str_pos], str_len);
+		telnet_sess_send(sess, &s);
+	    }
+	}
+    }
+    PJ_CATCH_ANY {
+	pj_scan_fini(&scanner);
+	return (PJ_GET_EXCEPTION());
+    }
+    PJ_END;
+
+    return PJ_SUCCESS;
+}
+
+static pj_status_t telnet_sess_send2(cli_telnet_sess *sess,
+                                     const unsigned char *str, int len)
+{
+    pj_str_t s;
+
+    pj_strset(&s, (char *)str, len);
+    return telnet_sess_send(sess, &s);
+}
+
+static void telnet_sess_destroy(pj_cli_sess *sess)
+{
+    cli_telnet_sess *tsess = (cli_telnet_sess *)sess;
+    pj_mutex_t *mutex = ((struct cli_telnet_fe *)sess->fe)->mutex;
+
+    pj_mutex_lock(mutex);
+    pj_list_erase(sess);
+    pj_mutex_unlock(mutex);
+
+    pj_mutex_lock(tsess->smutex);
+    pj_mutex_unlock(tsess->smutex);
+    pj_activesock_close(tsess->asock);
+    pj_mutex_destroy(tsess->smutex);    
+    pj_pool_release(tsess->pool);
+}
+
+static void telnet_fe_write_log(pj_cli_front_end *fe, int level,
+		                const char *data, int len)
+{
+    struct cli_telnet_fe * tfe = (struct cli_telnet_fe *)fe;
+    pj_cli_sess *sess;    
+
+    pj_mutex_lock(tfe->mutex);
+
+    sess = tfe->sess_head.next;
+    while (sess != &tfe->sess_head) {
+        cli_telnet_sess *tsess = (cli_telnet_sess *)sess;
+
+        sess = sess->next;        
+	if (tsess->base.log_level > level) {
+	    pj_str_t s;
+
+	    pj_strset(&s, (char *)data, len);
+	    telnet_sess_send_with_format(tsess, &s);
+	}
+    }
+    
+    pj_mutex_unlock(tfe->mutex);
+}
+
+static void telnet_fe_destroy(pj_cli_front_end *fe)
+{
+    struct cli_telnet_fe *tfe = (struct cli_telnet_fe *)fe;
+    pj_cli_sess *sess;
+
+    tfe->is_quitting = PJ_TRUE;
+    if (tfe->worker_thread) {
+        pj_thread_join(tfe->worker_thread);
+    }
+
+    pj_mutex_lock(tfe->mutex);
+
+    /* Destroy all the sessions */
+    sess = tfe->sess_head.next;
+    while (sess != &tfe->sess_head) {
+        (*sess->op->destroy)(sess);
+        sess = tfe->sess_head.next;
+    }
+
+    pj_mutex_unlock(tfe->mutex);
+
+    pj_activesock_close(tfe->asock);
+    if (tfe->own_ioqueue)
+        pj_ioqueue_destroy(tfe->cfg.ioqueue);
+
+    if (tfe->worker_thread) {
+	pj_thread_destroy(tfe->worker_thread);
+	tfe->worker_thread = NULL;
+    }
+
+    pj_mutex_destroy(tfe->mutex);
+    pj_pool_release(tfe->pool);
+}
+
+static int poll_worker_thread(void *p)
+{
+    struct cli_telnet_fe *fe = (struct cli_telnet_fe *)p;
+
+    while (!fe->is_quitting) {
+	pj_time_val delay = {0, 50};
+        pj_ioqueue_poll(fe->cfg.ioqueue, &delay);
+    }
+
+    return 0;
+}
+
+static pj_bool_t telnet_sess_on_data_sent(pj_activesock_t *asock,
+ 				          pj_ioqueue_op_key_t *op_key,
+				          pj_ssize_t sent)
+{
+    cli_telnet_sess *sess = (cli_telnet_sess *)
+			    pj_activesock_get_user_data(asock);
+
+    PJ_UNUSED_ARG(op_key);
+
+    if (sent <= 0) {
+        pj_cli_sess_end_session(&sess->base);
+        return PJ_FALSE;
+    }
+
+    pj_mutex_lock(sess->smutex);
+
+    if (sess->buf_len) {
+        int len = sess->buf_len;
+
+        sess->buf_len = 0;
+        if (telnet_sess_send2(sess, sess->buf, len) != PJ_SUCCESS) {
+            pj_mutex_unlock(sess->smutex);
+            pj_cli_sess_end_session(&sess->base);
+            return PJ_FALSE;
+        }
+    }
+
+    pj_mutex_unlock(sess->smutex);
+
+    return PJ_TRUE;
+}
+
+static pj_bool_t telnet_sess_on_data_read(pj_activesock_t *asock,
+		                          void *data,
+			                  pj_size_t size,
+			                  pj_status_t status,
+			                  pj_size_t *remainder)
+{
+    cli_telnet_sess *sess = (cli_telnet_sess *)
+                            pj_activesock_get_user_data(asock);
+    struct cli_telnet_fe *tfe = (struct cli_telnet_fe *)sess->base.fe;
+    unsigned char *cdata = (unsigned char*)data;    
+    pj_status_t is_valid = PJ_TRUE;
+
+    PJ_UNUSED_ARG(size);
+    PJ_UNUSED_ARG(remainder);
+
+    if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+        pj_cli_sess_end_session(&sess->base);
+        return PJ_FALSE;
+    }
+
+    if (tfe->is_quitting)
+        return PJ_FALSE;
+
+    pj_mutex_lock(sess->smutex);
+
+    switch (sess->parse_state) {
+	case ST_CR:
+	    sess->parse_state = ST_NORMAL;
+	    if (*cdata == 0 || *cdata == '\n')				
+		pj_mutex_unlock(sess->smutex);
+		is_valid = handle_return(sess);
+		if (!is_valid)		    
+		    return PJ_FALSE;
+		pj_mutex_lock(sess->smutex);	
+		break;
+	case ST_NORMAL:
+	    if (*cdata == IAC) {
+		sess->parse_state = ST_IAC;
+	    } else if (*cdata == 127) {
+		is_valid = handle_backspace(sess, cdata);
+	    } else if (*cdata == 27) {		    
+		sess->parse_state = ST_ESC;
+	    } else { 
+		if (recv_buf_insert(sess->rcmd, cdata)) {
+		    if (*cdata == '\r') {
+			sess->parse_state = ST_CR;
+		    } else if ((*cdata == '\t') || (*cdata == '?')) {
+			is_valid = handle_tab(sess);
+		    } else if (*cdata > 31 && *cdata < 127) {
+			is_valid = handle_alfa_num(sess, cdata);
+		    }
+		} else {
+		    is_valid = PJ_FALSE;
+		}
+	    }
+	    break;
+	case ST_ESC:
+	    if (*cdata == 91) {		
+		sess->parse_state = ST_VT100;
+	    } else {
+		sess->parse_state = ST_NORMAL;
+	    }
+	    break;
+	case ST_VT100:
+	    sess->parse_state = ST_NORMAL;
+	    is_valid = process_vt100_cmd(sess, cdata);
+	    break;
+	case ST_IAC:
+	    switch ((unsigned) *cdata) {
+		case DO:
+		    sess->parse_state = ST_DO;
+		    break;
+		case DONT:
+		    sess->parse_state = ST_DONT;
+		    break;
+		case WILL:
+		    sess->parse_state = ST_WILL;
+		    break;
+		case WONT:
+		    sess->parse_state = ST_WONT;
+		    break;
+		default:
+		    sess->parse_state = ST_NORMAL;
+		    break;
+	    }
+	    break;
+	case ST_DO:
+	    receive_do(sess, *cdata);
+	    sess->parse_state = ST_NORMAL;
+	    break;
+	case ST_DONT:
+	    receive_dont(sess, *cdata);
+	    sess->parse_state = ST_NORMAL;
+	    break;
+	case ST_WILL:
+	    receive_will(sess, *cdata);
+	    sess->parse_state = ST_NORMAL;
+	    break;
+	case ST_WONT:
+	    receive_wont(sess, *cdata);
+	    sess->parse_state = ST_NORMAL;
+	    break;
+	default:
+	    sess->parse_state = ST_NORMAL;
+	    break;
+    }
+    if (!is_valid) {
+	send_bell(sess);
+    }
+
+    pj_mutex_unlock(sess->smutex);
+
+    return PJ_TRUE;
+}
+
+static pj_bool_t telnet_fe_on_accept(pj_activesock_t *asock,
+				     pj_sock_t newsock,
+				     const pj_sockaddr_t *src_addr,
+				     int src_addr_len)
+{
+    struct cli_telnet_fe *fe = (struct cli_telnet_fe *)
+                                pj_activesock_get_user_data(asock);
+    pj_status_t sstatus;
+    pj_pool_t *pool;
+    cli_telnet_sess *sess;
+    pj_activesock_cb asock_cb;
+
+    PJ_UNUSED_ARG(src_addr);
+    PJ_UNUSED_ARG(src_addr_len);
+
+    if (fe->is_quitting)
+        return PJ_FALSE;
+
+    /* An incoming connection is accepted, create a new session */
+    pool = pj_pool_create(fe->pool->factory, "telnet_sess",
+                          PJ_CLI_TELNET_POOL_SIZE, PJ_CLI_TELNET_POOL_INC,
+                          NULL);
+    if (!pool) {
+        TRACE_((THIS_FILE, 
+                "Not enough memory to create a new telnet session"));
+        return PJ_TRUE;
+    }
+
+    sess = PJ_POOL_ZALLOC_T(pool, cli_telnet_sess);
+    sess->pool = pool;
+    sess->base.fe = &fe->base;
+    sess->base.log_level = fe->cfg.log_level;
+    sess->base.op = PJ_POOL_ZALLOC_T(pool, struct pj_cli_sess_op);
+    sess->base.op->destroy = &telnet_sess_destroy;
+    pj_bzero(&asock_cb, sizeof(asock_cb));
+    asock_cb.on_data_read = &telnet_sess_on_data_read;
+    asock_cb.on_data_sent = &telnet_sess_on_data_sent;
+    sess->rcmd = PJ_POOL_ZALLOC_T(pool, telnet_recv_buf);
+    sess->history = PJ_POOL_ZALLOC_T(pool, struct cmd_history);
+    pj_list_init(sess->history);
+    sess->active_history = sess->history;
+
+    sstatus = pj_mutex_create_recursive(pool, "mutex_telnet_sess",
+                                        &sess->smutex);
+    if (sstatus != PJ_SUCCESS)
+        goto on_exit;
+    
+    sstatus = pj_activesock_create(pool, newsock, pj_SOCK_STREAM(),
+                                   NULL, fe->cfg.ioqueue,
+			           &asock_cb, sess, &sess->asock);
+    if (sstatus != PJ_SUCCESS) {
+        TRACE_((THIS_FILE, "Failure creating active socket"));
+        goto on_exit;
+    }
+
+    pj_memset(sess->telnet_option, 0, sizeof(sess->telnet_option));
+    set_local_option(sess, TRANSMIT_BINARY, PJ_TRUE);
+    set_local_option(sess, STATUS, PJ_TRUE);
+    set_local_option(sess, SUPPRESS_GA, PJ_TRUE);
+    set_local_option(sess, TIMING_MARK, PJ_TRUE);
+    set_local_option(sess, TERM_SPEED, PJ_TRUE);
+    set_local_option(sess, TERM_TYPE, PJ_TRUE);
+
+    set_peer_option(sess, TRANSMIT_BINARY, PJ_TRUE);
+    set_peer_option(sess, SUPPRESS_GA, PJ_TRUE);
+    set_peer_option(sess, STATUS, PJ_TRUE);
+    set_peer_option(sess, TIMING_MARK, PJ_TRUE);
+    set_peer_option(sess, ECHO, PJ_TRUE);    
+
+    send_cmd_do(sess, SUPPRESS_GA);
+    send_cmd_will(sess, ECHO);
+    send_cmd_will(sess, STATUS);   
+    send_cmd_will(sess, SUPPRESS_GA);
+
+    /* Send prompt string */
+    telnet_sess_send(sess, &fe->cfg.prompt_str);
+
+    /* Start reading for input from the new telnet session */
+    sstatus = pj_activesock_start_read(sess->asock, pool, 1, 0);
+    if (sstatus != PJ_SUCCESS) {
+        TRACE_((THIS_FILE, "Failure reading active socket"));
+        goto on_exit;
+    }
+
+    pj_ioqueue_op_key_init(&sess->op_key, sizeof(sess->op_key));
+    pj_mutex_lock(fe->mutex);
+    pj_list_push_back(&fe->sess_head, &sess->base);
+    pj_mutex_unlock(fe->mutex);
+
+    return PJ_TRUE;
+
+on_exit:
+    if (sess->asock)
+        pj_activesock_close(sess->asock);
+    else
+        pj_sock_close(newsock);
+    
+    if (sess->smutex)
+        pj_mutex_destroy(sess->smutex);
+
+    pj_pool_release(pool);
+
+    return PJ_TRUE;
+}
+
+PJ_DEF(pj_status_t) pj_cli_telnet_create(pj_cli_t *cli,
+					 pj_cli_telnet_cfg *param,
+					 pj_cli_front_end **p_fe)
+{
+    struct cli_telnet_fe *fe;
+    pj_pool_t *pool;
+    pj_sock_t sock = PJ_INVALID_SOCKET;
+    pj_activesock_cb asock_cb;
+    pj_sockaddr_in addr;
+    pj_status_t sstatus;
+
+    PJ_ASSERT_RETURN(cli, PJ_EINVAL);
+
+    pool = pj_pool_create(pj_cli_get_param(cli)->pf, "telnet_fe",
+                          PJ_CLI_TELNET_POOL_SIZE, PJ_CLI_TELNET_POOL_INC,
+                          NULL);
+    fe = PJ_POOL_ZALLOC_T(pool, struct cli_telnet_fe);
+    if (!fe)
+        return PJ_ENOMEM;
+	
+    fe->base.op = PJ_POOL_ZALLOC_T(pool, struct pj_cli_front_end_op);
+
+    if (!param)
+        pj_cli_telnet_cfg_default(&fe->cfg);
+    else
+        pj_memcpy(&fe->cfg, param, sizeof(*param));
+	
+    pj_list_init(&fe->sess_head);
+    fe->base.cli = cli;
+    fe->base.type = PJ_CLI_TELNET_FRONT_END;
+    fe->base.op->on_write_log = &telnet_fe_write_log;
+    fe->base.op->on_destroy = &telnet_fe_destroy;
+    fe->pool = pool;
+
+    if (!fe->cfg.ioqueue) {
+        /* Create own ioqueue if application doesn't supply one */
+        sstatus = pj_ioqueue_create(pool, 8, &fe->cfg.ioqueue);
+        if (sstatus != PJ_SUCCESS)
+            goto on_exit;
+        fe->own_ioqueue = PJ_TRUE;
+    }
+
+    sstatus = pj_mutex_create_recursive(pool, "mutex_telnet_fe", &fe->mutex);
+    if (sstatus != PJ_SUCCESS)
+        goto on_exit;
+
+    /* Start telnet daemon */
+    sstatus = pj_sock_socket(pj_AF_INET(), pj_SOCK_STREAM(), 0, 
+                             &sock);
+    if (sstatus != PJ_SUCCESS)
+        goto on_exit;
+
+    pj_sockaddr_in_init(&addr, NULL, fe->cfg.port);
+
+    sstatus = pj_sock_bind(sock, &addr, sizeof(addr));
+    if (sstatus == PJ_SUCCESS) {
+	pj_sockaddr_in addr;
+	int addr_len = sizeof(addr);
+
+	sstatus = pj_sock_getsockname(sock, &addr, &addr_len);
+	if (sstatus != PJ_SUCCESS)
+	    goto on_exit;
+	    
+        fe->cfg.port = pj_sockaddr_in_get_port(&addr);
+	if (param) 
+	    param->port = fe->cfg.port;
+	
+        PJ_LOG(3, (THIS_FILE, "CLI telnet daemon listening at port %d",
+		   fe->cfg.port));
+	
+	if (fe->cfg.prompt_str.slen == 0) {
+	    pj_str_t prompt_sign = {"> ", 2};
+	    char *prompt_data = pj_pool_alloc(fe->pool, 
+					      pj_gethostname()->slen+2);
+	    fe->cfg.prompt_str.ptr = prompt_data;
+
+	    pj_strcpy(&fe->cfg.prompt_str, pj_gethostname());
+	    pj_strcat(&fe->cfg.prompt_str, &prompt_sign);
+	}	
+    } else {
+        PJ_LOG(3, (THIS_FILE, "Failed binding the socket"));
+        goto on_exit;
+    }
+
+    sstatus = pj_sock_listen(sock, 4);
+    if (sstatus != PJ_SUCCESS)
+        goto on_exit;
+
+    pj_bzero(&asock_cb, sizeof(asock_cb));
+    asock_cb.on_accept_complete = &telnet_fe_on_accept;
+    sstatus = pj_activesock_create(pool, sock, pj_SOCK_STREAM(),
+                                   NULL, fe->cfg.ioqueue,
+		                   &asock_cb, fe, &fe->asock);
+    if (sstatus != PJ_SUCCESS)
+        goto on_exit;
+
+    sstatus = pj_activesock_start_accept(fe->asock, pool);
+    if (sstatus != PJ_SUCCESS)
+        goto on_exit;
+
+    if (fe->own_ioqueue) {
+        /* Create our own worker thread */
+        sstatus = pj_thread_create(pool, "worker_telnet_fe",
+                                   &poll_worker_thread, fe, 0, 0,
+                                   &fe->worker_thread);
+        if (sstatus != PJ_SUCCESS)
+            goto on_exit;
+    }
+
+    pj_cli_register_front_end(cli, &fe->base);
+
+    if (p_fe)
+        *p_fe = &fe->base;
+
+    return PJ_SUCCESS;
+
+on_exit:
+    if (fe->asock)
+        pj_activesock_close(fe->asock);
+    else if (sock != PJ_INVALID_SOCKET)
+        pj_sock_close(sock);
+
+    if (fe->own_ioqueue)
+        pj_ioqueue_destroy(fe->cfg.ioqueue);
+
+    if (fe->mutex)
+        pj_mutex_destroy(fe->mutex);
+
+    pj_pool_release(pool);
+    return sstatus;
+}
Index: /pjproject/trunk/pjlib-util/src/pjlib-util/errno.c
===================================================================
--- /pjproject/trunk/pjlib-util/src/pjlib-util/errno.c	(revision 4439)
+++ /pjproject/trunk/pjlib-util/src/pjlib-util/errno.c	(revision 4440)
@@ -97,4 +97,14 @@
     PJ_BUILD_ERR( PJLIB_UTIL_EHTTPINSBUF,	"Insufficient buffer"),
     PJ_BUILD_ERR( PJLIB_UTIL_EHTTPLOST,	        "Connection lost"),
+
+    /* CLI */
+    PJ_BUILD_ERR( PJ_CLI_EEXIT,	                "Exit current session"),
+    PJ_BUILD_ERR( PJ_CLI_EMISSINGARG,	        "Missing argument"),
+    PJ_BUILD_ERR( PJ_CLI_ETOOMANYARGS,	        "Too many arguments"),
+    PJ_BUILD_ERR( PJ_CLI_EINVARG,	        "Invalid argument"),
+    PJ_BUILD_ERR( PJ_CLI_EBADNAME,	        "Command name already exists"),
+    PJ_BUILD_ERR( PJ_CLI_EBADID,	        "Command id already exists"),
+    PJ_BUILD_ERR( PJ_CLI_EBADXML,	        "Invalid XML format"),
+    PJ_BUILD_ERR( PJ_CLI_ETELNETLOST,	        "Connection lost"),
 };
 #endif	/* PJ_HAS_ERROR_STRING */
Index: /pjproject/trunk/pjlib/include/pj/string.h
===================================================================
--- /pjproject/trunk/pjlib/include/pj/string.h	(revision 4439)
+++ /pjproject/trunk/pjlib/include/pj/string.h	(revision 4440)
@@ -537,4 +537,15 @@
 
 /**
+ * Convert string to signed integer. The conversion will stop as
+ * soon as non-digit character is found or all the characters have
+ * been processed.
+ *
+ * @param str	the string.
+ *
+ * @return the integer.
+ */
+PJ_DECL(long) pj_strtol(const pj_str_t *str);
+
+/**
  * Convert string to unsigned integer. The conversion will stop as
  * soon as non-digit character is found or all the characters have
Index: /pjproject/trunk/pjlib/src/pj/string.c
===================================================================
--- /pjproject/trunk/pjlib/src/pj/string.c	(revision 4439)
+++ /pjproject/trunk/pjlib/src/pj/string.c	(revision 4440)
@@ -110,4 +110,17 @@
 }
 
+PJ_DEF(long) pj_strtol(const pj_str_t *str)
+{
+    PJ_CHECK_STACK();
+
+    if (str->slen > 0 && (str->ptr[0] == '+' || str->ptr[0] == '-')) {
+        pj_str_t s;
+        
+        s.ptr = str->ptr + 1;
+        s.slen = str->slen - 1;
+        return (str->ptr[0] == '-'? -(long)pj_strtoul(&s) : pj_strtoul(&s));
+    } else
+        return pj_strtoul(str);
+}
 
 PJ_DEF(unsigned long) pj_strtoul(const pj_str_t *str)
Index: /pjproject/trunk/pjsip-apps/build/Makefile
===================================================================
--- /pjproject/trunk/pjsip-apps/build/Makefile	(revision 4439)
+++ /pjproject/trunk/pjsip-apps/build/Makefile	(revision 4440)
@@ -34,5 +34,5 @@
 export PJSUA_SRCDIR = ../src/pjsua
 export PJSUA_OBJS += $(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \
-			main.o pjsua_app.o
+			main.o pjsua_app.o pjsua_cmd.o pjsua_cli_cmd.o pjsua_ui_cmd.o
 export PJSUA_CFLAGS += $(_CFLAGS)
 export PJSUA_LDFLAGS += $(APP_LDFLAGS) $(APP_LDLIBS) $(LDFLAGS)
Index: /pjproject/trunk/pjsip-apps/build/Samples-vc.mak
===================================================================
--- /pjproject/trunk/pjsip-apps/build/Samples-vc.mak	(revision 4439)
+++ /pjproject/trunk/pjsip-apps/build/Samples-vc.mak	(revision 4440)
@@ -65,4 +65,5 @@
 	  $(BINDIR)\aectest.exe \
 	  $(BINDIR)\aviplay.exe \
+	  $(BINDIR)\clidemo.exe \
 	  $(BINDIR)\confsample.exe \
 	  $(BINDIR)\confbench.exe \
Index: /pjproject/trunk/pjsip-apps/build/Samples.mak
===================================================================
--- /pjproject/trunk/pjsip-apps/build/Samples.mak	(revision 4439)
+++ /pjproject/trunk/pjsip-apps/build/Samples.mak	(revision 4440)
@@ -18,4 +18,5 @@
 	   aectest \
 	   aviplay \
+	   clidemo \
 	   confsample \
 	   encdec \
Index: /pjproject/trunk/pjsip-apps/build/pjsua.vcproj
===================================================================
--- /pjproject/trunk/pjsip-apps/build/pjsua.vcproj	(revision 4439)
+++ /pjproject/trunk/pjsip-apps/build/pjsua.vcproj	(revision 4440)
@@ -3620,4 +3620,1150 @@
 				</FileConfiguration>
 			</File>
+			<File
+				RelativePath="..\src\pjsua\pjsua_cli_cmd.c"
+				>
+				<FileConfiguration
+					Name="Release|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|Pocket PC 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|Smartphone 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug|Pocket PC 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug|Smartphone 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Static|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Static|Pocket PC 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Static|Smartphone 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Dynamic|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Dynamic|Pocket PC 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Dynamic|Smartphone 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Dynamic|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Dynamic|Pocket PC 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Dynamic|Smartphone 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Static|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Static|Pocket PC 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Static|Smartphone 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|Windows Mobile 6 Standard SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|Windows Mobile 6 Professional SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|Windows Mobile 5.0 Pocket PC SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|Windows Mobile 5.0 Smartphone SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug|Windows Mobile 6 Standard SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug|Windows Mobile 6 Professional SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug|Windows Mobile 5.0 Pocket PC SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug|Windows Mobile 5.0 Smartphone SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Static|Windows Mobile 6 Standard SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Static|Windows Mobile 6 Professional SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Static|Windows Mobile 5.0 Pocket PC SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Static|Windows Mobile 5.0 Smartphone SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Dynamic|Windows Mobile 6 Standard SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Dynamic|Windows Mobile 6 Professional SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Dynamic|Windows Mobile 5.0 Pocket PC SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Dynamic|Windows Mobile 5.0 Smartphone SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Dynamic|Windows Mobile 6 Standard SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Dynamic|Windows Mobile 6 Professional SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Dynamic|Windows Mobile 5.0 Pocket PC SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Dynamic|Windows Mobile 5.0 Smartphone SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Static|Windows Mobile 6 Standard SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Static|Windows Mobile 6 Professional SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Static|Windows Mobile 5.0 Pocket PC SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Static|Windows Mobile 5.0 Smartphone SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+			</File>
+			<File
+				RelativePath="..\src\pjsua\pjsua_cmd.c"
+				>
+				<FileConfiguration
+					Name="Release|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|Pocket PC 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|Smartphone 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug|Pocket PC 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug|Smartphone 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Static|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Static|Pocket PC 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Static|Smartphone 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Dynamic|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Dynamic|Pocket PC 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Dynamic|Smartphone 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Dynamic|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Dynamic|Pocket PC 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Dynamic|Smartphone 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Static|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Static|Pocket PC 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Static|Smartphone 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|Windows Mobile 6 Standard SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|Windows Mobile 6 Professional SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|Windows Mobile 5.0 Pocket PC SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|Windows Mobile 5.0 Smartphone SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug|Windows Mobile 6 Standard SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug|Windows Mobile 6 Professional SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug|Windows Mobile 5.0 Pocket PC SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug|Windows Mobile 5.0 Smartphone SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Static|Windows Mobile 6 Standard SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Static|Windows Mobile 6 Professional SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Static|Windows Mobile 5.0 Pocket PC SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Static|Windows Mobile 5.0 Smartphone SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Dynamic|Windows Mobile 6 Standard SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Dynamic|Windows Mobile 6 Professional SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Dynamic|Windows Mobile 5.0 Pocket PC SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Dynamic|Windows Mobile 5.0 Smartphone SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Dynamic|Windows Mobile 6 Standard SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Dynamic|Windows Mobile 6 Professional SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Dynamic|Windows Mobile 5.0 Pocket PC SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Dynamic|Windows Mobile 5.0 Smartphone SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Static|Windows Mobile 6 Standard SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Static|Windows Mobile 6 Professional SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Static|Windows Mobile 5.0 Pocket PC SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Static|Windows Mobile 5.0 Smartphone SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+			</File>
+			<File
+				RelativePath="..\src\pjsua\pjsua_ui_cmd.c"
+				>
+				<FileConfiguration
+					Name="Release|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|Pocket PC 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|Smartphone 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug|Pocket PC 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug|Smartphone 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Static|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Static|Pocket PC 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Static|Smartphone 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Dynamic|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Dynamic|Pocket PC 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Dynamic|Smartphone 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Dynamic|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Dynamic|Pocket PC 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Dynamic|Smartphone 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Static|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Static|Pocket PC 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Static|Smartphone 2003 (ARMV4)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|Windows Mobile 6 Standard SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|Windows Mobile 6 Professional SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|Windows Mobile 5.0 Pocket PC SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release|Windows Mobile 5.0 Smartphone SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug|Windows Mobile 6 Standard SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug|Windows Mobile 6 Professional SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug|Windows Mobile 5.0 Pocket PC SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug|Windows Mobile 5.0 Smartphone SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Static|Windows Mobile 6 Standard SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Static|Windows Mobile 6 Professional SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Static|Windows Mobile 5.0 Pocket PC SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Static|Windows Mobile 5.0 Smartphone SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Dynamic|Windows Mobile 6 Standard SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Dynamic|Windows Mobile 6 Professional SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Dynamic|Windows Mobile 5.0 Pocket PC SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Dynamic|Windows Mobile 5.0 Smartphone SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Dynamic|Windows Mobile 6 Standard SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Dynamic|Windows Mobile 6 Professional SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Dynamic|Windows Mobile 5.0 Pocket PC SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug-Dynamic|Windows Mobile 5.0 Smartphone SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Static|Windows Mobile 6 Standard SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Static|Windows Mobile 6 Professional SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Static|Windows Mobile 5.0 Pocket PC SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Release-Static|Windows Mobile 5.0 Smartphone SDK (ARMV4I)"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						AdditionalIncludeDirectories=""
+						PreprocessorDefinitions=""
+					/>
+				</FileConfiguration>
+			</File>
 		</Filter>
 		<Filter
@@ -3625,4 +4771,8 @@
 			Filter="h;hpp;hxx;hm;inl"
 			>
+			<File
+				RelativePath="..\src\pjsua\pjsua_cmd.h"
+				>
+			</File>
 		</Filter>
 		<Filter
Index: /pjproject/trunk/pjsip-apps/build/samples.vcproj
===================================================================
--- /pjproject/trunk/pjsip-apps/build/samples.vcproj	(revision 4439)
+++ /pjproject/trunk/pjsip-apps/build/samples.vcproj	(revision 4440)
@@ -1603,4 +1603,8 @@
 			</File>
 			<File
+				RelativePath="..\src\samples\clidemo.c"
+				>
+			</File>
+			<File
 				RelativePath="..\src\samples\confbench.c"
 				>
Index: /pjproject/trunk/pjsip-apps/src/pjsua/main.c
===================================================================
--- /pjproject/trunk/pjsip-apps/src/pjsua/main.c	(revision 4439)
+++ /pjproject/trunk/pjsip-apps/src/pjsua/main.c	(revision 4440)
@@ -30,6 +30,7 @@
 pj_status_t app_init(int argc, char *argv[]);
 pj_status_t app_main(void);
-pj_status_t app_destroy(void);
-
+pj_status_t app_destroy();
+pj_status_t receive_end_sig;
+pj_thread_t *sig_thread;
 
 #if defined(PJ_WIN32) && PJ_WIN32!=0
@@ -39,7 +40,5 @@
 
 static BOOL WINAPI CtrlHandler(DWORD fdwCtrlType)
-{
-    pj_thread_t *thread;
-
+{   
     switch (fdwCtrlType) 
     { 
@@ -51,7 +50,8 @@
         case CTRL_LOGOFF_EVENT: 
         case CTRL_SHUTDOWN_EVENT: 
-	    pj_thread_register("ctrlhandler", handler_desc, &thread);
+	    pj_thread_register("ctrlhandler", handler_desc, &sig_thread);
 	    PJ_LOG(3,(THIS_FILE, "Ctrl-C detected, quitting.."));
-            app_destroy();
+	    receive_end_sig = PJ_TRUE;
+            app_destroy();	    
 	    ExitProcess(1);
             PJ_UNREACHED(return TRUE;)
@@ -88,4 +88,5 @@
 static int main_func(int argc, char *argv[])
 {
+    receive_end_sig = PJ_FALSE;
     setup_socket_signal();
 
@@ -99,8 +100,12 @@
 
 	app_main();
-	app_destroy();
+	if (!receive_end_sig) {
+	    app_destroy();
 
-	/* This is on purpose */
-	app_destroy();
+	    /* This is on purpose */
+	    app_destroy();
+	} else {
+	    pj_thread_join(sig_thread);
+	}
     } while (app_restart);
 
Index: /pjproject/trunk/pjsip-apps/src/pjsua/pjsua_app.c
===================================================================
--- /pjproject/trunk/pjsip-apps/src/pjsua/pjsua_app.c	(revision 4439)
+++ /pjproject/trunk/pjsip-apps/src/pjsua/pjsua_app.c	(revision 4440)
@@ -18,10 +18,8 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
  */
-#include <pjsua-lib/pjsua.h>
+#include "pjsua_cmd.h"
 #include "gui.h"
 
-
 #define THIS_FILE	"pjsua_app.c"
-#define NO_LIMIT	(int)0x7FFFFFFF
 
 //#define STEREO_DEMO
@@ -44,133 +42,13 @@
 #define RING_INTERVAL	    3000
 
-#define MAX_AVI             4
-
-/* Call specific data */
-struct call_data
-{
-    pj_timer_entry	    timer;
-    pj_bool_t		    ringback_on;
-    pj_bool_t		    ring_on;
-};
-
-/* Video settings */
-struct app_vid
-{
-    unsigned		    vid_cnt;
-    int			    vcapture_dev;
-    int			    vrender_dev;
-    pj_bool_t		    in_auto_show;
-    pj_bool_t		    out_auto_transmit;
-};
-
-/* Pjsua application data */
-static struct app_config
-{
-    pjsua_config	    cfg;
-    pjsua_logging_config    log_cfg;
-    pjsua_media_config	    media_cfg;
-    pj_bool_t		    no_refersub;
-    pj_bool_t		    ipv6;
-    pj_bool_t		    enable_qos;
-    pj_bool_t		    no_tcp;
-    pj_bool_t		    no_udp;
-    pj_bool_t		    use_tls;
-    pjsua_transport_config  udp_cfg;
-    pjsua_transport_config  rtp_cfg;
-    pjsip_redirect_op	    redir_op;
-
-    unsigned		    acc_cnt;
-    pjsua_acc_config	    acc_cfg[PJSUA_MAX_ACC];
-
-    unsigned		    buddy_cnt;
-    pjsua_buddy_config	    buddy_cfg[PJSUA_MAX_BUDDIES];
-
-    struct call_data	    call_data[PJSUA_MAX_CALLS];
-
-    pj_pool_t		   *pool;
-    /* Compatibility with older pjsua */
-
-    unsigned		    codec_cnt;
-    pj_str_t		    codec_arg[32];
-    unsigned		    codec_dis_cnt;
-    pj_str_t                codec_dis[32];
-    pj_bool_t		    null_audio;
-    unsigned		    wav_count;
-    pj_str_t		    wav_files[32];
-    unsigned		    tone_count;
-    pjmedia_tone_desc	    tones[32];
-    pjsua_conf_port_id	    tone_slots[32];
-    pjsua_player_id	    wav_id;
-    pjsua_conf_port_id	    wav_port;
-    pj_bool_t		    auto_play;
-    pj_bool_t		    auto_play_hangup;
-    pj_timer_entry	    auto_hangup_timer;
-    pj_bool_t		    auto_loop;
-    pj_bool_t		    auto_conf;
-    pj_str_t		    rec_file;
-    pj_bool_t		    auto_rec;
-    pjsua_recorder_id	    rec_id;
-    pjsua_conf_port_id	    rec_port;
-    unsigned		    auto_answer;
-    unsigned		    duration;
-
-#ifdef STEREO_DEMO
-    pjmedia_snd_port	   *snd;
-    pjmedia_port	   *sc, *sc_ch1;
-    pjsua_conf_port_id	    sc_ch1_slot;
-#endif
-
-    float		    mic_level,
-			    speaker_level;
-
-    int			    capture_dev, playback_dev;
-    unsigned		    capture_lat, playback_lat;
-
-    pj_bool_t		    no_tones;
-    int			    ringback_slot;
-    int			    ringback_cnt;
-    pjmedia_port	   *ringback_port;
-    int			    ring_slot;
-    int			    ring_cnt;
-    pjmedia_port	   *ring_port;
-
-    struct app_vid	    vid;
-    unsigned		    aud_cnt;
-
-    /* AVI to play */
-    unsigned                avi_cnt;
-    struct {
-	pj_str_t		path;
-	pjmedia_vid_dev_index	dev_id;
-	pjsua_conf_port_id	slot;
-    } avi[MAX_AVI];
-    pj_bool_t               avi_auto_play;
-    int			    avi_def_idx;
-
-} app_config;
-
-
-//static pjsua_acc_id	current_acc;
 #define current_acc	pjsua_acc_get_default()
-static pjsua_call_id	current_call = PJSUA_INVALID_ID;
-static pj_bool_t	cmd_echo;
-static int		stdout_refresh = -1;
-static const char      *stdout_refresh_text = "STDOUT_REFRESH";
-static pj_bool_t	stdout_refresh_quit = PJ_FALSE;
+
 static pj_str_t		uri_arg;
-
-#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
-#   define SOME_BUF_SIZE	(1024 * 10)
-#else
-#   define SOME_BUF_SIZE	(1024 * 3)
-#endif
-
-static char some_buf[SOME_BUF_SIZE];
 
 #ifdef STEREO_DEMO
 static void stereo_demo();
 #endif
+
 pj_status_t app_destroy(void);
-
 static void ringback_start(pjsua_call_id call_id);
 static void ring_start(pjsua_call_id call_id);
@@ -179,4 +57,13 @@
 pj_bool_t 	app_restart;
 pj_log_func     *log_cb = NULL;
+static const char      *stdout_refresh_text = "STDOUT_REFRESH";
+
+/** Forward declaration **/
+void console_app_main(const pj_str_t *uri_to_call, pj_bool_t *app_restart);
+
+void cli_console_app_main(const pj_str_t *uri_to_call, pj_bool_t *app_restart);
+void app_config_init_video(pjsua_acc_config *acc_cfg);
+pj_status_t setup_cli();
+void destroy_cli();
 
 /*****************************************************************************
@@ -382,4 +269,10 @@
 
     puts  ("");
+    puts  ("CLI options:");
+    puts  ("  --use-cli           Use CLI as user interface");
+    puts  ("  --cli-telnet-port=N CLI telnet port");
+    puts  ("");
+
+    puts  ("");
     puts  ("When URL is specified, pjsua will immediately initiate call to that URL");
     puts  ("");
@@ -388,7 +281,25 @@
 }
 
+static int stdout_refresh_proc(void *arg)
+{
+    PJ_UNUSED_ARG(arg);
+
+    /* Set thread to lowest priority so that it doesn't clobber
+     * stdout output
+     */
+    pj_thread_set_prio(pj_thread_this(), 
+		       pj_thread_get_prio_min(pj_thread_this()));
+
+    while (!stdout_refresh_quit) {
+	pj_thread_sleep(stdout_refresh * 1000);
+	puts(stdout_refresh_text);
+	fflush(stdout);
+    }
+
+    return 0;
+}
 
 /* Set default config. */
-static void default_config(struct app_config *cfg)
+static void default_config(pjsua_app_config *cfg)
 {
     char tmp[80];
@@ -407,5 +318,5 @@
     cfg->rtp_cfg.port = 4000;
     cfg->redir_op = PJSIP_REDIRECT_ACCEPT_REPLACE;
-    cfg->duration = NO_LIMIT;
+    cfg->duration = NO_LIMIT_DURATION;
     cfg->wav_id = PJSUA_INVALID_ID;
     cfg->rec_id = PJSUA_INVALID_ID;
@@ -431,6 +342,8 @@
 
     cfg->avi_def_idx = PJSUA_INVALID_ID;
-}
-
+
+    cfg->use_cli = PJ_FALSE;
+    cfg->cli_telnet_port = 0;
+}
 
 /*
@@ -467,4 +380,5 @@
 	int   len, token_len;
 	
+	pj_bzero(line, sizeof(line));
 	if (fgets(line, sizeof(line), fhnd) == NULL) break;
 	
@@ -536,27 +450,9 @@
     *app_argv = argv;
     return 0;
-
-}
-
-static int my_atoi(const char *cs)
-{
-    pj_str_t s;
-
-    pj_cstr(&s, cs);
-    if (cs[0] == '-') {
-	s.ptr++, s.slen--;
-	return 0 - (int)pj_strtoul(&s);
-    } else if (cs[0] == '+') {
-	s.ptr++, s.slen--;
-	return pj_strtoul(&s);
-    } else {
-	return pj_strtoul(&s);
-    }
-}
-
+}
 
 /* Parse arguments. */
-static pj_status_t parse_args(int argc, char *argv[],
-			      struct app_config *cfg,
+static pj_status_t parse_args(int argc, char *argv[], 
+			      pjsua_app_config *cfg,
 			      pj_str_t *uri_to_call)
 {
@@ -598,5 +494,6 @@
 	   OPT_TIMER, OPT_TIMER_SE, OPT_TIMER_MIN_SE,
 	   OPT_VIDEO, OPT_EXTRA_AUDIO,
-	   OPT_VCAPTURE_DEV, OPT_VRENDER_DEV, OPT_PLAY_AVI, OPT_AUTO_PLAY_AVI
+	   OPT_VCAPTURE_DEV, OPT_VRENDER_DEV, OPT_PLAY_AVI, OPT_AUTO_PLAY_AVI,
+	   OPT_USE_CLI, OPT_CLI_TELNET_PORT
     };
     struct pj_getopt_option long_options[] = {
@@ -727,4 +624,6 @@
 	{ "play-avi",	1, 0, OPT_PLAY_AVI},
 	{ "auto-play-avi", 0, 0, OPT_AUTO_PLAY_AVI},
+	{ "use-cli",	0, 0, OPT_USE_CLI},
+	{ "cli-telnet-port", 1, 0, OPT_CLI_TELNET_PORT},
 	{ NULL, 0, 0, 0}
     };
@@ -749,5 +648,5 @@
 
     if (config_file) {
-	status = read_config_file(app_config.pool, config_file, &argc, &argv);
+	status = read_config_file(cfg->pool, config_file, &argc, &argv);
 	if (status != 0)
 	    return status;
@@ -1549,4 +1448,12 @@
 	case OPT_AUTO_PLAY_AVI:
 	    app_config.avi_auto_play = PJ_TRUE;
+	    break;
+
+	case OPT_USE_CLI:
+	    cfg->use_cli = PJ_TRUE;
+	    break;
+
+	case OPT_CLI_TELNET_PORT:
+	    cfg->cli_telnet_port = atoi(pj_optarg);
 	    break;
 
@@ -1631,730 +1538,5 @@
 	}
     }
-
-
     return PJ_SUCCESS;
-}
-
-
-/*
- * Save account settings
- */
-static void write_account_settings(int acc_index, pj_str_t *result)
-{
-    unsigned i;
-    char line[128];
-    pjsua_acc_config *acc_cfg = &app_config.acc_cfg[acc_index];
-
-    
-    pj_ansi_sprintf(line, "\n#\n# Account %d:\n#\n", acc_index);
-    pj_strcat2(result, line);
-
-
-    /* Identity */
-    if (acc_cfg->id.slen) {
-	pj_ansi_sprintf(line, "--id %.*s\n", 
-			(int)acc_cfg->id.slen, 
-			acc_cfg->id.ptr);
-	pj_strcat2(result, line);
-    }
-
-    /* Registrar server */
-    if (acc_cfg->reg_uri.slen) {
-	pj_ansi_sprintf(line, "--registrar %.*s\n",
-			      (int)acc_cfg->reg_uri.slen,
-			      acc_cfg->reg_uri.ptr);
-	pj_strcat2(result, line);
-
-	pj_ansi_sprintf(line, "--reg-timeout %u\n",
-			      acc_cfg->reg_timeout);
-	pj_strcat2(result, line);
-    }
-
-    /* Contact */
-    if (acc_cfg->force_contact.slen) {
-	pj_ansi_sprintf(line, "--contact %.*s\n", 
-			(int)acc_cfg->force_contact.slen, 
-			acc_cfg->force_contact.ptr);
-	pj_strcat2(result, line);
-    }
-
-    /* Contact header parameters */
-    if (acc_cfg->contact_params.slen) {
-	pj_ansi_sprintf(line, "--contact-params %.*s\n", 
-			(int)acc_cfg->contact_params.slen, 
-			acc_cfg->contact_params.ptr);
-	pj_strcat2(result, line);
-    }
-
-    /* Contact URI parameters */
-    if (acc_cfg->contact_uri_params.slen) {
-	pj_ansi_sprintf(line, "--contact-uri-params %.*s\n", 
-			(int)acc_cfg->contact_uri_params.slen, 
-			acc_cfg->contact_uri_params.ptr);
-	pj_strcat2(result, line);
-    }
-
-    /*  */
-    if (acc_cfg->allow_contact_rewrite!=1)
-    {
-	pj_ansi_sprintf(line, "--auto-update-nat %i\n",
-			(int)acc_cfg->allow_contact_rewrite);
-	pj_strcat2(result, line);
-    }
-
-#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
-    /* SRTP */
-    if (acc_cfg->use_srtp) {
-	int use_srtp = (int)acc_cfg->use_srtp;
-	if (use_srtp == PJMEDIA_SRTP_OPTIONAL && 
-	    acc_cfg->srtp_optional_dup_offer)
-	{
-	    use_srtp = 3;
-	}
-	pj_ansi_sprintf(line, "--use-srtp %i\n", use_srtp);
-	pj_strcat2(result, line);
-    }
-    if (acc_cfg->srtp_secure_signaling != 
-	PJSUA_DEFAULT_SRTP_SECURE_SIGNALING) 
-    {
-	pj_ansi_sprintf(line, "--srtp-secure %d\n",
-			acc_cfg->srtp_secure_signaling);
-	pj_strcat2(result, line);
-    }
-#endif
-
-    /* Proxy */
-    for (i=0; i<acc_cfg->proxy_cnt; ++i) {
-	pj_ansi_sprintf(line, "--proxy %.*s\n",
-			      (int)acc_cfg->proxy[i].slen,
-			      acc_cfg->proxy[i].ptr);
-	pj_strcat2(result, line);
-    }
-
-    /* Credentials */
-    for (i=0; i<acc_cfg->cred_count; ++i) {
-	if (acc_cfg->cred_info[i].realm.slen) {
-	    pj_ansi_sprintf(line, "--realm %.*s\n",
-				  (int)acc_cfg->cred_info[i].realm.slen,
-				  acc_cfg->cred_info[i].realm.ptr);
-	    pj_strcat2(result, line);
-	}
-
-	if (acc_cfg->cred_info[i].username.slen) {
-	    pj_ansi_sprintf(line, "--username %.*s\n",
-				  (int)acc_cfg->cred_info[i].username.slen,
-				  acc_cfg->cred_info[i].username.ptr);
-	    pj_strcat2(result, line);
-	}
-
-	if (acc_cfg->cred_info[i].data.slen) {
-	    pj_ansi_sprintf(line, "--password %.*s\n",
-				  (int)acc_cfg->cred_info[i].data.slen,
-				  acc_cfg->cred_info[i].data.ptr);
-	    pj_strcat2(result, line);
-	}
-
-	if (i != acc_cfg->cred_count - 1)
-	    pj_strcat2(result, "--next-cred\n");
-    }
-
-    /* reg-use-proxy */
-    if (acc_cfg->reg_use_proxy != 3) {
-	pj_ansi_sprintf(line, "--reg-use-proxy %d\n",
-			      acc_cfg->reg_use_proxy);
-	pj_strcat2(result, line);
-    }
-
-    /* rereg-delay */
-    if (acc_cfg->reg_retry_interval != PJSUA_REG_RETRY_INTERVAL) {
-	pj_ansi_sprintf(line, "--rereg-delay %d\n",
-		              acc_cfg->reg_retry_interval);
-	pj_strcat2(result, line);
-    }
-
-    /* 100rel extension */
-    if (acc_cfg->require_100rel) {
-	pj_strcat2(result, "--use-100rel\n");
-    }
-
-    /* Session Timer extension */
-    if (acc_cfg->use_timer) {
-	pj_ansi_sprintf(line, "--use-timer %d\n",
-			      acc_cfg->use_timer);
-	pj_strcat2(result, line);
-    }
-    if (acc_cfg->timer_setting.min_se != 90) {
-	pj_ansi_sprintf(line, "--timer-min-se %d\n",
-			      acc_cfg->timer_setting.min_se);
-	pj_strcat2(result, line);
-    }
-    if (acc_cfg->timer_setting.sess_expires != PJSIP_SESS_TIMER_DEF_SE) {
-	pj_ansi_sprintf(line, "--timer-se %d\n",
-			      acc_cfg->timer_setting.sess_expires);
-	pj_strcat2(result, line);
-    }
-
-    /* Publish */
-    if (acc_cfg->publish_enabled)
-	pj_strcat2(result, "--publish\n");
-
-    /* MWI */
-    if (acc_cfg->mwi_enabled)
-	pj_strcat2(result, "--mwi\n");
-
-    if (acc_cfg->sip_stun_use != PJSUA_STUN_USE_DEFAULT ||
-	acc_cfg->media_stun_use != PJSUA_STUN_USE_DEFAULT)
-    {
-	pj_strcat2(result, "--disable-stun\n");
-    }
-
-    /* Media Transport*/
-    if (acc_cfg->ice_cfg.enable_ice)
-	pj_strcat2(result, "--use-ice\n");
-
-    if (acc_cfg->ice_cfg.ice_opt.aggressive == PJ_FALSE)
-	pj_strcat2(result, "--ice-regular\n");
-
-    if (acc_cfg->turn_cfg.enable_turn)
-	pj_strcat2(result, "--use-turn\n");
-
-    if (acc_cfg->ice_cfg.ice_max_host_cands >= 0) {
-	pj_ansi_sprintf(line, "--ice_max_host_cands %d\n",
-	                acc_cfg->ice_cfg.ice_max_host_cands);
-	pj_strcat2(result, line);
-    }
-
-    if (acc_cfg->ice_cfg.ice_no_rtcp)
-	pj_strcat2(result, "--ice-no-rtcp\n");
-
-    if (acc_cfg->turn_cfg.turn_server.slen) {
-	pj_ansi_sprintf(line, "--turn-srv %.*s\n",
-			(int)acc_cfg->turn_cfg.turn_server.slen,
-			acc_cfg->turn_cfg.turn_server.ptr);
-	pj_strcat2(result, line);
-    }
-
-    if (acc_cfg->turn_cfg.turn_conn_type == PJ_TURN_TP_TCP)
-	pj_strcat2(result, "--turn-tcp\n");
-
-    if (acc_cfg->turn_cfg.turn_auth_cred.data.static_cred.username.slen) {
-	pj_ansi_sprintf(line, "--turn-user %.*s\n",
-			(int)acc_cfg->turn_cfg.turn_auth_cred.data.static_cred.username.slen,
-			acc_cfg->turn_cfg.turn_auth_cred.data.static_cred.username.ptr);
-	pj_strcat2(result, line);
-    }
-
-    if (acc_cfg->turn_cfg.turn_auth_cred.data.static_cred.data.slen) {
-	pj_ansi_sprintf(line, "--turn-passwd %.*s\n",
-			(int)acc_cfg->turn_cfg.turn_auth_cred.data.static_cred.data.slen,
-			acc_cfg->turn_cfg.turn_auth_cred.data.static_cred.data.ptr);
-	pj_strcat2(result, line);
-    }
-}
-
-
-/*
- * Write settings.
- */
-static int write_settings(const struct app_config *config,
-			  char *buf, pj_size_t max)
-{
-    unsigned acc_index;
-    unsigned i;
-    pj_str_t cfg;
-    char line[128];
-    extern pj_bool_t pjsip_use_compact_form;
-
-    PJ_UNUSED_ARG(max);
-
-    cfg.ptr = buf;
-    cfg.slen = 0;
-
-    /* Logging. */
-    pj_strcat2(&cfg, "#\n# Logging options:\n#\n");
-    pj_ansi_sprintf(line, "--log-level %d\n",
-		    config->log_cfg.level);
-    pj_strcat2(&cfg, line);
-
-    pj_ansi_sprintf(line, "--app-log-level %d\n",
-		    config->log_cfg.console_level);
-    pj_strcat2(&cfg, line);
-
-    if (config->log_cfg.log_filename.slen) {
-	pj_ansi_sprintf(line, "--log-file %.*s\n",
-			(int)config->log_cfg.log_filename.slen,
-			config->log_cfg.log_filename.ptr);
-	pj_strcat2(&cfg, line);
-    }
-
-    if (config->log_cfg.log_file_flags & PJ_O_APPEND) {
-	pj_strcat2(&cfg, "--log-append\n");
-    }
-
-    /* Save account settings. */
-    for (acc_index=0; acc_index < config->acc_cnt; ++acc_index) {
-	
-	write_account_settings(acc_index, &cfg);
-
-	if (acc_index < config->acc_cnt-1)
-	    pj_strcat2(&cfg, "--next-account\n");
-    }
-
-
-    pj_strcat2(&cfg, "\n#\n# Network settings:\n#\n");
-
-    /* Nameservers */
-    for (i=0; i<config->cfg.nameserver_count; ++i) {
-	pj_ansi_sprintf(line, "--nameserver %.*s\n",
-			      (int)config->cfg.nameserver[i].slen,
-			      config->cfg.nameserver[i].ptr);
-	pj_strcat2(&cfg, line);
-    }
-
-    /* Outbound proxy */
-    for (i=0; i<config->cfg.outbound_proxy_cnt; ++i) {
-	pj_ansi_sprintf(line, "--outbound %.*s\n",
-			      (int)config->cfg.outbound_proxy[i].slen,
-			      config->cfg.outbound_proxy[i].ptr);
-	pj_strcat2(&cfg, line);
-    }
-
-    /* Transport options */
-    if (config->ipv6) {
-	pj_strcat2(&cfg, "--ipv6\n");
-    }
-    if (config->enable_qos) {
-	pj_strcat2(&cfg, "--set-qos\n");
-    }
-
-    /* UDP Transport. */
-    pj_ansi_sprintf(line, "--local-port %d\n", config->udp_cfg.port);
-    pj_strcat2(&cfg, line);
-
-    /* IP address, if any. */
-    if (config->udp_cfg.public_addr.slen) {
-	pj_ansi_sprintf(line, "--ip-addr %.*s\n", 
-			(int)config->udp_cfg.public_addr.slen,
-			config->udp_cfg.public_addr.ptr);
-	pj_strcat2(&cfg, line);
-    }
-
-    /* Bound IP address, if any. */
-    if (config->udp_cfg.bound_addr.slen) {
-	pj_ansi_sprintf(line, "--bound-addr %.*s\n", 
-			(int)config->udp_cfg.bound_addr.slen,
-			config->udp_cfg.bound_addr.ptr);
-	pj_strcat2(&cfg, line);
-    }
-
-    /* No TCP ? */
-    if (config->no_tcp) {
-	pj_strcat2(&cfg, "--no-tcp\n");
-    }
-
-    /* No UDP ? */
-    if (config->no_udp) {
-	pj_strcat2(&cfg, "--no-udp\n");
-    }
-
-    /* STUN */
-    for (i=0; i<config->cfg.stun_srv_cnt; ++i) {
-	pj_ansi_sprintf(line, "--stun-srv %.*s\n",
-			(int)config->cfg.stun_srv[i].slen, 
-			config->cfg.stun_srv[i].ptr);
-	pj_strcat2(&cfg, line);
-    }
-
-#if defined(PJSIP_HAS_TLS_TRANSPORT) && (PJSIP_HAS_TLS_TRANSPORT != 0)
-    /* TLS */
-    if (config->use_tls)
-	pj_strcat2(&cfg, "--use-tls\n");
-    if (config->udp_cfg.tls_setting.ca_list_file.slen) {
-	pj_ansi_sprintf(line, "--tls-ca-file %.*s\n",
-			(int)config->udp_cfg.tls_setting.ca_list_file.slen, 
-			config->udp_cfg.tls_setting.ca_list_file.ptr);
-	pj_strcat2(&cfg, line);
-    }
-    if (config->udp_cfg.tls_setting.cert_file.slen) {
-	pj_ansi_sprintf(line, "--tls-cert-file %.*s\n",
-			(int)config->udp_cfg.tls_setting.cert_file.slen, 
-			config->udp_cfg.tls_setting.cert_file.ptr);
-	pj_strcat2(&cfg, line);
-    }
-    if (config->udp_cfg.tls_setting.privkey_file.slen) {
-	pj_ansi_sprintf(line, "--tls-privkey-file %.*s\n",
-			(int)config->udp_cfg.tls_setting.privkey_file.slen, 
-			config->udp_cfg.tls_setting.privkey_file.ptr);
-	pj_strcat2(&cfg, line);
-    }
-
-    if (config->udp_cfg.tls_setting.password.slen) {
-	pj_ansi_sprintf(line, "--tls-password %.*s\n",
-			(int)config->udp_cfg.tls_setting.password.slen, 
-			config->udp_cfg.tls_setting.password.ptr);
-	pj_strcat2(&cfg, line);
-    }
-
-    if (config->udp_cfg.tls_setting.verify_server)
-	pj_strcat2(&cfg, "--tls-verify-server\n");
-
-    if (config->udp_cfg.tls_setting.verify_client)
-	pj_strcat2(&cfg, "--tls-verify-client\n");
-
-    if (config->udp_cfg.tls_setting.timeout.sec) {
-	pj_ansi_sprintf(line, "--tls-neg-timeout %d\n",
-			(int)config->udp_cfg.tls_setting.timeout.sec);
-	pj_strcat2(&cfg, line);
-    }
-
-    for (i=0; i<config->udp_cfg.tls_setting.ciphers_num; ++i) {
-	pj_ansi_sprintf(line, "--tls-cipher 0x%06X # %s\n",
-			config->udp_cfg.tls_setting.ciphers[i],
-			pj_ssl_cipher_name(config->udp_cfg.tls_setting.ciphers[i]));
-	pj_strcat2(&cfg, line);
-    }
-#endif
-
-    pj_strcat2(&cfg, "\n#\n# Media settings:\n#\n");
-
-    /* Video & extra audio */
-    for (i=0; i<config->vid.vid_cnt; ++i) {
-	pj_strcat2(&cfg, "--video\n");
-    }
-    for (i=1; i<config->aud_cnt; ++i) {
-	pj_strcat2(&cfg, "--extra-audio\n");
-    }
-
-    /* SRTP */
-#if PJMEDIA_HAS_SRTP
-    if (app_config.cfg.use_srtp != PJSUA_DEFAULT_USE_SRTP) {
-	int use_srtp = (int)app_config.cfg.use_srtp;
-	if (use_srtp == PJMEDIA_SRTP_OPTIONAL && 
-	    app_config.cfg.srtp_optional_dup_offer)
-	{
-	    use_srtp = 3;
-	}
-	pj_ansi_sprintf(line, "--use-srtp %d\n", use_srtp);
-	pj_strcat2(&cfg, line);
-    }
-    if (app_config.cfg.srtp_secure_signaling != 
-	PJSUA_DEFAULT_SRTP_SECURE_SIGNALING) 
-    {
-	pj_ansi_sprintf(line, "--srtp-secure %d\n",
-			app_config.cfg.srtp_secure_signaling);
-	pj_strcat2(&cfg, line);
-    }
-#endif
-
-    /* Media */
-    if (config->null_audio)
-	pj_strcat2(&cfg, "--null-audio\n");
-    if (config->auto_play)
-	pj_strcat2(&cfg, "--auto-play\n");
-    if (config->auto_loop)
-	pj_strcat2(&cfg, "--auto-loop\n");
-    if (config->auto_conf)
-	pj_strcat2(&cfg, "--auto-conf\n");
-    for (i=0; i<config->wav_count; ++i) {
-	pj_ansi_sprintf(line, "--play-file %s\n",
-			config->wav_files[i].ptr);
-	pj_strcat2(&cfg, line);
-    }
-    for (i=0; i<config->tone_count; ++i) {
-	pj_ansi_sprintf(line, "--play-tone %d,%d,%d,%d\n",
-			config->tones[i].freq1, config->tones[i].freq2, 
-			config->tones[i].on_msec, config->tones[i].off_msec);
-	pj_strcat2(&cfg, line);
-    }
-    if (config->rec_file.slen) {
-	pj_ansi_sprintf(line, "--rec-file %s\n",
-			config->rec_file.ptr);
-	pj_strcat2(&cfg, line);
-    }
-    if (config->auto_rec)
-	pj_strcat2(&cfg, "--auto-rec\n");
-    if (config->capture_dev != PJSUA_INVALID_ID) {
-	pj_ansi_sprintf(line, "--capture-dev %d\n", config->capture_dev);
-	pj_strcat2(&cfg, line);
-    }
-    if (config->playback_dev != PJSUA_INVALID_ID) {
-	pj_ansi_sprintf(line, "--playback-dev %d\n", config->playback_dev);
-	pj_strcat2(&cfg, line);
-    }
-    if (config->media_cfg.snd_auto_close_time != -1) {
-	pj_ansi_sprintf(line, "--snd-auto-close %d\n", 
-			config->media_cfg.snd_auto_close_time);
-	pj_strcat2(&cfg, line);
-    }
-    if (config->no_tones) {
-	pj_strcat2(&cfg, "--no-tones\n");
-    }
-    if (config->media_cfg.jb_max != -1) {
-	pj_ansi_sprintf(line, "--jb-max-size %d\n", 
-			config->media_cfg.jb_max);
-	pj_strcat2(&cfg, line);
-    }
-
-    /* Sound device latency */
-    if (config->capture_lat != PJMEDIA_SND_DEFAULT_REC_LATENCY) {
-	pj_ansi_sprintf(line, "--capture-lat %d\n", config->capture_lat);
-	pj_strcat2(&cfg, line);
-    }
-    if (config->playback_lat != PJMEDIA_SND_DEFAULT_PLAY_LATENCY) {
-	pj_ansi_sprintf(line, "--playback-lat %d\n", config->playback_lat);
-	pj_strcat2(&cfg, line);
-    }
-
-    /* Media clock rate. */
-    if (config->media_cfg.clock_rate != PJSUA_DEFAULT_CLOCK_RATE) {
-	pj_ansi_sprintf(line, "--clock-rate %d\n",
-			config->media_cfg.clock_rate);
-	pj_strcat2(&cfg, line);
-    } else {
-	pj_ansi_sprintf(line, "#using default --clock-rate %d\n",
-			config->media_cfg.clock_rate);
-	pj_strcat2(&cfg, line);
-    }
-
-    if (config->media_cfg.snd_clock_rate && 
-	config->media_cfg.snd_clock_rate != config->media_cfg.clock_rate) 
-    {
-	pj_ansi_sprintf(line, "--snd-clock-rate %d\n",
-			config->media_cfg.snd_clock_rate);
-	pj_strcat2(&cfg, line);
-    }
-
-    /* Stereo mode. */
-    if (config->media_cfg.channel_count == 2) {
-	pj_ansi_sprintf(line, "--stereo\n");
-	pj_strcat2(&cfg, line);
-    }
-
-    /* quality */
-    if (config->media_cfg.quality != PJSUA_DEFAULT_CODEC_QUALITY) {
-	pj_ansi_sprintf(line, "--quality %d\n",
-			config->media_cfg.quality);
-	pj_strcat2(&cfg, line);
-    } else {
-	pj_ansi_sprintf(line, "#using default --quality %d\n",
-			config->media_cfg.quality);
-	pj_strcat2(&cfg, line);
-    }
-
-    if (config->vid.vcapture_dev != PJMEDIA_VID_DEFAULT_CAPTURE_DEV) {
-	pj_ansi_sprintf(line, "--vcapture-dev %d\n", config->vid.vcapture_dev);
-	pj_strcat2(&cfg, line);
-    }
-    if (config->vid.vrender_dev != PJMEDIA_VID_DEFAULT_RENDER_DEV) {
-	pj_ansi_sprintf(line, "--vrender-dev %d\n", config->vid.vrender_dev);
-	pj_strcat2(&cfg, line);
-    }
-    for (i=0; i<config->avi_cnt; ++i) {
-	pj_ansi_sprintf(line, "--play-avi %s\n", config->avi[i].path.ptr);
-	pj_strcat2(&cfg, line);
-    }
-    if (config->avi_auto_play) {
-	pj_ansi_sprintf(line, "--auto-play-avi\n");
-	pj_strcat2(&cfg, line);
-    }
-
-    /* ptime */
-    if (config->media_cfg.ptime) {
-	pj_ansi_sprintf(line, "--ptime %d\n",
-			config->media_cfg.ptime);
-	pj_strcat2(&cfg, line);
-    }
-
-    /* no-vad */
-    if (config->media_cfg.no_vad) {
-	pj_strcat2(&cfg, "--no-vad\n");
-    }
-
-    /* ec-tail */
-    if (config->media_cfg.ec_tail_len != PJSUA_DEFAULT_EC_TAIL_LEN) {
-	pj_ansi_sprintf(line, "--ec-tail %d\n",
-			config->media_cfg.ec_tail_len);
-	pj_strcat2(&cfg, line);
-    } else {
-	pj_ansi_sprintf(line, "#using default --ec-tail %d\n",
-			config->media_cfg.ec_tail_len);
-	pj_strcat2(&cfg, line);
-    }
-
-    /* ec-opt */
-    if (config->media_cfg.ec_options != 0) {
-	pj_ansi_sprintf(line, "--ec-opt %d\n",
-			config->media_cfg.ec_options);
-	pj_strcat2(&cfg, line);
-    } 
-
-    /* ilbc-mode */
-    if (config->media_cfg.ilbc_mode != PJSUA_DEFAULT_ILBC_MODE) {
-	pj_ansi_sprintf(line, "--ilbc-mode %d\n",
-			config->media_cfg.ilbc_mode);
-	pj_strcat2(&cfg, line);
-    } else {
-	pj_ansi_sprintf(line, "#using default --ilbc-mode %d\n",
-			config->media_cfg.ilbc_mode);
-	pj_strcat2(&cfg, line);
-    }
-
-    /* RTP drop */
-    if (config->media_cfg.tx_drop_pct) {
-	pj_ansi_sprintf(line, "--tx-drop-pct %d\n",
-			config->media_cfg.tx_drop_pct);
-	pj_strcat2(&cfg, line);
-
-    }
-    if (config->media_cfg.rx_drop_pct) {
-	pj_ansi_sprintf(line, "--rx-drop-pct %d\n",
-			config->media_cfg.rx_drop_pct);
-	pj_strcat2(&cfg, line);
-
-    }
-
-
-    /* Start RTP port. */
-    pj_ansi_sprintf(line, "--rtp-port %d\n",
-		    config->rtp_cfg.port);
-    pj_strcat2(&cfg, line);
-
-    /* Disable codec */
-    for (i=0; i<config->codec_dis_cnt; ++i) {
-	pj_ansi_sprintf(line, "--dis-codec %s\n",
-		    config->codec_dis[i].ptr);
-	pj_strcat2(&cfg, line);
-    }
-    /* Add codec. */
-    for (i=0; i<config->codec_cnt; ++i) {
-	pj_ansi_sprintf(line, "--add-codec %s\n",
-		    config->codec_arg[i].ptr);
-	pj_strcat2(&cfg, line);
-    }
-
-    pj_strcat2(&cfg, "\n#\n# User agent:\n#\n");
-
-    /* Auto-answer. */
-    if (config->auto_answer != 0) {
-	pj_ansi_sprintf(line, "--auto-answer %d\n",
-			config->auto_answer);
-	pj_strcat2(&cfg, line);
-    }
-
-    /* accept-redirect */
-    if (config->redir_op != PJSIP_REDIRECT_ACCEPT_REPLACE) {
-	pj_ansi_sprintf(line, "--accept-redirect %d\n",
-			config->redir_op);
-	pj_strcat2(&cfg, line);
-    }
-
-    /* Max calls. */
-    pj_ansi_sprintf(line, "--max-calls %d\n",
-		    config->cfg.max_calls);
-    pj_strcat2(&cfg, line);
-
-    /* Uas-duration. */
-    if (config->duration != NO_LIMIT) {
-	pj_ansi_sprintf(line, "--duration %d\n",
-			config->duration);
-	pj_strcat2(&cfg, line);
-    }
-
-    /* norefersub ? */
-    if (config->no_refersub) {
-	pj_strcat2(&cfg, "--norefersub\n");
-    }
-
-    if (pjsip_use_compact_form)
-    {
-	pj_strcat2(&cfg, "--use-compact-form\n");
-    }
-
-    if (!config->cfg.force_lr) {
-	pj_strcat2(&cfg, "--no-force-lr\n");
-    }
-
-    pj_strcat2(&cfg, "\n#\n# Buddies:\n#\n");
-
-    /* Add buddies. */
-    for (i=0; i<config->buddy_cnt; ++i) {
-	pj_ansi_sprintf(line, "--add-buddy %.*s\n",
-			      (int)config->buddy_cfg[i].uri.slen,
-			      config->buddy_cfg[i].uri.ptr);
-	pj_strcat2(&cfg, line);
-    }
-
-    /* SIP extensions. */
-    pj_strcat2(&cfg, "\n#\n# SIP extensions:\n#\n");
-    /* 100rel extension */
-    if (config->cfg.require_100rel) {
-	pj_strcat2(&cfg, "--use-100rel\n");
-    }
-    /* Session Timer extension */
-    if (config->cfg.use_timer) {
-	pj_ansi_sprintf(line, "--use-timer %d\n",
-			      config->cfg.use_timer);
-	pj_strcat2(&cfg, line);
-    }
-    if (config->cfg.timer_setting.min_se != 90) {
-	pj_ansi_sprintf(line, "--timer-min-se %d\n",
-			      config->cfg.timer_setting.min_se);
-	pj_strcat2(&cfg, line);
-    }
-    if (config->cfg.timer_setting.sess_expires != PJSIP_SESS_TIMER_DEF_SE) {
-	pj_ansi_sprintf(line, "--timer-se %d\n",
-			      config->cfg.timer_setting.sess_expires);
-	pj_strcat2(&cfg, line);
-    }
-
-    *(cfg.ptr + cfg.slen) = '\0';
-    return cfg.slen;
-}
-
-
-/*
- * Dump application states.
- */
-static void app_dump(pj_bool_t detail)
-{
-    pjsua_dump(detail);
-}
-
-/*
- * Print log of call states. Since call states may be too long for logger,
- * printing it is a bit tricky, it should be printed part by part as long 
- * as the logger can accept.
- */
-static void log_call_dump(int call_id) 
-{
-    unsigned call_dump_len;
-    unsigned part_len;
-    unsigned part_idx;
-    unsigned log_decor;
-
-    pjsua_call_dump(call_id, PJ_TRUE, some_buf, 
-		    sizeof(some_buf), "  ");
-    call_dump_len = strlen(some_buf);
-
-    log_decor = pj_log_get_decor();
-    pj_log_set_decor(log_decor & ~(PJ_LOG_HAS_NEWLINE | PJ_LOG_HAS_CR));
-    PJ_LOG(3,(THIS_FILE, "\n"));
-    pj_log_set_decor(0);
-
-    part_idx = 0;
-    part_len = PJ_LOG_MAX_SIZE-80;
-    while (part_idx < call_dump_len) {
-	char p_orig, *p;
-
-	p = &some_buf[part_idx];
-	if (part_idx + part_len > call_dump_len)
-	    part_len = call_dump_len - part_idx;
-	p_orig = p[part_len];
-	p[part_len] = '\0';
-	PJ_LOG(3,(THIS_FILE, "%s", p));
-	p[part_len] = p_orig;
-	part_idx += part_len;
-    }
-    pj_log_set_decor(log_decor);
 }
 
@@ -2362,5 +1544,4 @@
  * Console application
  */
-
 static void ringback_start(pjsua_call_id call_id)
 {
@@ -2427,89 +1608,4 @@
 }
 
-#ifdef HAVE_MULTIPART_TEST
-  /*
-   * Enable multipart in msg_data and add a dummy body into the
-   * multipart bodies.
-   */
-  static void add_multipart(pjsua_msg_data *msg_data)
-  {
-      static pjsip_multipart_part *alt_part;
-
-      if (!alt_part) {
-	  pj_str_t type, subtype, content;
-
-	  alt_part = pjsip_multipart_create_part(app_config.pool);
-
-	  type = pj_str("text");
-	  subtype = pj_str("plain");
-	  content = pj_str("Sample text body of a multipart bodies");
-	  alt_part->body = pjsip_msg_body_create(app_config.pool, &type,
-						 &subtype, &content);
-      }
-
-      msg_data->multipart_ctype.type = pj_str("multipart");
-      msg_data->multipart_ctype.subtype = pj_str("mixed");
-      pj_list_push_back(&msg_data->multipart_parts, alt_part);
-  }
-#  define TEST_MULTIPART(msg_data)	add_multipart(msg_data)
-#else
-#  define TEST_MULTIPART(msg_data)
-#endif
-
-/*
- * Find next call when current call is disconnected or when user
- * press ']'
- */
-static pj_bool_t find_next_call(void)
-{
-    int i, max;
-
-    max = pjsua_call_get_max_count();
-    for (i=current_call+1; i<max; ++i) {
-	if (pjsua_call_is_active(i)) {
-	    current_call = i;
-	    return PJ_TRUE;
-	}
-    }
-
-    for (i=0; i<current_call; ++i) {
-	if (pjsua_call_is_active(i)) {
-	    current_call = i;
-	    return PJ_TRUE;
-	}
-    }
-
-    current_call = PJSUA_INVALID_ID;
-    return PJ_FALSE;
-}
-
-
-/*
- * Find previous call when user press '['
- */
-static pj_bool_t find_prev_call(void)
-{
-    int i, max;
-
-    max = pjsua_call_get_max_count();
-    for (i=current_call-1; i>=0; --i) {
-	if (pjsua_call_is_active(i)) {
-	    current_call = i;
-	    return PJ_TRUE;
-	}
-    }
-
-    for (i=max-1; i>current_call; --i) {
-	if (pjsua_call_is_active(i)) {
-	    current_call = i;
-	    return PJ_TRUE;
-	}
-    }
-
-    current_call = PJSUA_INVALID_ID;
-    return PJ_FALSE;
-}
-
-
 /* Callback from timer when the maximum call duration has been
  * exceeded.
@@ -2596,5 +1692,5 @@
     } else {
 
-	if (app_config.duration!=NO_LIMIT && 
+	if (app_config.duration != NO_LIMIT_DURATION && 
 	    call_info.state == PJSIP_INV_STATE_CONFIRMED) 
 	{
@@ -2650,5 +1746,4 @@
 }
 
-
 /**
  * Handler when there is incoming call.
@@ -2704,5 +1799,5 @@
 		  "From: %s\n"
 		  "To: %s\n"
-		  "Press a to answer or h to reject call",
+		  "Press %s to answer or %s to reject call",
 		  acc_id,
 		  call_info.rem_aud_cnt,
@@ -2710,5 +1805,7 @@
 		  notif_st,
 		  call_info.remote_info.ptr,
-		  call_info.local_info.ptr));
+		  call_info.local_info.ptr,
+		  (app_config.use_cli?"c a":"a"),
+		  (app_config.use_cli?"c g":"h")));
     }
 }
@@ -2925,40 +2022,4 @@
 }
 
-/* arrange windows. arg:
- *   -1:    arrange all windows
- *   != -1: arrange only this window id
- */
-static void arrange_window(pjsua_vid_win_id wid)
-{
-#if PJSUA_HAS_VIDEO
-    pjmedia_coord pos;
-    int i, last;
-
-    pos.x = 0;
-    pos.y = 10;
-    last = (wid == PJSUA_INVALID_ID) ? PJSUA_MAX_VID_WINS : wid;
-
-    for (i=0; i<last; ++i) {
-	pjsua_vid_win_info wi;
-	pj_status_t status;
-
-	status = pjsua_vid_win_get_info(i, &wi);
-	if (status != PJ_SUCCESS)
-	    continue;
-
-	if (wid == PJSUA_INVALID_ID)
-	    pjsua_vid_win_set_pos(i, &pos);
-
-	if (wi.show)
-	    pos.y += wi.size.h;
-    }
-
-    if (wid != PJSUA_INVALID_ID)
-	pjsua_vid_win_set_pos(wid, &pos);
-#else
-    PJ_UNUSED_ARG(wid);
-#endif
-}
-
 /* Process video media state. "mi" is the media index. */
 static void on_call_video_state(pjsua_call_info *ci, unsigned mi,
@@ -3070,5 +2131,4 @@
     // Log already written.
 }
-
 
 /*
@@ -3444,70 +2504,4 @@
 #endif
 
-/*
- * Print buddy list.
- */
-static void print_buddy_list(void)
-{
-    pjsua_buddy_id ids[64];
-    int i;
-    unsigned count = PJ_ARRAY_SIZE(ids);
-
-    puts("Buddy list:");
-
-    pjsua_enum_buddies(ids, &count);
-
-    if (count == 0)
-	puts(" -none-");
-    else {
-	for (i=0; i<(int)count; ++i) {
-	    pjsua_buddy_info info;
-
-	    if (pjsua_buddy_get_info(ids[i], &info) != PJ_SUCCESS)
-		continue;
-
-	    printf(" [%2d] <%.*s>  %.*s\n", 
-		    ids[i]+1, 
-		    (int)info.status_text.slen,
-		    info.status_text.ptr, 
-		    (int)info.uri.slen,
-		    info.uri.ptr);
-	}
-    }
-    puts("");
-}
-
-
-/*
- * Print account status.
- */
-static void print_acc_status(int acc_id)
-{
-    char buf[80];
-    pjsua_acc_info info;
-
-    pjsua_acc_get_info(acc_id, &info);
-
-    if (!info.has_registration) {
-	pj_ansi_snprintf(buf, sizeof(buf), "%.*s", 
-			 (int)info.status_text.slen,
-			 info.status_text.ptr);
-
-    } else {
-	pj_ansi_snprintf(buf, sizeof(buf),
-			 "%d/%.*s (expires=%d)",
-			 info.status,
-			 (int)info.status_text.slen,
-			 info.status_text.ptr,
-			 info.expires);
-
-    }
-
-    printf(" %c[%2d] %.*s: %s\n", (acc_id==current_acc?'*':' '),
-	   acc_id,  (int)info.acc_uri.slen, info.acc_uri.ptr, buf);
-    printf("       Online status: %.*s\n", 
-	(int)info.online_status_text.slen,
-	info.online_status_text.ptr);
-}
-
 /* Playfile done notification, set timer to hangup calls */
 pj_status_t on_playfile_done(pjmedia_port *port, void *usr_data)
@@ -3550,1865 +2544,4 @@
 
 /*
- * Show a bit of help.
- */
-static void keystroke_help(void)
-{
-    pjsua_acc_id acc_ids[16];
-    unsigned count = PJ_ARRAY_SIZE(acc_ids);
-    int i;
-
-    printf(">>>>\n");
-
-    pjsua_enum_accs(acc_ids, &count);
-
-    printf("Account list:\n");
-    for (i=0; i<(int)count; ++i)
-	print_acc_status(acc_ids[i]);
-
-    print_buddy_list();
-    
-    //puts("Commands:");
-    puts("+=============================================================================+");
-    puts("|       Call Commands:         |   Buddy, IM & Presence:  |     Account:      |");
-    puts("|                              |                          |                   |");
-    puts("|  m  Make new call            | +b  Add new buddy       .| +a  Add new accnt |");
-    puts("|  M  Make multiple calls      | -b  Delete buddy         | -a  Delete accnt. |");
-    puts("|  a  Answer call              |  i  Send IM              | !a  Modify accnt. |");
-    puts("|  h  Hangup call  (ha=all)    |  s  Subscribe presence   | rr  (Re-)register |");
-    puts("|  H  Hold call                |  u  Unsubscribe presence | ru  Unregister    |");
-    puts("|  v  re-inVite (release hold) |  t  ToGgle Online status |  >  Cycle next ac.|");
-    puts("|  U  send UPDATE              |  T  Set online status    |  <  Cycle prev ac.|");
-    puts("| ],[ Select next/prev call    +--------------------------+-------------------+");
-    puts("|  x  Xfer call                |      Media Commands:     |  Status & Config: |");
-    puts("|  X  Xfer with Replaces       |                          |                   |");
-    puts("|  #  Send RFC 2833 DTMF       | cl  List ports           |  d  Dump status   |");
-    puts("|  *  Send DTMF with INFO      | cc  Connect port         | dd  Dump detailed |");
-    puts("| dq  Dump curr. call quality  | cd  Disconnect port      | dc  Dump config   |");
-    puts("|                              |  V  Adjust audio Volume  |  f  Save config   |");
-    puts("|  S  Send arbitrary REQUEST   | Cp  Codec priorities     |                   |");
-    puts("+-----------------------------------------------------------------------------+");
-#if PJSUA_HAS_VIDEO
-    puts("| Video: \"vid help\" for more info                                             |");
-    puts("+-----------------------------------------------------------------------------+");
-#endif
-    puts("|  q  QUIT   L  ReLoad   sleep MS   echo [0|1|txt]     n: detect NAT type     |");
-    puts("+=============================================================================+");
-
-    i = pjsua_call_get_count();
-    printf("You have %d active call%s\n", i, (i>1?"s":""));
-
-    if (current_call != PJSUA_INVALID_ID) {
-	pjsua_call_info ci;
-	if (pjsua_call_get_info(current_call, &ci)==PJ_SUCCESS)
-	    printf("Current call id=%d to %.*s [%.*s]\n", current_call,
-		   (int)ci.remote_info.slen, ci.remote_info.ptr,
-		   (int)ci.state_text.slen, ci.state_text.ptr);
-    }
-}
-
-/* Help screen for video */
-#if PJSUA_HAS_VIDEO
-static void vid_show_help(void)
-{
-    pj_bool_t vid_enabled = (app_config.vid.vid_cnt > 0);
-
-    puts("+=============================================================================+");
-    puts("|                            Video commands:                                  |");
-    puts("|                                                                             |");
-    puts("| vid help                  Show this help screen                             |");
-    puts("| vid enable|disable        Enable or disable video in next offer/answer      |");
-    puts("| vid acc show              Show current account video settings               |");
-    puts("| vid acc autorx on|off     Automatically show incoming video on/off          |");
-    puts("| vid acc autotx on|off     Automatically offer video on/off                  |");
-    puts("| vid acc cap ID            Set default capture device for current acc        |");
-    puts("| vid acc rend ID           Set default renderer device for current acc       |");
-    puts("| vid call rx on|off N      Enable/disable video RX for stream N in curr call |");
-    puts("| vid call tx on|off N      Enable/disable video TX for stream N in curr call |");
-    puts("| vid call add              Add video stream for current call                 |");
-    puts("| vid call enable|disable N Enable/disable stream #N in current call          |");
-    puts("| vid call cap N ID         Set capture dev ID for stream #N in current call  |");
-    puts("| vid dev list              List all video devices                            |");
-    puts("| vid dev refresh           Refresh video device list                         |");
-    puts("| vid dev prev on|off ID    Enable/disable preview for specified device ID    |");
-    puts("| vid codec list            List video codecs                                 |");
-    puts("| vid codec prio ID PRIO    Set codec ID priority to PRIO                     |");
-    puts("| vid codec fps ID NUM DEN  Set codec ID framerate to (NUM/DEN) fps           |");
-    puts("| vid codec bw ID AVG MAX   Set codec ID bitrate to AVG & MAX kbps            |");
-    puts("| vid codec size ID W H     Set codec ID size/resolution to W x H             |");
-    puts("| vid win list              List all active video windows                     |");
-    puts("| vid win arrange           Auto arrange windows                              |");
-    puts("| vid win show|hide ID      Show/hide the specified video window ID           |");
-    puts("| vid win move ID X Y       Move window ID to position X,Y                    |");
-    puts("| vid win resize ID w h     Resize window ID to the specified width, height   |");
-    puts("+=============================================================================+");
-    printf("| Video will be %s in the next offer/answer %s                            |\n",
-	   (vid_enabled? "enabled" : "disabled"), (vid_enabled? " " : ""));
-    puts("+=============================================================================+");
-}
-#endif
-
-/*
- * Input simple string
- */
-static pj_bool_t simple_input(const char *title, char *buf, pj_size_t len)
-{
-    char *p;
-
-    printf("%s (empty to cancel): ", title); fflush(stdout);
-    if (fgets(buf, len, stdin) == NULL)
-	return PJ_FALSE;
-
-    /* Remove trailing newlines. */
-    for (p=buf; ; ++p) {
-	if (*p=='\r' || *p=='\n') *p='\0';
-	else if (!*p) break;
-    }
-
-    if (!*buf)
-	return PJ_FALSE;
-    
-    return PJ_TRUE;
-}
-
-
-#define NO_NB	-2
-struct input_result
-{
-    int	  nb_result;
-    char *uri_result;
-};
-
-
-/*
- * Input URL.
- */
-static void ui_input_url(const char *title, char *buf, int len, 
-			 struct input_result *result)
-{
-    result->nb_result = NO_NB;
-    result->uri_result = NULL;
-
-    print_buddy_list();
-
-    printf("Choices:\n"
-	   "   0         For current dialog.\n"
-	   "  -1         All %d buddies in buddy list\n"
-	   "  [1 -%2d]    Select from buddy list\n"
-	   "  URL        An URL\n"
-	   "  <Enter>    Empty input (or 'q') to cancel\n"
-	   , pjsua_get_buddy_count(), pjsua_get_buddy_count());
-    printf("%s: ", title);
-
-    fflush(stdout);
-    if (fgets(buf, len, stdin) == NULL)
-	return;
-    len = strlen(buf);
-
-    /* Left trim */
-    while (pj_isspace(*buf)) {
-	++buf;
-	--len;
-    }
-
-    /* Remove trailing newlines */
-    while (len && (buf[len-1] == '\r' || buf[len-1] == '\n'))
-	buf[--len] = '\0';
-
-    if (len == 0 || buf[0]=='q')
-	return;
-
-    if (pj_isdigit(*buf) || *buf=='-') {
-	
-	int i;
-	
-	if (*buf=='-')
-	    i = 1;
-	else
-	    i = 0;
-
-	for (; i<len; ++i) {
-	    if (!pj_isdigit(buf[i])) {
-		puts("Invalid input");
-		return;
-	    }
-	}
-
-	result->nb_result = my_atoi(buf);
-
-	if (result->nb_result >= 0 && 
-	    result->nb_result <= (int)pjsua_get_buddy_count()) 
-	{
-	    return;
-	}
-	if (result->nb_result == -1)
-	    return;
-
-	puts("Invalid input");
-	result->nb_result = NO_NB;
-	return;
-
-    } else {
-	pj_status_t status;
-
-	if ((status=pjsua_verify_url(buf)) != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, "Invalid URL", status);
-	    return;
-	}
-
-	result->uri_result = buf;
-    }
-}
-
-/*
- * List the ports in conference bridge
- */
-static void conf_list(void)
-{
-    unsigned i, count;
-    pjsua_conf_port_id id[PJSUA_MAX_CALLS];
-
-    printf("Conference ports:\n");
-
-    count = PJ_ARRAY_SIZE(id);
-    pjsua_enum_conf_ports(id, &count);
-
-    for (i=0; i<count; ++i) {
-	char txlist[PJSUA_MAX_CALLS*4+10];
-	unsigned j;
-	pjsua_conf_port_info info;
-
-	pjsua_conf_get_port_info(id[i], &info);
-
-	txlist[0] = '\0';
-	for (j=0; j<info.listener_cnt; ++j) {
-	    char s[10];
-	    pj_ansi_sprintf(s, "#%d ", info.listeners[j]);
-	    pj_ansi_strcat(txlist, s);
-	}
-	printf("Port #%02d[%2dKHz/%dms/%d] %20.*s  transmitting to: %s\n", 
-	       info.slot_id, 
-	       info.clock_rate/1000,
-	       info.samples_per_frame*1000/info.channel_count/info.clock_rate,
-	       info.channel_count,
-	       (int)info.name.slen, 
-	       info.name.ptr,
-	       txlist);
-
-    }
-    puts("");
-}
-
-
-/*
- * Send arbitrary request to remote host
- */
-static void send_request(char *cstr_method, const pj_str_t *dst_uri)
-{
-    pj_str_t str_method;
-    pjsip_method method;
-    pjsip_tx_data *tdata;
-    pjsip_endpoint *endpt;
-    pj_status_t status;
-
-    endpt = pjsua_get_pjsip_endpt();
-
-    str_method = pj_str(cstr_method);
-    pjsip_method_init_np(&method, &str_method);
-
-    status = pjsua_acc_create_request(current_acc, &method, dst_uri, &tdata);
-
-    status = pjsip_endpt_send_request(endpt, tdata, -1, NULL, NULL);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Unable to send request", status);
-	return;
-    }
-}
-
-
-/*
- * Change extended online status.
- */
-static void change_online_status(void)
-{
-    char menuin[32];
-    pj_bool_t online_status;
-    pjrpid_element elem;
-    int i, choice;
-
-    enum {
-	AVAILABLE, BUSY, OTP, IDLE, AWAY, BRB, OFFLINE, OPT_MAX
-    };
-
-    struct opt {
-	int id;
-	char *name;
-    } opts[] = {
-	{ AVAILABLE, "Available" },
-	{ BUSY, "Busy"},
-	{ OTP, "On the phone"},
-	{ IDLE, "Idle"},
-	{ AWAY, "Away"},
-	{ BRB, "Be right back"},
-	{ OFFLINE, "Offline"}
-    };
-
-    printf("\n"
-	   "Choices:\n");
-    for (i=0; i<PJ_ARRAY_SIZE(opts); ++i) {
-	printf("  %d  %s\n", opts[i].id+1, opts[i].name);
-    }
-
-    if (!simple_input("Select status", menuin, sizeof(menuin)))
-	return;
-
-    choice = atoi(menuin) - 1;
-    if (choice < 0 || choice >= OPT_MAX) {
-	puts("Invalid selection");
-	return;
-    }
-
-    pj_bzero(&elem, sizeof(elem));
-    elem.type = PJRPID_ELEMENT_TYPE_PERSON;
-
-    online_status = PJ_TRUE;
-
-    switch (choice) {
-    case AVAILABLE:
-	break;
-    case BUSY:
-	elem.activity = PJRPID_ACTIVITY_BUSY;
-	elem.note = pj_str("Busy");
-	break;
-    case OTP:
-	elem.activity = PJRPID_ACTIVITY_BUSY;
-	elem.note = pj_str("On the phone");
-	break;
-    case IDLE:
-	elem.activity = PJRPID_ACTIVITY_UNKNOWN;
-	elem.note = pj_str("Idle");
-	break;
-    case AWAY:
-	elem.activity = PJRPID_ACTIVITY_AWAY;
-	elem.note = pj_str("Away");
-	break;
-    case BRB:
-	elem.activity = PJRPID_ACTIVITY_UNKNOWN;
-	elem.note = pj_str("Be right back");
-	break;
-    case OFFLINE:
-	online_status = PJ_FALSE;
-	break;
-    }
-
-    pjsua_acc_set_online_status2(current_acc, online_status, &elem);
-}
-
-
-/*
- * Change codec priorities.
- */
-static void manage_codec_prio(void)
-{
-    pjsua_codec_info c[32];
-    unsigned i, count = PJ_ARRAY_SIZE(c);
-    char input[32];
-    char *codec, *prio;
-    pj_str_t id;
-    int new_prio;
-    pj_status_t status;
-
-    printf("List of audio codecs:\n");
-    pjsua_enum_codecs(c, &count);
-    for (i=0; i<count; ++i) {
-	printf("  %d\t%.*s\n", c[i].priority, (int)c[i].codec_id.slen,
-			       c[i].codec_id.ptr);
-    }
-
-#if PJSUA_HAS_VIDEO
-    puts("");
-    printf("List of video codecs:\n");
-    pjsua_vid_enum_codecs(c, &count);
-    for (i=0; i<count; ++i) {
-	printf("  %d\t%.*s%s%.*s\n", c[i].priority,
-				     (int)c[i].codec_id.slen,
-				     c[i].codec_id.ptr,
-				     c[i].desc.slen? " - ":"",
-				     (int)c[i].desc.slen,
-				     c[i].desc.ptr);
-    }
-#endif
-
-    puts("");
-    puts("Enter codec id and its new priority (e.g. \"speex/16000 200\", ""\"H263 200\"),");
-    puts("or empty to cancel.");
-
-    printf("Codec name (\"*\" for all) and priority: ");
-    if (fgets(input, sizeof(input), stdin) == NULL)
-	return;
-    if (input[0]=='\r' || input[0]=='\n') {
-	puts("Done");
-	return;
-    }
-
-    codec = strtok(input, " \t\r\n");
-    prio = strtok(NULL, " \r\n");
-
-    if (!codec || !prio) {
-	puts("Invalid input");
-	return;
-    }
-
-    new_prio = atoi(prio);
-    if (new_prio < 0) 
-	new_prio = 0;
-    else if (new_prio > PJMEDIA_CODEC_PRIO_HIGHEST) 
-	new_prio = PJMEDIA_CODEC_PRIO_HIGHEST;
-
-    status = pjsua_codec_set_priority(pj_cstr(&id, codec), 
-				      (pj_uint8_t)new_prio);
-#if PJSUA_HAS_VIDEO
-    if (status != PJ_SUCCESS) {
-	status = pjsua_vid_codec_set_priority(pj_cstr(&id, codec), 
-					      (pj_uint8_t)new_prio);
-    }
-#endif
-    if (status != PJ_SUCCESS)
-	pjsua_perror(THIS_FILE, "Error setting codec priority", status);
-}
-
-
-#if PJSUA_HAS_VIDEO
-static void vid_print_dev(int id, const pjmedia_vid_dev_info *vdi,
-                          const char *title)
-{
-    char capnames[120];
-    char formats[120];
-    const char *dirname;
-    unsigned i;
-
-    if (vdi->dir == PJMEDIA_DIR_CAPTURE_RENDER) {
-	dirname = "capture, render";
-    } else if (vdi->dir == PJMEDIA_DIR_CAPTURE) {
-	dirname = "capture";
-    } else {
-	dirname = "render";
-    }
-
-
-    capnames[0] = '\0';
-    for (i=0; i<sizeof(int)*8 && (1 << i) < PJMEDIA_VID_DEV_CAP_MAX; ++i) {
-	if (vdi->caps & (1 << i)) {
-	    const char *capname = pjmedia_vid_dev_cap_name(1 << i, NULL);
-	    if (capname) {
-		if (*capnames)
-		    strcat(capnames, ", ");
-		strncat(capnames, capname,
-		        sizeof(capnames)-strlen(capnames)-1);
-	    }
-	}
-    }
-
-    formats[0] = '\0';
-    for (i=0; i<vdi->fmt_cnt; ++i) {
-	const pjmedia_video_format_info *vfi =
-		pjmedia_get_video_format_info(NULL, vdi->fmt[i].id);
-	if (vfi) {
-	    if (*formats)
-		strcat(formats, ", ");
-	    strncat(formats, vfi->name, sizeof(formats)-strlen(formats)-1);
-	}
-    }
-
-    PJ_LOG(3,(THIS_FILE, "%3d %s [%s][%s] %s", id, vdi->name, vdi->driver,
-	      dirname, title));
-    PJ_LOG(3,(THIS_FILE, "    Supported capabilities: %s", capnames));
-    PJ_LOG(3,(THIS_FILE, "    Supported formats: %s", formats));
-}
-
-static void vid_list_devs(void)
-{
-    unsigned i, count;
-    pjmedia_vid_dev_info vdi;
-    pj_status_t status;
-
-    PJ_LOG(3,(THIS_FILE, "Video device list:"));
-    count = pjsua_vid_dev_count();
-    if (count == 0) {
-	PJ_LOG(3,(THIS_FILE, " - no device detected -"));
-	return;
-    } else {
-	PJ_LOG(3,(THIS_FILE, "%d device(s) detected:", count));
-    }
-
-    status = pjsua_vid_dev_get_info(PJMEDIA_VID_DEFAULT_RENDER_DEV, &vdi);
-    if (status == PJ_SUCCESS)
-	vid_print_dev(PJMEDIA_VID_DEFAULT_RENDER_DEV, &vdi,
-	              "(default renderer device)");
-
-    status = pjsua_vid_dev_get_info(PJMEDIA_VID_DEFAULT_CAPTURE_DEV, &vdi);
-    if (status == PJ_SUCCESS)
-	vid_print_dev(PJMEDIA_VID_DEFAULT_CAPTURE_DEV, &vdi,
-	              "(default capture device)");
-
-    for (i=0; i<count; ++i) {
-	status = pjsua_vid_dev_get_info(i, &vdi);
-	if (status == PJ_SUCCESS)
-	    vid_print_dev(i, &vdi, "");
-    }
-}
-
-static void app_config_init_video(pjsua_acc_config *acc_cfg)
-{
-    acc_cfg->vid_in_auto_show = app_config.vid.in_auto_show;
-    acc_cfg->vid_out_auto_transmit = app_config.vid.out_auto_transmit;
-    /* Note that normally GUI application will prefer a borderless
-     * window.
-     */
-    acc_cfg->vid_wnd_flags = PJMEDIA_VID_DEV_WND_BORDER |
-                             PJMEDIA_VID_DEV_WND_RESIZABLE;
-    acc_cfg->vid_cap_dev = app_config.vid.vcapture_dev;
-    acc_cfg->vid_rend_dev = app_config.vid.vrender_dev;
-
-    if (app_config.avi_auto_play &&
-	app_config.avi_def_idx != PJSUA_INVALID_ID &&
-	app_config.avi[app_config.avi_def_idx].dev_id != PJMEDIA_VID_INVALID_DEV)
-    {
-	acc_cfg->vid_cap_dev = app_config.avi[app_config.avi_def_idx].dev_id;
-    }
-}
-
-static void app_config_show_video(int acc_id, const pjsua_acc_config *acc_cfg)
-{
-    PJ_LOG(3,(THIS_FILE,
-	      "Account %d:\n"
-	      "  RX auto show:     %d\n"
-	      "  TX auto transmit: %d\n"
-	      "  Capture dev:      %d\n"
-	      "  Render dev:       %d",
-	      acc_id,
-	      acc_cfg->vid_in_auto_show,
-	      acc_cfg->vid_out_auto_transmit,
-	      acc_cfg->vid_cap_dev,
-	      acc_cfg->vid_rend_dev));
-}
-
-static void vid_handle_menu(char *menuin)
-{
-    char *argv[8];
-    int argc = 0;
-
-    /* Tokenize */
-    argv[argc] = strtok(menuin, " \t\r\n");
-    while (argv[argc] && *argv[argc]) {
-	argc++;
-	argv[argc] = strtok(NULL, " \t\r\n");
-    }
-
-    if (argc == 1 || strcmp(argv[1], "help")==0) {
-	vid_show_help();
-    } else if (argc == 2 && (strcmp(argv[1], "enable")==0 ||
-			     strcmp(argv[1], "disable")==0))
-    {
-	pj_bool_t enabled = (strcmp(argv[1], "enable")==0);
-	app_config.vid.vid_cnt = (enabled ? 1 : 0);
-	PJ_LOG(3,(THIS_FILE, "Video will be %s in next offer/answer",
-		  (enabled?"enabled":"disabled")));
-    } else if (strcmp(argv[1], "acc")==0) {
-	pjsua_acc_config acc_cfg;
-	pj_bool_t changed = PJ_FALSE;
-
-	pjsua_acc_get_config(current_acc, &acc_cfg);
-
-	if (argc == 3 && strcmp(argv[2], "show")==0) {
-	    app_config_show_video(current_acc, &acc_cfg);
-	} else if (argc == 4 && strcmp(argv[2], "autorx")==0) {
-	    int on = (strcmp(argv[3], "on")==0);
-	    acc_cfg.vid_in_auto_show = on;
-	    changed = PJ_TRUE;
-	} else if (argc == 4 && strcmp(argv[2], "autotx")==0) {
-	    int on = (strcmp(argv[3], "on")==0);
-	    acc_cfg.vid_out_auto_transmit = on;
-	    changed = PJ_TRUE;
-	} else if (argc == 4 && strcmp(argv[2], "cap")==0) {
-	    int dev = atoi(argv[3]);
-	    acc_cfg.vid_cap_dev = dev;
-	    changed = PJ_TRUE;
-	} else if (argc == 4 && strcmp(argv[2], "rend")==0) {
-	    int dev = atoi(argv[3]);
-	    acc_cfg.vid_rend_dev = dev;
-	    changed = PJ_TRUE;
-	} else {
-	    goto on_error;
-	}
-
-	if (changed) {
-	    pj_status_t status = pjsua_acc_modify(current_acc, &acc_cfg);
-	    if (status != PJ_SUCCESS)
-		PJ_PERROR(1,(THIS_FILE, status, "Error modifying account %d",
-			     current_acc));
-	}
-
-    } else if (strcmp(argv[1], "call")==0) {
-	pjsua_call_vid_strm_op_param param;
-	pj_status_t status = PJ_SUCCESS;
-
-	pjsua_call_vid_strm_op_param_default(&param);
-
-	if (argc == 5 && strcmp(argv[2], "rx")==0) {
-	    pjsua_stream_info si;
-	    pj_bool_t on = (strcmp(argv[3], "on") == 0);
-
-	    param.med_idx = atoi(argv[4]);
-	    if (pjsua_call_get_stream_info(current_call, param.med_idx, &si) ||
-		si.type != PJMEDIA_TYPE_VIDEO)
-	    {
-		PJ_PERROR(1,(THIS_FILE, PJ_EINVAL, "Invalid stream"));
-		return;
-	    }
-
-	    if (on) param.dir = (si.info.vid.dir | PJMEDIA_DIR_DECODING);
-	    else param.dir = (si.info.vid.dir & PJMEDIA_DIR_ENCODING);
-
-	    status = pjsua_call_set_vid_strm(current_call,
-	                                     PJSUA_CALL_VID_STRM_CHANGE_DIR,
-	                                     &param);
-	}
-	else if (argc == 5 && strcmp(argv[2], "tx")==0) {
-	    pj_bool_t on = (strcmp(argv[3], "on") == 0);
-	    pjsua_call_vid_strm_op op = on? PJSUA_CALL_VID_STRM_START_TRANSMIT :
-					    PJSUA_CALL_VID_STRM_STOP_TRANSMIT;
-
-	    param.med_idx = atoi(argv[4]);
-
-	    status = pjsua_call_set_vid_strm(current_call, op, &param);
-	}
-	else if (argc == 3 && strcmp(argv[2], "add")==0) {
-	    status = pjsua_call_set_vid_strm(current_call,
-	                                     PJSUA_CALL_VID_STRM_ADD, NULL);
-	}
-	else if (argc >= 3 && 
-		 (strcmp(argv[2], "disable")==0 || strcmp(argv[2], "enable")==0))
-	{
-	    pj_bool_t enable = (strcmp(argv[2], "enable") == 0);
-	    pjsua_call_vid_strm_op op = enable? PJSUA_CALL_VID_STRM_CHANGE_DIR :
-						PJSUA_CALL_VID_STRM_REMOVE;
-
-	    param.med_idx = argc >= 4? atoi(argv[3]) : -1;
-	    param.dir = PJMEDIA_DIR_ENCODING_DECODING;
-	    status = pjsua_call_set_vid_strm(current_call, op, &param);
-	}
-	else if (argc >= 3 && strcmp(argv[2], "cap")==0) {
-	    param.med_idx = argc >= 4? atoi(argv[3]) : -1;
-	    param.cap_dev = argc >= 5? atoi(argv[4]) : PJMEDIA_VID_DEFAULT_CAPTURE_DEV;
-	    status = pjsua_call_set_vid_strm(current_call,
-	                                     PJSUA_CALL_VID_STRM_CHANGE_CAP_DEV,
-	                                     &param);
-	} else
-	    goto on_error;
-
-	if (status != PJ_SUCCESS) {
-	    PJ_PERROR(1,(THIS_FILE, status, "Error modifying video stream"));
-	}
-
-    } else if (argc >= 3 && strcmp(argv[1], "dev")==0) {
-	if (strcmp(argv[2], "list")==0) {
-	    vid_list_devs();
-	} else if (strcmp(argv[2], "refresh")==0) {
-	    pjmedia_vid_dev_refresh();
-	} else if (strcmp(argv[2], "prev")==0) {
-	    if (argc != 5) {
-		goto on_error;
-	    } else {
-		pj_bool_t on = (strcmp(argv[3], "on") == 0);
-		int dev_id = atoi(argv[4]);
-		if (on) {
-                    pjsua_vid_preview_param param;
-
-                    pjsua_vid_preview_param_default(&param);
-                    param.wnd_flags = PJMEDIA_VID_DEV_WND_BORDER |
-                                      PJMEDIA_VID_DEV_WND_RESIZABLE;
-		    pjsua_vid_preview_start(dev_id, &param);
-		    arrange_window(pjsua_vid_preview_get_win(dev_id));
-		} else {
-		    pjsua_vid_win_id wid;
-		    wid = pjsua_vid_preview_get_win(dev_id);
-		    if (wid != PJSUA_INVALID_ID) {
-			/* Preview window hiding once it is stopped is
-			 * responsibility of app */
-			pjsua_vid_win_set_show(wid, PJ_FALSE);
-			pjsua_vid_preview_stop(dev_id);
-		    }
-		}
-	    }
-	} else
-	    goto on_error;
-    } else if (strcmp(argv[1], "win")==0) {
-	pj_status_t status = PJ_SUCCESS;
-
-	if (argc==3 && strcmp(argv[2], "list")==0) {
-	    pjsua_vid_win_id wids[PJSUA_MAX_VID_WINS];
-	    unsigned i, cnt = PJ_ARRAY_SIZE(wids);
-
-	    pjsua_vid_enum_wins(wids, &cnt);
-
-	    PJ_LOG(3,(THIS_FILE, "Found %d video windows:", cnt));
-	    PJ_LOG(3,(THIS_FILE, "WID show    pos       size"));
-	    PJ_LOG(3,(THIS_FILE, "------------------------------"));
-	    for (i = 0; i < cnt; ++i) {
-		pjsua_vid_win_info wi;
-		pjsua_vid_win_get_info(wids[i], &wi);
-		PJ_LOG(3,(THIS_FILE, "%3d   %c  (%d,%d)  %dx%d",
-			  wids[i], (wi.show?'Y':'N'), wi.pos.x, wi.pos.y,
-			  wi.size.w, wi.size.h));
-	    }
-	} else if (argc==4 && (strcmp(argv[2], "show")==0 ||
-			       strcmp(argv[2], "hide")==0))
-	{
-	    pj_bool_t show = (strcmp(argv[2], "show")==0);
-	    pjsua_vid_win_id wid = atoi(argv[3]);
-	    status = pjsua_vid_win_set_show(wid, show);
-	} else if (argc==6 && strcmp(argv[2], "move")==0) {
-	    pjsua_vid_win_id wid = atoi(argv[3]);
-	    pjmedia_coord pos;
-
-	    pos.x = atoi(argv[4]);
-	    pos.y = atoi(argv[5]);
-	    status = pjsua_vid_win_set_pos(wid, &pos);
-	} else if (argc==6 && strcmp(argv[2], "resize")==0) {
-	    pjsua_vid_win_id wid = atoi(argv[3]);
-	    pjmedia_rect_size size;
-
-	    size.w = atoi(argv[4]);
-	    size.h = atoi(argv[5]);
-	    status = pjsua_vid_win_set_size(wid, &size);
-	} else if (argc==3 && strcmp(argv[2], "arrange")==0) {
-	    arrange_window(PJSUA_INVALID_ID);
-	} else
-	    goto on_error;
-
-	if (status != PJ_SUCCESS) {
-	    PJ_PERROR(1,(THIS_FILE, status, "Window operation error"));
-	}
-
-    } else if (strcmp(argv[1], "codec")==0) {
-	pjsua_codec_info ci[PJMEDIA_CODEC_MGR_MAX_CODECS];
-	unsigned count = PJ_ARRAY_SIZE(ci);
-	pj_status_t status;
-
-	if (argc==3 && strcmp(argv[2], "list")==0) {
-	    status = pjsua_vid_enum_codecs(ci, &count);
-	    if (status != PJ_SUCCESS) {
-		PJ_PERROR(1,(THIS_FILE, status, "Error enumerating codecs"));
-	    } else {
-		unsigned i;
-		PJ_LOG(3,(THIS_FILE, "Found %d video codecs:", count));
-		PJ_LOG(3,(THIS_FILE, "codec id      prio  fps    bw(kbps)   size"));
-		PJ_LOG(3,(THIS_FILE, "------------------------------------------"));
-		for (i=0; i<count; ++i) {
-		    pjmedia_vid_codec_param cp;
-		    pjmedia_video_format_detail *vfd;
-
-		    status = pjsua_vid_codec_get_param(&ci[i].codec_id, &cp);
-		    if (status != PJ_SUCCESS)
-			continue;
-
-		    vfd = pjmedia_format_get_video_format_detail(&cp.enc_fmt,
-								 PJ_TRUE);
-		    PJ_LOG(3,(THIS_FILE, "%.*s%.*s %3d %7.2f  %4d/%4d  %dx%d", 
-			      (int)ci[i].codec_id.slen, ci[i].codec_id.ptr,
-			      13-(int)ci[i].codec_id.slen, "                ",
-			      ci[i].priority,
-			      (vfd->fps.num*1.0/vfd->fps.denum),
-			      vfd->avg_bps/1000, vfd->max_bps/1000,
-			      vfd->size.w, vfd->size.h));
-		}
-	    }
-	} else if (argc==5 && strcmp(argv[2], "prio")==0) {
-	    pj_str_t cid;
-	    int prio;
-	    cid = pj_str(argv[3]);
-	    prio = atoi(argv[4]);
-	    status = pjsua_vid_codec_set_priority(&cid, (pj_uint8_t)prio);
-	    if (status != PJ_SUCCESS)
-		PJ_PERROR(1,(THIS_FILE, status, "Set codec priority error"));
-	} else if (argc==6 && strcmp(argv[2], "fps")==0) {
-	    pjmedia_vid_codec_param cp;
-	    pj_str_t cid;
-	    int M, N;
-	    cid = pj_str(argv[3]);
-	    M = atoi(argv[4]);
-	    N = atoi(argv[5]);
-	    status = pjsua_vid_codec_get_param(&cid, &cp);
-	    if (status == PJ_SUCCESS) {
-		cp.enc_fmt.det.vid.fps.num = M;
-		cp.enc_fmt.det.vid.fps.denum = N;
-		status = pjsua_vid_codec_set_param(&cid, &cp);
-	    }
-	    if (status != PJ_SUCCESS)
-		PJ_PERROR(1,(THIS_FILE, status, "Set codec framerate error"));
-	} else if (argc==6 && strcmp(argv[2], "bw")==0) {
-	    pjmedia_vid_codec_param cp;
-	    pj_str_t cid;
-	    int M, N;
-	    cid = pj_str(argv[3]);
-	    M = atoi(argv[4]);
-	    N = atoi(argv[5]);
-	    status = pjsua_vid_codec_get_param(&cid, &cp);
-	    if (status == PJ_SUCCESS) {
-		cp.enc_fmt.det.vid.avg_bps = M * 1000;
-		cp.enc_fmt.det.vid.max_bps = N * 1000;
-		status = pjsua_vid_codec_set_param(&cid, &cp);
-	    }
-	    if (status != PJ_SUCCESS)
-		PJ_PERROR(1,(THIS_FILE, status, "Set codec bitrate error"));
-	} else if (argc==6 && strcmp(argv[2], "size")==0) {
-	    pjmedia_vid_codec_param cp;
-	    pj_str_t cid;
-	    int M, N;
-	    cid = pj_str(argv[3]);
-	    M = atoi(argv[4]);
-	    N = atoi(argv[5]);
-	    status = pjsua_vid_codec_get_param(&cid, &cp);
-	    if (status == PJ_SUCCESS) {
-		cp.enc_fmt.det.vid.size.w = M;
-		cp.enc_fmt.det.vid.size.h = N;
-		status = pjsua_vid_codec_set_param(&cid, &cp);
-	    }
-	    if (status != PJ_SUCCESS)
-		PJ_PERROR(1,(THIS_FILE, status, "Set codec size error"));
-	} else
-	    goto on_error;
-    } else
-	goto on_error;
-
-    return;
-
-on_error:
-    PJ_LOG(1,(THIS_FILE, "Invalid command, use 'vid help'"));
-}
-
-#else
-
-static void app_config_init_video(pjsua_acc_config *acc_cfg)
-{
-    PJ_UNUSED_ARG(acc_cfg);
-}
-
-#endif /* PJSUA_HAS_VIDEO */
-
-
-/*
- * Main "user interface" loop.
- */
-void console_app_main(const pj_str_t *uri_to_call)
-{
-    char menuin[80];
-    char buf[128];
-    char text[128];
-    int i, count;
-    char *uri;
-    pj_str_t tmp;
-    struct input_result result;
-    pjsua_msg_data msg_data;
-    pjsua_call_info call_info;
-    pjsua_acc_info acc_info;
-    pjsua_call_setting call_opt;
-
-    pjsua_call_setting_default(&call_opt);
-    call_opt.aud_cnt = app_config.aud_cnt;
-    call_opt.vid_cnt = app_config.vid.vid_cnt;
-
-    /* If user specifies URI to call, then call the URI */
-    if (uri_to_call->slen) {
-	pjsua_call_make_call( current_acc, uri_to_call, &call_opt, NULL, NULL, NULL);
-    }
-
-    keystroke_help();
-
-    for (;;) {
-
-	printf(">>> ");
-	fflush(stdout);
-
-	if (fgets(menuin, sizeof(menuin), stdin) == NULL) {
-	    /* 
-	     * Be friendly to users who redirect commands into
-	     * program, when file ends, resume with kbd.
-	     * If exit is desired end script with q for quit
-	     */
- 	    /* Reopen stdin/stdout/stderr to /dev/console */
-#if defined(PJ_WIN32) && PJ_WIN32!=0
-	    if (freopen ("CONIN$", "r", stdin) == NULL) {
-#else
-	    if (1) {
-#endif
-		puts("Cannot switch back to console from file redirection");
-		menuin[0] = 'q';
-		menuin[1] = '\0';
-	    } else {
-		puts("Switched back to console from file redirection");
-		continue;
-	    }
-	}
-
-	if (cmd_echo) {
-	    printf("%s", menuin);
-	}
-
-	/* Update call setting */
-	pjsua_call_setting_default(&call_opt);
-	call_opt.aud_cnt = app_config.aud_cnt;
-	call_opt.vid_cnt = app_config.vid.vid_cnt;
-
-	switch (menuin[0]) {
-
-	case 'm':
-	    /* Make call! : */
-	    printf("(You currently have %d calls)\n", 
-		     pjsua_call_get_count());
-	    
-	    uri = NULL;
-	    ui_input_url("Make call", buf, sizeof(buf), &result);
-	    if (result.nb_result != NO_NB) {
-
-		if (result.nb_result == -1 || result.nb_result == 0) {
-		    puts("You can't do that with make call!");
-		    continue;
-		} else {
-		    pjsua_buddy_info binfo;
-		    pjsua_buddy_get_info(result.nb_result-1, &binfo);
-		    tmp.ptr = buf;
-		    pj_strncpy(&tmp, &binfo.uri, sizeof(buf));
-		}
-
-	    } else if (result.uri_result) {
-		tmp = pj_str(result.uri_result);
-	    } else {
-		tmp.slen = 0;
-	    }
-	    
-	    pjsua_msg_data_init(&msg_data);
-	    TEST_MULTIPART(&msg_data);
-	    pjsua_call_make_call( current_acc, &tmp, &call_opt, NULL,
-	                          &msg_data, &current_call);
-	    break;
-
-	case 'M':
-	    /* Make multiple calls! : */
-	    printf("(You currently have %d calls)\n", 
-		   pjsua_call_get_count());
-	    
-	    if (!simple_input("Number of calls", menuin, sizeof(menuin)))
-		continue;
-
-	    count = my_atoi(menuin);
-	    if (count < 1)
-		continue;
-
-	    ui_input_url("Make call", buf, sizeof(buf), &result);
-	    if (result.nb_result != NO_NB) {
-		pjsua_buddy_info binfo;
-		if (result.nb_result == -1 || result.nb_result == 0) {
-		    puts("You can't do that with make call!");
-		    continue;
-		}
-		pjsua_buddy_get_info(result.nb_result-1, &binfo);
-		tmp.ptr = buf;
-		pj_strncpy(&tmp, &binfo.uri, sizeof(buf));
-	    } else {
-		tmp = pj_str(result.uri_result);
-	    }
-
-	    for (i=0; i<my_atoi(menuin); ++i) {
-		pj_status_t status;
-	    
-		status = pjsua_call_make_call(current_acc, &tmp, &call_opt, NULL,
-					      NULL, NULL);
-		if (status != PJ_SUCCESS)
-		    break;
-	    }
-	    break;
-
-	case 'n':
-	    i = pjsua_detect_nat_type();
-	    if (i != PJ_SUCCESS)
-		pjsua_perror(THIS_FILE, "Error", i);
-	    break;
-
-	case 'i':
-	    /* Send instant messaeg */
-
-	    /* i is for call index to send message, if any */
-	    i = -1;
-    
-	    /* Make compiler happy. */
-	    uri = NULL;
-
-	    /* Input destination. */
-	    ui_input_url("Send IM to", buf, sizeof(buf), &result);
-	    if (result.nb_result != NO_NB) {
-
-		if (result.nb_result == -1) {
-		    puts("You can't send broadcast IM like that!");
-		    continue;
-
-		} else if (result.nb_result == 0) {
-    
-		    i = current_call;
-
-		} else {
-		    pjsua_buddy_info binfo;
-		    pjsua_buddy_get_info(result.nb_result-1, &binfo);
-		    tmp.ptr = buf;
-		    pj_strncpy_with_null(&tmp, &binfo.uri, sizeof(buf));
-		    uri = buf;
-		}
-
-	    } else if (result.uri_result) {
-		uri = result.uri_result;
-	    }
-	    
-
-	    /* Send typing indication. */
-	    if (i != -1)
-		pjsua_call_send_typing_ind(i, PJ_TRUE, NULL);
-	    else {
-		pj_str_t tmp_uri = pj_str(uri);
-		pjsua_im_typing(current_acc, &tmp_uri, PJ_TRUE, NULL);
-	    }
-
-	    /* Input the IM . */
-	    if (!simple_input("Message", text, sizeof(text))) {
-		/*
-		 * Cancelled.
-		 * Send typing notification too, saying we're not typing.
-		 */
-		if (i != -1)
-		    pjsua_call_send_typing_ind(i, PJ_FALSE, NULL);
-		else {
-		    pj_str_t tmp_uri = pj_str(uri);
-		    pjsua_im_typing(current_acc, &tmp_uri, PJ_FALSE, NULL);
-		}
-		continue;
-	    }
-
-	    tmp = pj_str(text);
-
-	    /* Send the IM */
-	    if (i != -1)
-		pjsua_call_send_im(i, NULL, &tmp, NULL, NULL);
-	    else {
-		pj_str_t tmp_uri = pj_str(uri);
-		pjsua_im_send(current_acc, &tmp_uri, NULL, &tmp, NULL, NULL);
-	    }
-
-	    break;
-
-	case 'a':
-
-	    if (current_call != -1) {
-		pjsua_call_get_info(current_call, &call_info);
-	    } else {
-		/* Make compiler happy */
-		call_info.role = PJSIP_ROLE_UAC;
-		call_info.state = PJSIP_INV_STATE_DISCONNECTED;
-	    }
-
-	    if (current_call == -1 || 
-		call_info.role != PJSIP_ROLE_UAS ||
-		call_info.state >= PJSIP_INV_STATE_CONNECTING)
-	    {
-		puts("No pending incoming call");
-		fflush(stdout);
-		continue;
-
-	    } else {
-		int st_code;
-		char contact[120];
-		pj_str_t hname = { "Contact", 7 };
-		pj_str_t hvalue;
-		pjsip_generic_string_hdr hcontact;
-
-		if (!simple_input("Answer with code (100-699)", buf, sizeof(buf)))
-		    continue;
-		
-		st_code = my_atoi(buf);
-		if (st_code < 100)
-		    continue;
-
-		pjsua_msg_data_init(&msg_data);
-
-		if (st_code/100 == 3) {
-		    if (!simple_input("Enter URL to be put in Contact", 
-				      contact, sizeof(contact)))
-			continue;
-		    hvalue = pj_str(contact);
-		    pjsip_generic_string_hdr_init2(&hcontact, &hname, &hvalue);
-
-		    pj_list_push_back(&msg_data.hdr_list, &hcontact);
-		}
-
-		/*
-		 * Must check again!
-		 * Call may have been disconnected while we're waiting for 
-		 * keyboard input.
-		 */
-		if (current_call == -1) {
-		    puts("Call has been disconnected");
-		    fflush(stdout);
-		    continue;
-		}
-
-		pjsua_call_answer2(current_call, &call_opt, st_code, NULL, &msg_data);
-	    }
-
-	    break;
-
-
-	case 'h':
-
-	    if (current_call == -1) {
-		puts("No current call");
-		fflush(stdout);
-		continue;
-
-	    } else if (menuin[1] == 'a') {
-		
-		/* Hangup all calls */
-		pjsua_call_hangup_all();
-
-	    } else {
-
-		/* Hangup current calls */
-		pjsua_call_hangup(current_call, 0, NULL, NULL);
-	    }
-	    break;
-
-	case ']':
-	case '[':
-	    /*
-	     * Cycle next/prev dialog.
-	     */
-	    if (menuin[0] == ']') {
-		find_next_call();
-
-	    } else {
-		find_prev_call();
-	    }
-
-	    if (current_call != -1) {
-		
-		pjsua_call_get_info(current_call, &call_info);
-		PJ_LOG(3,(THIS_FILE,"Current dialog: %.*s", 
-			  (int)call_info.remote_info.slen, 
-			  call_info.remote_info.ptr));
-
-	    } else {
-		PJ_LOG(3,(THIS_FILE,"No current dialog"));
-	    }
-	    break;
-
-
-	case '>':
-	case '<':
-	    if (!simple_input("Enter account ID to select", buf, sizeof(buf)))
-		break;
-
-	    i = my_atoi(buf);
-	    if (pjsua_acc_is_valid(i)) {
-		pjsua_acc_set_default(i);
-		PJ_LOG(3,(THIS_FILE, "Current account changed to %d", i));
-	    } else {
-		PJ_LOG(3,(THIS_FILE, "Invalid account id %d", i));
-	    }
-	    break;
-
-
-	case '+':
-	    if (menuin[1] == 'b') {
-	    
-		pjsua_buddy_config buddy_cfg;
-		pjsua_buddy_id buddy_id;
-		pj_status_t status;
-
-		if (!simple_input("Enter buddy's URI:", buf, sizeof(buf)))
-		    break;
-
-		if (pjsua_verify_url(buf) != PJ_SUCCESS) {
-		    printf("Invalid URI '%s'\n", buf);
-		    break;
-		}
-
-		pj_bzero(&buddy_cfg, sizeof(pjsua_buddy_config));
-
-		buddy_cfg.uri = pj_str(buf);
-		buddy_cfg.subscribe = PJ_TRUE;
-
-		status = pjsua_buddy_add(&buddy_cfg, &buddy_id);
-		if (status == PJ_SUCCESS) {
-		    printf("New buddy '%s' added at index %d\n",
-			   buf, buddy_id+1);
-		}
-
-	    } else if (menuin[1] == 'a') {
-
-		char id[80], registrar[80], realm[80], uname[80], passwd[30];
-		pjsua_acc_config acc_cfg;
-		pj_status_t status;
-
-		if (!simple_input("Your SIP URL:", id, sizeof(id)))
-		    break;
-		if (!simple_input("URL of the registrar:", registrar, sizeof(registrar)))
-		    break;
-		if (!simple_input("Auth Realm:", realm, sizeof(realm)))
-		    break;
-		if (!simple_input("Auth Username:", uname, sizeof(uname)))
-		    break;
-		if (!simple_input("Auth Password:", passwd, sizeof(passwd)))
-		    break;
-
-		pjsua_acc_config_default(&acc_cfg);
-		acc_cfg.id = pj_str(id);
-		acc_cfg.reg_uri = pj_str(registrar);
-		acc_cfg.cred_count = 1;
-		acc_cfg.cred_info[0].scheme = pj_str("Digest");
-		acc_cfg.cred_info[0].realm = pj_str(realm);
-		acc_cfg.cred_info[0].username = pj_str(uname);
-		acc_cfg.cred_info[0].data_type = 0;
-		acc_cfg.cred_info[0].data = pj_str(passwd);
-
-		acc_cfg.rtp_cfg = app_config.rtp_cfg;
-		app_config_init_video(&acc_cfg);
-
-		status = pjsua_acc_add(&acc_cfg, PJ_TRUE, NULL);
-		if (status != PJ_SUCCESS) {
-		    pjsua_perror(THIS_FILE, "Error adding new account", status);
-		}
-
-	    } else {
-		printf("Invalid input %s\n", menuin);
-	    }
-	    break;
-
-	case '-':
-	    if (menuin[1] == 'b') {
-		if (!simple_input("Enter buddy ID to delete",buf,sizeof(buf)))
-		    break;
-
-		i = my_atoi(buf) - 1;
-
-		if (!pjsua_buddy_is_valid(i)) {
-		    printf("Invalid buddy id %d\n", i);
-		} else {
-		    pjsua_buddy_del(i);
-		    printf("Buddy %d deleted\n", i);
-		}
-
-	    } else if (menuin[1] == 'a') {
-
-		if (!simple_input("Enter account ID to delete",buf,sizeof(buf)))
-		    break;
-
-		i = my_atoi(buf);
-
-		if (!pjsua_acc_is_valid(i)) {
-		    printf("Invalid account id %d\n", i);
-		} else {
-		    pjsua_acc_del(i);
-		    printf("Account %d deleted\n", i);
-		}
-
-	    } else {
-		printf("Invalid input %s\n", menuin);
-	    }
-	    break;
-
-	case 'H':
-	    /*
-	     * Hold call.
-	     */
-	    if (current_call != -1) {
-		
-		pjsua_call_set_hold(current_call, NULL);
-
-	    } else {
-		PJ_LOG(3,(THIS_FILE, "No current call"));
-	    }
-	    break;
-
-	case 'v':
-#if PJSUA_HAS_VIDEO
-	    if (menuin[1]=='i' && menuin[2]=='d' && menuin[3]==' ') {
-
-		vid_handle_menu(menuin);
-
-	    } else
-#endif
-	    if (current_call != -1) {
-		/*
-		 * re-INVITE
-		 */
-		call_opt.flag |= PJSUA_CALL_UNHOLD;
-		pjsua_call_reinvite2(current_call, &call_opt, NULL);
-
-	    } else {
-		PJ_LOG(3,(THIS_FILE, "No current call"));
-	    }
-	    break;
-
-	case 'U':
-	    /*
-	     * Send UPDATE
-	     */
-	    if (current_call != -1) {
-		
-		pjsua_call_update2(current_call, &call_opt, NULL);
-
-	    } else {
-		PJ_LOG(3,(THIS_FILE, "No current call"));
-	    }
-	    break;
-
-	case 'C':
-	    if (menuin[1] == 'p') {
-		manage_codec_prio();
-	    }
-	    break;
-
-	case 'x':
-	    /*
-	     * Transfer call.
-	     */
-	    if (current_call == -1) {
-		
-		PJ_LOG(3,(THIS_FILE, "No current call"));
-
-	    } else {
-		int call = current_call;
-		pjsip_generic_string_hdr refer_sub;
-		pj_str_t STR_REFER_SUB = { "Refer-Sub", 9 };
-		pj_str_t STR_FALSE = { "false", 5 };
-		pjsua_call_info ci;
-
-		pjsua_call_get_info(current_call, &ci);
-		printf("Transfering current call [%d] %.*s\n",
-		       current_call,
-		       (int)ci.remote_info.slen, ci.remote_info.ptr);
-
-		ui_input_url("Transfer to URL", buf, sizeof(buf), &result);
-
-		/* Check if call is still there. */
-
-		if (call != current_call) {
-		    puts("Call has been disconnected");
-		    continue;
-		}
-
-		pjsua_msg_data_init(&msg_data);
-		if (app_config.no_refersub) {
-		    /* Add Refer-Sub: false in outgoing REFER request */
-		    pjsip_generic_string_hdr_init2(&refer_sub, &STR_REFER_SUB,
-						   &STR_FALSE);
-		    pj_list_push_back(&msg_data.hdr_list, &refer_sub);
-		}
-		if (result.nb_result != NO_NB) {
-		    if (result.nb_result == -1 || result.nb_result == 0)
-			puts("You can't do that with transfer call!");
-		    else {
-			pjsua_buddy_info binfo;
-			pjsua_buddy_get_info(result.nb_result-1, &binfo);
-			pjsua_call_xfer( current_call, &binfo.uri, &msg_data);
-		    }
-
-		} else if (result.uri_result) {
-		    pj_str_t tmp;
-		    tmp = pj_str(result.uri_result);
-		    pjsua_call_xfer( current_call, &tmp, &msg_data);
-		}
-	    }
-	    break;
-
-	case 'X':
-	    /*
-	     * Transfer call with replaces.
-	     */
-	    if (current_call == -1) {
-		
-		PJ_LOG(3,(THIS_FILE, "No current call"));
-
-	    } else {
-		int call = current_call;
-		int dst_call;
-		pjsip_generic_string_hdr refer_sub;
-		pj_str_t STR_REFER_SUB = { "Refer-Sub", 9 };
-		pj_str_t STR_FALSE = { "false", 5 };
-		pjsua_call_id ids[PJSUA_MAX_CALLS];
-		pjsua_call_info ci;
-		unsigned i, count;
-
-		count = PJ_ARRAY_SIZE(ids);
-		pjsua_enum_calls(ids, &count);
-
-		if (count <= 1) {
-		    puts("There are no other calls");
-		    continue;
-		}
-
-		pjsua_call_get_info(current_call, &ci);
-		printf("Transfer call [%d] %.*s to one of the following:\n",
-		       current_call,
-		       (int)ci.remote_info.slen, ci.remote_info.ptr);
-
-		for (i=0; i<count; ++i) {
-		    pjsua_call_info call_info;
-
-		    if (ids[i] == call)
-			continue;
-
-		    pjsua_call_get_info(ids[i], &call_info);
-		    printf("%d  %.*s [%.*s]\n",
-			   ids[i],
-			   (int)call_info.remote_info.slen,
-			   call_info.remote_info.ptr,
-			   (int)call_info.state_text.slen,
-			   call_info.state_text.ptr);
-		}
-
-		if (!simple_input("Enter call number to be replaced", 
-			          buf, sizeof(buf)))
-		    continue;
-
-		dst_call = my_atoi(buf);
-
-		/* Check if call is still there. */
-
-		if (call != current_call) {
-		    puts("Call has been disconnected");
-		    continue;
-		}
-
-		/* Check that destination call is valid. */
-		if (dst_call == call) {
-		    puts("Destination call number must not be the same "
-			 "as the call being transfered");
-		    continue;
-		}
-		if (dst_call >= PJSUA_MAX_CALLS) {
-		    puts("Invalid destination call number");
-		    continue;
-		}
-		if (!pjsua_call_is_active(dst_call)) {
-		    puts("Invalid destination call number");
-		    continue;
-		}
-
-		pjsua_msg_data_init(&msg_data);
-		if (app_config.no_refersub) {
-		    /* Add Refer-Sub: false in outgoing REFER request */
-		    pjsip_generic_string_hdr_init2(&refer_sub, &STR_REFER_SUB,
-						   &STR_FALSE);
-		    pj_list_push_back(&msg_data.hdr_list, &refer_sub);
-		}
-
-		pjsua_call_xfer_replaces(call, dst_call, 
-					 PJSUA_XFER_NO_REQUIRE_REPLACES, 
-					 &msg_data);
-	    }
-	    break;
-
-	case '#':
-	    /*
-	     * Send DTMF strings.
-	     */
-	    if (current_call == -1) {
-		
-		PJ_LOG(3,(THIS_FILE, "No current call"));
-
-	    } else if (!pjsua_call_has_media(current_call)) {
-
-		PJ_LOG(3,(THIS_FILE, "Media is not established yet!"));
-
-	    } else {
-		pj_str_t digits;
-		int call = current_call;
-		pj_status_t status;
-
-		if (!simple_input("DTMF strings to send (0-9*#A-B)", buf, 
-				  sizeof(buf)))
-		{
-			break;
-		}
-
-		if (call != current_call) {
-		    puts("Call has been disconnected");
-		    continue;
-		}
-
-		digits = pj_str(buf);
-		status = pjsua_call_dial_dtmf(current_call, &digits);
-		if (status != PJ_SUCCESS) {
-		    pjsua_perror(THIS_FILE, "Unable to send DTMF", status);
-		} else {
-		    puts("DTMF digits enqueued for transmission");
-		}
-	    }
-	    break;
-
-	case '*':
-	    /* Send DTMF with INFO */
-	    if (current_call == -1) {
-		
-		PJ_LOG(3,(THIS_FILE, "No current call"));
-
-	    } else {
-		const pj_str_t SIP_INFO = pj_str("INFO");
-		pj_str_t digits;
-		int call = current_call;
-		int i;
-		pj_status_t status;
-
-		if (!simple_input("DTMF strings to send (0-9*#A-B)", buf, 
-				  sizeof(buf)))
-		{
-			break;
-		}
-
-		if (call != current_call) {
-		    puts("Call has been disconnected");
-		    continue;
-		}
-
-		digits = pj_str(buf);
-		for (i=0; i<digits.slen; ++i) {
-		    char body[80];
-
-		    pjsua_msg_data_init(&msg_data);
-		    msg_data.content_type = pj_str("application/dtmf-relay");
-		    
-		    pj_ansi_snprintf(body, sizeof(body),
-				     "Signal=%c\r\n"
-				     "Duration=160",
-				     buf[i]);
-		    msg_data.msg_body = pj_str(body);
-
-		    status = pjsua_call_send_request(current_call, &SIP_INFO, 
-						     &msg_data);
-		    if (status != PJ_SUCCESS) {
-			break;
-		    }
-		}
-	    }
-	    break;
-
-	case 'S':
-	    /*
-	     * Send arbitrary request
-	     */
-	    if (pjsua_acc_get_count() == 0) {
-		puts("Sorry, need at least one account configured");
-		break;
-	    }
-
-	    puts("Send arbitrary request to remote host");
-
-	    /* Input METHOD */
-	    if (!simple_input("Request method:",text,sizeof(text)))
-		break;
-
-	    /* Input destination URI */
-	    uri = NULL;
-	    ui_input_url("Destination URI", buf, sizeof(buf), &result);
-	    if (result.nb_result != NO_NB) {
-
-		if (result.nb_result == -1) {
-		    puts("Sorry you can't do that!");
-		    continue;
-		} else if (result.nb_result == 0) {
-		    uri = NULL;
-		    if (current_call == PJSUA_INVALID_ID) {
-			puts("No current call");
-			continue;
-		    }
-		} else {
-		    pjsua_buddy_info binfo;
-		    pjsua_buddy_get_info(result.nb_result-1, &binfo);
-		    tmp.ptr = buf;
-		    pj_strncpy_with_null(&tmp, &binfo.uri, sizeof(buf));
-		    uri = buf;
-		}
-
-	    } else if (result.uri_result) {
-		uri = result.uri_result;
-	    } else {
-		continue;
-	    }
-	    
-	    if (uri) {
-		tmp = pj_str(uri);
-		send_request(text, &tmp);
-	    } else {
-		/* If you send call control request using this method
-		 * (such requests includes BYE, CANCEL, etc.), it will
-		 * not go well with the call state, so don't do it
-		 * unless it's for testing.
-		 */
-		pj_str_t method = pj_str(text);
-		pjsua_call_send_request(current_call, &method, NULL);
-	    }
-	    break;
-
-	case 'e':
-	    if (pj_ansi_strnicmp(menuin, "echo", 4)==0) {
-		pj_str_t tmp;
-
-		tmp.ptr = menuin+5;
-		tmp.slen = pj_ansi_strlen(menuin)-6;
-
-		if (tmp.slen < 1) {
-		    puts("Usage: echo [0|1]");
-		    break;
-		}
-
-		cmd_echo = *tmp.ptr != '0' || tmp.slen!=1;
-	    }
-	    break;
-
-	case 's':
-	    if (pj_ansi_strnicmp(menuin, "sleep", 5)==0) {
-		pj_str_t tmp;
-		int delay;
-
-		tmp.ptr = menuin+6;
-		tmp.slen = pj_ansi_strlen(menuin)-7;
-
-		if (tmp.slen < 1) {
-		    puts("Usage: sleep MSEC");
-		    break;
-		}
-
-		delay = pj_strtoul(&tmp);
-		if (delay < 0) delay = 0;
-		pj_thread_sleep(delay);
-		break;
-	    }
-	    /* Continue below */
-
-	case 'u':
-	    /*
-	     * Subscribe/unsubscribe presence.
-	     */
-	    ui_input_url("(un)Subscribe presence of", buf, sizeof(buf), &result);
-	    if (result.nb_result != NO_NB) {
-		if (result.nb_result == -1) {
-		    int i, count;
-		    count = pjsua_get_buddy_count();
-		    for (i=0; i<count; ++i)
-			pjsua_buddy_subscribe_pres(i, menuin[0]=='s');
-		} else if (result.nb_result == 0) {
-		    puts("Sorry, can only subscribe to buddy's presence, "
-			 "not from existing call");
-		} else {
-		    pjsua_buddy_subscribe_pres(result.nb_result-1, (menuin[0]=='s'));
-		}
-
-	    } else if (result.uri_result) {
-		puts("Sorry, can only subscribe to buddy's presence, "
-		     "not arbitrary URL (for now)");
-	    }
-
-	    break;
-
-	case 'r':
-	    switch (menuin[1]) {
-	    case 'r':
-		/*
-		 * Re-Register.
-		 */
-		pjsua_acc_set_registration(current_acc, PJ_TRUE);
-		break;
-	    case 'u':
-		/*
-		 * Unregister
-		 */
-		pjsua_acc_set_registration(current_acc, PJ_FALSE);
-		break;
-	    }
-	    break;
-	    
-	case 't':
-	    pjsua_acc_get_info(current_acc, &acc_info);
-	    acc_info.online_status = !acc_info.online_status;
-	    pjsua_acc_set_online_status(current_acc, acc_info.online_status);
-	    printf("Setting %s online status to %s\n",
-		   acc_info.acc_uri.ptr,
-		   (acc_info.online_status?"online":"offline"));
-	    break;
-
-	case 'T':
-	    change_online_status();
-	    break;
-
-	case 'c':
-	    switch (menuin[1]) {
-	    case 'l':
-		conf_list();
-		break;
-	    case 'c':
-	    case 'd':
-		{
-		    char tmp[10], src_port[10], dst_port[10];
-		    pj_status_t status;
-		    int cnt;
-		    const char *src_title, *dst_title;
-
-		    cnt = sscanf(menuin, "%s %s %s", tmp, src_port, dst_port);
-
-		    if (cnt != 3) {
-			conf_list();
-
-			src_title = (menuin[1]=='c'?
-				     "Connect src port #":
-				     "Disconnect src port #");
-			dst_title = (menuin[1]=='c'?
-				     "To dst port #":
-				     "From dst port #");
-
-			if (!simple_input(src_title, src_port, sizeof(src_port)))
-			    break;
-
-			if (!simple_input(dst_title, dst_port, sizeof(dst_port)))
-			    break;
-		    }
-
-		    if (menuin[1]=='c') {
-			status = pjsua_conf_connect(my_atoi(src_port), 
-						    my_atoi(dst_port));
-		    } else {
-			status = pjsua_conf_disconnect(my_atoi(src_port), 
-						       my_atoi(dst_port));
-		    }
-		    if (status == PJ_SUCCESS) {
-			puts("Success");
-		    } else {
-			puts("ERROR!!");
-		    }
-		}
-		break;
-	    }
-	    break;
-
-	case 'V':
-	    /* Adjust audio volume */
-	    sprintf(buf, "Adjust mic level: [%4.1fx] ", app_config.mic_level);
-	    if (simple_input(buf,text,sizeof(text))) {
-		char *err;
-		app_config.mic_level = (float)strtod(text, &err);
-		pjsua_conf_adjust_rx_level(0, app_config.mic_level);
-	    }
-	    sprintf(buf, "Adjust speaker level: [%4.1fx] ", 
-		    app_config.speaker_level);
-	    if (simple_input(buf,text,sizeof(text))) {
-		char *err;
-		app_config.speaker_level = (float)strtod(text, &err);
-		pjsua_conf_adjust_tx_level(0, app_config.speaker_level);
-	    }
-
-	    break;
-
-	case 'd':
-	    if (menuin[1] == 'c') {
-		char settings[2000];
-		int len;
-
-		len = write_settings(&app_config, settings, sizeof(settings));
-		if (len < 1)
-		    PJ_LOG(1,(THIS_FILE, "Error: not enough buffer"));
-		else
-		    PJ_LOG(3,(THIS_FILE, 
-			      "Dumping configuration (%d bytes):\n%s\n",
-			      len, settings));
-
-	    } else if (menuin[1] == 'q') {
-
-		if (current_call != PJSUA_INVALID_ID) {
-		    log_call_dump(current_call);
-		} else {
-		    PJ_LOG(3,(THIS_FILE, "No current call"));
-		}
-
-	    } else {
-		app_dump(menuin[1]=='d');
-	    }
-	    break;
-
-
-	case 'f':
-	    if (simple_input("Enter output filename", buf, sizeof(buf))) {
-		char settings[2000];
-		int len;
-
-		len = write_settings(&app_config, settings, sizeof(settings));
-		if (len < 1)
-		    PJ_LOG(1,(THIS_FILE, "Error: not enough buffer"));
-		else {
-		    pj_oshandle_t fd;
-		    pj_status_t status;
-
-		    status = pj_file_open(app_config.pool, buf, 
-					  PJ_O_WRONLY, &fd);
-		    if (status != PJ_SUCCESS) {
-			pjsua_perror(THIS_FILE, "Unable to open file", status);
-		    } else {
-			pj_ssize_t size = len;
-			pj_file_write(fd, settings, &size);
-			pj_file_close(fd);
-
-			printf("Settings successfully written to '%s'\n", buf);
-		    }
-		}
-		
-	    }
-	    break;
-
-
-	case 'L':   /* Restart */
-	    app_restart = PJ_TRUE;
-	    /* Continues below */
-
-	case 'q':
-	    goto on_exit;
-
-	case 'R':
-	    if (!pjsua_call_is_active(current_call)) {
-		PJ_LOG(1,(THIS_FILE, "Call %d has gone", current_call));
-	    } else if (menuin[1] == 'a') {
-		pjsua_call_process_redirect(current_call, 
-					    PJSIP_REDIRECT_ACCEPT_REPLACE);
-	    } else if (menuin[1] == 'A') {
-		pjsua_call_process_redirect(current_call, 
-					    PJSIP_REDIRECT_ACCEPT);
-	    } else if (menuin[1] == 'r') {
-		pjsua_call_process_redirect(current_call,
-					    PJSIP_REDIRECT_REJECT);
-	    } else {
-		pjsua_call_process_redirect(current_call,
-					    PJSIP_REDIRECT_STOP);
-	    }
-	    break;
-
-	default:
-	    if (menuin[0] != '\n' && menuin[0] != '\r') {
-		printf("Invalid input %s", menuin);
-	    }
-	    keystroke_help();
-	    break;
-	}
-    }
-
-on_exit:
-    ;
-}
-
-/*
  * A simple registrar, invoked by default_mod_on_rx_request()
  */
@@ -5460,6 +2593,4 @@
 		       rdata, tdata, NULL, NULL);
 }
-
-
 
 /*****************************************************************************
@@ -5539,5 +2670,4 @@
     return PJ_TRUE;
 }
-
 
 /* The module instance. */
@@ -5559,7 +2689,4 @@
 
 };
-
-
-
 
 /*****************************************************************************
@@ -6110,4 +3237,16 @@
     }
 
+    if (app_config.use_cli) {
+	/*
+	 * Init CLI
+	 */    
+	status = setup_cli();
+	if (status != PJ_SUCCESS)
+	    goto on_error;
+	
+	PJ_LOG(3,(THIS_FILE, "CLI telnet daemon listening at port %d", 
+		  app_config.cli_telnet_port));
+    }
+
     return PJ_SUCCESS;
 
@@ -6115,24 +3254,4 @@
     app_destroy();
     return status;
-}
-
-
-static int stdout_refresh_proc(void *arg)
-{
-    PJ_UNUSED_ARG(arg);
-
-    /* Set thread to lowest priority so that it doesn't clobber
-     * stdout output
-     */
-    pj_thread_set_prio(pj_thread_this(), 
-		       pj_thread_get_prio_min(pj_thread_this()));
-
-    while (!stdout_refresh_quit) {
-	pj_thread_sleep(stdout_refresh * 1000);
-	puts(stdout_refresh_text);
-	fflush(stdout);
-    }
-
-    return 0;
 }
 
@@ -6155,5 +3274,8 @@
     }
 
-    console_app_main(&uri_arg);
+    if (app_config.use_cli)
+	cli_console_app_main(&uri_arg, &app_restart);
+    else 
+	console_app_main(&uri_arg, &app_restart);
 
     if (stdout_refresh_thread) {
@@ -6219,4 +3341,8 @@
     for (i=0; i<app_config.tone_count; ++i) {
 	pjsua_conf_remove_port(app_config.tone_slots[i]);
+    }
+
+    if (app_config.use_cli) {	
+	destroy_cli();
     }
 
@@ -6303,5 +3429,4 @@
     status = pjmedia_snd_port_connect(app_config.snd, app_config.sc);
     pj_assert(status == PJ_SUCCESS);
-
 }
 #endif
Index: /pjproject/trunk/pjsip-apps/src/pjsua/pjsua_cli_cmd.c
===================================================================
--- /pjproject/trunk/pjsip-apps/src/pjsua/pjsua_cli_cmd.c	(revision 4440)
+++ /pjproject/trunk/pjsip-apps/src/pjsua/pjsua_cli_cmd.c	(revision 4440)
@@ -0,0 +1,2952 @@
+/* $Id: pjsua_cli_cmd.c 3664 2011-07-19 03:42:28Z nanang $ */
+/* 
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+
+#include "pjsua_cmd.h"
+#include <pjlib-util/cli.h>
+#include <pjlib-util/cli_imp.h>
+#include <pjlib-util/cli_console.h>
+#include <pjlib-util/cli_telnet.h>
+
+#define THIS_FILE	"pjsua_cli_cmd.c"
+
+/* CLI command id */
+/* level 1 command */
+#define CMD_CALL		    100
+#define CMD_PRESENCE		    200
+#define CMD_ACCOUNT		    300
+#define CMD_MEDIA		    400
+#define CMD_CONFIG		    500
+#define CMD_VIDEO		    600
+#define CMD_SLEEP		    700
+#define CMD_ECHO		    800
+#define CMD_NETWORK		    900
+#define CMD_QUIT		    110
+
+/* call level 2 command */
+#define CMD_CALL_NEW		    ((CMD_CALL*10)+1)
+#define CMD_CALL_MULTI		    ((CMD_CALL*10)+2)
+#define CMD_CALL_ANSWER		    ((CMD_CALL*10)+3)
+#define CMD_CALL_HANGUP		    ((CMD_CALL*10)+4)
+#define CMD_CALL_HANGUP_ALL	    ((CMD_CALL*10)+5)
+#define CMD_CALL_HOLD		    ((CMD_CALL*10)+6)
+#define CMD_CALL_REINVITE	    ((CMD_CALL*10)+7)
+#define CMD_CALL_UPDATE		    ((CMD_CALL*10)+8)
+#define CMD_CALL_NEXT		    ((CMD_CALL*10)+9)
+#define CMD_CALL_PREVIOUS	    ((CMD_CALL*10)+10)
+#define CMD_CALL_TRANSFER	    ((CMD_CALL*10)+11)
+#define CMD_CALL_TRANSFER_REPLACE   ((CMD_CALL*10)+12)
+#define CMD_CALL_REDIRECT	    ((CMD_CALL*10)+13)
+#define CMD_CALL_D2833		    ((CMD_CALL*10)+14)
+#define CMD_CALL_INFO		    ((CMD_CALL*10)+15)
+#define CMD_CALL_DUMP_Q		    ((CMD_CALL*10)+16)
+#define CMD_CALL_SEND_ARB	    ((CMD_CALL*10)+17)
+#define CMD_CALL_LIST		    ((CMD_CALL*10)+18)
+
+/* im & presence level 2 command */
+#define CMD_PRESENCE_ADD_BUDDY	    ((CMD_PRESENCE*10)+1)
+#define CMD_PRESENCE_DEL_BUDDY	    ((CMD_PRESENCE*10)+2)
+#define CMD_PRESENCE_SEND_IM	    ((CMD_PRESENCE*10)+3)
+#define CMD_PRESENCE_SUB	    ((CMD_PRESENCE*10)+4)
+#define CMD_PRESENCE_UNSUB	    ((CMD_PRESENCE*10)+5)
+#define CMD_PRESENCE_TOG_STATE	    ((CMD_PRESENCE*10)+6)
+#define CMD_PRESENCE_TEXT	    ((CMD_PRESENCE*10)+7)
+#define CMD_PRESENCE_LIST	    ((CMD_PRESENCE*10)+8)
+
+/* account level 2 command */
+#define CMD_ACCOUNT_ADD		    ((CMD_ACCOUNT*10)+1)
+#define CMD_ACCOUNT_DEL		    ((CMD_ACCOUNT*10)+2)
+#define CMD_ACCOUNT_MOD		    ((CMD_ACCOUNT*10)+3)
+#define CMD_ACCOUNT_REG		    ((CMD_ACCOUNT*10)+4)
+#define CMD_ACCOUNT_UNREG	    ((CMD_ACCOUNT*10)+5)
+#define CMD_ACCOUNT_NEXT	    ((CMD_ACCOUNT*10)+6)
+#define CMD_ACCOUNT_PREV	    ((CMD_ACCOUNT*10)+7)
+#define CMD_ACCOUNT_SHOW	    ((CMD_ACCOUNT*10)+8)
+
+/* conference & media level 2 command */
+#define CMD_MEDIA_LIST		    ((CMD_MEDIA*10)+1)
+#define CMD_MEDIA_CONF_CONNECT	    ((CMD_MEDIA*10)+2)
+#define CMD_MEDIA_CONF_DISCONNECT   ((CMD_MEDIA*10)+3)
+#define CMD_MEDIA_ADJUST_VOL	    ((CMD_MEDIA*10)+4)
+#define CMD_MEDIA_CODEC_PRIO	    ((CMD_MEDIA*10)+5)
+
+/* status & config level 2 command */
+#define CMD_CONFIG_DUMP_STAT	    ((CMD_CONFIG*10)+1)
+#define CMD_CONFIG_DUMP_DETAIL	    ((CMD_CONFIG*10)+2)
+#define CMD_CONFIG_DUMP_CONF	    ((CMD_CONFIG*10)+3)
+#define CMD_CONFIG_WRITE_SETTING    ((CMD_CONFIG*10)+4)
+
+/* video level 2 command */
+#define CMD_VIDEO_ENABLE	    ((CMD_VIDEO*10)+1)
+#define CMD_VIDEO_DISABLE	    ((CMD_VIDEO*10)+2)
+#define CMD_VIDEO_ACC		    ((CMD_VIDEO*10)+3)
+#define CMD_VIDEO_CALL		    ((CMD_VIDEO*10)+4)
+#define CMD_VIDEO_DEVICE	    ((CMD_VIDEO*10)+5)
+#define CMD_VIDEO_CODEC		    ((CMD_VIDEO*10)+6)
+#define CMD_VIDEO_WIN		    ((CMD_VIDEO*10)+7)
+
+/* video level 3 command */
+#define CMD_VIDEO_ACC_SHOW	    ((CMD_VIDEO_ACC*10)+1)
+#define CMD_VIDEO_ACC_AUTORX	    ((CMD_VIDEO_ACC*10)+2)
+#define CMD_VIDEO_ACC_AUTOTX	    ((CMD_VIDEO_ACC*10)+3)
+#define CMD_VIDEO_ACC_CAP_ID	    ((CMD_VIDEO_ACC*10)+4)
+#define CMD_VIDEO_ACC_REN_ID	    ((CMD_VIDEO_ACC*10)+5)
+#define CMD_VIDEO_CALL_RX	    ((CMD_VIDEO_CALL*10)+1)
+#define CMD_VIDEO_CALL_TX	    ((CMD_VIDEO_CALL*10)+2)
+#define CMD_VIDEO_CALL_ADD	    ((CMD_VIDEO_CALL*10)+3)
+#define CMD_VIDEO_CALL_ENABLE	    ((CMD_VIDEO_CALL*10)+4)
+#define CMD_VIDEO_CALL_DISABLE	    ((CMD_VIDEO_CALL*10)+5)
+#define CMD_VIDEO_CALL_CAP	    ((CMD_VIDEO_CALL*10)+6)
+#define CMD_VIDEO_DEVICE_LIST	    ((CMD_VIDEO_DEVICE*10)+1)
+#define CMD_VIDEO_DEVICE_REFRESH    ((CMD_VIDEO_DEVICE*10)+2)
+#define CMD_VIDEO_DEVICE_PREVIEW    ((CMD_VIDEO_DEVICE*10)+3)
+#define CMD_VIDEO_CODEC_LIST	    ((CMD_VIDEO_CODEC*10)+1)
+#define CMD_VIDEO_CODEC_PRIO	    ((CMD_VIDEO_CODEC*10)+2)
+#define CMD_VIDEO_CODEC_FPS	    ((CMD_VIDEO_CODEC*10)+3)
+#define CMD_VIDEO_CODEC_BITRATE	    ((CMD_VIDEO_CODEC*10)+4)
+#define CMD_VIDEO_CODEC_SIZE	    ((CMD_VIDEO_CODEC*10)+5)
+#define CMD_VIDEO_WIN_LIST	    ((CMD_VIDEO_WIN*10)+1)
+#define CMD_VIDEO_WIN_ARRANGE	    ((CMD_VIDEO_WIN*10)+2)
+#define CMD_VIDEO_WIN_SHOW	    ((CMD_VIDEO_WIN*10)+3)
+#define CMD_VIDEO_WIN_HIDE	    ((CMD_VIDEO_WIN*10)+4)
+#define CMD_VIDEO_WIN_MOVE	    ((CMD_VIDEO_WIN*10)+5)
+#define CMD_VIDEO_WIN_RESIZE	    ((CMD_VIDEO_WIN*10)+6)
+
+/* dynamic choice argument list */
+#define DYN_CHOICE_START	    9900
+#define DYN_CHOICE_BUDDY_ID	    (DYN_CHOICE_START)+1
+#define DYN_CHOICE_ACCOUNT_ID	    (DYN_CHOICE_START)+2
+#define DYN_CHOICE_MEDIA_PORT	    (DYN_CHOICE_START)+3
+#define DYN_CHOICE_AUDIO_CODEC_ID   (DYN_CHOICE_START)+4
+#define DYN_CHOICE_CAP_DEV_ID	    (DYN_CHOICE_START)+5
+#define DYN_CHOICE_REN_DEV_ID	    (DYN_CHOICE_START)+6
+#define DYN_CHOICE_VID_DEV_ID	    (DYN_CHOICE_START)+7
+#define DYN_CHOICE_STREAM_ID	    (DYN_CHOICE_START)+8
+#define DYN_CHOICE_VIDEO_CODEC_ID   (DYN_CHOICE_START)+9
+#define DYN_CHOICE_WIN_ID	    (DYN_CHOICE_START)+10
+#define DYN_CHOICE_CALL_ID	    (DYN_CHOICE_START)+11
+#define DYN_CHOICE_ADDED_BUDDY_ID   (DYN_CHOICE_START)+12
+
+static pj_cli_cfg cli_cfg;
+static pj_cli_telnet_cfg telnet_cfg;
+static pj_cli_t *cli = NULL;
+static pj_cli_sess *sess = NULL;
+static pj_cli_console_cfg console_cfg;
+
+/* Get input URL */
+static void get_input_url(char *buf, 
+			  int len,
+			  pj_cli_cmd_val *cval,
+struct input_result *result)
+{
+    static const pj_str_t err_invalid_input = {"Invalid input\n", 15};
+    result->nb_result = NO_NB;
+    result->uri_result = NULL;
+
+    len = strlen(buf);
+
+    /* Left trim */
+    while (pj_isspace(*buf)) {
+	++buf;
+	--len;
+    }
+
+    /* Remove trailing newlines */
+    while (len && (buf[len-1] == '\r' || buf[len-1] == '\n'))
+	buf[--len] = '\0';
+
+    if (len == 0 || buf[0]=='q')
+	return;
+
+    if (pj_isdigit(*buf) || *buf=='-') {
+
+	int i;
+
+	if (*buf=='-')
+	    i = 1;
+	else
+	    i = 0;
+
+	for (; i<len; ++i) {
+	    if (!pj_isdigit(buf[i])) {
+		pj_cli_sess_write_msg(cval->sess, err_invalid_input.ptr, 
+		    err_invalid_input.slen);
+		return;
+	    }
+	}
+
+	result->nb_result = my_atoi(buf);
+
+	if (result->nb_result >= 0 && 
+	    result->nb_result <= (int)pjsua_get_buddy_count()) 
+	{
+	    return;
+	}
+	if (result->nb_result == -1)
+	    return;
+
+	pj_cli_sess_write_msg(cval->sess, err_invalid_input.ptr, 
+	    err_invalid_input.slen);
+	result->nb_result = NO_NB;
+	return;
+
+    } else {
+	pj_status_t status;
+
+	if ((status=pjsua_verify_url(buf)) != PJ_SUCCESS) {
+	    pjsua_perror(THIS_FILE, "Invalid URL", status);
+	    return;
+	}
+
+	result->uri_result = buf;
+    }
+}
+
+/* CLI dynamic choice handler */
+/* Get buddy id */
+static void get_buddy_id(pj_cli_dyn_choice_param *param)
+{
+    if (param->cnt < param->max_cnt) {	
+	pjsua_buddy_id ids[64];
+	int i = 0;
+	unsigned count = PJ_ARRAY_SIZE(ids);
+	char data_out[64];
+
+	pjsua_enum_buddies(ids, &count);
+	
+	if (count > 0) {
+	    for (i=0; i<(int)count; ++i) {
+		pjsua_buddy_info info;
+
+		if (pjsua_buddy_get_info(ids[i], &info) != PJ_SUCCESS)
+		    continue;
+
+		/* Fill buddy id */
+		pj_ansi_snprintf(data_out, sizeof(data_out), "%d", ids[i]+1);
+		pj_strdup2(param->pool, &param->choice[i].value, data_out);
+		pj_bzero(data_out, PJ_ARRAY_SIZE(data_out));
+    	    
+		/* Format & fill description */
+		pj_ansi_snprintf(data_out, 
+				sizeof(data_out),
+				"<%.*s>  %.*s", 		    
+				(int)info.status_text.slen,
+				info.status_text.ptr, 
+				(int)info.uri.slen,
+				info.uri.ptr);
+
+		pj_strdup2(param->pool, &param->choice[i].desc, data_out);		
+		if (++param->cnt >= (param->max_cnt-1))
+		    break;	
+	    }
+	}
+	if (param->arg_id == DYN_CHOICE_BUDDY_ID) {
+	    /* Add URL input option */
+	    pj_ansi_snprintf(data_out, sizeof(data_out), "URL");
+	    pj_strdup2(param->pool, &param->choice[i].value, data_out);
+	    pj_ansi_snprintf(data_out, sizeof(data_out), "An URL");
+	    pj_strdup2(param->pool, &param->choice[i].desc, data_out);
+	    ++param->cnt;
+	}
+    }
+}
+
+static void get_account_id(pj_cli_dyn_choice_param *param)
+{
+    if (param->cnt < param->max_cnt) {	
+	char buf[8];
+	char buf_out[80];
+	pjsua_acc_info info;
+
+	pjsua_acc_id acc_ids[16];
+	unsigned count = PJ_ARRAY_SIZE(acc_ids);
+	int i;    
+
+	pjsua_enum_accs(acc_ids, &count);
+        
+	for (i=0; i<(int)count; ++i) {	
+	    pj_bzero(&buf_out[0], PJ_ARRAY_SIZE(buf_out));
+
+	    pjsua_acc_get_info(acc_ids[i], &info);
+
+	    pj_ansi_snprintf(buf_out, 
+			     sizeof(buf_out),
+			     "%c%.*s", 
+			     (acc_ids[i]==current_acc?'*':' '),
+			     (int)info.acc_uri.slen, 
+			     info.acc_uri.ptr);
+
+	    pj_bzero(buf, sizeof(buf));
+	    pj_ansi_snprintf(buf, sizeof(buf), "%d", acc_ids[i]);
+	    pj_strdup2(param->pool, &param->choice[i].value, buf);
+	    pj_strdup2(param->pool, &param->choice[i].desc, buf_out);
+	    if (++param->cnt >= param->max_cnt)
+		break;	
+	}
+    }
+}
+
+static void get_media_port(pj_cli_dyn_choice_param *param)
+{
+    unsigned i, count;
+    pjsua_conf_port_id id[PJSUA_MAX_CALLS];
+
+    count = PJ_ARRAY_SIZE(id);
+    pjsua_enum_conf_ports(id, &count);
+
+    for (i=0; i<count; ++i) {
+	char slot_id[8];
+	char desc[PJSUA_MAX_CALLS*6];
+	char txlist[PJSUA_MAX_CALLS*4+10];
+	unsigned j;
+	pjsua_conf_port_info info;
+
+	pjsua_conf_get_port_info(id[i], &info);
+
+	pj_ansi_snprintf(slot_id, sizeof(slot_id), 
+			 "%d", info.slot_id);
+	pj_strdup2(param->pool, &param->choice[i].value, slot_id);
+
+	txlist[0] = '\0';
+	for (j=0; j<info.listener_cnt; ++j) {
+	    char s[10];
+	    pj_ansi_snprintf(s, sizeof(s), "#%d ", info.listeners[j]);
+	    pj_ansi_strcat(txlist, s);
+	}	
+
+	pj_ansi_snprintf(desc,  
+	       sizeof(desc),
+	       "[%2dKHz/%dms/%d] %20.*s  transmitting to: %s", 	       
+	       info.clock_rate/1000,
+	       info.samples_per_frame*1000/info.channel_count/info.clock_rate,
+	       info.channel_count,
+	       (int)info.name.slen, 
+	       info.name.ptr,
+	       txlist);
+
+	pj_strdup2(param->pool, &param->choice[i].desc, desc);	
+	if (++param->cnt >= param->max_cnt)
+	    break;	
+    }
+}
+
+static void get_audio_codec_id(pj_cli_dyn_choice_param *param)
+{
+    if (param->cnt < param->max_cnt) {	
+	pjsua_codec_info c[32];
+	unsigned i, count = PJ_ARRAY_SIZE(c);
+	char codec_id[64];
+	char desc[128];
+        
+	pjsua_enum_codecs(c, &count);
+	for (i=0; i<count; ++i) {	
+	    pj_ansi_snprintf(codec_id, sizeof(codec_id), 
+			     "%.*s", (int)c[i].codec_id.slen, 
+			     c[i].codec_id.ptr);
+
+	    pj_strdup2(param->pool, &param->choice[param->cnt].value, codec_id);
+
+	    pj_ansi_snprintf(desc, sizeof(desc), 
+			     "Audio, prio: %d%s%.*s", 
+			     c[i].priority, 
+			     c[i].desc.slen? " - ":"",
+			     (int)c[i].desc.slen,
+			     c[i].desc.ptr);
+
+	    pj_strdup2(param->pool, &param->choice[param->cnt].desc, desc);
+	    if (++param->cnt >= param->max_cnt)
+		break;	
+	}
+    }
+}
+
+#if PJSUA_HAS_VIDEO  
+static void get_video_stream_id(pj_cli_dyn_choice_param *param)
+{
+    if (param->cnt < param->max_cnt) {	
+	pjsua_call_info call_info;
+
+	if (current_call != PJSUA_INVALID_ID) {
+	    unsigned i;
+	    pjsua_call_get_info(current_call, &call_info);
+	    for (i=0; i<call_info.media_cnt; ++i) {
+		if (call_info.media[i].type == PJMEDIA_TYPE_VIDEO) {
+		    char med_idx[8];
+		    pj_ansi_snprintf(med_idx, sizeof(med_idx), "%d", 
+				     call_info.media[i].index);
+		    pj_strdup2(param->pool, &param->choice[i].value, med_idx);
+
+		    switch (call_info.media[i].status) {		    
+		    case PJSUA_CALL_MEDIA_NONE:
+			pj_strdup2(param->pool, &param->choice[i].desc, 
+				   "Status:None");
+			break;
+		    case PJSUA_CALL_MEDIA_ACTIVE:
+			pj_strdup2(param->pool, &param->choice[i].desc, 
+				   "Status:Active");
+			break;
+		    case PJSUA_CALL_MEDIA_LOCAL_HOLD:
+			pj_strdup2(param->pool, &param->choice[i].desc, 
+				   "Status:Local Hold");
+			break;
+		    case PJSUA_CALL_MEDIA_REMOTE_HOLD:
+			pj_strdup2(param->pool, &param->choice[i].desc, 
+				   "Status:Remote Hold");
+			break;
+		    case PJSUA_CALL_MEDIA_ERROR:
+			pj_strdup2(param->pool, &param->choice[i].desc, 
+				   "Status:Media Error");
+			break;
+		    }
+		    if (++param->cnt >= param->max_cnt)
+			break;	
+		}
+	    }
+	}
+    }
+}
+  
+static void get_video_dev_hint(pj_cli_dyn_choice_param *param, 
+			       pjmedia_vid_dev_info *vdi,
+			       unsigned vid_dev_id) 
+{
+    char desc[128];
+    char dev_id[8];
+    pj_ansi_snprintf(dev_id, sizeof(dev_id),
+	"%d", vid_dev_id);
+    pj_ansi_snprintf(desc, sizeof(desc), "%s [%s]", 
+	vdi->name, vdi->driver);
+
+    pj_strdup2(param->pool, &param->choice[param->cnt].value, 
+	       dev_id);
+    pj_strdup2(param->pool, &param->choice[param->cnt].desc, 
+	       desc);    
+}
+
+static void get_video_dev_id(pj_cli_dyn_choice_param *param, 
+			     pj_bool_t all,
+			     pj_bool_t capture)
+{
+    if (param->cnt < param->max_cnt) {	
+	unsigned i, count;
+	pjmedia_vid_dev_info vdi;
+	pj_status_t status;
+
+	count = pjsua_vid_dev_count();
+	if (count == 0) {	
+	    return;
+	} 
+
+	for (i=0; i<count; ++i) {
+	    status = pjsua_vid_dev_get_info(i, &vdi);
+	    if (status == PJ_SUCCESS) {
+		if ((all) ||
+		    ((capture) && (vdi.dir == PJMEDIA_DIR_CAPTURE)) ||
+		    ((!capture) && (vdi.dir == PJMEDIA_DIR_RENDER)))
+		{		
+		    get_video_dev_hint(param, &vdi, i);
+		    if (++param->cnt >= param->max_cnt)
+			break;	
+		} 
+	    }
+	}
+    }
+}
+
+static void get_video_codec_id(pj_cli_dyn_choice_param *param)
+{    
+    if (param->cnt < param->max_cnt) {	
+	pjsua_codec_info ci[32];
+	unsigned i, count = PJ_ARRAY_SIZE(ci);
+	char codec_id[64];
+	char desc[128];
+
+	pjsua_vid_enum_codecs(ci, &count);
+	for (i=0; i<count; ++i) {
+	    pjmedia_vid_codec_param cp;
+	    pjmedia_video_format_detail *vfd;
+	    pj_status_t status = PJ_SUCCESS;
+
+	    status = pjsua_vid_codec_get_param(&ci[i].codec_id, &cp);
+	    if (status != PJ_SUCCESS)
+		continue;
+
+	    vfd = pjmedia_format_get_video_format_detail(&cp.enc_fmt, PJ_TRUE);
+
+	    pj_ansi_snprintf(codec_id, sizeof(codec_id), 
+			     "%.*s", (int)ci[i].codec_id.slen, 
+			     ci[i].codec_id.ptr);
+
+	    pj_strdup2(param->pool, &param->choice[param->cnt].value, codec_id);
+
+	    pj_ansi_snprintf(desc, sizeof(desc), 
+	    		     "Video, p[%d], f[%.2f], b[%d/%d], s[%dx%d]", 
+	    		     ci[i].priority, 
+			     (vfd->fps.num*1.0/vfd->fps.denum),
+			     vfd->avg_bps/1000, vfd->max_bps/1000,
+			     vfd->size.w, vfd->size.h);
+
+	    pj_strdup2(param->pool, &param->choice[param->cnt].desc, desc);	
+	    if (++param->cnt >= param->max_cnt)
+		break;	
+	}
+    }
+}
+
+static void get_video_window_id(pj_cli_dyn_choice_param *param)
+{
+    if (param->cnt < param->max_cnt) {	
+	pjsua_vid_win_id wids[PJSUA_MAX_VID_WINS];
+	unsigned i, cnt = PJ_ARRAY_SIZE(wids);
+	char win_id[64];
+	char desc[128];
+
+	pjsua_vid_enum_wins(wids, &cnt);
+
+	for (i = 0; i < cnt; ++i) {
+	    pjsua_vid_win_info wi;
+
+	    pjsua_vid_win_get_info(wids[i], &wi);
+	    pj_ansi_snprintf(win_id, sizeof(win_id), "%d", wids[i]);
+	    pj_strdup2(param->pool, &param->choice[i].value, win_id);
+
+	    pj_ansi_snprintf(desc, sizeof(desc), 
+			     "Show:%c Pos(%d,%d)  Size(%dx%d)", 
+			     (wi.show?'Y':'N'), wi.pos.x, wi.pos.y,
+			     wi.size.w, wi.size.h);
+
+	    pj_strdup2(param->pool, &param->choice[i].desc, desc);	
+	    if (++param->cnt >= param->max_cnt)
+		break;	
+	}
+    }
+}
+
+static void get_call_id(pj_cli_dyn_choice_param *param)
+{
+    if (param->cnt < param->max_cnt) {	
+	char call_id[64];
+	char desc[128];
+	unsigned i, count;
+	pjsua_call_id ids[PJSUA_MAX_CALLS];
+	int call = current_call;
+
+	count = PJ_ARRAY_SIZE(ids);
+	pjsua_enum_calls(ids, &count);
+
+	if (count > 1) {
+	    for (i=0; i<count; ++i) {
+		pjsua_call_info call_info;
+
+		if (ids[i] == call)
+		    return;
+
+		pjsua_call_get_info(ids[i], &call_info);
+		pj_ansi_snprintf(call_id, sizeof(call_id), "%d", ids[i]);
+		pj_strdup2(param->pool, &param->choice[i].value, call_id);
+		pj_ansi_snprintf(desc, sizeof(desc), "%.*s [%.*s]", 
+		    (int)call_info.remote_info.slen,
+		    call_info.remote_info.ptr,
+		    (int)call_info.state_text.slen,
+		    call_info.state_text.ptr);
+		pj_strdup2(param->pool, &param->choice[i].desc, desc);
+		if (++param->cnt >= param->max_cnt)
+		    break;	
+
+	    }
+	}
+    }
+}
+
+#endif
+
+static void get_choice_value(pj_cli_dyn_choice_param *param)
+{
+    switch (param->arg_id) {
+    case DYN_CHOICE_BUDDY_ID:
+    case DYN_CHOICE_ADDED_BUDDY_ID:
+	get_buddy_id(param);
+	break;
+    case DYN_CHOICE_ACCOUNT_ID:
+	get_account_id(param);
+	break;
+    case DYN_CHOICE_MEDIA_PORT:
+	get_media_port(param);
+	break;
+    case DYN_CHOICE_AUDIO_CODEC_ID:
+	get_audio_codec_id(param);
+	break;
+#if PJSUA_HAS_VIDEO    
+    case DYN_CHOICE_CAP_DEV_ID:	
+    case DYN_CHOICE_REN_DEV_ID:
+    case DYN_CHOICE_VID_DEV_ID:
+	get_video_dev_id(param, 
+			 (param->arg_id==DYN_CHOICE_VID_DEV_ID),
+			 (param->arg_id==DYN_CHOICE_CAP_DEV_ID));
+	break;
+    case DYN_CHOICE_STREAM_ID:
+	get_video_stream_id(param);
+	break;
+    case DYN_CHOICE_VIDEO_CODEC_ID:
+	get_video_codec_id(param);
+	break;
+    case DYN_CHOICE_WIN_ID:
+	get_video_window_id(param);
+	break;
+    case DYN_CHOICE_CALL_ID:
+	get_call_id(param);
+	break;
+#endif
+    default:
+	param->cnt = 0;
+    }
+}
+
+void cli_console_app_main(const pj_str_t *uri_to_call, pj_bool_t *app_restart)
+{        
+    pjsua_call_setting_default(&call_opt);
+    call_opt.aud_cnt = app_config.aud_cnt;
+    call_opt.vid_cnt = app_config.vid.vid_cnt;    
+
+    /* If user specifies URI to call, then call the URI */
+    if (uri_to_call->slen) {
+	pjsua_call_make_call(current_acc, uri_to_call, &call_opt, NULL, 
+			     NULL, NULL);
+    }
+
+    /*
+     * Main loop.
+     */
+    for (;;) {
+	char cmdline[PJ_CLI_MAX_CMDBUF];	
+        pj_status_t status;
+
+        status = pj_cli_console_process(sess, &cmdline[0], sizeof(cmdline));
+
+	if (status == PJ_CLI_EEXIT) {
+	    /* exit is called */
+	    break;
+	} else if (status != PJ_SUCCESS) {
+	    /* Something wrong with the cmdline */
+	    PJ_PERROR(1,(THIS_FILE, status, "Exec error"));
+	}
+    }    
+    *app_restart = pj_cli_is_restarting(cli);  
+}
+
+/* 
+ * CLI command handler
+ */
+
+/* Add account */
+static pj_status_t cmd_add_account(pj_cli_cmd_val *cval)
+{
+    pjsua_acc_config acc_cfg;
+    pj_status_t status;
+
+    pjsua_acc_config_default(&acc_cfg);
+    acc_cfg.id = cval->argv[1];
+    acc_cfg.reg_uri = cval->argv[2];
+    acc_cfg.cred_count = 1;
+    acc_cfg.cred_info[0].scheme = pj_str("Digest");
+    acc_cfg.cred_info[0].realm = cval->argv[3];
+    acc_cfg.cred_info[0].username = cval->argv[4];
+    acc_cfg.cred_info[0].data_type = 0;
+    acc_cfg.cred_info[0].data = cval->argv[5];
+
+    acc_cfg.rtp_cfg = app_config.rtp_cfg;
+    app_config_init_video(&acc_cfg);
+
+    status = pjsua_acc_add(&acc_cfg, PJ_TRUE, NULL);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Error adding new account", status);
+    }
+    
+    return status;
+}
+
+/* Delete account */
+static pj_status_t cmd_del_account(pj_cli_cmd_val *cval)
+{
+    char out_str[64];
+    unsigned str_len;
+
+    int i = my_atoi(cval->argv[1].ptr);
+
+    if (!pjsua_acc_is_valid(i)) {
+	pj_ansi_snprintf(out_str, sizeof(out_str), 
+		        "Invalid account id %d\n", i);
+	str_len = pj_ansi_strlen(out_str);
+	pj_cli_sess_write_msg(cval->sess, out_str, str_len);
+    } else {
+	pjsua_acc_del(i);
+	pj_ansi_snprintf(out_str, sizeof(out_str),
+			 "Account %d deleted\n", i);
+	str_len = pj_ansi_strlen(out_str);
+	pj_cli_sess_write_msg(cval->sess, out_str, str_len);
+    }
+    return PJ_SUCCESS;
+}
+
+/* Modify account */
+static pj_status_t cmd_mod_account(pj_cli_cmd_val *cval)
+{
+    PJ_UNUSED_ARG(cval);
+    return PJ_SUCCESS;
+}
+
+/* Register account */
+static pj_status_t cmd_reg_account()
+{    
+    pjsua_acc_set_registration(current_acc, PJ_TRUE);
+    return PJ_SUCCESS;
+}
+
+/* Unregister account */
+static pj_status_t cmd_unreg_account()
+{    
+    pjsua_acc_set_registration(current_acc, PJ_FALSE);
+    return PJ_SUCCESS;
+}
+
+/* Select account to be used for sending outgoing request */
+static pj_status_t cmd_next_account(pj_cli_cmd_val *cval)
+{
+    int i = my_atoi(cval->argv[1].ptr);
+    if (pjsua_acc_is_valid(i)) {
+	pjsua_acc_set_default(i);
+	PJ_LOG(3,(THIS_FILE, "Current account changed to %d", i));
+    } else {
+	PJ_LOG(3,(THIS_FILE, "Invalid account id %d", i));
+    }    
+    return PJ_SUCCESS;
+}
+
+/* Show account list */
+static pj_status_t cmd_show_account(pj_cli_cmd_val *cval)
+{
+    pjsua_acc_id acc_ids[16];
+    unsigned count = PJ_ARRAY_SIZE(acc_ids);
+    int i;
+    static const pj_str_t header = {"Account list:\n", 15};    
+
+    pjsua_enum_accs(acc_ids, &count);
+
+    pj_cli_sess_write_msg(cval->sess, header.ptr, header.slen);
+    for (i=0; i<(int)count; ++i) {
+	char acc_info[80];
+	char out_str[160];
+	pjsua_acc_info info;
+
+	pjsua_acc_get_info(acc_ids[i], &info);
+
+	if (!info.has_registration) {
+	    pj_ansi_snprintf(acc_info, sizeof(acc_info), "%.*s", 
+			     (int)info.status_text.slen,
+			     info.status_text.ptr);
+
+	} else {
+	    pj_ansi_snprintf(acc_info, sizeof(acc_info),
+			     "%d/%.*s (expires=%d)",
+			     info.status,
+			     (int)info.status_text.slen,
+			     info.status_text.ptr,
+			     info.expires);
+
+	}
+
+	pj_ansi_snprintf(out_str, sizeof(out_str), 
+			 " %c[%2d] %.*s: %s\n", 
+			 (acc_ids[i]==current_acc?'*':' '), acc_ids[i],  
+			 (int)info.acc_uri.slen, info.acc_uri.ptr, 
+			 acc_info);
+	pj_cli_sess_write_msg(cval->sess, out_str, pj_ansi_strlen(out_str));
+
+	pj_bzero(out_str, sizeof(out_str));
+	pj_ansi_snprintf(out_str, sizeof(out_str),
+			 "       Online status: %.*s\n", 
+			 (int)info.online_status_text.slen,
+			 info.online_status_text.ptr);
+
+	pj_cli_sess_write_msg(cval->sess, out_str, pj_ansi_strlen(out_str));
+    }
+
+    return PJ_SUCCESS;
+}
+
+/* Account command handler */
+pj_status_t cmd_account_handler(pj_cli_cmd_val *cval)
+{    
+    pj_status_t status = PJ_SUCCESS;
+
+    switch(pj_cli_get_cmd_id(cval->cmd)) {
+    case CMD_ACCOUNT_ADD:
+	status = cmd_add_account(cval);
+	break;
+    case CMD_ACCOUNT_DEL:
+	status = cmd_del_account(cval);
+	break;
+    case CMD_ACCOUNT_MOD:
+	status = cmd_mod_account(cval);
+	break;
+    case CMD_ACCOUNT_REG:
+	status = cmd_reg_account();
+	break;
+    case CMD_ACCOUNT_UNREG:
+	status = cmd_unreg_account();
+	break;
+    case CMD_ACCOUNT_NEXT:	
+    case CMD_ACCOUNT_PREV:
+	status = cmd_next_account(cval);	
+	break;
+    case CMD_ACCOUNT_SHOW:
+	status = cmd_show_account(cval);
+	break;
+    }    
+    return status;
+}
+
+/* Add buddy */
+static pj_status_t cmd_add_buddy(pj_cli_cmd_val *cval)
+{    
+    char out_str[80];
+    pjsua_buddy_config buddy_cfg;
+    pjsua_buddy_id buddy_id;
+    pj_status_t status = PJ_SUCCESS;
+    cval->argv[1].ptr[cval->argv[1].slen] = 0;
+
+    if (pjsua_verify_url(cval->argv[1].ptr) != PJ_SUCCESS) {
+	pj_ansi_snprintf(out_str, sizeof(out_str), 
+			 "Invalid URI '%s'\n", cval->argv[1].ptr);
+    } else {
+	pj_bzero(&buddy_cfg, sizeof(pjsua_buddy_config));
+
+	buddy_cfg.uri = pj_str(cval->argv[1].ptr);
+	buddy_cfg.subscribe = PJ_TRUE;
+
+	status = pjsua_buddy_add(&buddy_cfg, &buddy_id);
+	if (status == PJ_SUCCESS) {
+	    pj_ansi_snprintf(out_str, sizeof(out_str), 
+			      "New buddy '%s' added at index %d\n",
+			      cval->argv[1].ptr, buddy_id+1);
+	}   
+    }
+    pj_cli_sess_write_msg(cval->sess, out_str, pj_ansi_strlen(out_str));
+    return status;
+}
+
+/* Delete buddy */
+static pj_status_t cmd_del_buddy(pj_cli_cmd_val *cval)
+{    
+    int i = my_atoi(cval->argv[1].ptr) - 1;
+    char out_str[80];
+
+    if (!pjsua_buddy_is_valid(i)) {
+	pj_ansi_snprintf(out_str, sizeof(out_str), 
+			 "Invalid buddy id %d\n", i);	
+    } else {
+	pjsua_buddy_del(i);
+	pj_ansi_snprintf(out_str, sizeof(out_str), 
+			 "Buddy %d deleted\n", i);		
+    }
+    pj_cli_sess_write_msg(cval->sess, out_str, pj_ansi_strlen(out_str));
+    return PJ_SUCCESS;
+}
+
+/* Send IM */
+static pj_status_t cmd_send_im(pj_cli_cmd_val *cval)
+{    
+    int i = -1;    
+    struct input_result result;
+    char dest[64];
+    pj_str_t tmp = pj_str(dest);    
+
+    /* make compiler happy. */
+    char *uri = NULL;
+
+    pj_strncpy_with_null(&tmp, &cval->argv[1], sizeof(dest));
+    
+    /* input destination. */
+    get_input_url(tmp.ptr, tmp.slen, cval, &result);
+    if (result.nb_result != NO_NB) {
+
+	if (result.nb_result == -1) {	    
+	    static const pj_str_t err_msg = {"you can't send broadcast im "
+					     "like that!\n", 40 };	    
+	    pj_cli_sess_write_msg(cval->sess, err_msg.ptr, err_msg.slen);
+	    return PJ_SUCCESS;
+	} else if (result.nb_result == 0) {
+	    i = current_call;
+	} else {
+	    pjsua_buddy_info binfo;
+	    pjsua_buddy_get_info(result.nb_result-1, &binfo);	    
+	    pj_strncpy_with_null(&tmp, &binfo.uri, sizeof(dest));
+	    uri = tmp.ptr;
+	}
+
+    } else if (result.uri_result) {
+	uri = result.uri_result;
+    }
+
+    /* send typing indication. */
+    if (i != -1)
+	pjsua_call_send_typing_ind(i, PJ_TRUE, NULL);
+    else {
+	pj_str_t tmp_uri = pj_str(uri);
+	pjsua_im_typing(current_acc, &tmp_uri, PJ_TRUE, NULL);
+    }
+
+    /* send the im */
+    if (i != -1)
+	pjsua_call_send_im(i, NULL, &cval->argv[2], NULL, NULL);
+    else {
+	pj_str_t tmp_uri = pj_str(uri);
+	pjsua_im_send(current_acc, &tmp_uri, NULL, &cval->argv[2], NULL, NULL);
+    }
+    return PJ_SUCCESS;
+}
+
+/* Subscribe/unsubscribe presence */
+static pj_status_t cmd_subs_pres(pj_cli_cmd_val *cval, pj_bool_t subscribe)
+{   
+    struct input_result result;    
+    char dest[64] = {0};
+    pj_str_t tmp = pj_str(dest);
+    
+    pj_strncpy_with_null(&tmp, &cval->argv[1], sizeof(dest));
+    get_input_url(tmp.ptr, tmp.slen, cval, &result);    
+    if (result.nb_result != NO_NB) {
+	if (result.nb_result == -1) {
+	    int i, count;
+	    count = pjsua_get_buddy_count();
+	    for (i=0; i<count; ++i)
+		pjsua_buddy_subscribe_pres(i, subscribe);
+	} else if (result.nb_result == 0) {
+	    static const pj_str_t err_msg = {"Sorry, can only subscribe to "
+					     "buddy's presence, not from "
+					     "existing call\n", 71};
+	    pj_cli_sess_write_msg(cval->sess, err_msg.ptr, err_msg.slen);
+	} else {
+	    pjsua_buddy_subscribe_pres(result.nb_result-1, subscribe);
+	}
+
+    } else if (result.uri_result) {
+	static const pj_str_t err_msg = {"Sorry, can only subscribe to "
+					 "buddy's presence, not arbitrary "
+					 "URL (for now)\n", 76};
+	pj_cli_sess_write_msg(cval->sess, err_msg.ptr, err_msg.slen);
+    }
+    return PJ_SUCCESS;
+}
+
+/* Toggle online state */
+static pj_status_t cmd_toggle_state(pj_cli_cmd_val *cval)
+{    
+    char out_str[128];
+    pjsua_acc_info acc_info;
+
+    pjsua_acc_get_info(current_acc, &acc_info);
+    acc_info.online_status = !acc_info.online_status;
+    pjsua_acc_set_online_status(current_acc, acc_info.online_status);
+    pj_ansi_snprintf(out_str, sizeof(out_str), 
+		     "Setting %s online status to %s\n",
+		     acc_info.acc_uri.ptr,
+		    (acc_info.online_status?"online":"offline"));
+    pj_cli_sess_write_msg(cval->sess, out_str, pj_ansi_strlen(out_str));
+    return PJ_SUCCESS;
+}
+
+/* Set presence text */
+static pj_status_t cmd_set_presence_text(pj_cli_cmd_val *cval)
+{    
+    pjrpid_element elem;
+    int choice;
+    pj_bool_t online_status;
+
+    enum {
+	AVAILABLE, BUSY, OTP, IDLE, AWAY, BRB, OFFLINE, OPT_MAX
+    };
+
+    choice = pj_strtol(&cval->argv[1]) - 1;
+
+    pj_bzero(&elem, sizeof(elem));
+    elem.type = PJRPID_ELEMENT_TYPE_PERSON;
+
+    online_status = PJ_TRUE;
+
+    switch (choice) {
+    case AVAILABLE:
+	break;
+    case BUSY:
+	elem.activity = PJRPID_ACTIVITY_BUSY;
+	elem.note = pj_str("Busy");
+	break;
+    case OTP:
+	elem.activity = PJRPID_ACTIVITY_BUSY;
+	elem.note = pj_str("On the phone");
+	break;
+    case IDLE:
+	elem.activity = PJRPID_ACTIVITY_UNKNOWN;
+	elem.note = pj_str("Idle");
+	break;
+    case AWAY:
+	elem.activity = PJRPID_ACTIVITY_AWAY;
+	elem.note = pj_str("Away");
+	break;
+    case BRB:
+	elem.activity = PJRPID_ACTIVITY_UNKNOWN;
+	elem.note = pj_str("Be right back");
+	break;
+    case OFFLINE:
+	online_status = PJ_FALSE;
+	break;
+    }
+    pjsua_acc_set_online_status2(current_acc, online_status, &elem);
+    return PJ_SUCCESS;
+}
+
+/* Show buddy list */
+static pj_status_t cmd_show_buddy(pj_cli_cmd_val *cval)
+{        
+    pjsua_buddy_id ids[64];
+    int i;
+    unsigned count = PJ_ARRAY_SIZE(ids);
+    static const pj_str_t header = {"Buddy list:\n", 13};    
+    char out_str[64];
+
+    pj_cli_sess_write_msg(cval->sess, header.ptr, header.slen);
+
+    pjsua_enum_buddies(ids, &count);
+
+    if (count == 0) {
+	pj_ansi_snprintf(out_str, sizeof(out_str), " -none-\n");
+	pj_cli_sess_write_msg(cval->sess, out_str, pj_ansi_strlen(out_str));
+    } else {
+	for (i=0; i<(int)count; ++i) {	    
+	    pjsua_buddy_info info;
+	    pj_bzero(out_str, sizeof(out_str));
+
+	    if (pjsua_buddy_get_info(ids[i], &info) != PJ_SUCCESS)
+		continue;
+
+	    pj_ansi_snprintf(out_str, sizeof(out_str), 
+		    " [%2d] <%.*s>  %.*s\n", 
+		    ids[i]+1, 
+		    (int)info.status_text.slen,
+		    info.status_text.ptr, 
+		    (int)info.uri.slen,
+		    info.uri.ptr);
+
+	    pj_cli_sess_write_msg(cval->sess, out_str, pj_ansi_strlen(out_str));
+	}
+    }        
+    return PJ_SUCCESS;
+}
+
+/* Presence/buddy command handler */
+pj_status_t cmd_presence_handler(pj_cli_cmd_val *cval)
+{    
+    pj_status_t status = PJ_SUCCESS;
+
+    switch(pj_cli_get_cmd_id(cval->cmd)) {
+    case CMD_PRESENCE_ADD_BUDDY:
+	status = cmd_add_buddy(cval);
+	break;
+    case CMD_PRESENCE_DEL_BUDDY:
+	status = cmd_del_buddy(cval);
+	break;
+    case CMD_PRESENCE_SEND_IM:
+	status = cmd_send_im(cval);
+	break;
+    case CMD_PRESENCE_SUB:	
+    case CMD_PRESENCE_UNSUB:
+	status = cmd_subs_pres(cval, 
+		               pj_cli_get_cmd_id(cval->cmd)==CMD_PRESENCE_SUB);	
+	break;
+    case CMD_PRESENCE_TOG_STATE:
+	status = cmd_toggle_state(cval);
+	break;
+    case CMD_PRESENCE_TEXT:
+	status = cmd_set_presence_text(cval);
+	break;
+    case CMD_PRESENCE_LIST:
+	status = cmd_show_buddy(cval);
+	break;
+    }    
+
+    return status;
+}
+
+/* Show conference list */
+static pj_status_t cmd_media_list(pj_cli_cmd_val *cval)
+{
+    unsigned i, count;
+    pjsua_conf_port_id id[PJSUA_MAX_CALLS];
+    static const pj_str_t header = {"Conference ports:\n", 19};
+
+    pj_cli_sess_write_msg(cval->sess, header.ptr, header.slen);    
+
+    count = PJ_ARRAY_SIZE(id);
+    pjsua_enum_conf_ports(id, &count);
+
+    for (i=0; i<count; ++i) {
+	char out_str[PJSUA_MAX_CALLS*6];
+	char txlist[PJSUA_MAX_CALLS*4+10];
+	unsigned j;
+	pjsua_conf_port_info info;
+
+	pjsua_conf_get_port_info(id[i], &info);
+
+	pj_bzero(txlist, sizeof(txlist));
+	for (j=0; j<info.listener_cnt; ++j) {
+	    char s[10];
+	    pj_ansi_snprintf(s, sizeof(s), "#%d ", info.listeners[j]);
+	    pj_ansi_strcat(txlist, s);
+	}
+	pj_ansi_snprintf(out_str, 
+	       sizeof(out_str),
+	       "Port #%02d[%2dKHz/%dms/%d] %20.*s  transmitting to: %s\n", 
+	       info.slot_id, 
+	       info.clock_rate/1000,
+	       info.samples_per_frame*1000/info.channel_count/info.clock_rate,
+	       info.channel_count,
+	       (int)info.name.slen, 
+	       info.name.ptr,
+	       txlist);
+	pj_cli_sess_write_msg(cval->sess, out_str, pj_ansi_strlen(out_str));    
+    }    
+    return PJ_SUCCESS;
+}
+
+/* Conference connect/disconnect */
+static pj_status_t cmd_media_connect(pj_cli_cmd_val *cval, pj_bool_t connect)
+{
+    pj_status_t status;
+    
+    if (connect)
+	status = pjsua_conf_connect(pj_strtol(&cval->argv[1]), 
+				    pj_strtol(&cval->argv[2]));
+    else
+	status = pjsua_conf_disconnect(pj_strtol(&cval->argv[1]), 
+				       pj_strtol(&cval->argv[2]));
+
+    if (status == PJ_SUCCESS) {
+	static const pj_str_t success_msg = {"Success\n", 9};
+	pj_cli_sess_write_msg(cval->sess, success_msg.ptr, success_msg.slen);	
+    } else {
+	static const pj_str_t err_msg = {"ERROR!!\n", 9};
+	pj_cli_sess_write_msg(cval->sess, err_msg.ptr, err_msg.slen);
+    }
+    return status;
+}
+
+/* Adjust audio volume */
+static pj_status_t cmd_adjust_vol(pj_cli_cmd_val *cval)
+{
+    char buf[80];
+    float orig_level;
+    char *err;
+    char level_val[16] = {0};
+    pj_str_t tmp = pj_str(level_val);
+
+    /* Adjust mic level */
+    orig_level = app_config.mic_level;    
+    pj_strncpy_with_null(&tmp, &cval->argv[1], sizeof(level_val));
+    app_config.mic_level = (float)strtod(level_val, &err);
+    pjsua_conf_adjust_rx_level(0, app_config.mic_level);
+
+    pj_ansi_snprintf(buf, sizeof(buf),
+		     "Adjust mic level: [%4.1fx] -> [%4.1fx]\n", 
+		     orig_level, app_config.mic_level);
+
+    pj_cli_sess_write_msg(cval->sess, buf, pj_ansi_strlen(buf));
+
+    /* Adjust speaker level */
+    orig_level = app_config.speaker_level;        
+    pj_strncpy_with_null(&tmp, &cval->argv[2], sizeof(level_val));
+    app_config.speaker_level = (float)strtod(level_val, &err);
+    pjsua_conf_adjust_tx_level(0, app_config.speaker_level);
+
+    pj_ansi_snprintf(buf, sizeof(buf),
+		      "Adjust speaker level: [%4.1fx] -> [%4.1fx]\n", 
+		      orig_level, app_config.speaker_level);
+
+    pj_cli_sess_write_msg(cval->sess, buf, pj_ansi_strlen(buf));
+
+    return PJ_SUCCESS;
+}
+
+/* Set codec priority */
+static pj_status_t cmd_set_codec_prio(pj_cli_cmd_val *cval)
+{        
+    int new_prio;    
+    pj_status_t status;    
+
+    new_prio = pj_strtol(&cval->argv[2]);
+    if (new_prio < 0) 
+	new_prio = 0;
+    else if (new_prio > PJMEDIA_CODEC_PRIO_HIGHEST) 
+	new_prio = PJMEDIA_CODEC_PRIO_HIGHEST;
+
+    status = pjsua_codec_set_priority(&cval->argv[1], 
+				      (pj_uint8_t)new_prio);
+#if PJSUA_HAS_VIDEO
+    if (status != PJ_SUCCESS) {
+	status = pjsua_vid_codec_set_priority(&cval->argv[1], 
+					      (pj_uint8_t)new_prio);
+    }
+#endif
+    if (status != PJ_SUCCESS)
+	pjsua_perror(THIS_FILE, "Error setting codec priority", status);
+
+    return status;
+}
+
+/* Conference/media command handler */
+pj_status_t cmd_media_handler(pj_cli_cmd_val *cval)
+{
+    pj_status_t status = PJ_SUCCESS;
+
+    switch(pj_cli_get_cmd_id(cval->cmd)) {
+    case CMD_MEDIA_LIST:
+	status = cmd_media_list(cval);
+	break;
+    case CMD_MEDIA_CONF_CONNECT:
+    case CMD_MEDIA_CONF_DISCONNECT:
+	status = cmd_media_connect(cval, 
+		          pj_cli_get_cmd_id(cval->cmd)==CMD_MEDIA_CONF_CONNECT);	
+	break;
+    case CMD_MEDIA_ADJUST_VOL:	
+	status = cmd_adjust_vol(cval);
+	break;
+    case CMD_MEDIA_CODEC_PRIO:
+	status = cmd_set_codec_prio(cval);	
+	break;
+    }    
+
+    return status;
+}
+
+/* Dump status */
+static pj_status_t cmd_stat_dump(pj_bool_t detail)
+{
+    pjsua_dump(detail);
+    return PJ_SUCCESS;
+}
+
+static pj_status_t cmd_show_config()
+{
+    char settings[2000];
+    int len;
+
+    len = write_settings(&app_config, settings, sizeof(settings));
+    if (len < 1)
+	PJ_LOG(1,(THIS_FILE, "Error: not enough buffer"));
+    else
+	PJ_LOG(3,(THIS_FILE, 
+		  "Dumping configuration (%d bytes):\n%s\n",
+		  len, settings));
+
+    return PJ_SUCCESS;
+}
+
+static pj_status_t cmd_write_config(pj_cli_cmd_val *cval)
+{
+    char settings[2000];
+    char buf[128] = {0};
+    int len;
+    pj_str_t tmp = pj_str(buf);
+
+    pj_strncpy_with_null(&tmp, &cval->argv[1], sizeof(buf));    
+
+    len = write_settings(&app_config, settings, sizeof(settings));
+    if (len < 1)
+	PJ_LOG(1,(THIS_FILE, "Error: not enough buffer"));
+    else {
+	pj_oshandle_t fd;
+	pj_status_t status;
+
+	status = pj_file_open(app_config.pool, buf, PJ_O_WRONLY, &fd);
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror(THIS_FILE, "Unable to open file", status);
+	} else {
+	    char out_str[256];
+	    pj_ssize_t size = len;
+	    pj_file_write(fd, settings, &size);
+	    pj_file_close(fd);
+
+	    pj_ansi_snprintf(out_str, sizeof(out_str),
+			     "Settings successfully written to '%s'\n", buf);
+
+	    pj_cli_sess_write_msg(cval->sess, out_str, pj_ansi_strlen(out_str));
+	}
+    }
+
+    return PJ_SUCCESS;
+}
+
+/* Status and config command handler */
+pj_status_t cmd_config_handler(pj_cli_cmd_val *cval)
+{
+    pj_status_t status = PJ_SUCCESS;
+
+    switch(pj_cli_get_cmd_id(cval->cmd)) {
+    case CMD_CONFIG_DUMP_STAT:
+	status = cmd_stat_dump(PJ_FALSE);
+	break;
+    case CMD_CONFIG_DUMP_DETAIL:
+	status = cmd_stat_dump(PJ_TRUE);
+	break;
+    case CMD_CONFIG_DUMP_CONF:
+	status = cmd_show_config();	
+	break;
+    case CMD_CONFIG_WRITE_SETTING:	
+	status = cmd_write_config(cval);
+	break;
+    }    
+
+    return status;
+}
+
+/* Make single call */
+static pj_status_t cmd_make_single_call(pj_cli_cmd_val *cval)
+{
+    struct input_result result;
+    char dest[64] = {0};
+    char out_str[128];
+    pj_str_t tmp = pj_str(dest);      
+
+    pj_strncpy_with_null(&tmp, &cval->argv[1], sizeof(dest));    
+
+    pj_ansi_snprintf(out_str, 
+		     sizeof(out_str), 
+		     "(You currently have %d calls)\n",
+		     pjsua_call_get_count());
+
+    pj_cli_sess_write_msg(cval->sess, out_str, pj_ansi_strlen(out_str));
+    
+    /* input destination. */
+    get_input_url(tmp.ptr, tmp.slen, cval, &result);    
+    if (result.nb_result != NO_NB) {
+	pjsua_buddy_info binfo;
+	if (result.nb_result == -1 || result.nb_result == 0) {
+	    static const pj_str_t err_msg = 
+		    {"You can't do that with make call!\n", 35};
+	    pj_cli_sess_write_msg(cval->sess, err_msg.ptr, err_msg.slen);
+	    return PJ_SUCCESS;
+	} 	
+	pjsua_buddy_get_info(result.nb_result-1, &binfo);	    
+	pj_strncpy(&tmp, &binfo.uri, sizeof(dest));
+    } else if (result.uri_result) {
+	tmp = pj_str(result.uri_result);
+    } else {
+	tmp.slen = 0;
+    }
+
+    pjsua_msg_data_init(&msg_data);
+    TEST_MULTIPART(&msg_data);
+    pjsua_call_make_call(current_acc, &tmp, &call_opt, NULL, 
+			 &msg_data, &current_call);
+    return PJ_SUCCESS;
+}
+
+/* Make multi call */
+static pj_status_t cmd_make_multi_call(pj_cli_cmd_val *cval)
+{
+    struct input_result result;
+    char dest[64] = {0};    
+    char out_str[128];
+    int i, count;
+    pj_str_t tmp = pj_str(dest);
+
+    pj_ansi_snprintf(out_str, 
+		     sizeof(out_str), 
+		     "(You currently have %d calls)\n",
+		     pjsua_call_get_count());
+    
+    count = pj_strtol(&cval->argv[1]);
+    if (count < 1)
+	return PJ_SUCCESS;
+    
+    pj_strncpy_with_null(&tmp, &cval->argv[2], sizeof(dest));
+    
+    /* input destination. */
+    get_input_url(tmp.ptr, tmp.slen, cval, &result);    
+    if (result.nb_result != NO_NB) {
+	pjsua_buddy_info binfo;
+	if (result.nb_result == -1 || result.nb_result == 0) {
+	    static const pj_str_t err_msg = 
+			    {"You can't do that with make call!\n", 35};
+	    pj_cli_sess_write_msg(cval->sess, err_msg.ptr, err_msg.slen);
+	    return PJ_SUCCESS;
+	} 	
+	pjsua_buddy_get_info(result.nb_result-1, &binfo);	    
+	pj_strncpy(&tmp, &binfo.uri, sizeof(dest));	
+    } else {
+	tmp = pj_str(result.uri_result);
+    } 
+
+    for (i=0; i<count; ++i) {
+	pj_status_t status;
+
+	status = pjsua_call_make_call(current_acc, &tmp, &call_opt, NULL,
+	    NULL, NULL);
+	if (status != PJ_SUCCESS)
+	    break;
+    }
+    return PJ_SUCCESS;
+}
+
+/* Answer call */
+static pj_status_t cmd_answer_call(pj_cli_cmd_val *cval)
+{
+    pjsua_call_info call_info;
+
+    if (current_call != PJSUA_INVALID_ID) {
+	pjsua_call_get_info(current_call, &call_info);
+    } else {
+	/* Make compiler happy */
+	call_info.role = PJSIP_ROLE_UAC;
+	call_info.state = PJSIP_INV_STATE_DISCONNECTED;
+    }
+
+    if (current_call == PJSUA_INVALID_ID || 
+	call_info.role != PJSIP_ROLE_UAS ||
+	call_info.state >= PJSIP_INV_STATE_CONNECTING)
+    {	
+	static const pj_str_t err_msg = {"No pending incoming call\n", 26};	  
+	pj_cli_sess_write_msg(cval->sess, err_msg.ptr, err_msg.slen);
+
+    } else {
+	int st_code;
+	char contact[120];
+	pj_str_t hname = { "Contact", 7 };
+	pj_str_t hvalue;	
+	pjsip_generic_string_hdr hcontact;	
+
+	st_code = pj_strtol(&cval->argv[1]);
+	if ((st_code < 100) || (st_code > 699))
+	    return PJ_SUCCESS;
+
+	pjsua_msg_data_init(&msg_data);
+
+	if (st_code/100 == 3) {
+	    if (cval->argc < 3) {
+		static const pj_str_t err_msg = {"Enter URL to be put "
+						 "in Contact\n",  32};
+		pj_cli_sess_write_msg(cval->sess, err_msg.ptr, err_msg.slen);		
+		return PJ_SUCCESS;
+	    }
+
+	    hvalue = pj_str(contact);
+	    pjsip_generic_string_hdr_init2(&hcontact, &hname, &hvalue);
+
+	    pj_list_push_back(&msg_data.hdr_list, &hcontact);
+	}
+
+	/*
+	* Must check again!
+	* Call may have been disconnected while we're waiting for 
+	* keyboard input.
+	*/
+	if (current_call == PJSUA_INVALID_ID) {
+	    static const pj_str_t err_msg = {"Call has been disconnected\n", 
+					     28};	    
+	    pj_cli_sess_write_msg(cval->sess, err_msg.ptr, err_msg.slen);
+	}
+
+	pjsua_call_answer2(current_call, &call_opt, st_code, NULL, &msg_data);
+    }
+    return PJ_SUCCESS;
+}
+
+/* Hangup call */
+static pj_status_t cmd_hangup_call(pj_cli_cmd_val *cval, pj_bool_t all)
+{
+    if (current_call == PJSUA_INVALID_ID) {
+	static const pj_str_t err_msg = {"No current call\n", 17};
+	pj_cli_sess_write_msg(cval->sess, err_msg.ptr, err_msg.slen);
+    } else {
+	if (all)
+	    pjsua_call_hangup_all();	    
+	else
+	    pjsua_call_hangup(current_call, 0, NULL, NULL);
+    }
+    return PJ_SUCCESS;
+}
+
+/* Hold call */
+static pj_status_t cmd_hold_call()
+{
+    if (current_call != PJSUA_INVALID_ID) {	
+	pjsua_call_set_hold(current_call, NULL);
+
+    } else {
+	PJ_LOG(3,(THIS_FILE, "No current call"));
+    }
+    return PJ_SUCCESS;
+}
+
+/* Call reinvite */
+static pj_status_t cmd_call_reinvite()
+{
+    if (current_call != PJSUA_INVALID_ID) {
+	/*
+	 * re-INVITE
+	 */
+	call_opt.flag |= PJSUA_CALL_UNHOLD;
+	pjsua_call_reinvite2(current_call, &call_opt, NULL);
+
+    } else {
+	PJ_LOG(3,(THIS_FILE, "No current call"));
+    }
+    return PJ_SUCCESS;
+}
+
+/* Send update */
+static pj_status_t cmd_call_update()
+{
+    if (current_call != PJSUA_INVALID_ID) {
+	pjsua_call_update2(current_call, &call_opt, NULL);
+    } else {
+	PJ_LOG(3,(THIS_FILE, "No current call"));
+    }
+    return PJ_SUCCESS;
+}
+
+/* Select next call */
+static pj_status_t cmd_next_call(pj_bool_t next)
+{
+    /*
+     * Cycle next/prev dialog.
+     */
+    if (next) {
+	find_next_call(current_call);
+    } else {
+	find_prev_call(current_call);
+    }
+
+    if (current_call != PJSUA_INVALID_ID) {	
+	pjsua_call_info call_info;
+
+	pjsua_call_get_info(current_call, &call_info);
+	PJ_LOG(3,(THIS_FILE,"Current dialog: %.*s", 
+		  (int)call_info.remote_info.slen, 
+		  call_info.remote_info.ptr));
+
+    } else {
+	PJ_LOG(3,(THIS_FILE,"No current dialog"));
+    }
+    return PJ_SUCCESS;
+}
+
+/* Transfer call */
+static pj_status_t cmd_transfer_call(pj_cli_cmd_val *cval)
+{
+    if (current_call == PJSUA_INVALID_ID) {
+
+	PJ_LOG(3,(THIS_FILE, "No current call"));
+
+    } else {
+	char out_str[64];
+	int call = current_call;
+	char dest[64] = {0};
+	pj_str_t tmp = pj_str(dest);
+	struct input_result result;
+	pjsip_generic_string_hdr refer_sub;
+	pj_str_t STR_REFER_SUB = { "Refer-Sub", 9 };
+	pj_str_t STR_FALSE = { "false", 5 };
+	pjsua_call_info ci;
+	
+	pj_strncpy_with_null(&tmp, &cval->argv[1], sizeof(dest));	
+
+	pjsua_call_get_info(current_call, &ci);
+	pj_ansi_snprintf(out_str, 
+			 sizeof(out_str), 
+			 "Transfering current call [%d] %.*s\n",
+			 current_call,
+			 (int)ci.remote_info.slen, 
+			 ci.remote_info.ptr);
+
+	get_input_url(tmp.ptr, tmp.slen, cval, &result);
+
+	/* Check if call is still there. */
+
+	if (call != current_call) {
+	    puts("Call has been disconnected");
+	    return PJ_SUCCESS;
+	}
+
+	pjsua_msg_data_init(&msg_data);
+	if (app_config.no_refersub) {
+	    /* Add Refer-Sub: false in outgoing REFER request */
+	    pjsip_generic_string_hdr_init2(&refer_sub, &STR_REFER_SUB,
+		&STR_FALSE);
+	    pj_list_push_back(&msg_data.hdr_list, &refer_sub);
+	}
+	if (result.nb_result != NO_NB) {
+	    if (result.nb_result == -1 || result.nb_result == 0) {
+		static const pj_str_t err_msg = {"You can't do that with "
+						 "transfer call!\n", 39};
+
+		pj_cli_sess_write_msg(cval->sess, err_msg.ptr, err_msg.slen);
+	    } else {
+		pjsua_buddy_info binfo;
+		pjsua_buddy_get_info(result.nb_result-1, &binfo);
+		pjsua_call_xfer( current_call, &binfo.uri, &msg_data);
+	    }
+	} else if (result.uri_result) {
+	    pj_str_t tmp;
+	    tmp = pj_str(result.uri_result);
+	    pjsua_call_xfer( current_call, &tmp, &msg_data);
+	}
+    }	    
+    return PJ_SUCCESS;
+}
+
+/* Transfer call */
+static pj_status_t cmd_transfer_replace_call(pj_cli_cmd_val *cval)
+{    
+    if (current_call == -1) {
+	PJ_LOG(3,(THIS_FILE, "No current call"));
+    } else {
+	int call = current_call;
+	int dst_call;
+	pjsip_generic_string_hdr refer_sub;
+	pj_str_t STR_REFER_SUB = { "Refer-Sub", 9 };
+	pj_str_t STR_FALSE = { "false", 5 };
+	pjsua_call_id ids[PJSUA_MAX_CALLS];	
+	pjsua_msg_data msg_data;
+	char buf[8] = {0};	
+	pj_str_t tmp = pj_str(buf);
+	unsigned count;	
+	static const pj_str_t err_invalid_num = 
+				    {"Invalid destination call number\n", 32 };
+	count = PJ_ARRAY_SIZE(ids);
+	pjsua_enum_calls(ids, &count);
+
+	if (count <= 1) {	    
+	    static const pj_str_t err_no_other_call = 
+				    {"There are no other calls\n", 25};
+
+	    pj_cli_sess_write_msg(cval->sess, err_no_other_call.ptr, 
+				  err_no_other_call.slen);
+	    return PJ_SUCCESS;
+	}
+
+	pj_strncpy_with_null(&tmp, &cval->argv[1], sizeof(buf));
+	dst_call = my_atoi(tmp.ptr);
+
+	/* Check if call is still there. */
+	if (call != current_call) {	    
+	    static pj_str_t err_call_dc = 
+				    {"Call has been disconnected\n", 27};
+
+	    pj_cli_sess_write_msg(cval->sess, err_call_dc.ptr, 
+				  err_call_dc.slen);
+	    return PJ_SUCCESS;
+	}
+
+	/* Check that destination call is valid. */
+	if (dst_call == call) {
+	    static pj_str_t err_same_num = 
+				    {"Destination call number must not be the "
+				     "same as the call being transfered\n", 74};
+
+	    pj_cli_sess_write_msg(cval->sess, err_same_num.ptr, 
+				  err_same_num.slen);
+	    return PJ_SUCCESS;
+	}
+
+	if (dst_call >= PJSUA_MAX_CALLS) {
+	    pj_cli_sess_write_msg(cval->sess, err_invalid_num.ptr, 
+				  err_invalid_num.slen);
+	    return PJ_SUCCESS;
+	}
+
+	if (!pjsua_call_is_active(dst_call)) {
+	    pj_cli_sess_write_msg(cval->sess, err_invalid_num.ptr, 
+				  err_invalid_num.slen);
+	    return PJ_SUCCESS;
+	}
+
+	pjsua_msg_data_init(&msg_data);
+	if (app_config.no_refersub) {
+	    /* Add Refer-Sub: false in outgoing REFER request */
+	    pjsip_generic_string_hdr_init2(&refer_sub, &STR_REFER_SUB, 
+					   &STR_FALSE);
+	    pj_list_push_back(&msg_data.hdr_list, &refer_sub);
+	}
+
+	pjsua_call_xfer_replaces(call, dst_call, 
+				 PJSUA_XFER_NO_REQUIRE_REPLACES, 
+				 &msg_data);
+    }
+    return PJ_SUCCESS;
+}
+
+static pj_status_t cmd_redirect_call(pj_cli_cmd_val *cval)
+{  
+    if (current_call == PJSUA_INVALID_ID) {
+	PJ_LOG(3,(THIS_FILE, "No current call"));
+	return PJ_SUCCESS;
+    }
+    if (!pjsua_call_is_active(current_call)) {
+	PJ_LOG(1,(THIS_FILE, "Call %d has gone", current_call));
+    } else {	
+	enum {
+	    ACCEPT_REPLACE, ACCEPT, REJECT, STOP
+	};
+	int choice = pj_strtol(&cval->argv[1]);
+
+	switch (choice) {
+	case ACCEPT_REPLACE:
+	    pjsua_call_process_redirect(current_call, 
+	    				PJSIP_REDIRECT_ACCEPT_REPLACE);	    
+	    break;
+	case ACCEPT:
+	    pjsua_call_process_redirect(current_call, PJSIP_REDIRECT_ACCEPT);
+	    break;
+	case REJECT:
+	    pjsua_call_process_redirect(current_call, PJSIP_REDIRECT_REJECT);
+	    break;
+	default:
+	    pjsua_call_process_redirect(current_call, PJSIP_REDIRECT_STOP);
+	    break;
+	}	
+    }
+    return PJ_SUCCESS;
+}
+
+/* Send DTMF (RFC2833) */
+static pj_status_t cmd_dtmf_2833(pj_cli_cmd_val *cval)
+{
+    if (current_call == PJSUA_INVALID_ID) {
+
+	PJ_LOG(3,(THIS_FILE, "No current call"));
+
+    } else if (!pjsua_call_has_media(current_call)) {
+
+	PJ_LOG(3,(THIS_FILE, "Media is not established yet!"));
+
+    } else {	
+	int call = current_call;
+	pj_status_t status;
+
+	if (call != current_call) {
+	    static const pj_str_t err_msg = {"Call has been disconnected\n",
+					     28};
+	    pj_cli_sess_write_msg(cval->sess, err_msg.ptr, err_msg.slen);
+	    return PJ_SUCCESS;;
+	}
+	
+	status = pjsua_call_dial_dtmf(current_call, &cval->argv[1]);
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror(THIS_FILE, "Unable to send DTMF", status);
+	} else {
+	    static const pj_str_t msg = {"DTMF digits enqueued "
+					 "for transmission\n", 39};
+	    pj_cli_sess_write_msg(cval->sess, msg.ptr, msg.slen);
+	}
+    }
+    return PJ_SUCCESS;
+}
+
+/* Send DTMF with SIP Info */
+static pj_status_t cmd_call_info(pj_cli_cmd_val *cval)
+{
+    if (current_call == PJSUA_INVALID_ID) {
+
+	PJ_LOG(3,(THIS_FILE, "No current call"));
+
+    } else {
+	const pj_str_t SIP_INFO = pj_str("INFO");	
+	int call = current_call;
+	int i;
+	pj_status_t status;
+
+	if (call != current_call) {
+	    static const pj_str_t err_msg = {"Call has been disconnected\n",
+					     28};
+	    pj_cli_sess_write_msg(cval->sess, err_msg.ptr, err_msg.slen);
+	    return PJ_SUCCESS;;
+	}
+	
+	for (i=0; i<cval->argv[1].slen; ++i) {
+	    char body[64];
+
+	    pjsua_msg_data_init(&msg_data);
+	    msg_data.content_type = pj_str("application/dtmf-relay");
+
+	    pj_ansi_snprintf(body, 
+			     sizeof(body),
+			     "Signal=%c\n"
+			     "Duration=160",
+			     cval->argv[1].ptr[i]);
+
+	    msg_data.msg_body = pj_str(body);
+
+	    status = pjsua_call_send_request(current_call, &SIP_INFO, 
+		&msg_data);
+	    if (status != PJ_SUCCESS) {
+		break;
+	    }
+	}
+    }
+    return PJ_SUCCESS;
+}
+
+/* Dump call quality */
+static pj_status_t cmd_call_quality()
+{
+    if (current_call != PJSUA_INVALID_ID) {
+	log_call_dump(current_call);
+    } else {
+	PJ_LOG(3,(THIS_FILE, "No current call"));
+    }
+    return PJ_SUCCESS;
+}
+
+/* Send arbitrary request */
+static pj_status_t cmd_send_arbitrary(pj_cli_cmd_val *cval)
+{
+    if (pjsua_acc_get_count() == 0) {
+	static const pj_str_t err_msg = {"Sorry, need at least one "
+					 "account configured\n", 45};
+	pj_cli_sess_write_msg(cval->sess, err_msg.ptr, err_msg.slen);	
+    } else {
+	char *uri;
+	char dest[64] = {0};
+	pj_str_t tmp = pj_str(dest);	
+	struct input_result result;
+	static const pj_str_t header = {"Send arbitrary request to "
+					"remote host\n", 39};
+	
+	pj_cli_sess_write_msg(cval->sess, header.ptr, header.slen);	
+	
+	pj_strncpy_with_null(&tmp, &cval->argv[2], sizeof(dest));	
+	/* Input destination URI */
+	uri = NULL;
+	get_input_url(tmp.ptr, tmp.slen, cval, &result);
+	if (result.nb_result != NO_NB) {
+	    if (result.nb_result == -1) {		
+		static const pj_str_t err_msg = {"Sorry you can't do that!\n",
+						 26};
+		pj_cli_sess_write_msg(cval->sess, err_msg.ptr, err_msg.slen);
+		return PJ_SUCCESS;
+	    } else if (result.nb_result == 0) {
+		uri = NULL;
+		if (current_call == PJSUA_INVALID_ID) {
+		    static const pj_str_t err_msg = {"No current call\n",
+						     17};
+		    pj_cli_sess_write_msg(cval->sess, err_msg.ptr,
+				          err_msg.slen);
+
+		    return PJ_SUCCESS;
+		}
+	    } else {
+		pjsua_buddy_info binfo;
+		pjsua_buddy_get_info(result.nb_result-1, &binfo);		
+		pj_strncpy_with_null(&tmp, &binfo.uri, sizeof(dest));
+		uri = tmp.ptr;
+	    }
+	} else if (result.uri_result) {
+	    uri = result.uri_result;
+	} else {
+	    return PJ_SUCCESS;;
+	}
+
+	if (uri) {
+	    char method[64] = {0};
+	    pj_str_t tmp_method = pj_str(method);	    
+	    pj_strncpy_with_null(&tmp_method, &cval->argv[1], sizeof(method));
+	    tmp = pj_str(uri);
+	    send_request(method, &tmp);
+	} else {
+	    /* If you send call control request using this method
+	    * (such requests includes BYE, CANCEL, etc.), it will
+	    * not go well with the call state, so don't do it
+	    * unless it's for testing.
+	    */	    
+	    pjsua_call_send_request(current_call, &cval->argv[1], NULL);
+	}    
+    }
+    return PJ_SUCCESS;
+}
+
+static pj_status_t cmd_show_current_call(pj_cli_cmd_val *cval)
+{
+    char out_str[128];
+    int i = pjsua_call_get_count();
+    pj_ansi_snprintf(out_str, sizeof(out_str),
+		     "You have %d active call%s\n", i, (i>1?"s":""));
+
+    pj_cli_sess_write_msg(cval->sess, out_str, 
+	pj_ansi_strlen(out_str));
+
+    if (current_call != PJSUA_INVALID_ID) {
+	pjsua_call_info ci;
+	if (pjsua_call_get_info(current_call, &ci)==PJ_SUCCESS) {	    
+	    pj_ansi_snprintf(out_str, sizeof(out_str), 
+		   "Current call id=%d to %.*s [%.*s]\n", current_call,
+		   (int)ci.remote_info.slen, ci.remote_info.ptr,
+		   (int)ci.state_text.slen, ci.state_text.ptr);
+
+	    pj_cli_sess_write_msg(cval->sess, out_str, 
+				  pj_ansi_strlen(out_str));
+	}
+    } 
+    return PJ_SUCCESS;
+}
+
+/* Call handler */
+pj_status_t cmd_call_handler(pj_cli_cmd_val *cval)
+{    
+    pj_status_t status = PJ_SUCCESS;
+    pj_cli_cmd_id cmd_id = pj_cli_get_cmd_id(cval->cmd);
+
+    switch(cmd_id) {
+    case CMD_CALL_NEW:
+	status = cmd_make_single_call(cval);
+	break;
+    case CMD_CALL_MULTI:
+	status = cmd_make_multi_call(cval);
+	break;
+    case CMD_CALL_ANSWER:
+	status = cmd_answer_call(cval);	
+	break;
+    case CMD_CALL_HANGUP:	
+    case CMD_CALL_HANGUP_ALL:	
+	status = cmd_hangup_call(cval, (cmd_id==CMD_CALL_HANGUP_ALL));
+	break;
+    case CMD_CALL_HOLD:	
+	status = cmd_hold_call();
+	break;
+    case CMD_CALL_REINVITE:	
+	status = cmd_call_reinvite();
+	break;
+    case CMD_CALL_UPDATE:	
+	status = cmd_call_update();
+	break;
+    case CMD_CALL_NEXT:			
+    case CMD_CALL_PREVIOUS:	
+	status = cmd_next_call(cmd_id==CMD_CALL_NEXT);	
+	break;
+    case CMD_CALL_TRANSFER:	
+	status = cmd_transfer_call(cval);
+	break;
+    case CMD_CALL_TRANSFER_REPLACE:	
+	status = cmd_transfer_replace_call(cval);
+	break;
+    case CMD_CALL_REDIRECT:
+	status = cmd_redirect_call(cval);
+	break;
+    case CMD_CALL_D2833:	
+	status = cmd_dtmf_2833(cval);
+	break;
+    case CMD_CALL_INFO:	
+	status = cmd_call_info(cval);
+	break;
+    case CMD_CALL_DUMP_Q:	
+	status = cmd_call_quality();
+	break;
+    case CMD_CALL_SEND_ARB:	
+	status = cmd_send_arbitrary(cval);
+	break;
+    case CMD_CALL_LIST:
+	status = cmd_show_current_call(cval);
+	break;
+    }    
+
+    return status;
+}
+
+#if PJSUA_HAS_VIDEO 
+static pj_status_t cmd_set_video_enable(pj_bool_t enabled)
+{    
+    app_config.vid.vid_cnt = (enabled ? 1 : 0);
+    PJ_LOG(3,(THIS_FILE, "Video will be %s in next offer/answer",
+	(enabled?"enabled":"disabled")));
+
+    return PJ_SUCCESS;
+}
+
+static pj_status_t modify_video_account(pjsua_acc_config *acc_cfg)
+{
+    pj_status_t status = pjsua_acc_modify(current_acc, acc_cfg);
+    if (status != PJ_SUCCESS)
+	PJ_PERROR(1,(THIS_FILE, status, "Error modifying account %d",
+		     current_acc));
+
+    return status;
+}
+
+static pj_status_t cmd_show_account_video()
+{
+    pjsua_acc_config acc_cfg;
+    
+    pjsua_acc_get_config(current_acc, &acc_cfg);
+    app_config_show_video(current_acc, &acc_cfg);
+    return PJ_SUCCESS;
+}
+
+static pj_status_t cmd_video_acc_handler(pj_cli_cmd_val *cval)
+{
+    pjsua_acc_config acc_cfg;
+    pj_cli_cmd_id cmd_id = pj_cli_get_cmd_id(cval->cmd);
+
+    pjsua_acc_get_config(current_acc, &acc_cfg);
+
+    switch(cmd_id) {
+    case CMD_VIDEO_ACC_AUTORX:	
+    case CMD_VIDEO_ACC_AUTOTX:	
+	{
+	    int on = (pj_ansi_strnicmp(cval->argv[1].ptr, "On", 2)==0);
+       	
+	    if (cmd_id == CMD_VIDEO_ACC_AUTORX)
+		acc_cfg.vid_in_auto_show = on;
+	    else
+		acc_cfg.vid_out_auto_transmit = on;
+	}
+	break;
+    case CMD_VIDEO_ACC_CAP_ID:		
+    case CMD_VIDEO_ACC_REN_ID:
+	{
+	    int dev = pj_strtol(&cval->argv[1]);	    	    
+
+	    if (cmd_id == CMD_VIDEO_ACC_CAP_ID)
+		acc_cfg.vid_cap_dev = dev;
+	    else 
+		acc_cfg.vid_rend_dev = dev;		
+	}	
+	break;
+    }
+    modify_video_account(&acc_cfg);
+    return PJ_SUCCESS;
+}
+
+static pj_status_t cmd_add_vid_strm()
+{
+    return pjsua_call_set_vid_strm(current_call,
+				   PJSUA_CALL_VID_STRM_ADD, NULL);
+}
+
+static pj_status_t cmd_enable_vid_rx(pj_cli_cmd_val *cval)
+{
+    pjsua_call_vid_strm_op_param param;    
+    pjsua_stream_info si;    
+    pj_status_t status = PJ_SUCCESS;
+    pj_bool_t on = (pj_ansi_strnicmp(cval->argv[1].ptr, "On", 2) == 0);
+
+    pjsua_call_vid_strm_op_param_default(&param);    
+
+    param.med_idx = pj_strtol(&cval->argv[2]); 
+    if (pjsua_call_get_stream_info(current_call, param.med_idx, &si) ||
+	si.type != PJMEDIA_TYPE_VIDEO)
+    {
+	PJ_PERROR(1,(THIS_FILE, PJ_EINVAL, "Invalid stream"));
+	return status;
+    }
+
+    if (on) param.dir = (si.info.vid.dir | PJMEDIA_DIR_DECODING);
+    else param.dir = (si.info.vid.dir & PJMEDIA_DIR_ENCODING);
+
+    status = pjsua_call_set_vid_strm(current_call,
+				     PJSUA_CALL_VID_STRM_CHANGE_DIR,
+				     &param);
+    return status;
+}
+
+static pj_status_t cmd_enable_vid_tx(pj_cli_cmd_val *cval)
+{
+    pjsua_call_vid_strm_op_param param;    
+    pj_status_t status = PJ_SUCCESS;
+    pj_bool_t on = (pj_ansi_strnicmp(cval->argv[1].ptr, "On", 2) == 0);    
+    
+    pjsua_call_vid_strm_op op = on? PJSUA_CALL_VID_STRM_START_TRANSMIT :
+				PJSUA_CALL_VID_STRM_STOP_TRANSMIT;
+
+    pjsua_call_vid_strm_op_param_default(&param);
+    
+    param.med_idx = pj_strtol(&cval->argv[2]);
+
+    status = pjsua_call_set_vid_strm(current_call, op, &param);    
+    return status;
+}
+
+static pj_status_t cmd_enable_vid_stream(pj_cli_cmd_val *cval, 
+					   pj_bool_t enable)
+{    
+    pjsua_call_vid_strm_op_param param;    
+    pjsua_call_vid_strm_op op = enable? PJSUA_CALL_VID_STRM_CHANGE_DIR :
+				PJSUA_CALL_VID_STRM_REMOVE;
+
+    pjsua_call_vid_strm_op_param_default(&param);
+
+    param.med_idx = cval->argc > 1 ? pj_strtol(&cval->argv[1]) : -1;
+    param.dir = PJMEDIA_DIR_ENCODING_DECODING;
+    return pjsua_call_set_vid_strm(current_call, op, &param);    
+}
+
+static pj_status_t cmd_set_cap_dev_id(pj_cli_cmd_val *cval)
+{
+    pjsua_call_vid_strm_op_param param;
+
+    pjsua_call_vid_strm_op_param_default(&param);
+    param.med_idx = cval->argc > 1? pj_strtol(&cval->argv[1]) : -1;
+    param.cap_dev = cval->argc > 2? pj_strtol(&cval->argv[2]) : 
+				    PJMEDIA_VID_DEFAULT_CAPTURE_DEV;
+
+    return pjsua_call_set_vid_strm(current_call,
+				   PJSUA_CALL_VID_STRM_CHANGE_CAP_DEV,
+				   &param);
+}
+
+static pj_status_t cmd_list_vid_dev()
+{
+    vid_list_devs();
+    return PJ_SUCCESS;
+}
+
+static pj_status_t cmd_vid_device_refresh()
+{
+    pjmedia_vid_dev_refresh();
+    return PJ_SUCCESS;
+}
+
+static pj_status_t cmd_vid_device_preview(pj_cli_cmd_val *cval)
+{        
+    int dev_id = pj_strtol(&cval->argv[2]);    
+    pj_bool_t on = (pj_ansi_strnicmp(cval->argv[1].ptr, "On", 2) == 0);          
+
+    if (on) {
+	pjsua_vid_preview_param param;
+
+	pjsua_vid_preview_param_default(&param);
+	param.wnd_flags = PJMEDIA_VID_DEV_WND_BORDER |
+	    PJMEDIA_VID_DEV_WND_RESIZABLE;
+	pjsua_vid_preview_start(dev_id, &param);
+	arrange_window(pjsua_vid_preview_get_win(dev_id));
+    } else {
+	pjsua_vid_win_id wid;
+	wid = pjsua_vid_preview_get_win(dev_id);
+	if (wid != PJSUA_INVALID_ID) {
+	    /* Preview window hiding once it is stopped is
+	    * responsibility of app */
+	    pjsua_vid_win_set_show(wid, PJ_FALSE);
+	    pjsua_vid_preview_stop(dev_id);
+	}
+    }    
+    return PJ_SUCCESS;
+}
+
+static pj_status_t cmd_vid_codec_list()
+{    
+    pjsua_codec_info ci[PJMEDIA_CODEC_MGR_MAX_CODECS];
+    unsigned count = PJ_ARRAY_SIZE(ci);
+    pj_status_t status = pjsua_vid_enum_codecs(ci, &count);
+    if (status != PJ_SUCCESS) {
+	PJ_PERROR(1,(THIS_FILE, status, "Error enumerating codecs"));
+    } else {
+	unsigned i;
+	PJ_LOG(3,(THIS_FILE, "Found %d video codecs:", count));
+	PJ_LOG(3,(THIS_FILE, "codec id      prio  fps    bw(kbps)   size"));
+	PJ_LOG(3,(THIS_FILE, "------------------------------------------"));
+	for (i=0; i<count; ++i) {
+	    pjmedia_vid_codec_param cp;
+	    pjmedia_video_format_detail *vfd;
+
+	    status = pjsua_vid_codec_get_param(&ci[i].codec_id, &cp);
+	    if (status != PJ_SUCCESS)
+		continue;
+
+	    vfd = pjmedia_format_get_video_format_detail(&cp.enc_fmt,
+		PJ_TRUE);
+	    PJ_LOG(3,(THIS_FILE, "%.*s%.*s %3d %7.2f  %4d/%4d  %dx%d", 
+		(int)ci[i].codec_id.slen, ci[i].codec_id.ptr,
+		13-(int)ci[i].codec_id.slen, "                ",
+		ci[i].priority,
+		(vfd->fps.num*1.0/vfd->fps.denum),
+		vfd->avg_bps/1000, vfd->max_bps/1000,
+		vfd->size.w, vfd->size.h));
+	}
+    }
+    return PJ_SUCCESS;
+}
+
+static pj_status_t cmd_set_vid_codec_prio(pj_cli_cmd_val *cval)
+{    
+    int prio = pj_strtol(&cval->argv[2]);
+    pj_status_t status;
+        
+    status = pjsua_vid_codec_set_priority(&cval->argv[1], (pj_uint8_t)prio);
+    if (status != PJ_SUCCESS)
+	PJ_PERROR(1,(THIS_FILE, status, "Set codec priority error"));
+
+    return PJ_SUCCESS;
+}
+
+static pj_status_t cmd_set_vid_codec_fps(pj_cli_cmd_val *cval)
+{    
+    pjmedia_vid_codec_param cp;
+    int M, N;    
+    pj_status_t status;
+
+    M = pj_strtol(&cval->argv[2]);
+    N = pj_strtol(&cval->argv[3]);
+    status = pjsua_vid_codec_get_param(&cval->argv[1], &cp);
+    if (status == PJ_SUCCESS) {
+	cp.enc_fmt.det.vid.fps.num = M;
+	cp.enc_fmt.det.vid.fps.denum = N;
+	status = pjsua_vid_codec_set_param(&cval->argv[1], &cp);
+    }
+    if (status != PJ_SUCCESS)
+	PJ_PERROR(1,(THIS_FILE, status, "Set codec framerate error"));
+
+    return PJ_SUCCESS;
+}
+
+static pj_status_t cmd_set_vid_codec_bitrate(pj_cli_cmd_val *cval)
+{    
+    pjmedia_vid_codec_param cp;    
+    int M, N;
+    pj_status_t status;
+
+    M = pj_strtol(&cval->argv[2]);
+    N = pj_strtol(&cval->argv[3]);
+    status = pjsua_vid_codec_get_param(&cval->argv[1], &cp);
+    if (status == PJ_SUCCESS) {
+	cp.enc_fmt.det.vid.avg_bps = M * 1000;
+	cp.enc_fmt.det.vid.max_bps = N * 1000;
+	status = pjsua_vid_codec_set_param(&cval->argv[1], &cp);
+    }
+    if (status != PJ_SUCCESS)
+	PJ_PERROR(1,(THIS_FILE, status, "Set codec bitrate error"));
+
+    return status;
+}
+
+static pj_status_t cmd_set_vid_codec_size(pj_cli_cmd_val *cval)
+{
+    pjmedia_vid_codec_param cp;    
+    int M, N;
+    pj_status_t status;
+
+    M = pj_strtol(&cval->argv[2]);
+    N = pj_strtol(&cval->argv[3]);
+    status = pjsua_vid_codec_get_param(&cval->argv[1], &cp);
+    if (status == PJ_SUCCESS) {
+	cp.enc_fmt.det.vid.size.w = M;
+	cp.enc_fmt.det.vid.size.h = N;
+	status = pjsua_vid_codec_set_param(&cval->argv[1], &cp);
+    }
+    if (status != PJ_SUCCESS)
+	PJ_PERROR(1,(THIS_FILE, status, "Set codec size error"));
+
+    return status;
+}
+
+static pj_status_t cmd_vid_win_list()
+{
+    pjsua_vid_win_id wids[PJSUA_MAX_VID_WINS];
+    unsigned i, cnt = PJ_ARRAY_SIZE(wids);
+
+    pjsua_vid_enum_wins(wids, &cnt);
+
+    PJ_LOG(3,(THIS_FILE, "Found %d video windows:", cnt));
+    PJ_LOG(3,(THIS_FILE, "WID show    pos       size"));
+    PJ_LOG(3,(THIS_FILE, "------------------------------"));
+    for (i = 0; i < cnt; ++i) {
+	pjsua_vid_win_info wi;
+	pjsua_vid_win_get_info(wids[i], &wi);
+	PJ_LOG(3,(THIS_FILE, "%3d   %c  (%d,%d)  %dx%d",
+	    wids[i], (wi.show?'Y':'N'), wi.pos.x, wi.pos.y,
+	    wi.size.w, wi.size.h));
+    }
+    return PJ_SUCCESS;
+}
+
+static pj_status_t cmd_arrange_vid_win()
+{
+    arrange_window(PJSUA_INVALID_ID);
+    return PJ_SUCCESS;
+}
+
+static pj_status_t cmd_show_vid_win(pj_cli_cmd_val *cval, pj_bool_t show)
+{            
+    pjsua_vid_win_id wid = pj_strtol(&cval->argv[1]);
+    return pjsua_vid_win_set_show(wid, show);
+}
+
+static pj_status_t cmd_move_vid_win(pj_cli_cmd_val *cval)
+{
+    pjsua_vid_win_id wid = pj_strtol(&cval->argv[1]);
+    pjmedia_coord pos;
+
+    pos.x = pj_strtol(&cval->argv[2]);
+    pos.y = pj_strtol(&cval->argv[3]);
+    return pjsua_vid_win_set_pos(wid, &pos);
+}
+
+static pj_status_t cmd_resize_vid_win(pj_cli_cmd_val *cval)
+{
+    pjsua_vid_win_id wid = pj_strtol(&cval->argv[1]);
+    pjmedia_rect_size size;
+
+    size.w = pj_strtol(&cval->argv[2]);
+    size.h = pj_strtol(&cval->argv[3]);
+    return pjsua_vid_win_set_size(wid, &size);
+}
+
+/* Video handler */
+static pj_status_t cmd_video_handler(pj_cli_cmd_val *cval)
+{    
+    pj_status_t status = PJ_SUCCESS;
+    pj_cli_cmd_id cmd_id = pj_cli_get_cmd_id(cval->cmd);    
+
+    switch(cmd_id) {
+    case CMD_VIDEO_ENABLE:
+	status = cmd_set_video_enable(PJ_TRUE);
+	break;
+    case CMD_VIDEO_DISABLE:
+	status = cmd_set_video_enable(PJ_FALSE);
+	break;
+    case CMD_VIDEO_ACC_SHOW:
+	status = cmd_show_account_video();
+	break;
+    case CMD_VIDEO_ACC_AUTORX:
+    case CMD_VIDEO_ACC_AUTOTX:
+    case CMD_VIDEO_ACC_CAP_ID:
+    case CMD_VIDEO_ACC_REN_ID:
+	status = cmd_video_acc_handler(cval);
+	break;
+    case CMD_VIDEO_CALL_ADD:
+	status = cmd_add_vid_strm();
+	break;
+    case CMD_VIDEO_CALL_RX:
+	status = cmd_enable_vid_rx(cval);
+	break;
+    case CMD_VIDEO_CALL_TX:
+	status = cmd_enable_vid_tx(cval);
+	break;
+    case CMD_VIDEO_CALL_ENABLE:
+    case CMD_VIDEO_CALL_DISABLE:
+	status = cmd_enable_vid_stream(cval, (cmd_id==CMD_VIDEO_CALL_ENABLE));
+	break;
+    case CMD_VIDEO_CALL_CAP:
+	status = cmd_set_cap_dev_id(cval);
+	break;
+    case CMD_VIDEO_DEVICE_LIST:
+	status = cmd_list_vid_dev();
+	break;
+    case CMD_VIDEO_DEVICE_REFRESH:
+	status = cmd_vid_device_refresh();
+	break;
+    case CMD_VIDEO_DEVICE_PREVIEW:
+	status = cmd_vid_device_preview(cval);
+	break;
+    case CMD_VIDEO_CODEC_LIST:
+	status = cmd_vid_codec_list();
+	break;
+    case CMD_VIDEO_CODEC_PRIO:
+	status = cmd_set_vid_codec_prio(cval);
+	break;
+    case CMD_VIDEO_CODEC_FPS:
+	status = cmd_set_vid_codec_fps(cval);
+	break;
+    case CMD_VIDEO_CODEC_BITRATE:
+	status = cmd_set_vid_codec_bitrate(cval);
+	break;
+    case CMD_VIDEO_CODEC_SIZE:
+	status = cmd_set_vid_codec_size(cval);
+	break;
+    case CMD_VIDEO_WIN_LIST:
+	status = cmd_vid_win_list();
+	break;
+    case CMD_VIDEO_WIN_ARRANGE:
+	status = cmd_arrange_vid_win(cval);
+	break;
+    case CMD_VIDEO_WIN_SHOW:	
+    case CMD_VIDEO_WIN_HIDE:
+	status = cmd_show_vid_win(cval, (cmd_id==CMD_VIDEO_WIN_SHOW));
+	break;
+    case CMD_VIDEO_WIN_MOVE:
+	status = cmd_move_vid_win(cval);
+	break;
+    case CMD_VIDEO_WIN_RESIZE:
+	status = cmd_resize_vid_win(cval);
+	break;
+    }    
+
+    return status;
+}
+#endif
+
+/* Other command handler */
+static pj_status_t cmd_sleep_handler(pj_cli_cmd_val *cval)
+{    
+    int delay;
+
+    delay = pj_strtoul(&cval->argv[1]);
+    if (delay < 0) delay = 0;
+    pj_thread_sleep(delay);    
+
+    return PJ_SUCCESS;
+}
+
+static pj_status_t cmd_network_handler(pj_cli_cmd_val *cval)
+{
+    pj_status_t status = PJ_SUCCESS;
+    PJ_UNUSED_ARG(cval);    
+    status = pjsua_detect_nat_type();
+    if (status != PJ_SUCCESS)
+	pjsua_perror(THIS_FILE, "Error", status);
+
+    return status;
+}
+
+static pj_status_t cmd_quit_handler(pj_cli_cmd_val *cval)
+{
+    PJ_LOG(3,(THIS_FILE, "Quitting app.."));
+    pj_cli_quit(cval->sess->fe->cli, cval->sess, PJ_FALSE);    
+    return PJ_SUCCESS;
+}
+
+static pj_status_t cmd_restart_handler(pj_cli_cmd_val *cval)
+{
+    PJ_LOG(3,(THIS_FILE, "Restarting app.."));
+    pj_cli_quit(cval->sess->fe->cli, cval->sess, PJ_TRUE);    
+    return PJ_SUCCESS;
+}
+
+static pj_status_t add_call_command(pj_cli_t *cli)
+{  
+    char* call_command = 
+	"<CMD name='call' id='100' desc='Call related commands'>"
+	"  <CMD name='new' id='1001' desc='Show Help'>"
+	"    <ARG name='buddy_id' type='choice' id='9901' validate='0' "
+	"     desc='Buddy Id'>"
+	"      <CHOICE value='-1' desc='All buddies'/>"
+	"      <CHOICE value='0' desc='Current dialog'/>"
+	"    </ARG>"
+	"  </CMD>"
+	"  <CMD name='multi' id='1002' desc='Make multiple calls'>"
+	"    <ARG name='number_of_calls' type='int' desc='Number of calls'/>"
+	"    <ARG name='buddy_id' type='choice' id='9901' validate='0' "
+	"     desc='Buddy Id'>"
+	"      <CHOICE value='-1' desc='All buddies'/>"
+	"      <CHOICE value='0' desc='Current dialog'/>"
+	"    </ARG>"
+	"  </CMD>"
+	"  <CMD name='answer' id='1003' desc='Answer call'>"
+	"    <ARG name='code' type='int' desc='Answer code'/>"
+	"    <ARG name='new_url' type='string' optional='1' "
+	"     desc='New URL(for 3xx resp)'/>"
+	"  </CMD>"
+	"  <CMD name='hangup' id='1004' sc='g' desc='Hangup call'/>"
+	"  <CMD name='hangup_all' id='1005' sc='hA' desc='Hangup all call'/>"
+	"  <CMD name='hold' id='1006' sc='H' desc='Hold call'/>"
+	"  <CMD name='reinvite' id='1007' sc='v' "
+	"   desc='Re-invite (release hold)'/>"
+	"  <CMD name='update' id='1008' sc='U' desc='Send Update request'/>"
+	"  <CMD name='next' id='1009' sc='>' desc='Select next call'/>"
+	"  <CMD name='previous' id='1010' sc='<' desc='Select previous call'/>"
+	"  <CMD name='transfer' id='1011' sc='x' desc='Transfer call'>"
+	"    <ARG name='buddy_id' type='choice' id='9901' validate='0' "
+	"     desc='Buddy Id'>"
+	"      <CHOICE value='-1' desc='All buddies'/>"
+	"      <CHOICE value='0' desc='Current dialog'/>"
+	"    </ARG>"
+	"  </CMD>"
+	"  <CMD name='transfer_replaces' id='1012' sc='X' "
+	"   desc='Transfer replace call'>"
+	"    <ARG name='call_id' type='choice' id='9911' desc='Call Id'/>"	
+	"  </CMD>"
+	"  <CMD name='redirect' id='1013' sc='R' desc='Redirect call'>"
+	"    <ARG name='redirect_option' type='choice' desc='Redirect option'>"
+	"      <CHOICE value='0' desc='Redirect accept replace'/>"
+	"      <CHOICE value='1' desc='Redirect accept'/>"
+	"      <CHOICE value='2' desc='Redirect reject'/>"
+	"      <CHOICE value='3' desc='Redirect stop'/>"
+	"    </ARG>"
+	"  </CMD>"
+	"  <CMD name='d_2833' id='1014' sc='#' desc='Send DTMF (RFC 2833)'>"
+	"    <ARG name='dtmf_to_send' type='string' "
+	"     desc='DTMF String to send'/>"
+	"  </CMD>"
+	"  <CMD name='d_info' id='1015' sc='*' desc='Send DTMF with SIP INFO'>"
+	"    <ARG name='dtmf_to_send' type='string' "
+	"     desc='DTMF String to send'/>"
+	"  </CMD>"
+	"  <CMD name='dump_q' id='1016' sc='dq' desc='Dump (call) quality'/>"
+	"  <CMD name='send_arb' id='1017' sc='S' desc='Send arbitrary request'>"
+	"    <ARG name='request_method' type='string' desc='Request method'/>"
+	"    <ARG name='buddy_id' type='choice' id='9901' validate='0' "
+	"     desc='Buddy Id'>"
+	"      <CHOICE value='-1' desc='All buddies'/>"
+	"      <CHOICE value='0' desc='Current dialog'/>"
+	"    </ARG>"
+	"  </CMD>"
+	"  <CMD name='list' id='1018' desc='Show current call'/>"
+	"</CMD>";
+
+    pj_str_t xml = pj_str(call_command);
+    return pj_cli_add_cmd_from_xml(cli, NULL, 
+				   &xml, cmd_call_handler,
+				   NULL, get_choice_value);
+}
+
+static pj_status_t add_presence_command(pj_cli_t *cli)
+{
+    char* presence_command =
+	"<CMD name='im' id='200' desc='IM and Presence Commands'>"
+	"  <CMD name='add_b' id='2001' sc='+b' desc='Add buddy'>"
+	"    <ARG name='buddy_uri' type='string' desc='Buddy URI'/>"
+	"  </CMD>"
+	"  <CMD name='del_b' id='2002' sc='-b' desc='Delete buddy'>"
+	"    <ARG name='added_buddy_id' type='choice' id='9912' "
+	"     desc='Buddy ID'/>"
+	"  </CMD>"
+	"  <CMD name='send_im' id='2003' sc='i' desc='Send IM'>"
+	"    <ARG name='buddy_id' type='choice' id='9901' validate='0' "
+	"     desc='Buddy Id'>"
+	"      <CHOICE value='-1' desc='All buddies'/>"
+	"      <CHOICE value='0' desc='Current dialog'/>"
+	"    </ARG>"
+	"    <ARG name='message_content' type='string' desc='Message Content'/>"
+	"  </CMD>"
+	"  <CMD name='sub_pre' id='2004' desc='Subscribe presence'>"
+	"    <ARG name='buddy_id' type='choice' id='9901' validate='0' "
+	"     desc='Buddy Id'>"
+	"      <CHOICE value='-1' desc='All buddies'/>"
+	"      <CHOICE value='0' desc='Current dialog'/>"
+	"    </ARG>"
+	"  </CMD>"
+	"  <CMD name='unsub_pre' id='2005' desc='Unsubscribe Presence'>"
+	"    <ARG name='buddy_id' type='choice' id='9901' validate='0' "
+	"     desc='Buddy Id'>"
+	"      <CHOICE value='-1' desc='All buddies'/>"
+	"      <CHOICE value='0' desc='Current dialog'/>"
+	"    </ARG>"
+	"  </CMD>"
+	"  <CMD name='tog_state' id='2006' desc='Toggle online state'/>"
+	"  <CMD name='pre_text' id='2007' sc='T' "
+	"   desc='Specify custom presence text'>"
+	"    <ARG name='online_state' type='choice' desc='Online state'>"
+	"      <CHOICE value='1' desc='Available'/>"
+	"      <CHOICE value='2' desc='Busy'/>"
+	"      <CHOICE value='3' desc='On The Phone'/>"
+	"      <CHOICE value='4' desc='Idle'/>"
+	"      <CHOICE value='5' desc='Away'/>"
+	"      <CHOICE value='6' desc='Be Right Back'/>"
+	"      <CHOICE value='7' desc='Offline'/>"
+	"    </ARG>"
+	"  </CMD>"
+	"  <CMD name='bud_list' id='2008' sc='l' desc='Show buddy list'/>"
+	"</CMD>";
+
+    pj_str_t xml = pj_str(presence_command);
+
+    return pj_cli_add_cmd_from_xml(cli, NULL, 
+				   &xml, cmd_presence_handler, 
+				   NULL, get_choice_value);
+}
+
+static pj_status_t add_account_command(pj_cli_t *cli)
+{
+    char* account_command = 
+	"<CMD name='acc' id='300' desc='Account commands'>"
+	"  <CMD name='add' id='3001' sc='+a' desc='Add new account'>"
+	"    <ARG name='sip_url' type='string' desc='Your SIP URL'/>"
+	"    <ARG name='registrar_url' type='string' "
+	"     desc='URL of the registrar'/>"
+	"    <ARG name='auth_realm' type='string' desc='Auth realm'/>"
+	"    <ARG name='auth_username' type='string' desc='Auth username'/>"
+	"    <ARG name='auth_password' type='string' desc='Auth password'/>"
+	"  </CMD>"
+	"  <CMD name='del' id='3002' sc='-a' desc='Delete account'>"
+	"    <ARG name='account_id' type='choice' id='9902' desc='Account Id'/>"
+	"  </CMD>"
+	"  <CMD name='mod' id='3003' sc='!a' desc='Modify account'>"
+	"    <ARG name='account_id' type='choice' id='9902' desc='Account Id'/>"
+	"    <ARG name='sip_url' type='string' desc='Your SIP URL'/>"
+	"    <ARG name='registrar_url' type='string' "
+	"     desc='URL of the registrar'/>"
+	"    <ARG name='auth_realm' type='string' desc='Auth realm'/>"
+	"    <ARG name='auth_username' type='string' desc='Auth username'/>"
+	"    <ARG name='auth_password' type='string' desc='Auth password'/>"
+	"  </CMD>"
+	"  <CMD name='reg' id='3004' sc='rr' "
+	"   desc='Send (Refresh) Register request to register'/>"
+	"  <CMD name='unreg' id='3005' sc='ru' "
+	"   desc='Send Register request to unregister'/>"
+	"  <CMD name='next' id='3006' sc='<' "
+	"   desc='Select the next account for sending outgoing requests'>"
+	"    <ARG name='account_id' type='choice' id='9902' desc='Account Id'/>"
+	"  </CMD>"
+	"  <CMD name='prev' id='3007' sc='>' "
+	"   desc='Select the previous account for sending outgoing requests'>"
+	"    <ARG name='account_id' type='choice' id='9902' desc='Account Id'/>"
+	"  </CMD>"
+	"  <CMD name='show' id='3008' sc='l' desc='Show account list'/>"
+	"</CMD>";
+
+    pj_str_t xml = pj_str(account_command);
+    return pj_cli_add_cmd_from_xml(cli, NULL, 
+				   &xml, cmd_account_handler, 
+				   NULL, get_choice_value);
+}
+
+static pj_status_t add_media_command(pj_cli_t *cli)
+{
+    char* media_command = 
+	"<CMD name='audio' id='400' desc='Conference and Media commands'>"
+	"  <CMD name='list' id='4001' sc='cl' desc='Show conference list'/>"
+	"  <CMD name='conf_con' id='4002' sc='cc' desc='Conference connect'>"
+	"    <ARG name='source_port' type='choice' id='9903' "
+	"     desc='Source Port'/>"
+	"    <ARG name='destination_port' type='choice' id='9903' "
+	"     desc='Destination Port'/>"
+	"  </CMD>"
+	"  <CMD name='conf_dis' id='4003' sc='cd' desc='Conference disconnect'>"
+	"    <ARG name='source_port' type='choice' id='9903' "
+	"     desc='Source Port'/>"
+	"    <ARG name='destination_port' type='choice' id='9903' "
+	"     desc='Destination Port'/>"
+	"  </CMD>"
+	"  <CMD name='adjust_vol' id='4004' sc='V' desc='Adjust volume'>"
+	"    <ARG name='mic_level' type='int' desc='Mic Level'/>"
+	"    <ARG name='speaker_port' type='int' desc='Speaker Level'/>"
+	"  </CMD>"
+	"  <CMD name='codec_prio' id='4005' sc='Cp' "
+	"   desc='Arrange codec priorities'>"
+	"    <ARG name='codec_id' type='choice' id='9904' desc='Codec Id'/>"
+	"    <ARG name='priority' type='int' desc='Codec Priority'/>"
+	"  </CMD>"
+	"</CMD>";
+
+    pj_str_t xml = pj_str(media_command);
+    return pj_cli_add_cmd_from_xml(cli, NULL, 
+				   &xml, cmd_media_handler, 
+				   NULL, get_choice_value);
+}
+
+static pj_status_t add_config_command(pj_cli_t *cli)
+{
+    char* config_command = 
+	"<CMD name='stat' id='500' desc='Status and config commands'>"
+	"  <CMD name='dump_stat' id='5001' sc='ds' desc='Dump status'/>"
+	"  <CMD name='dump_detail' id='5002' sc='dd' "
+	"   desc='Dump detail status'/>"
+	"  <CMD name='dump_conf' id='5003' sc='dc' "
+	"   desc='Dump configuration to screen'/>"
+	"  <CMD name='write_setting' id='5004' sc='f' "
+	"   desc='Write current configuration file'>"
+	"    <ARG name='output_file' type='string' desc='Output filename'/>"
+	"  </CMD>"
+	"</CMD>";
+
+    pj_str_t xml = pj_str(config_command);
+    return pj_cli_add_cmd_from_xml(cli, NULL, 
+				   &xml, cmd_config_handler,
+				   NULL, get_choice_value);
+}
+
+#if PJSUA_HAS_VIDEO    
+static pj_status_t add_video_command(pj_cli_t *cli)
+{
+    char* video_command =
+	"<CMD name='video' id='600' desc='Video commands'>"
+	"  <CMD name='enable' id='6001' desc='Enable video'/>"
+	"  <CMD name='disable' id='6002' desc='Disable video'/>"
+	"  <CMD name='acc' id='6003' desc='Video setting for current account'>"
+	"    <CMD name='show' id='60031' "
+	"     desc='Show video setting for current account'/>"
+	"    <CMD name='autorx' id='60032' sc='ar' "
+	"     desc='Automatically show incoming video'>"
+	"      <ARG name='enable_option' type='choice' "
+	"       desc='Enable/Disable option'>"
+	"        <CHOICE value='On' desc='Enable'/>"
+	"        <CHOICE value='Off' desc='Disable'/>"
+	"      </ARG>"
+	"    </CMD>"
+	"    <CMD name='autotx' id='60033' sc='at' "
+	"     desc='Automatically offer video'>"
+	"      <ARG name='enable_option' type='choice' "
+	"       desc='Enable/Disable option'>"
+	"        <CHOICE value='On' desc='Enable'/>"
+	"        <CHOICE value='Off' desc='Disable'/>"
+	"      </ARG>"
+	"    </CMD>"
+	"    <CMD name='cap_id' id='60034' sc='ci' "
+	"     desc='Set default capture device for current account'>"
+	"      <ARG name='cap_dev_id' type='choice' id='9905' "
+	"       desc='Capture device Id'/>"
+	"    </CMD>"
+	"    <CMD name='ren_id' id='60035' sc='ri' "
+	"     desc='Set default renderer device for current account'>"
+	"      <ARG name='ren_dev_id' type='choice' id='9906' "
+	"       desc='Renderer device Id'/>"
+	"    </CMD>"
+	"  </CMD>"
+	"  <CMD name='call' id='6004' sc='cl' " 
+	"   desc='Video call commands/settings'>"
+	"    <CMD name='rx' id='60041' sc='rx' "
+	"     desc='Enable/disable video RX for stream in curr call'>"
+	"      <ARG name='enable_option' type='choice' "
+	"       desc='Enable/Disable option'>"
+	"        <CHOICE value='On' desc='Enable'/>"
+	"        <CHOICE value='Off' desc='Disable'/>"
+	"      </ARG>"
+	"      <ARG name='stream_id' type='choice' id='9908' desc='Stream Id'/>"
+	"    </CMD>"
+	"    <CMD name='tx' id='60042' "
+	"     desc='Enable/disable video TX for stream in curr call'>"
+	"      <ARG name='enable_option' type='choice' "
+	"       desc='Enable/Disable option'>"
+	"        <CHOICE value='On' desc='Enable'/>"
+	"        <CHOICE value='Off' desc='Disable'/>"
+	"      </ARG>"
+	"      <ARG name='stream_id' type='choice' id='9908' desc='Stream Id'/>"
+	"    </CMD>"
+	"    <CMD name='add' id='60043' "
+	"     desc='Add video stream for current call'/>"
+	"    <CMD name='enable' id='60044' "
+	"     desc='Enable stream #N in current call'>"
+	"      <ARG name='stream_id' type='choice' id='9908' optional='1' "
+	"       desc='Stream Id'/>"
+	"    </CMD>"
+	"    <CMD name='disable' id='60045' "
+	"     desc='Disable stream #N in current call'>"
+	"      <ARG name='stream_id' type='choice' id='9908' optional='1' "
+	"       desc='Stream Id'/>"
+	"    </CMD>"
+	"    <CMD name='cap' id='60046' "
+	"     desc='Set capture dev ID for stream #N in current call'>"
+	"      <ARG name='stream_id' type='choice' id='9908' desc='Stream Id'/>"
+	"      <ARG name='cap_device_id' type='choice' id='9905' "
+	"       desc='Device Id'/>"
+	"    </CMD>"
+	"  </CMD>"
+	"  <CMD name='device' id='6005' sc='v' desc='Video device commands'>"
+	"    <CMD name='list' id='60051' desc='Show all video devices'/>"
+	"    <CMD name='refresh' id='60052' desc='Refresh video device list'/>"
+	"    <CMD name='prev' id='60053' "
+	"     desc='Enable/disable preview for specified device ID'>"
+	"      <ARG name='enable_option' type='choice' "
+	"       desc='Enable/Disable option'>"
+	"        <CHOICE value='On' desc='Enable'/>"
+	"        <CHOICE value='Off' desc='Disable'/>"
+	"      </ARG>"
+	"      <ARG name='device_id' type='choice' id='9907' "
+	"       desc='Video Device Id'/>"
+	"    </CMD>"
+	"  </CMD>"
+	"  <CMD name='codec' id='6006' desc='Video codec commands'>"
+	"    <CMD name='list' id='60061' desc='Show video codec list'/>"
+	"    <CMD name='prio' id='60062' desc='Set video codec priority'>"
+	"      <ARG name='codec_id' type='choice' id='9909' desc='Codec Id'/>"
+	"      <ARG name='priority' type='int' desc='Priority'/>"
+	"    </CMD>"
+	"    <CMD name='fps' id='60063' desc='Set video codec framerate'>"
+	"      <ARG name='codec_id' type='choice' id='9909' desc='Codec Id'/>"
+	"      <ARG name='num' type='int' desc='Numerator'/>"
+	"      <ARG name='denum' type='int' desc='Denumerator'/>"
+	"    </CMD>"
+	"    <CMD name='bitrate' id='60064' desc='Set video codec bitrate'>"
+	"      <ARG name='codec_id' type='choice' id='9909' desc='Codec Id'/>"
+	"      <ARG name='avg' type='int' desc='Average bps'/>"
+	"      <ARG name='max' type='int' desc='Maximum bps'/>"
+	"    </CMD>"
+	"    <CMD name='size' id='60065' desc='Set codec ID size/resolution'>"
+	"      <ARG name='codec_id' type='choice' id='9909' desc='Codec Id'/>"
+	"      <ARG name='width' type='int' desc='Width'/>"
+	"      <ARG name='height' type='int' desc='Height'/>"
+	"    </CMD>"
+	"  </CMD>"
+	"  <CMD name='win' id='6007' desc='Video windows settings/commands'>"
+	"    <CMD name='list' id='60071' desc='List all active video windows'/>"
+	"    <CMD name='arrange' id='60072' desc='Auto arrange windows'/>"
+	"    <CMD name='show' id='60073' desc='Show specific windows'>"
+	"      <ARG name='window_id' type='choice' id='9910' "
+	"       desc='Windows Id'/>"
+	"    </CMD>"
+	"    <CMD name='hide' id='60074' desc='Hide specific windows'>"
+	"      <ARG name='window_id' type='choice' id='9910' "
+	"       desc='Windows Id'/>"
+	"    </CMD>"
+	"    <CMD name='move' id='60075' desc='Move window position'>"
+	"      <ARG name='window_id' type='choice' id='9910' "
+	"       desc='Windows Id'/>"
+	"      <ARG name='x' type='int' desc='Horizontal position'/>"
+	"      <ARG name='y' type='int' desc='Vertical position'/>"
+	"    </CMD>"
+	"    <CMD name='resize' id='60076' "
+	"     desc='Resize window to specific width/height'>"
+	"      <ARG name='window_id' type='choice' id='9910' "
+	"       desc='Windows Id'/>"
+	"      <ARG name='width' type='int' desc='Width'/>"
+	"      <ARG name='height' type='int' desc='Height'/>"
+	"    </CMD>"
+	"  </CMD>"
+	"</CMD>";
+
+    pj_str_t xml = pj_str(video_command);
+    return pj_cli_add_cmd_from_xml(cli, NULL, 
+				   &xml, cmd_video_handler, 
+				   NULL, get_choice_value);
+}
+#endif
+
+static pj_status_t add_other_command(pj_cli_t *cli)
+{
+    char* sleep_command =
+	"<CMD name='sleep' id='700' desc='Suspend keyboard input'>"
+	"  <ARG name='msec' type='int' desc='Millisecond'/>"
+	"</CMD>";
+
+    char* network_command =
+	"<CMD name='network' id='900' desc='Detect network type'/>";
+
+    char* shutdown_command =
+	"<CMD name='shutdown' id='110' desc='Shutdown application'/>";
+
+    char* restart_command =
+	"<CMD name='restart' id='120' desc='Restart application'/>";
+
+    pj_status_t status;
+    pj_str_t sleep_xml = pj_str(sleep_command);    
+    pj_str_t network_xml = pj_str(network_command);
+    pj_str_t shutdown_xml = pj_str(shutdown_command);
+    pj_str_t restart_xml = pj_str(restart_command);
+
+    status = pj_cli_add_cmd_from_xml(cli, NULL, 
+				     &sleep_xml, cmd_sleep_handler, 
+				     NULL, NULL);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    status = pj_cli_add_cmd_from_xml(cli, NULL, 
+				     &network_xml, cmd_network_handler, 
+				     NULL, NULL);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    status = pj_cli_add_cmd_from_xml(cli, NULL, 
+				     &shutdown_xml, cmd_quit_handler, 
+				     NULL, NULL);
+
+    if (status != PJ_SUCCESS)
+	return status;
+
+    status = pj_cli_add_cmd_from_xml(cli, NULL, 
+				     &restart_xml, cmd_restart_handler, 
+				     NULL, NULL);
+
+    return status;
+}
+
+static pj_status_t setup_command()
+{
+    pj_status_t status;
+
+    status = add_call_command(cli);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    status = add_presence_command(cli);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    status = add_account_command(cli);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    status = add_media_command(cli);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    status = add_config_command(cli);
+    if (status != PJ_SUCCESS)
+	return status;
+
+#if PJSUA_HAS_VIDEO    
+    status = add_video_command(cli);
+    if (status != PJ_SUCCESS)
+	return status;
+#endif
+
+    status = add_other_command(cli);
+
+    return status;
+}
+
+static void log_writer(int level, const char *buffer, int len)
+{
+    if (cli)
+	pj_cli_write_log(cli, level, buffer, len);
+}
+
+pj_status_t setup_cli()
+{
+    pj_status_t status;    
+    pj_cli_cfg_default(&cli_cfg);
+    cli_cfg.pf = app_config.pool->factory;    
+    cli_cfg.name = pj_str("pjsua_cli");
+    cli_cfg.title = pj_str("Pjsua CLI Application");
+
+    status = pj_cli_create(&cli_cfg, &cli);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    status = setup_command();
+    if (status != PJ_SUCCESS)
+	return status;
+
+    pj_log_set_log_func(&log_writer);
+
+    /* 
+    * Init telnet
+    */ 
+    pj_cli_telnet_cfg_default(&telnet_cfg);
+    telnet_cfg.log_level = 5;
+    if (app_config.cli_telnet_port)
+	telnet_cfg.port = (pj_uint16_t)app_config.cli_telnet_port;
+
+    status = pj_cli_telnet_create(cli, &telnet_cfg, NULL);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    app_config.cli_telnet_port = telnet_cfg.port;;
+
+    /*
+    * Init console
+    */ 
+    pj_cli_console_cfg_default(&console_cfg);
+    console_cfg.quit_command = pj_str("shutdown");
+    status = pj_cli_console_create(cli, &console_cfg, &sess, NULL);
+    return status;
+}
+
+void destroy_cli()
+{
+    pj_log_set_log_func(&pj_log_write);    
+    pj_cli_destroy(cli);
+    cli = NULL;  
+}
Index: /pjproject/trunk/pjsip-apps/src/pjsua/pjsua_cmd.c
===================================================================
--- /pjproject/trunk/pjsip-apps/src/pjsua/pjsua_cmd.c	(revision 4440)
+++ /pjproject/trunk/pjsip-apps/src/pjsua/pjsua_cmd.c	(revision 4440)
@@ -0,0 +1,1027 @@
+/* $Id: pjsua_cmd.c 4347 2013-02-13 10:19:25Z nanang $ */
+/* 
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+
+#include "pjsua_cmd.h"
+
+#define THIS_FILE	"pjsua_cmd.c"
+
+#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
+#   define SOME_BUF_SIZE	(1024 * 10)
+#else
+#   define SOME_BUF_SIZE	(1024 * 3)
+#endif
+
+static char some_buf[SOME_BUF_SIZE];
+
+/** Variable definition **/
+int		    stdout_refresh = -1;
+pj_bool_t	    stdout_refresh_quit = PJ_FALSE;
+pjsua_call_id	    current_call = PJSUA_INVALID_ID;
+pjsua_app_config    app_config;
+pjsua_call_setting  call_opt;
+pjsua_msg_data	    msg_data;
+
+PJ_DEF(int) my_atoi(const char *cs)
+{
+    pj_str_t s;
+
+    pj_cstr(&s, cs);
+    if (cs[0] == '-') {
+	s.ptr++, s.slen--;
+	return 0 - (int)pj_strtoul(&s);
+    } else if (cs[0] == '+') {
+	s.ptr++, s.slen--;
+	return pj_strtoul(&s);
+    } else {
+	return pj_strtoul(&s);
+    }
+}
+
+/*
+ * Find next call when current call is disconnected or when user
+ * press ']'
+ */
+PJ_DEF(pj_bool_t) find_next_call()
+{
+    int i, max;
+
+    max = pjsua_call_get_max_count();
+    for (i=current_call+1; i<max; ++i) {
+	if (pjsua_call_is_active(i)) {
+	    current_call = i;
+	    return PJ_TRUE;
+	}
+    }
+
+    for (i=0; i<current_call; ++i) {
+	if (pjsua_call_is_active(i)) {
+	    current_call = i;
+	    return PJ_TRUE;
+	}
+    }
+
+    current_call = PJSUA_INVALID_ID;
+    return PJ_FALSE;
+}
+
+PJ_DEF(pj_bool_t) find_prev_call()
+{
+    int i, max;
+
+    max = pjsua_call_get_max_count();
+    for (i=current_call-1; i>=0; --i) {
+	if (pjsua_call_is_active(i)) {
+	    current_call = i;
+	    return PJ_TRUE;
+	}
+    }
+
+    for (i=max-1; i>current_call; --i) {
+	if (pjsua_call_is_active(i)) {
+	    current_call = i;
+	    return PJ_TRUE;
+	}
+    }
+
+    current_call = PJSUA_INVALID_ID;
+    return PJ_FALSE;
+}
+
+/*
+ * Send arbitrary request to remote host
+ */
+PJ_DEF(void) send_request(char *cstr_method, const pj_str_t *dst_uri)
+{
+    pj_str_t str_method;
+    pjsip_method method;
+    pjsip_tx_data *tdata;
+    pjsip_endpoint *endpt;
+    pj_status_t status;
+
+    endpt = pjsua_get_pjsip_endpt();
+
+    str_method = pj_str(cstr_method);
+    pjsip_method_init_np(&method, &str_method);
+
+    status = pjsua_acc_create_request(current_acc, &method, dst_uri, &tdata);
+
+    status = pjsip_endpt_send_request(endpt, tdata, -1, NULL, NULL);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to send request", status);
+	return;
+    }
+}
+
+/*
+ * Print log of call states. Since call states may be too long for logger,
+ * printing it is a bit tricky, it should be printed part by part as long 
+ * as the logger can accept.
+ */
+PJ_DEF(void) log_call_dump(int call_id) 
+{
+    unsigned call_dump_len;
+    unsigned part_len;
+    unsigned part_idx;
+    unsigned log_decor;
+
+    pjsua_call_dump(call_id, PJ_TRUE, some_buf, sizeof(some_buf), "  ");
+    call_dump_len = strlen(some_buf);
+
+    log_decor = pj_log_get_decor();
+    pj_log_set_decor(log_decor & ~(PJ_LOG_HAS_NEWLINE | PJ_LOG_HAS_CR));
+    PJ_LOG(3,(THIS_FILE, "\n"));
+    pj_log_set_decor(0);
+
+    part_idx = 0;
+    part_len = PJ_LOG_MAX_SIZE-80;
+    while (part_idx < call_dump_len) {
+	char p_orig, *p;
+
+	p = &some_buf[part_idx];
+	if (part_idx + part_len > call_dump_len)
+	    part_len = call_dump_len - part_idx;
+	p_orig = p[part_len];
+	p[part_len] = '\0';
+	PJ_LOG(3,(THIS_FILE, "%s", p));
+	p[part_len] = p_orig;
+	part_idx += part_len;
+    }
+    pj_log_set_decor(log_decor);
+}
+
+/*
+ * Save account settings
+ */
+static void write_account_settings(int acc_index, pj_str_t *result)
+{
+    unsigned i;
+    char line[128];
+    pjsua_acc_config *acc_cfg = &app_config.acc_cfg[acc_index];
+
+    
+    pj_ansi_sprintf(line, "\n#\n# Account %d:\n#\n", acc_index);
+    pj_strcat2(result, line);
+
+
+    /* Identity */
+    if (acc_cfg->id.slen) {
+	pj_ansi_sprintf(line, "--id %.*s\n", 
+			(int)acc_cfg->id.slen, 
+			acc_cfg->id.ptr);
+	pj_strcat2(result, line);
+    }
+
+    /* Registrar server */
+    if (acc_cfg->reg_uri.slen) {
+	pj_ansi_sprintf(line, "--registrar %.*s\n",
+			      (int)acc_cfg->reg_uri.slen,
+			      acc_cfg->reg_uri.ptr);
+	pj_strcat2(result, line);
+
+	pj_ansi_sprintf(line, "--reg-timeout %u\n",
+			      acc_cfg->reg_timeout);
+	pj_strcat2(result, line);
+    }
+
+    /* Contact */
+    if (acc_cfg->force_contact.slen) {
+	pj_ansi_sprintf(line, "--contact %.*s\n", 
+			(int)acc_cfg->force_contact.slen, 
+			acc_cfg->force_contact.ptr);
+	pj_strcat2(result, line);
+    }
+
+    /* Contact header parameters */
+    if (acc_cfg->contact_params.slen) {
+	pj_ansi_sprintf(line, "--contact-params %.*s\n", 
+			(int)acc_cfg->contact_params.slen, 
+			acc_cfg->contact_params.ptr);
+	pj_strcat2(result, line);
+    }
+
+    /* Contact URI parameters */
+    if (acc_cfg->contact_uri_params.slen) {
+	pj_ansi_sprintf(line, "--contact-uri-params %.*s\n", 
+			(int)acc_cfg->contact_uri_params.slen, 
+			acc_cfg->contact_uri_params.ptr);
+	pj_strcat2(result, line);
+    }
+
+    /*  */
+    if (acc_cfg->allow_contact_rewrite!=1)
+    {
+	pj_ansi_sprintf(line, "--auto-update-nat %i\n",
+			(int)acc_cfg->allow_contact_rewrite);
+	pj_strcat2(result, line);
+    }
+
+#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+    /* SRTP */
+    if (acc_cfg->use_srtp) {
+	int use_srtp = (int)acc_cfg->use_srtp;
+	if (use_srtp == PJMEDIA_SRTP_OPTIONAL && 
+	    acc_cfg->srtp_optional_dup_offer)
+	{
+	    use_srtp = 3;
+	}
+	pj_ansi_sprintf(line, "--use-srtp %i\n", use_srtp);
+	pj_strcat2(result, line);
+    }
+    if (acc_cfg->srtp_secure_signaling != 
+	PJSUA_DEFAULT_SRTP_SECURE_SIGNALING) 
+    {
+	pj_ansi_sprintf(line, "--srtp-secure %d\n",
+			acc_cfg->srtp_secure_signaling);
+	pj_strcat2(result, line);
+    }
+#endif
+
+    /* Proxy */
+    for (i=0; i<acc_cfg->proxy_cnt; ++i) {
+	pj_ansi_sprintf(line, "--proxy %.*s\n",
+			      (int)acc_cfg->proxy[i].slen,
+			      acc_cfg->proxy[i].ptr);
+	pj_strcat2(result, line);
+    }
+
+    /* Credentials */
+    for (i=0; i<acc_cfg->cred_count; ++i) {
+	if (acc_cfg->cred_info[i].realm.slen) {
+	    pj_ansi_sprintf(line, "--realm %.*s\n",
+				  (int)acc_cfg->cred_info[i].realm.slen,
+				  acc_cfg->cred_info[i].realm.ptr);
+	    pj_strcat2(result, line);
+	}
+
+	if (acc_cfg->cred_info[i].username.slen) {
+	    pj_ansi_sprintf(line, "--username %.*s\n",
+				  (int)acc_cfg->cred_info[i].username.slen,
+				  acc_cfg->cred_info[i].username.ptr);
+	    pj_strcat2(result, line);
+	}
+
+	if (acc_cfg->cred_info[i].data.slen) {
+	    pj_ansi_sprintf(line, "--password %.*s\n",
+				  (int)acc_cfg->cred_info[i].data.slen,
+				  acc_cfg->cred_info[i].data.ptr);
+	    pj_strcat2(result, line);
+	}
+
+	if (i != acc_cfg->cred_count - 1)
+	    pj_strcat2(result, "--next-cred\n");
+    }
+
+    /* reg-use-proxy */
+    if (acc_cfg->reg_use_proxy != 3) {
+	pj_ansi_sprintf(line, "--reg-use-proxy %d\n",
+			      acc_cfg->reg_use_proxy);
+	pj_strcat2(result, line);
+    }
+
+    /* rereg-delay */
+    if (acc_cfg->reg_retry_interval != PJSUA_REG_RETRY_INTERVAL) {
+	pj_ansi_sprintf(line, "--rereg-delay %d\n",
+		              acc_cfg->reg_retry_interval);
+	pj_strcat2(result, line);
+    }
+
+    /* 100rel extension */
+    if (acc_cfg->require_100rel) {
+	pj_strcat2(result, "--use-100rel\n");
+    }
+
+    /* Session Timer extension */
+    if (acc_cfg->use_timer) {
+	pj_ansi_sprintf(line, "--use-timer %d\n",
+			      acc_cfg->use_timer);
+	pj_strcat2(result, line);
+    }
+    if (acc_cfg->timer_setting.min_se != 90) {
+	pj_ansi_sprintf(line, "--timer-min-se %d\n",
+			      acc_cfg->timer_setting.min_se);
+	pj_strcat2(result, line);
+    }
+    if (acc_cfg->timer_setting.sess_expires != PJSIP_SESS_TIMER_DEF_SE) {
+	pj_ansi_sprintf(line, "--timer-se %d\n",
+			      acc_cfg->timer_setting.sess_expires);
+	pj_strcat2(result, line);
+    }
+
+    /* Publish */
+    if (acc_cfg->publish_enabled)
+	pj_strcat2(result, "--publish\n");
+
+    /* MWI */
+    if (acc_cfg->mwi_enabled)
+	pj_strcat2(result, "--mwi\n");
+
+    if (acc_cfg->sip_stun_use != PJSUA_STUN_USE_DEFAULT ||
+	acc_cfg->media_stun_use != PJSUA_STUN_USE_DEFAULT)
+    {
+	pj_strcat2(result, "--disable-stun\n");
+    }
+
+    /* Media Transport*/
+    if (acc_cfg->ice_cfg.enable_ice)
+	pj_strcat2(result, "--use-ice\n");
+
+    if (acc_cfg->ice_cfg.ice_opt.aggressive == PJ_FALSE)
+	pj_strcat2(result, "--ice-regular\n");
+
+    if (acc_cfg->turn_cfg.enable_turn)
+	pj_strcat2(result, "--use-turn\n");
+
+    if (acc_cfg->ice_cfg.ice_max_host_cands >= 0) {
+	pj_ansi_sprintf(line, "--ice_max_host_cands %d\n",
+	                acc_cfg->ice_cfg.ice_max_host_cands);
+	pj_strcat2(result, line);
+    }
+
+    if (acc_cfg->ice_cfg.ice_no_rtcp)
+	pj_strcat2(result, "--ice-no-rtcp\n");
+
+    if (acc_cfg->turn_cfg.turn_server.slen) {
+	pj_ansi_sprintf(line, "--turn-srv %.*s\n",
+			(int)acc_cfg->turn_cfg.turn_server.slen,
+			acc_cfg->turn_cfg.turn_server.ptr);
+	pj_strcat2(result, line);
+    }
+
+    if (acc_cfg->turn_cfg.turn_conn_type == PJ_TURN_TP_TCP)
+	pj_strcat2(result, "--turn-tcp\n");
+
+    if (acc_cfg->turn_cfg.turn_auth_cred.data.static_cred.username.slen) {
+	pj_ansi_sprintf(line, "--turn-user %.*s\n",
+			(int)acc_cfg->turn_cfg.turn_auth_cred.data.static_cred.username.slen,
+			acc_cfg->turn_cfg.turn_auth_cred.data.static_cred.username.ptr);
+	pj_strcat2(result, line);
+    }
+
+    if (acc_cfg->turn_cfg.turn_auth_cred.data.static_cred.data.slen) {
+	pj_ansi_sprintf(line, "--turn-passwd %.*s\n",
+			(int)acc_cfg->turn_cfg.turn_auth_cred.data.static_cred.data.slen,
+			acc_cfg->turn_cfg.turn_auth_cred.data.static_cred.data.ptr);
+	pj_strcat2(result, line);
+    }
+}
+
+/*
+ * Write settings.
+ */
+PJ_DEF(int) write_settings(pjsua_app_config *config, char *buf, pj_size_t max)
+{
+    unsigned acc_index;
+    unsigned i;
+    pj_str_t cfg;
+    char line[128];
+    extern pj_bool_t pjsip_use_compact_form;
+
+    PJ_UNUSED_ARG(max);
+
+    cfg.ptr = buf;
+    cfg.slen = 0;
+
+    /* Logging. */
+    pj_strcat2(&cfg, "#\n# Logging options:\n#\n");
+    pj_ansi_sprintf(line, "--log-level %d\n",
+		    config->log_cfg.level);
+    pj_strcat2(&cfg, line);
+
+    pj_ansi_sprintf(line, "--app-log-level %d\n",
+		    config->log_cfg.console_level);
+    pj_strcat2(&cfg, line);
+
+    if (config->log_cfg.log_filename.slen) {
+	pj_ansi_sprintf(line, "--log-file %.*s\n",
+			(int)config->log_cfg.log_filename.slen,
+			config->log_cfg.log_filename.ptr);
+	pj_strcat2(&cfg, line);
+    }
+
+    if (config->log_cfg.log_file_flags & PJ_O_APPEND) {
+	pj_strcat2(&cfg, "--log-append\n");
+    }
+
+    /* Save account settings. */
+    for (acc_index=0; acc_index < config->acc_cnt; ++acc_index) {
+	
+	write_account_settings(acc_index, &cfg);
+
+	if (acc_index < config->acc_cnt-1)
+	    pj_strcat2(&cfg, "--next-account\n");
+    }
+
+    pj_strcat2(&cfg, "\n#\n# Network settings:\n#\n");
+
+    /* Nameservers */
+    for (i=0; i<config->cfg.nameserver_count; ++i) {
+	pj_ansi_sprintf(line, "--nameserver %.*s\n",
+			      (int)config->cfg.nameserver[i].slen,
+			      config->cfg.nameserver[i].ptr);
+	pj_strcat2(&cfg, line);
+    }
+
+    /* Outbound proxy */
+    for (i=0; i<config->cfg.outbound_proxy_cnt; ++i) {
+	pj_ansi_sprintf(line, "--outbound %.*s\n",
+			      (int)config->cfg.outbound_proxy[i].slen,
+			      config->cfg.outbound_proxy[i].ptr);
+	pj_strcat2(&cfg, line);
+    }
+
+    /* Transport options */
+    if (config->ipv6) {
+	pj_strcat2(&cfg, "--ipv6\n");
+    }
+    if (config->enable_qos) {
+	pj_strcat2(&cfg, "--set-qos\n");
+    }
+
+    /* UDP Transport. */
+    pj_ansi_sprintf(line, "--local-port %d\n", config->udp_cfg.port);
+    pj_strcat2(&cfg, line);
+
+    /* IP address, if any. */
+    if (config->udp_cfg.public_addr.slen) {
+	pj_ansi_sprintf(line, "--ip-addr %.*s\n", 
+			(int)config->udp_cfg.public_addr.slen,
+			config->udp_cfg.public_addr.ptr);
+	pj_strcat2(&cfg, line);
+    }
+
+    /* Bound IP address, if any. */
+    if (config->udp_cfg.bound_addr.slen) {
+	pj_ansi_sprintf(line, "--bound-addr %.*s\n", 
+			(int)config->udp_cfg.bound_addr.slen,
+			config->udp_cfg.bound_addr.ptr);
+	pj_strcat2(&cfg, line);
+    }
+
+    /* No TCP ? */
+    if (config->no_tcp) {
+	pj_strcat2(&cfg, "--no-tcp\n");
+    }
+
+    /* No UDP ? */
+    if (config->no_udp) {
+	pj_strcat2(&cfg, "--no-udp\n");
+    }
+
+    /* STUN */
+    for (i=0; i<config->cfg.stun_srv_cnt; ++i) {
+	pj_ansi_sprintf(line, "--stun-srv %.*s\n",
+			(int)config->cfg.stun_srv[i].slen, 
+			config->cfg.stun_srv[i].ptr);
+	pj_strcat2(&cfg, line);
+    }
+
+#if defined(PJSIP_HAS_TLS_TRANSPORT) && (PJSIP_HAS_TLS_TRANSPORT != 0)
+    /* TLS */
+    if (config->use_tls)
+	pj_strcat2(&cfg, "--use-tls\n");
+    if (config->udp_cfg.tls_setting.ca_list_file.slen) {
+	pj_ansi_sprintf(line, "--tls-ca-file %.*s\n",
+			(int)config->udp_cfg.tls_setting.ca_list_file.slen, 
+			config->udp_cfg.tls_setting.ca_list_file.ptr);
+	pj_strcat2(&cfg, line);
+    }
+    if (config->udp_cfg.tls_setting.cert_file.slen) {
+	pj_ansi_sprintf(line, "--tls-cert-file %.*s\n",
+			(int)config->udp_cfg.tls_setting.cert_file.slen, 
+			config->udp_cfg.tls_setting.cert_file.ptr);
+	pj_strcat2(&cfg, line);
+    }
+    if (config->udp_cfg.tls_setting.privkey_file.slen) {
+	pj_ansi_sprintf(line, "--tls-privkey-file %.*s\n",
+			(int)config->udp_cfg.tls_setting.privkey_file.slen, 
+			config->udp_cfg.tls_setting.privkey_file.ptr);
+	pj_strcat2(&cfg, line);
+    }
+
+    if (config->udp_cfg.tls_setting.password.slen) {
+	pj_ansi_sprintf(line, "--tls-password %.*s\n",
+			(int)config->udp_cfg.tls_setting.password.slen, 
+			config->udp_cfg.tls_setting.password.ptr);
+	pj_strcat2(&cfg, line);
+    }
+
+    if (config->udp_cfg.tls_setting.verify_server)
+	pj_strcat2(&cfg, "--tls-verify-server\n");
+
+    if (config->udp_cfg.tls_setting.verify_client)
+	pj_strcat2(&cfg, "--tls-verify-client\n");
+
+    if (config->udp_cfg.tls_setting.timeout.sec) {
+	pj_ansi_sprintf(line, "--tls-neg-timeout %d\n",
+			(int)config->udp_cfg.tls_setting.timeout.sec);
+	pj_strcat2(&cfg, line);
+    }
+
+    for (i=0; i<config->udp_cfg.tls_setting.ciphers_num; ++i) {
+	pj_ansi_sprintf(line, "--tls-cipher 0x%06X # %s\n",
+			config->udp_cfg.tls_setting.ciphers[i],
+			pj_ssl_cipher_name(config->udp_cfg.tls_setting.ciphers[i]));
+	pj_strcat2(&cfg, line);
+    }
+#endif
+
+    pj_strcat2(&cfg, "\n#\n# Media settings:\n#\n");
+
+    /* Video & extra audio */
+    for (i=0; i<config->vid.vid_cnt; ++i) {
+	pj_strcat2(&cfg, "--video\n");
+    }
+    for (i=1; i<config->aud_cnt; ++i) {
+	pj_strcat2(&cfg, "--extra-audio\n");
+    }
+
+    /* SRTP */
+#if PJMEDIA_HAS_SRTP
+    if (app_config.cfg.use_srtp != PJSUA_DEFAULT_USE_SRTP) {
+	int use_srtp = (int)app_config.cfg.use_srtp;
+	if (use_srtp == PJMEDIA_SRTP_OPTIONAL && 
+	    app_config.cfg.srtp_optional_dup_offer)
+	{
+	    use_srtp = 3;
+	}
+	pj_ansi_sprintf(line, "--use-srtp %d\n", use_srtp);
+	pj_strcat2(&cfg, line);
+    }
+    if (app_config.cfg.srtp_secure_signaling != 
+	PJSUA_DEFAULT_SRTP_SECURE_SIGNALING) 
+    {
+	pj_ansi_sprintf(line, "--srtp-secure %d\n",
+			app_config.cfg.srtp_secure_signaling);
+	pj_strcat2(&cfg, line);
+    }
+#endif
+
+    /* Media */
+    if (config->null_audio)
+	pj_strcat2(&cfg, "--null-audio\n");
+    if (config->auto_play)
+	pj_strcat2(&cfg, "--auto-play\n");
+    if (config->auto_loop)
+	pj_strcat2(&cfg, "--auto-loop\n");
+    if (config->auto_conf)
+	pj_strcat2(&cfg, "--auto-conf\n");
+    for (i=0; i<config->wav_count; ++i) {
+	pj_ansi_sprintf(line, "--play-file %s\n",
+			config->wav_files[i].ptr);
+	pj_strcat2(&cfg, line);
+    }
+    for (i=0; i<config->tone_count; ++i) {
+	pj_ansi_sprintf(line, "--play-tone %d,%d,%d,%d\n",
+			config->tones[i].freq1, config->tones[i].freq2, 
+			config->tones[i].on_msec, config->tones[i].off_msec);
+	pj_strcat2(&cfg, line);
+    }
+    if (config->rec_file.slen) {
+	pj_ansi_sprintf(line, "--rec-file %s\n",
+			config->rec_file.ptr);
+	pj_strcat2(&cfg, line);
+    }
+    if (config->auto_rec)
+	pj_strcat2(&cfg, "--auto-rec\n");
+    if (config->capture_dev != PJSUA_INVALID_ID) {
+	pj_ansi_sprintf(line, "--capture-dev %d\n", config->capture_dev);
+	pj_strcat2(&cfg, line);
+    }
+    if (config->playback_dev != PJSUA_INVALID_ID) {
+	pj_ansi_sprintf(line, "--playback-dev %d\n", config->playback_dev);
+	pj_strcat2(&cfg, line);
+    }
+    if (config->media_cfg.snd_auto_close_time != -1) {
+	pj_ansi_sprintf(line, "--snd-auto-close %d\n", 
+			config->media_cfg.snd_auto_close_time);
+	pj_strcat2(&cfg, line);
+    }
+    if (config->no_tones) {
+	pj_strcat2(&cfg, "--no-tones\n");
+    }
+    if (config->media_cfg.jb_max != -1) {
+	pj_ansi_sprintf(line, "--jb-max-size %d\n", 
+			config->media_cfg.jb_max);
+	pj_strcat2(&cfg, line);
+    }
+
+    /* Sound device latency */
+    if (config->capture_lat != PJMEDIA_SND_DEFAULT_REC_LATENCY) {
+	pj_ansi_sprintf(line, "--capture-lat %d\n", config->capture_lat);
+	pj_strcat2(&cfg, line);
+    }
+    if (config->playback_lat != PJMEDIA_SND_DEFAULT_PLAY_LATENCY) {
+	pj_ansi_sprintf(line, "--playback-lat %d\n", config->playback_lat);
+	pj_strcat2(&cfg, line);
+    }
+
+    /* Media clock rate. */
+    if (config->media_cfg.clock_rate != PJSUA_DEFAULT_CLOCK_RATE) {
+	pj_ansi_sprintf(line, "--clock-rate %d\n",
+			config->media_cfg.clock_rate);
+	pj_strcat2(&cfg, line);
+    } else {
+	pj_ansi_sprintf(line, "#using default --clock-rate %d\n",
+			config->media_cfg.clock_rate);
+	pj_strcat2(&cfg, line);
+    }
+
+    if (config->media_cfg.snd_clock_rate && 
+	config->media_cfg.snd_clock_rate != config->media_cfg.clock_rate) 
+    {
+	pj_ansi_sprintf(line, "--snd-clock-rate %d\n",
+			config->media_cfg.snd_clock_rate);
+	pj_strcat2(&cfg, line);
+    }
+
+    /* Stereo mode. */
+    if (config->media_cfg.channel_count == 2) {
+	pj_ansi_sprintf(line, "--stereo\n");
+	pj_strcat2(&cfg, line);
+    }
+
+    /* quality */
+    if (config->media_cfg.quality != PJSUA_DEFAULT_CODEC_QUALITY) {
+	pj_ansi_sprintf(line, "--quality %d\n",
+			config->media_cfg.quality);
+	pj_strcat2(&cfg, line);
+    } else {
+	pj_ansi_sprintf(line, "#using default --quality %d\n",
+			config->media_cfg.quality);
+	pj_strcat2(&cfg, line);
+    }
+
+    if (config->vid.vcapture_dev != PJMEDIA_VID_DEFAULT_CAPTURE_DEV) {
+	pj_ansi_sprintf(line, "--vcapture-dev %d\n", config->vid.vcapture_dev);
+	pj_strcat2(&cfg, line);
+    }
+    if (config->vid.vrender_dev != PJMEDIA_VID_DEFAULT_RENDER_DEV) {
+	pj_ansi_sprintf(line, "--vrender-dev %d\n", config->vid.vrender_dev);
+	pj_strcat2(&cfg, line);
+    }
+    for (i=0; i<config->avi_cnt; ++i) {
+	pj_ansi_sprintf(line, "--play-avi %s\n", config->avi[i].path.ptr);
+	pj_strcat2(&cfg, line);
+    }
+    if (config->avi_auto_play) {
+	pj_ansi_sprintf(line, "--auto-play-avi\n");
+	pj_strcat2(&cfg, line);
+    }
+
+    /* ptime */
+    if (config->media_cfg.ptime) {
+	pj_ansi_sprintf(line, "--ptime %d\n",
+			config->media_cfg.ptime);
+	pj_strcat2(&cfg, line);
+    }
+
+    /* no-vad */
+    if (config->media_cfg.no_vad) {
+	pj_strcat2(&cfg, "--no-vad\n");
+    }
+
+    /* ec-tail */
+    if (config->media_cfg.ec_tail_len != PJSUA_DEFAULT_EC_TAIL_LEN) {
+	pj_ansi_sprintf(line, "--ec-tail %d\n",
+			config->media_cfg.ec_tail_len);
+	pj_strcat2(&cfg, line);
+    } else {
+	pj_ansi_sprintf(line, "#using default --ec-tail %d\n",
+			config->media_cfg.ec_tail_len);
+	pj_strcat2(&cfg, line);
+    }
+
+    /* ec-opt */
+    if (config->media_cfg.ec_options != 0) {
+	pj_ansi_sprintf(line, "--ec-opt %d\n",
+			config->media_cfg.ec_options);
+	pj_strcat2(&cfg, line);
+    } 
+
+    /* ilbc-mode */
+    if (config->media_cfg.ilbc_mode != PJSUA_DEFAULT_ILBC_MODE) {
+	pj_ansi_sprintf(line, "--ilbc-mode %d\n",
+			config->media_cfg.ilbc_mode);
+	pj_strcat2(&cfg, line);
+    } else {
+	pj_ansi_sprintf(line, "#using default --ilbc-mode %d\n",
+			config->media_cfg.ilbc_mode);
+	pj_strcat2(&cfg, line);
+    }
+
+    /* RTP drop */
+    if (config->media_cfg.tx_drop_pct) {
+	pj_ansi_sprintf(line, "--tx-drop-pct %d\n",
+			config->media_cfg.tx_drop_pct);
+	pj_strcat2(&cfg, line);
+
+    }
+    if (config->media_cfg.rx_drop_pct) {
+	pj_ansi_sprintf(line, "--rx-drop-pct %d\n",
+			config->media_cfg.rx_drop_pct);
+	pj_strcat2(&cfg, line);
+
+    }
+
+    /* Start RTP port. */
+    pj_ansi_sprintf(line, "--rtp-port %d\n",
+		    config->rtp_cfg.port);
+    pj_strcat2(&cfg, line);
+
+    /* Disable codec */
+    for (i=0; i<config->codec_dis_cnt; ++i) {
+	pj_ansi_sprintf(line, "--dis-codec %s\n",
+		    config->codec_dis[i].ptr);
+	pj_strcat2(&cfg, line);
+    }
+    /* Add codec. */
+    for (i=0; i<config->codec_cnt; ++i) {
+	pj_ansi_sprintf(line, "--add-codec %s\n",
+		    config->codec_arg[i].ptr);
+	pj_strcat2(&cfg, line);
+    }
+
+    pj_strcat2(&cfg, "\n#\n# User agent:\n#\n");
+
+    /* Auto-answer. */
+    if (config->auto_answer != 0) {
+	pj_ansi_sprintf(line, "--auto-answer %d\n",
+			config->auto_answer);
+	pj_strcat2(&cfg, line);
+    }
+
+    /* accept-redirect */
+    if (config->redir_op != PJSIP_REDIRECT_ACCEPT_REPLACE) {
+	pj_ansi_sprintf(line, "--accept-redirect %d\n",
+			config->redir_op);
+	pj_strcat2(&cfg, line);
+    }
+
+    /* Max calls. */
+    pj_ansi_sprintf(line, "--max-calls %d\n",
+		    config->cfg.max_calls);
+    pj_strcat2(&cfg, line);
+
+    /* Uas-duration. */
+    if (config->duration != NO_LIMIT_DURATION) {
+	pj_ansi_sprintf(line, "--duration %d\n",
+			config->duration);
+	pj_strcat2(&cfg, line);
+    }
+
+    /* norefersub ? */
+    if (config->no_refersub) {
+	pj_strcat2(&cfg, "--norefersub\n");
+    }
+
+    if (pjsip_use_compact_form)
+    {
+	pj_strcat2(&cfg, "--use-compact-form\n");
+    }
+
+    if (!config->cfg.force_lr) {
+	pj_strcat2(&cfg, "--no-force-lr\n");
+    }
+
+    pj_strcat2(&cfg, "\n#\n# Buddies:\n#\n");
+
+    /* Add buddies. */
+    for (i=0; i<config->buddy_cnt; ++i) {
+	pj_ansi_sprintf(line, "--add-buddy %.*s\n",
+			      (int)config->buddy_cfg[i].uri.slen,
+			      config->buddy_cfg[i].uri.ptr);
+	pj_strcat2(&cfg, line);
+    }
+
+    /* SIP extensions. */
+    pj_strcat2(&cfg, "\n#\n# SIP extensions:\n#\n");
+    /* 100rel extension */
+    if (config->cfg.require_100rel) {
+	pj_strcat2(&cfg, "--use-100rel\n");
+    }
+    /* Session Timer extension */
+    if (config->cfg.use_timer) {
+	pj_ansi_sprintf(line, "--use-timer %d\n",
+			      config->cfg.use_timer);
+	pj_strcat2(&cfg, line);
+    }
+    if (config->cfg.timer_setting.min_se != 90) {
+	pj_ansi_sprintf(line, "--timer-min-se %d\n",
+			      config->cfg.timer_setting.min_se);
+	pj_strcat2(&cfg, line);
+    }
+    if (config->cfg.timer_setting.sess_expires != PJSIP_SESS_TIMER_DEF_SE) {
+	pj_ansi_sprintf(line, "--timer-se %d\n",
+			      config->cfg.timer_setting.sess_expires);
+	pj_strcat2(&cfg, line);
+    }
+
+    *(cfg.ptr + cfg.slen) = '\0';
+    return cfg.slen;
+}
+
+#ifdef PJSUA_HAS_VIDEO
+PJ_DEF(void) app_config_init_video(pjsua_acc_config *acc_cfg)
+{
+    acc_cfg->vid_in_auto_show = app_config.vid.in_auto_show;
+    acc_cfg->vid_out_auto_transmit = app_config.vid.out_auto_transmit;
+    /* Note that normally GUI application will prefer a borderless
+     * window.
+     */
+    acc_cfg->vid_wnd_flags = PJMEDIA_VID_DEV_WND_BORDER |
+                             PJMEDIA_VID_DEV_WND_RESIZABLE;
+    acc_cfg->vid_cap_dev = app_config.vid.vcapture_dev;
+    acc_cfg->vid_rend_dev = app_config.vid.vrender_dev;
+
+    if (app_config.avi_auto_play &&
+	app_config.avi_def_idx != PJSUA_INVALID_ID &&
+	app_config.avi[app_config.avi_def_idx].dev_id != PJMEDIA_VID_INVALID_DEV)
+    {
+	acc_cfg->vid_cap_dev = app_config.avi[app_config.avi_def_idx].dev_id;
+    }
+}
+#else
+PJ_DEF(void) app_config_init_video(pjsua_acc_config *acc_cfg)
+{
+    PJ_UNUSED_ARG(acc_cfg);
+}
+#endif
+
+#ifdef HAVE_MULTIPART_TEST
+  /*
+   * Enable multipart in msg_data and add a dummy body into the
+   * multipart bodies.
+   */
+  PJ_DEF(void) add_multipart(pjsua_msg_data *msg_data)
+  {
+      static pjsip_multipart_part *alt_part;
+
+      if (!alt_part) {
+	  pj_str_t type, subtype, content;
+
+	  alt_part = pjsip_multipart_create_part(app_config.pool);
+
+	  type = pj_str("text");
+	  subtype = pj_str("plain");
+	  content = pj_str("Sample text body of a multipart bodies");
+	  alt_part->body = pjsip_msg_body_create(app_config.pool, &type,
+						 &subtype, &content);
+      }
+
+      msg_data->multipart_ctype.type = pj_str("multipart");
+      msg_data->multipart_ctype.subtype = pj_str("mixed");
+      pj_list_push_back(&msg_data->multipart_parts, alt_part);
+  }
+#endif
+
+/* arrange windows. arg:
+ *   -1:    arrange all windows
+ *   != -1: arrange only this window id
+ */
+PJ_DEF(void) arrange_window(pjsua_vid_win_id wid)
+{
+#if PJSUA_HAS_VIDEO
+    pjmedia_coord pos;
+    int i, last;
+
+    pos.x = 0;
+    pos.y = 10;
+    last = (wid == PJSUA_INVALID_ID) ? PJSUA_MAX_VID_WINS : wid;
+
+    for (i=0; i<last; ++i) {
+	pjsua_vid_win_info wi;
+	pj_status_t status;
+
+	status = pjsua_vid_win_get_info(i, &wi);
+	if (status != PJ_SUCCESS)
+	    continue;
+
+	if (wid == PJSUA_INVALID_ID)
+	    pjsua_vid_win_set_pos(i, &pos);
+
+	if (wi.show)
+	    pos.y += wi.size.h;
+    }
+
+    if (wid != PJSUA_INVALID_ID)
+	pjsua_vid_win_set_pos(wid, &pos);
+#else
+    PJ_UNUSED_ARG(wid);
+#endif
+}
+
+
+#if PJSUA_HAS_VIDEO
+PJ_DEF(void) vid_print_dev(int id, const pjmedia_vid_dev_info *vdi,
+                          const char *title)
+{
+    char capnames[120];
+    char formats[120];
+    const char *dirname;
+    unsigned i;
+
+    if (vdi->dir == PJMEDIA_DIR_CAPTURE_RENDER) {
+	dirname = "capture, render";
+    } else if (vdi->dir == PJMEDIA_DIR_CAPTURE) {
+	dirname = "capture";
+    } else {
+	dirname = "render";
+    }
+
+
+    capnames[0] = '\0';
+    for (i=0; i<sizeof(int)*8 && (1 << i) < PJMEDIA_VID_DEV_CAP_MAX; ++i) {
+	if (vdi->caps & (1 << i)) {
+	    const char *capname = pjmedia_vid_dev_cap_name(1 << i, NULL);
+	    if (capname) {
+		if (*capnames)
+		    strcat(capnames, ", ");
+		strncat(capnames, capname,
+		        sizeof(capnames)-strlen(capnames)-1);
+	    }
+	}
+    }
+
+    formats[0] = '\0';
+    for (i=0; i<vdi->fmt_cnt; ++i) {
+	const pjmedia_video_format_info *vfi =
+		pjmedia_get_video_format_info(NULL, vdi->fmt[i].id);
+	if (vfi) {
+	    if (*formats)
+		strcat(formats, ", ");
+	    strncat(formats, vfi->name, sizeof(formats)-strlen(formats)-1);
+	}
+    }
+
+    PJ_LOG(3,(THIS_FILE, "%3d %s [%s][%s] %s", id, vdi->name, vdi->driver,
+	      dirname, title));
+    PJ_LOG(3,(THIS_FILE, "    Supported capabilities: %s", capnames));
+    PJ_LOG(3,(THIS_FILE, "    Supported formats: %s", formats));
+}
+
+PJ_DEF(void) vid_list_devs()
+{
+    unsigned i, count;
+    pjmedia_vid_dev_info vdi;
+    pj_status_t status;
+
+    PJ_LOG(3,(THIS_FILE, "Video device list:"));
+    count = pjsua_vid_dev_count();
+    if (count == 0) {
+	PJ_LOG(3,(THIS_FILE, " - no device detected -"));
+	return;
+    } else {
+	PJ_LOG(3,(THIS_FILE, "%d device(s) detected:", count));
+    }
+
+    status = pjsua_vid_dev_get_info(PJMEDIA_VID_DEFAULT_RENDER_DEV, &vdi);
+    if (status == PJ_SUCCESS)
+	vid_print_dev(PJMEDIA_VID_DEFAULT_RENDER_DEV, &vdi,
+	              "(default renderer device)");
+
+    status = pjsua_vid_dev_get_info(PJMEDIA_VID_DEFAULT_CAPTURE_DEV, &vdi);
+    if (status == PJ_SUCCESS)
+	vid_print_dev(PJMEDIA_VID_DEFAULT_CAPTURE_DEV, &vdi,
+	              "(default capture device)");
+
+    for (i=0; i<count; ++i) {
+	status = pjsua_vid_dev_get_info(i, &vdi);
+	if (status == PJ_SUCCESS)
+	    vid_print_dev(i, &vdi, "");
+    }
+}
+
+PJ_DEF(void) app_config_show_video(int acc_id, const pjsua_acc_config *acc_cfg)
+{
+    PJ_LOG(3,(THIS_FILE,
+	      "Account %d:\n"
+	      "  RX auto show:     %d\n"
+	      "  TX auto transmit: %d\n"
+	      "  Capture dev:      %d\n"
+	      "  Render dev:       %d",
+	      acc_id,
+	      acc_cfg->vid_in_auto_show,
+	      acc_cfg->vid_out_auto_transmit,
+	      acc_cfg->vid_cap_dev,
+	      acc_cfg->vid_rend_dev));
+}
+
+
+#endif
Index: /pjproject/trunk/pjsip-apps/src/pjsua/pjsua_cmd.h
===================================================================
--- /pjproject/trunk/pjsip-apps/src/pjsua/pjsua_cmd.h	(revision 4440)
+++ /pjproject/trunk/pjsip-apps/src/pjsua/pjsua_cmd.h	(revision 4440)
@@ -0,0 +1,179 @@
+/* $Id: pjsua_cmd.h 3553 2011-05-05 06:14:19Z nanang $ */
+/* 
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+#ifndef __PJSUA_CMD_H__
+#define __PJSUA_CMD_H__
+
+#include <pjsua-lib/pjsua.h>
+
+PJ_BEGIN_DECL
+
+#define current_acc	pjsua_acc_get_default()
+
+#define NO_LIMIT_DURATION   (int)0x7FFFFFFF
+#define MAX_AVI		    4
+#define NO_NB		    -2
+
+typedef struct input_result
+{
+    int	  nb_result;
+    char *uri_result;
+} input_result;
+
+/* Call specific data */
+typedef struct call_data
+{
+    pj_timer_entry	    timer;
+    pj_bool_t		    ringback_on;
+    pj_bool_t		    ring_on;
+} call_data;
+
+/* Video settings */
+typedef struct app_vid
+{
+    unsigned		    vid_cnt;
+    int			    vcapture_dev;
+    int			    vrender_dev;
+    pj_bool_t		    in_auto_show;
+    pj_bool_t		    out_auto_transmit;
+} app_vid;
+
+/* Pjsua application data */
+typedef struct pjsua_app_config
+{
+    pjsua_config	    cfg;
+    pjsua_logging_config    log_cfg;
+    pjsua_media_config	    media_cfg;
+    pj_bool_t		    no_refersub;
+    pj_bool_t		    ipv6;
+    pj_bool_t		    enable_qos;
+    pj_bool_t		    no_tcp;
+    pj_bool_t		    no_udp;
+    pj_bool_t		    use_tls;
+    pjsua_transport_config  udp_cfg;
+    pjsua_transport_config  rtp_cfg;
+    pjsip_redirect_op	    redir_op;
+
+    unsigned		    acc_cnt;
+    pjsua_acc_config	    acc_cfg[PJSUA_MAX_ACC];
+
+    unsigned		    buddy_cnt;
+    pjsua_buddy_config	    buddy_cfg[PJSUA_MAX_BUDDIES];
+
+    call_data		    call_data[PJSUA_MAX_CALLS];
+
+    pj_pool_t		   *pool;
+    /* Compatibility with older pjsua */
+
+    unsigned		    codec_cnt;
+    pj_str_t		    codec_arg[32];
+    unsigned		    codec_dis_cnt;
+    pj_str_t                codec_dis[32];
+    pj_bool_t		    null_audio;
+    unsigned		    wav_count;
+    pj_str_t		    wav_files[32];
+    unsigned		    tone_count;
+    pjmedia_tone_desc	    tones[32];
+    pjsua_conf_port_id	    tone_slots[32];
+    pjsua_player_id	    wav_id;
+    pjsua_conf_port_id	    wav_port;
+    pj_bool_t		    auto_play;
+    pj_bool_t		    auto_play_hangup;
+    pj_timer_entry	    auto_hangup_timer;
+    pj_bool_t		    auto_loop;
+    pj_bool_t		    auto_conf;
+    pj_str_t		    rec_file;
+    pj_bool_t		    auto_rec;
+    pjsua_recorder_id	    rec_id;
+    pjsua_conf_port_id	    rec_port;
+    unsigned		    auto_answer;
+    unsigned		    duration;
+
+#ifdef STEREO_DEMO
+    pjmedia_snd_port	   *snd;
+    pjmedia_port	   *sc, *sc_ch1;
+    pjsua_conf_port_id	    sc_ch1_slot;
+#endif
+
+    float		    mic_level,
+			    speaker_level;
+
+    int			    capture_dev, playback_dev;
+    unsigned		    capture_lat, playback_lat;
+
+    pj_bool_t		    no_tones;
+    int			    ringback_slot;
+    int			    ringback_cnt;
+    pjmedia_port	   *ringback_port;
+    int			    ring_slot;
+    int			    ring_cnt;
+    pjmedia_port	   *ring_port;
+
+    app_vid		    vid;
+    unsigned		    aud_cnt;
+
+    /* AVI to play */
+    unsigned                avi_cnt;
+    struct {
+	pj_str_t		path;
+	pjmedia_vid_dev_index	dev_id;
+	pjsua_conf_port_id	slot;
+    } avi[MAX_AVI];
+    pj_bool_t               avi_auto_play;
+    int			    avi_def_idx;
+
+    /* CLI setting */
+    pj_bool_t		    use_cli;
+    unsigned		    cli_telnet_port;    
+} pjsua_app_config;
+
+/** Extern variable declaration **/
+extern pjsua_call_id	    current_call;
+extern pjsua_app_config	    app_config;
+extern int		    stdout_refresh;
+extern pj_bool_t	    stdout_refresh_quit;
+extern pjsua_call_setting call_opt;
+extern pjsua_msg_data msg_data;
+
+PJ_DECL(int) my_atoi(const char *cs);
+PJ_DECL(pj_bool_t) find_next_call();
+PJ_DECL(pj_bool_t) find_prev_call();
+PJ_DECL(void) send_request(char *cstr_method, const pj_str_t *dst_uri);
+PJ_DECL(void) log_call_dump(int call_id);
+PJ_DECL(int) write_settings(pjsua_app_config *cfg, char *buf, pj_size_t max);
+PJ_DECL(void) app_config_init_video(pjsua_acc_config *acc_cfg);
+PJ_DECL(void) arrange_window(pjsua_vid_win_id wid);
+
+#if PJSUA_HAS_VIDEO
+PJ_DECL(void) vid_print_dev(int id, const pjmedia_vid_dev_info *vdi, const char *title);
+PJ_DECL(void) vid_list_devs();
+PJ_DECL(void) app_config_show_video(int acc_id, const pjsua_acc_config *acc_cfg);
+#endif
+
+#ifdef HAVE_MULTIPART_TEST
+    /*
+    * Enable multipart in msg_data and add a dummy body into the
+    * multipart bodies.
+    */
+    PJ_DECL(void) add_multipart(pjsua_msg_data *msg_data);
+#  define TEST_MULTIPART(msg_data)	add_multipart(msg_data)
+#else
+#  define TEST_MULTIPART(msg_data)
+#endif
+
+#endif	/* __PJSUA_CMD_H__ */
Index: /pjproject/trunk/pjsip-apps/src/pjsua/pjsua_ui_cmd.c
===================================================================
--- /pjproject/trunk/pjsip-apps/src/pjsua/pjsua_ui_cmd.c	(revision 4440)
+++ /pjproject/trunk/pjsip-apps/src/pjsua/pjsua_ui_cmd.c	(revision 4440)
@@ -0,0 +1,1938 @@
+/* $Id: pjsua_ui_cmd.c 3664 2011-07-19 03:42:28Z nanang $ */
+/* 
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+
+#include <pjsua-lib/pjsua.h>
+#include "pjsua_cmd.h"
+
+#define THIS_FILE	"pjsua_ui_cmd.c"
+
+static pj_bool_t	cmd_echo;
+
+/*
+ * Print buddy list.
+ */
+static void print_buddy_list()
+{
+    pjsua_buddy_id ids[64];
+    int i;
+    unsigned count = PJ_ARRAY_SIZE(ids);
+
+    puts("Buddy list:");
+
+    pjsua_enum_buddies(ids, &count);
+
+    if (count == 0)
+	puts(" -none-");
+    else {
+	for (i=0; i<(int)count; ++i) {
+	    pjsua_buddy_info info;
+
+	    if (pjsua_buddy_get_info(ids[i], &info) != PJ_SUCCESS)
+		continue;
+
+	    printf(" [%2d] <%.*s>  %.*s\n", 
+		    ids[i]+1, 
+		    (int)info.status_text.slen,
+		    info.status_text.ptr, 
+		    (int)info.uri.slen,
+		    info.uri.ptr);
+	}
+    }
+    puts("");
+}
+
+/*
+ * Input URL.
+ */
+static void ui_input_url(const char *title, char *buf, int len, 
+			 input_result *result)
+{
+    result->nb_result = NO_NB;
+    result->uri_result = NULL;
+
+    print_buddy_list();
+
+    printf("Choices:\n"
+	   "   0         For current dialog.\n"
+	   "  -1         All %d buddies in buddy list\n"
+	   "  [1 -%2d]    Select from buddy list\n"
+	   "  URL        An URL\n"
+	   "  <Enter>    Empty input (or 'q') to cancel\n"
+	   , pjsua_get_buddy_count(), pjsua_get_buddy_count());
+    printf("%s: ", title);
+
+    fflush(stdout);
+    if (fgets(buf, len, stdin) == NULL)
+	return;
+    len = strlen(buf);
+
+    /* Left trim */
+    while (pj_isspace(*buf)) {
+	++buf;
+	--len;
+    }
+
+    /* Remove trailing newlines */
+    while (len && (buf[len-1] == '\r' || buf[len-1] == '\n'))
+	buf[--len] = '\0';
+
+    if (len == 0 || buf[0]=='q')
+	return;
+
+    if (pj_isdigit(*buf) || *buf=='-') {
+	
+	int i;
+	
+	if (*buf=='-')
+	    i = 1;
+	else
+	    i = 0;
+
+	for (; i<len; ++i) {
+	    if (!pj_isdigit(buf[i])) {
+		puts("Invalid input");
+		return;
+	    }
+	}
+
+	result->nb_result = my_atoi(buf);
+
+	if (result->nb_result >= 0 && 
+	    result->nb_result <= (int)pjsua_get_buddy_count()) 
+	{
+	    return;
+	}
+	if (result->nb_result == -1)
+	    return;
+
+	puts("Invalid input");
+	result->nb_result = NO_NB;
+	return;
+
+    } else {
+	pj_status_t status;
+
+	if ((status=pjsua_verify_url(buf)) != PJ_SUCCESS) {
+	    pjsua_perror(THIS_FILE, "Invalid URL", status);
+	    return;
+	}
+
+	result->uri_result = buf;
+    }
+}
+
+static pj_bool_t simple_input(const char *title, char *buf, pj_size_t len)
+{
+    char *p;
+
+    printf("%s (empty to cancel): ", title); fflush(stdout);
+    if (fgets(buf, len, stdin) == NULL)
+	return PJ_FALSE;
+
+    /* Remove trailing newlines. */
+    for (p=buf; ; ++p) {
+	if (*p=='\r' || *p=='\n') *p='\0';
+	else if (!*p) break;
+    }
+
+    if (!*buf)
+	return PJ_FALSE;
+    
+    return PJ_TRUE;
+}
+
+/*
+ * Print account status.
+ */
+static void print_acc_status(int acc_id)
+{
+    char buf[80];
+    pjsua_acc_info info;
+
+    pjsua_acc_get_info(acc_id, &info);
+
+    if (!info.has_registration) {
+	pj_ansi_snprintf(buf, sizeof(buf), "%.*s", 
+			 (int)info.status_text.slen,
+			 info.status_text.ptr);
+
+    } else {
+	pj_ansi_snprintf(buf, sizeof(buf),
+			 "%d/%.*s (expires=%d)",
+			 info.status,
+			 (int)info.status_text.slen,
+			 info.status_text.ptr,
+			 info.expires);
+
+    }
+
+    printf(" %c[%2d] %.*s: %s\n", (acc_id==current_acc?'*':' '),
+	   acc_id,  (int)info.acc_uri.slen, info.acc_uri.ptr, buf);
+    printf("       Online status: %.*s\n", 
+	(int)info.online_status_text.slen,
+	info.online_status_text.ptr);
+}
+
+/*
+ * Show a bit of help.
+ */
+static void keystroke_help()
+{
+    pjsua_acc_id acc_ids[16];
+    unsigned count = PJ_ARRAY_SIZE(acc_ids);
+    int i;
+
+    printf(">>>>\n");
+
+    pjsua_enum_accs(acc_ids, &count);
+
+    printf("Account list:\n");
+    for (i=0; i<(int)count; ++i)
+	print_acc_status(acc_ids[i]);
+
+    print_buddy_list();
+    
+    //puts("Commands:");
+    puts("+=============================================================================+");
+    puts("|       Call Commands:         |   Buddy, IM & Presence:  |     Account:      |");
+    puts("|                              |                          |                   |");
+    puts("|  m  Make new call            | +b  Add new buddy       .| +a  Add new accnt |");
+    puts("|  M  Make multiple calls      | -b  Delete buddy         | -a  Delete accnt. |");
+    puts("|  a  Answer call              |  i  Send IM              | !a  Modify accnt. |");
+    puts("|  h  Hangup call  (ha=all)    |  s  Subscribe presence   | rr  (Re-)register |");
+    puts("|  H  Hold call                |  u  Unsubscribe presence | ru  Unregister    |");
+    puts("|  v  re-inVite (release hold) |  t  ToGgle Online status |  >  Cycle next ac.|");
+    puts("|  U  send UPDATE              |  T  Set online status    |  <  Cycle prev ac.|");
+    puts("| ],[ Select next/prev call    +--------------------------+-------------------+");
+    puts("|  x  Xfer call                |      Media Commands:     |  Status & Config: |");
+    puts("|  X  Xfer with Replaces       |                          |                   |");
+    puts("|  #  Send RFC 2833 DTMF       | cl  List ports           |  d  Dump status   |");
+    puts("|  *  Send DTMF with INFO      | cc  Connect port         | dd  Dump detailed |");
+    puts("| dq  Dump curr. call quality  | cd  Disconnect port      | dc  Dump config   |");
+    puts("|                              |  V  Adjust audio Volume  |  f  Save config   |");
+    puts("|  S  Send arbitrary REQUEST   | Cp  Codec priorities     |                   |");
+    puts("+-----------------------------------------------------------------------------+");
+#if PJSUA_HAS_VIDEO
+    puts("| Video: \"vid help\" for more info                                             |");
+    puts("+-----------------------------------------------------------------------------+");
+#endif
+    puts("|  q  QUIT   L  ReLoad   sleep MS   echo [0|1|txt]     n: detect NAT type     |");
+    puts("+=============================================================================+");
+
+    i = pjsua_call_get_count();
+    printf("You have %d active call%s\n", i, (i>1?"s":""));
+
+    if (current_call != PJSUA_INVALID_ID) {
+	pjsua_call_info ci;
+	if (pjsua_call_get_info(current_call, &ci)==PJ_SUCCESS)
+	    printf("Current call id=%d to %.*s [%.*s]\n", current_call,
+		   (int)ci.remote_info.slen, ci.remote_info.ptr,
+		   (int)ci.state_text.slen, ci.state_text.ptr);
+    }
+}
+
+/* Help screen for video */
+#if PJSUA_HAS_VIDEO
+static void vid_show_help()
+{
+    pj_bool_t vid_enabled = (app_config.vid.vid_cnt > 0);
+
+    puts("+=============================================================================+");
+    puts("|                            Video commands:                                  |");
+    puts("|                                                                             |");
+    puts("| vid help                  Show this help screen                             |");
+    puts("| vid enable|disable        Enable or disable video in next offer/answer      |");
+    puts("| vid acc show              Show current account video settings               |");
+    puts("| vid acc autorx on|off     Automatically show incoming video on/off          |");
+    puts("| vid acc autotx on|off     Automatically offer video on/off                  |");
+    puts("| vid acc cap ID            Set default capture device for current acc        |");
+    puts("| vid acc rend ID           Set default renderer device for current acc       |");
+    puts("| vid call rx on|off N      Enable/disable video RX for stream N in curr call |");
+    puts("| vid call tx on|off N      Enable/disable video TX for stream N in curr call |");
+    puts("| vid call add              Add video stream for current call                 |");
+    puts("| vid call enable|disable N Enable/disable stream #N in current call          |");
+    puts("| vid call cap N ID         Set capture dev ID for stream #N in current call  |");
+    puts("| vid dev list              List all video devices                            |");
+    puts("| vid dev refresh           Refresh video device list                         |");
+    puts("| vid dev prev on|off ID    Enable/disable preview for specified device ID    |");
+    puts("| vid codec list            List video codecs                                 |");
+    puts("| vid codec prio ID PRIO    Set codec ID priority to PRIO                     |");
+    puts("| vid codec fps ID NUM DEN  Set codec ID framerate to (NUM/DEN) fps           |");
+    puts("| vid codec bw ID AVG MAX   Set codec ID bitrate to AVG & MAX kbps            |");
+    puts("| vid codec size ID W H     Set codec ID size/resolution to W x H             |");
+    puts("| vid win list              List all active video windows                     |");
+    puts("| vid win arrange           Auto arrange windows                              |");
+    puts("| vid win show|hide ID      Show/hide the specified video window ID           |");
+    puts("| vid win move ID X Y       Move window ID to position X,Y                    |");
+    puts("| vid win resize ID w h     Resize window ID to the specified width, height   |");
+    puts("+=============================================================================+");
+    printf("| Video will be %s in the next offer/answer %s                            |\n",
+	   (vid_enabled? "enabled" : "disabled"), (vid_enabled? " " : ""));
+    puts("+=============================================================================+");
+}
+
+static void vid_handle_menu(char *menuin)
+{
+    char *argv[8];
+    int argc = 0;
+
+    /* Tokenize */
+    argv[argc] = strtok(menuin, " \t\r\n");
+    while (argv[argc] && *argv[argc]) {
+	argc++;
+	argv[argc] = strtok(NULL, " \t\r\n");
+    }
+
+    if (argc == 1 || strcmp(argv[1], "help")==0) {
+	vid_show_help();
+    } else if (argc == 2 && (strcmp(argv[1], "enable")==0 ||
+			     strcmp(argv[1], "disable")==0))
+    {
+	pj_bool_t enabled = (strcmp(argv[1], "enable")==0);
+	app_config.vid.vid_cnt = (enabled ? 1 : 0);
+	PJ_LOG(3,(THIS_FILE, "Video will be %s in next offer/answer",
+		  (enabled?"enabled":"disabled")));
+    } else if (strcmp(argv[1], "acc")==0) {
+	pjsua_acc_config acc_cfg;
+	pj_bool_t changed = PJ_FALSE;
+
+	pjsua_acc_get_config(current_acc, &acc_cfg);
+
+	if (argc == 3 && strcmp(argv[2], "show")==0) {
+	    app_config_show_video(current_acc, &acc_cfg);
+	} else if (argc == 4 && strcmp(argv[2], "autorx")==0) {
+	    int on = (strcmp(argv[3], "on")==0);
+	    acc_cfg.vid_in_auto_show = on;
+	    changed = PJ_TRUE;
+	} else if (argc == 4 && strcmp(argv[2], "autotx")==0) {
+	    int on = (strcmp(argv[3], "on")==0);
+	    acc_cfg.vid_out_auto_transmit = on;
+	    changed = PJ_TRUE;
+	} else if (argc == 4 && strcmp(argv[2], "cap")==0) {
+	    int dev = atoi(argv[3]);
+	    acc_cfg.vid_cap_dev = dev;
+	    changed = PJ_TRUE;
+	} else if (argc == 4 && strcmp(argv[2], "rend")==0) {
+	    int dev = atoi(argv[3]);
+	    acc_cfg.vid_rend_dev = dev;
+	    changed = PJ_TRUE;
+	} else {
+	    goto on_error;
+	}
+
+	if (changed) {
+	    pj_status_t status = pjsua_acc_modify(current_acc, &acc_cfg);
+	    if (status != PJ_SUCCESS)
+		PJ_PERROR(1,(THIS_FILE, status, "Error modifying account %d",
+			     current_acc));
+	}
+
+    } else if (strcmp(argv[1], "call")==0) {
+	pjsua_call_vid_strm_op_param param;
+	pj_status_t status = PJ_SUCCESS;
+
+	pjsua_call_vid_strm_op_param_default(&param);
+
+	if (argc == 5 && strcmp(argv[2], "rx")==0) {
+	    pjsua_stream_info si;
+	    pj_bool_t on = (strcmp(argv[3], "on") == 0);
+
+	    param.med_idx = atoi(argv[4]);
+	    if (pjsua_call_get_stream_info(current_call, param.med_idx, &si) ||
+		si.type != PJMEDIA_TYPE_VIDEO)
+	    {
+		PJ_PERROR(1,(THIS_FILE, PJ_EINVAL, "Invalid stream"));
+		return;
+	    }
+
+	    if (on) param.dir = (si.info.vid.dir | PJMEDIA_DIR_DECODING);
+	    else param.dir = (si.info.vid.dir & PJMEDIA_DIR_ENCODING);
+
+	    status = pjsua_call_set_vid_strm(current_call,
+	                                     PJSUA_CALL_VID_STRM_CHANGE_DIR,
+	                                     &param);
+	}
+	else if (argc == 5 && strcmp(argv[2], "tx")==0) {
+	    pj_bool_t on = (strcmp(argv[3], "on") == 0);
+	    pjsua_call_vid_strm_op op = on? PJSUA_CALL_VID_STRM_START_TRANSMIT :
+					    PJSUA_CALL_VID_STRM_STOP_TRANSMIT;
+
+	    param.med_idx = atoi(argv[4]);
+
+	    status = pjsua_call_set_vid_strm(current_call, op, &param);
+	}
+	else if (argc == 3 && strcmp(argv[2], "add")==0) {
+	    status = pjsua_call_set_vid_strm(current_call,
+	                                     PJSUA_CALL_VID_STRM_ADD, NULL);
+	}
+	else if (argc >= 3 && 
+		 (strcmp(argv[2], "disable")==0 || strcmp(argv[2], "enable")==0))
+	{
+	    pj_bool_t enable = (strcmp(argv[2], "enable") == 0);
+	    pjsua_call_vid_strm_op op = enable? PJSUA_CALL_VID_STRM_CHANGE_DIR :
+						PJSUA_CALL_VID_STRM_REMOVE;
+
+	    param.med_idx = argc >= 4? atoi(argv[3]) : -1;
+	    param.dir = PJMEDIA_DIR_ENCODING_DECODING;
+	    status = pjsua_call_set_vid_strm(current_call, op, &param);
+	}
+	else if (argc >= 3 && strcmp(argv[2], "cap")==0) {
+	    param.med_idx = argc >= 4? atoi(argv[3]) : -1;
+	    param.cap_dev = argc >= 5? atoi(argv[4]) : PJMEDIA_VID_DEFAULT_CAPTURE_DEV;
+	    status = pjsua_call_set_vid_strm(current_call,
+	                                     PJSUA_CALL_VID_STRM_CHANGE_CAP_DEV,
+	                                     &param);
+	} else
+	    goto on_error;
+
+	if (status != PJ_SUCCESS) {
+	    PJ_PERROR(1,(THIS_FILE, status, "Error modifying video stream"));
+	}
+
+    } else if (argc >= 3 && strcmp(argv[1], "dev")==0) {
+	if (strcmp(argv[2], "list")==0) {
+	    vid_list_devs();
+	} else if (strcmp(argv[2], "refresh")==0) {
+	    pjmedia_vid_dev_refresh();
+	} else if (strcmp(argv[2], "prev")==0) {
+	    if (argc != 5) {
+		goto on_error;
+	    } else {
+		pj_bool_t on = (strcmp(argv[3], "on") == 0);
+		int dev_id = atoi(argv[4]);
+		if (on) {
+                    pjsua_vid_preview_param param;
+
+                    pjsua_vid_preview_param_default(&param);
+                    param.wnd_flags = PJMEDIA_VID_DEV_WND_BORDER |
+                                      PJMEDIA_VID_DEV_WND_RESIZABLE;
+		    pjsua_vid_preview_start(dev_id, &param);
+		    arrange_window(pjsua_vid_preview_get_win(dev_id));
+		} else {
+		    pjsua_vid_win_id wid;
+		    wid = pjsua_vid_preview_get_win(dev_id);
+		    if (wid != PJSUA_INVALID_ID) {
+			/* Preview window hiding once it is stopped is
+			 * responsibility of app */
+			pjsua_vid_win_set_show(wid, PJ_FALSE);
+			pjsua_vid_preview_stop(dev_id);
+		    }
+		}
+	    }
+	} else
+	    goto on_error;
+    } else if (strcmp(argv[1], "win")==0) {
+	pj_status_t status = PJ_SUCCESS;
+
+	if (argc==3 && strcmp(argv[2], "list")==0) {
+	    pjsua_vid_win_id wids[PJSUA_MAX_VID_WINS];
+	    unsigned i, cnt = PJ_ARRAY_SIZE(wids);
+
+	    pjsua_vid_enum_wins(wids, &cnt);
+
+	    PJ_LOG(3,(THIS_FILE, "Found %d video windows:", cnt));
+	    PJ_LOG(3,(THIS_FILE, "WID show    pos       size"));
+	    PJ_LOG(3,(THIS_FILE, "------------------------------"));
+	    for (i = 0; i < cnt; ++i) {
+		pjsua_vid_win_info wi;
+		pjsua_vid_win_get_info(wids[i], &wi);
+		PJ_LOG(3,(THIS_FILE, "%3d   %c  (%d,%d)  %dx%d",
+			  wids[i], (wi.show?'Y':'N'), wi.pos.x, wi.pos.y,
+			  wi.size.w, wi.size.h));
+	    }
+	} else if (argc==4 && (strcmp(argv[2], "show")==0 ||
+			       strcmp(argv[2], "hide")==0))
+	{
+	    pj_bool_t show = (strcmp(argv[2], "show")==0);
+	    pjsua_vid_win_id wid = atoi(argv[3]);
+	    status = pjsua_vid_win_set_show(wid, show);
+	} else if (argc==6 && strcmp(argv[2], "move")==0) {
+	    pjsua_vid_win_id wid = atoi(argv[3]);
+	    pjmedia_coord pos;
+
+	    pos.x = atoi(argv[4]);
+	    pos.y = atoi(argv[5]);
+	    status = pjsua_vid_win_set_pos(wid, &pos);
+	} else if (argc==6 && strcmp(argv[2], "resize")==0) {
+	    pjsua_vid_win_id wid = atoi(argv[3]);
+	    pjmedia_rect_size size;
+
+	    size.w = atoi(argv[4]);
+	    size.h = atoi(argv[5]);
+	    status = pjsua_vid_win_set_size(wid, &size);
+	} else if (argc==3 && strcmp(argv[2], "arrange")==0) {
+	    arrange_window(PJSUA_INVALID_ID);
+	} else
+	    goto on_error;
+
+	if (status != PJ_SUCCESS) {
+	    PJ_PERROR(1,(THIS_FILE, status, "Window operation error"));
+	}
+
+    } else if (strcmp(argv[1], "codec")==0) {
+	pjsua_codec_info ci[PJMEDIA_CODEC_MGR_MAX_CODECS];
+	unsigned count = PJ_ARRAY_SIZE(ci);
+	pj_status_t status;
+
+	if (argc==3 && strcmp(argv[2], "list")==0) {
+	    status = pjsua_vid_enum_codecs(ci, &count);
+	    if (status != PJ_SUCCESS) {
+		PJ_PERROR(1,(THIS_FILE, status, "Error enumerating codecs"));
+	    } else {
+		unsigned i;
+		PJ_LOG(3,(THIS_FILE, "Found %d video codecs:", count));
+		PJ_LOG(3,(THIS_FILE, "codec id      prio  fps    bw(kbps)   size"));
+		PJ_LOG(3,(THIS_FILE, "------------------------------------------"));
+		for (i=0; i<count; ++i) {
+		    pjmedia_vid_codec_param cp;
+		    pjmedia_video_format_detail *vfd;
+
+		    status = pjsua_vid_codec_get_param(&ci[i].codec_id, &cp);
+		    if (status != PJ_SUCCESS)
+			continue;
+
+		    vfd = pjmedia_format_get_video_format_detail(&cp.enc_fmt,
+								 PJ_TRUE);
+		    PJ_LOG(3,(THIS_FILE, "%.*s%.*s %3d %7.2f  %4d/%4d  %dx%d", 
+			      (int)ci[i].codec_id.slen, ci[i].codec_id.ptr,
+			      13-(int)ci[i].codec_id.slen, "                ",
+			      ci[i].priority,
+			      (vfd->fps.num*1.0/vfd->fps.denum),
+			      vfd->avg_bps/1000, vfd->max_bps/1000,
+			      vfd->size.w, vfd->size.h));
+		}
+	    }
+	} else if (argc==5 && strcmp(argv[2], "prio")==0) {
+	    pj_str_t cid;
+	    int prio;
+	    cid = pj_str(argv[3]);
+	    prio = atoi(argv[4]);
+	    status = pjsua_vid_codec_set_priority(&cid, (pj_uint8_t)prio);
+	    if (status != PJ_SUCCESS)
+		PJ_PERROR(1,(THIS_FILE, status, "Set codec priority error"));
+	} else if (argc==6 && strcmp(argv[2], "fps")==0) {
+	    pjmedia_vid_codec_param cp;
+	    pj_str_t cid;
+	    int M, N;
+	    cid = pj_str(argv[3]);
+	    M = atoi(argv[4]);
+	    N = atoi(argv[5]);
+	    status = pjsua_vid_codec_get_param(&cid, &cp);
+	    if (status == PJ_SUCCESS) {
+		cp.enc_fmt.det.vid.fps.num = M;
+		cp.enc_fmt.det.vid.fps.denum = N;
+		status = pjsua_vid_codec_set_param(&cid, &cp);
+	    }
+	    if (status != PJ_SUCCESS)
+		PJ_PERROR(1,(THIS_FILE, status, "Set codec framerate error"));
+	} else if (argc==6 && strcmp(argv[2], "bw")==0) {
+	    pjmedia_vid_codec_param cp;
+	    pj_str_t cid;
+	    int M, N;
+	    cid = pj_str(argv[3]);
+	    M = atoi(argv[4]);
+	    N = atoi(argv[5]);
+	    status = pjsua_vid_codec_get_param(&cid, &cp);
+	    if (status == PJ_SUCCESS) {
+		cp.enc_fmt.det.vid.avg_bps = M * 1000;
+		cp.enc_fmt.det.vid.max_bps = N * 1000;
+		status = pjsua_vid_codec_set_param(&cid, &cp);
+	    }
+	    if (status != PJ_SUCCESS)
+		PJ_PERROR(1,(THIS_FILE, status, "Set codec bitrate error"));
+	} else if (argc==6 && strcmp(argv[2], "size")==0) {
+	    pjmedia_vid_codec_param cp;
+	    pj_str_t cid;
+	    int M, N;
+	    cid = pj_str(argv[3]);
+	    M = atoi(argv[4]);
+	    N = atoi(argv[5]);
+	    status = pjsua_vid_codec_get_param(&cid, &cp);
+	    if (status == PJ_SUCCESS) {
+		cp.enc_fmt.det.vid.size.w = M;
+		cp.enc_fmt.det.vid.size.h = N;
+		status = pjsua_vid_codec_set_param(&cid, &cp);
+	    }
+	    if (status != PJ_SUCCESS)
+		PJ_PERROR(1,(THIS_FILE, status, "Set codec size error"));
+	} else
+	    goto on_error;
+    } else
+	goto on_error;
+
+    return;
+
+on_error:
+    PJ_LOG(1,(THIS_FILE, "Invalid command, use 'vid help'"));
+}
+
+#endif /* PJSUA_HAS_VIDEO */
+
+/** UI Command **/
+static void ui_make_new_call()
+{    
+    char buf[128];
+    pjsua_msg_data msg_data;
+    input_result result;
+    pj_str_t tmp;
+
+    printf("(You currently have %d calls)\n", pjsua_call_get_count());
+    
+    ui_input_url("Make call", buf, sizeof(buf), &result);
+    if (result.nb_result != NO_NB) {
+
+	if (result.nb_result == -1 || result.nb_result == 0) {
+	    puts("You can't do that with make call!");
+	    return;
+	} else {
+	    pjsua_buddy_info binfo;
+	    pjsua_buddy_get_info(result.nb_result-1, &binfo);
+	    tmp.ptr = buf;
+	    pj_strncpy(&tmp, &binfo.uri, sizeof(buf));
+	}
+
+    } else if (result.uri_result) {
+	tmp = pj_str(result.uri_result);
+    } else {
+	tmp.slen = 0;
+    }
+
+    pjsua_msg_data_init(&msg_data);
+    TEST_MULTIPART(&msg_data);
+    pjsua_call_make_call(current_acc, &tmp, &call_opt, NULL, 
+			 &msg_data, &current_call);
+}
+
+static void ui_make_multi_call()
+{
+    char menuin[32];
+    int count;
+    char buf[128];
+    input_result result;
+    pj_str_t tmp;
+    int i;
+
+    printf("(You currently have %d calls)\n", pjsua_call_get_count());
+
+    if (!simple_input("Number of calls", menuin, sizeof(menuin)))
+	return;
+
+    count = my_atoi(menuin);
+    if (count < 1)
+	return;
+
+    ui_input_url("Make call", buf, sizeof(buf), &result);
+    if (result.nb_result != NO_NB) {
+	pjsua_buddy_info binfo;
+	if (result.nb_result == -1 || result.nb_result == 0) {
+	    puts("You can't do that with make call!");
+	    return;
+	}
+	pjsua_buddy_get_info(result.nb_result-1, &binfo);
+	tmp.ptr = buf;
+	pj_strncpy(&tmp, &binfo.uri, sizeof(buf));
+    } else {
+	tmp = pj_str(result.uri_result);
+    }
+
+    for (i=0; i<my_atoi(menuin); ++i) {
+	pj_status_t status;
+
+	status = pjsua_call_make_call(current_acc, &tmp, &call_opt, NULL,
+	    NULL, NULL);
+	if (status != PJ_SUCCESS)
+	    break;
+    }
+}
+
+static void ui_detect_nat_type()
+{
+    int i = pjsua_detect_nat_type();
+    if (i != PJ_SUCCESS)
+	pjsua_perror(THIS_FILE, "Error", i);
+}
+
+static void ui_send_instant_message()
+{
+    char *uri = NULL;
+    /* i is for call index to send message, if any */
+    int i = -1;
+    input_result result;
+    char buf[128];
+    char text[128];
+    pj_str_t tmp;
+
+    /* Input destination. */
+    ui_input_url("Send IM to", buf, sizeof(buf), &result);
+    if (result.nb_result != NO_NB) {
+
+	if (result.nb_result == -1) {
+	    puts("You can't send broadcast IM like that!");
+	    return;
+
+	} else if (result.nb_result == 0) {
+	    i = current_call;
+	} else {
+	    pjsua_buddy_info binfo;
+	    pjsua_buddy_get_info(result.nb_result-1, &binfo);
+	    tmp.ptr = buf;
+	    pj_strncpy_with_null(&tmp, &binfo.uri, sizeof(buf));
+	    uri = buf;
+	}
+
+    } else if (result.uri_result) {
+	uri = result.uri_result;
+    }
+
+
+    /* Send typing indication. */
+    if (i != -1)
+	pjsua_call_send_typing_ind(i, PJ_TRUE, NULL);
+    else {
+	pj_str_t tmp_uri = pj_str(uri);
+	pjsua_im_typing(current_acc, &tmp_uri, PJ_TRUE, NULL);
+    }
+
+    /* Input the IM . */
+    if (!simple_input("Message", text, sizeof(text))) {
+	/*
+	* Cancelled.
+	* Send typing notification too, saying we're not typing.
+	*/
+	if (i != -1)
+	    pjsua_call_send_typing_ind(i, PJ_FALSE, NULL);
+	else {
+	    pj_str_t tmp_uri = pj_str(uri);
+	    pjsua_im_typing(current_acc, &tmp_uri, PJ_FALSE, NULL);
+	}
+	return;
+    }
+
+    tmp = pj_str(text);
+
+    /* Send the IM */
+    if (i != -1)
+	pjsua_call_send_im(i, NULL, &tmp, NULL, NULL);
+    else {
+	pj_str_t tmp_uri = pj_str(uri);
+	pjsua_im_send(current_acc, &tmp_uri, NULL, &tmp, NULL, NULL);
+    }
+}
+
+static void ui_answer_call()
+{
+    pjsua_call_info call_info;
+    char buf[128];
+    pjsua_msg_data msg_data;
+
+    if (current_call != -1) {
+	pjsua_call_get_info(current_call, &call_info);
+    } else {
+	/* Make compiler happy */
+	call_info.role = PJSIP_ROLE_UAC;
+	call_info.state = PJSIP_INV_STATE_DISCONNECTED;
+    }
+
+    if (current_call == -1 || 
+	call_info.role != PJSIP_ROLE_UAS ||
+	call_info.state >= PJSIP_INV_STATE_CONNECTING)
+    {
+	puts("No pending incoming call");
+	fflush(stdout);
+	return;
+
+    } else {
+	int st_code;
+	char contact[120];
+	pj_str_t hname = { "Contact", 7 };
+	pj_str_t hvalue;
+	pjsip_generic_string_hdr hcontact;
+
+	if (!simple_input("Answer with code (100-699)", buf, sizeof(buf)))
+	    return;
+
+	st_code = my_atoi(buf);
+	if (st_code < 100)
+	    return;
+
+	pjsua_msg_data_init(&msg_data);
+
+	if (st_code/100 == 3) {
+	    if (!simple_input("Enter URL to be put in Contact", 
+		contact, sizeof(contact)))
+		return;
+	    hvalue = pj_str(contact);
+	    pjsip_generic_string_hdr_init2(&hcontact, &hname, &hvalue);
+
+	    pj_list_push_back(&msg_data.hdr_list, &hcontact);
+	}
+
+	/*
+	* Must check again!
+	* Call may have been disconnected while we're waiting for 
+	* keyboard input.
+	*/
+	if (current_call == -1) {
+	    puts("Call has been disconnected");
+	    fflush(stdout);
+	    return;
+	}
+
+	pjsua_call_answer2(current_call, &call_opt, st_code, NULL, &msg_data);
+    }    
+}
+
+static void ui_hangup_call(char menuin[])
+{
+    if (current_call == -1) {
+	puts("No current call");
+	fflush(stdout);
+	return;
+
+    } else if (menuin[1] == 'a') {
+	/* Hangup all calls */
+	pjsua_call_hangup_all();
+    } else {
+	/* Hangup current calls */
+	pjsua_call_hangup(current_call, 0, NULL, NULL);
+    }
+}
+
+static void ui_cycle_dialog(char menuin[])
+{
+    if (menuin[0] == ']') {
+	find_next_call();
+
+    } else {
+	find_prev_call();
+    }
+
+    if (current_call != -1) {
+	pjsua_call_info call_info;
+
+	pjsua_call_get_info(current_call, &call_info);
+	PJ_LOG(3,(THIS_FILE,"Current dialog: %.*s", 
+	    (int)call_info.remote_info.slen, 
+	    call_info.remote_info.ptr));
+
+    } else {
+	PJ_LOG(3,(THIS_FILE,"No current dialog"));
+    }
+}
+
+static void ui_cycle_account()
+{
+    int i;
+    char buf[128];
+
+    if (!simple_input("Enter account ID to select", buf, sizeof(buf)))
+	return;
+
+    i = my_atoi(buf);
+    if (pjsua_acc_is_valid(i)) {
+	pjsua_acc_set_default(i);
+	PJ_LOG(3,(THIS_FILE, "Current account changed to %d", i));
+    } else {
+	PJ_LOG(3,(THIS_FILE, "Invalid account id %d", i));
+    }
+}
+
+static void ui_add_buddy()
+{
+    char buf[128];
+    pjsua_buddy_config buddy_cfg;
+    pjsua_buddy_id buddy_id;
+    pj_status_t status;
+
+    if (!simple_input("Enter buddy's URI:", buf, sizeof(buf)))
+	return;
+
+    if (pjsua_verify_url(buf) != PJ_SUCCESS) {
+	printf("Invalid URI '%s'\n", buf);
+	return;
+    }
+
+    pj_bzero(&buddy_cfg, sizeof(pjsua_buddy_config));
+
+    buddy_cfg.uri = pj_str(buf);
+    buddy_cfg.subscribe = PJ_TRUE;
+
+    status = pjsua_buddy_add(&buddy_cfg, &buddy_id);
+    if (status == PJ_SUCCESS) {
+	printf("New buddy '%s' added at index %d\n",
+	    buf, buddy_id+1);
+    }
+}
+
+static void ui_add_account(pjsua_transport_config *rtp_cfg)
+{
+    char id[80], registrar[80], realm[80], uname[80], passwd[30];
+    pjsua_acc_config acc_cfg;
+    pj_status_t status;
+
+    if (!simple_input("Your SIP URL:", id, sizeof(id)))
+	return;
+    if (!simple_input("URL of the registrar:", registrar, sizeof(registrar)))
+	return;
+    if (!simple_input("Auth Realm:", realm, sizeof(realm)))
+	return;
+    if (!simple_input("Auth Username:", uname, sizeof(uname)))
+	return;
+    if (!simple_input("Auth Password:", passwd, sizeof(passwd)))
+	return;
+
+    pjsua_acc_config_default(&acc_cfg);
+    acc_cfg.id = pj_str(id);
+    acc_cfg.reg_uri = pj_str(registrar);
+    acc_cfg.cred_count = 1;
+    acc_cfg.cred_info[0].scheme = pj_str("Digest");
+    acc_cfg.cred_info[0].realm = pj_str(realm);
+    acc_cfg.cred_info[0].username = pj_str(uname);
+    acc_cfg.cred_info[0].data_type = 0;
+    acc_cfg.cred_info[0].data = pj_str(passwd);
+
+    acc_cfg.rtp_cfg = *rtp_cfg;    
+    app_config_init_video(&acc_cfg);
+
+    status = pjsua_acc_add(&acc_cfg, PJ_TRUE, NULL);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Error adding new account", status);
+    }
+}
+
+static void ui_delete_buddy()
+{
+    char buf[128];
+    int i;
+
+    if (!simple_input("Enter buddy ID to delete", buf, sizeof(buf)))
+	return;
+
+    i = my_atoi(buf) - 1;
+
+    if (!pjsua_buddy_is_valid(i)) {
+	printf("Invalid buddy id %d\n", i);
+    } else {
+	pjsua_buddy_del(i);
+	printf("Buddy %d deleted\n", i);
+    }
+}
+
+static void ui_delete_account()
+{
+    char buf[128];
+    int i;
+
+    if (!simple_input("Enter account ID to delete", buf, sizeof(buf)))
+	return;
+
+    i = my_atoi(buf);
+
+    if (!pjsua_acc_is_valid(i)) {
+	printf("Invalid account id %d\n", i);
+    } else {
+	pjsua_acc_del(i);
+	printf("Account %d deleted\n", i);
+    }
+}
+
+static void ui_call_hold()
+{
+    if (current_call != -1) {		
+	pjsua_call_set_hold(current_call, NULL);
+    } else {
+	PJ_LOG(3,(THIS_FILE, "No current call"));
+    }
+}
+
+static void ui_call_reinvite()
+{
+    call_opt.flag |= PJSUA_CALL_UNHOLD;
+    pjsua_call_reinvite2(current_call, &call_opt, NULL);
+}
+
+static void ui_send_update()
+{
+    if (current_call != -1) {		
+	pjsua_call_update2(current_call, &call_opt, NULL);
+    } else {
+	PJ_LOG(3,(THIS_FILE, "No current call"));
+    }
+}
+
+/*
+ * Change codec priorities.
+ */
+static void ui_manage_codec_prio()
+{
+    pjsua_codec_info c[32];
+    unsigned i, count = PJ_ARRAY_SIZE(c);
+    char input[32];
+    char *codec, *prio;
+    pj_str_t id;
+    int new_prio;
+    pj_status_t status;
+
+    printf("List of audio codecs:\n");
+    pjsua_enum_codecs(c, &count);
+    for (i=0; i<count; ++i) {
+	printf("  %d\t%.*s\n", c[i].priority, (int)c[i].codec_id.slen,
+			       c[i].codec_id.ptr);
+    }
+
+#if PJSUA_HAS_VIDEO
+    puts("");
+    printf("List of video codecs:\n");
+    pjsua_vid_enum_codecs(c, &count);
+    for (i=0; i<count; ++i) {
+	printf("  %d\t%.*s%s%.*s\n", c[i].priority,
+				     (int)c[i].codec_id.slen,
+				     c[i].codec_id.ptr,
+				     c[i].desc.slen? " - ":"",
+				     (int)c[i].desc.slen,
+				     c[i].desc.ptr);
+    }
+#endif
+
+    puts("");
+    puts("Enter codec id and its new priority (e.g. \"speex/16000 200\", "
+	 """\"H263 200\"),");
+    puts("or empty to cancel.");
+
+    printf("Codec name (\"*\" for all) and priority: ");
+    if (fgets(input, sizeof(input), stdin) == NULL)
+	return;
+    if (input[0]=='\r' || input[0]=='\n') {
+	puts("Done");
+	return;
+    }
+
+    codec = strtok(input, " \t\r\n");
+    prio = strtok(NULL, " \r\n");
+
+    if (!codec || !prio) {
+	puts("Invalid input");
+	return;
+    }
+
+    new_prio = atoi(prio);
+    if (new_prio < 0) 
+	new_prio = 0;
+    else if (new_prio > PJMEDIA_CODEC_PRIO_HIGHEST) 
+	new_prio = PJMEDIA_CODEC_PRIO_HIGHEST;
+
+    status = pjsua_codec_set_priority(pj_cstr(&id, codec), 
+				      (pj_uint8_t)new_prio);
+#if PJSUA_HAS_VIDEO
+    if (status != PJ_SUCCESS) {
+	status = pjsua_vid_codec_set_priority(pj_cstr(&id, codec), 
+					      (pj_uint8_t)new_prio);
+    }
+#endif
+    if (status != PJ_SUCCESS)
+	pjsua_perror(THIS_FILE, "Error setting codec priority", status);
+}
+
+static void ui_call_transfer(pj_bool_t no_refersub)
+{
+    if (current_call == -1) {
+	PJ_LOG(3,(THIS_FILE, "No current call"));
+    } else {
+	int call = current_call;
+	char buf[128];
+	pjsip_generic_string_hdr refer_sub;
+	pj_str_t STR_REFER_SUB = { "Refer-Sub", 9 };
+	pj_str_t STR_FALSE = { "false", 5 };
+	pjsua_call_info ci;
+	input_result result;
+	pjsua_msg_data msg_data;
+
+	pjsua_call_get_info(current_call, &ci);
+	printf("Transfering current call [%d] %.*s\n", current_call,
+	       (int)ci.remote_info.slen, ci.remote_info.ptr);
+
+	ui_input_url("Transfer to URL", buf, sizeof(buf), &result);
+
+	/* Check if call is still there. */
+
+	if (call != current_call) {
+	    puts("Call has been disconnected");
+	    return;
+	}
+
+	pjsua_msg_data_init(&msg_data);
+	if (no_refersub) {
+	    /* Add Refer-Sub: false in outgoing REFER request */
+	    pjsip_generic_string_hdr_init2(&refer_sub, &STR_REFER_SUB,
+		&STR_FALSE);
+	    pj_list_push_back(&msg_data.hdr_list, &refer_sub);
+	}
+	if (result.nb_result != NO_NB) {
+	    if (result.nb_result == -1 || result.nb_result == 0)
+		puts("You can't do that with transfer call!");
+	    else {
+		pjsua_buddy_info binfo;
+		pjsua_buddy_get_info(result.nb_result-1, &binfo);
+		pjsua_call_xfer( current_call, &binfo.uri, &msg_data);
+	    }
+
+	} else if (result.uri_result) {
+	    pj_str_t tmp;
+	    tmp = pj_str(result.uri_result);
+	    pjsua_call_xfer( current_call, &tmp, &msg_data);
+	}
+    }
+}
+
+static void ui_call_transfer_replaces(pj_bool_t no_refersub)
+{        
+    if (current_call == -1) {
+	PJ_LOG(3,(THIS_FILE, "No current call"));
+    } else {
+	int call = current_call;
+	int dst_call;
+	pjsip_generic_string_hdr refer_sub;
+	pj_str_t STR_REFER_SUB = { "Refer-Sub", 9 };
+	pj_str_t STR_FALSE = { "false", 5 };
+	pjsua_call_id ids[PJSUA_MAX_CALLS];
+	pjsua_call_info ci;
+	pjsua_msg_data msg_data;
+	char buf[128];
+	unsigned i, count;
+
+	count = PJ_ARRAY_SIZE(ids);
+	pjsua_enum_calls(ids, &count);
+
+	if (count <= 1) {
+	    puts("There are no other calls");
+	    return;
+	}
+
+	pjsua_call_get_info(current_call, &ci);
+	printf("Transfer call [%d] %.*s to one of the following:\n",
+	       current_call,
+	       (int)ci.remote_info.slen, ci.remote_info.ptr);
+
+	for (i=0; i<count; ++i) {
+	    pjsua_call_info call_info;
+
+	    if (ids[i] == call)
+		return;
+
+	    pjsua_call_get_info(ids[i], &call_info);
+	    printf("%d  %.*s [%.*s]\n",
+		ids[i],
+		(int)call_info.remote_info.slen,
+		call_info.remote_info.ptr,
+		(int)call_info.state_text.slen,
+		call_info.state_text.ptr);
+	}
+
+	if (!simple_input("Enter call number to be replaced", buf, sizeof(buf)))
+	    return;
+
+	dst_call = my_atoi(buf);
+
+	/* Check if call is still there. */
+
+	if (call != current_call) {
+	    puts("Call has been disconnected");
+	    return;
+	}
+
+	/* Check that destination call is valid. */
+	if (dst_call == call) {
+	    puts("Destination call number must not be the same "
+		"as the call being transfered");
+	    return;
+	}
+	if (dst_call >= PJSUA_MAX_CALLS) {
+	    puts("Invalid destination call number");
+	    return;
+	}
+	if (!pjsua_call_is_active(dst_call)) {
+	    puts("Invalid destination call number");
+	    return;
+	}
+
+	pjsua_msg_data_init(&msg_data);
+	if (no_refersub) {
+	    /* Add Refer-Sub: false in outgoing REFER request */
+	    pjsip_generic_string_hdr_init2(&refer_sub, &STR_REFER_SUB, 
+					   &STR_FALSE);
+	    pj_list_push_back(&msg_data.hdr_list, &refer_sub);
+	}
+
+	pjsua_call_xfer_replaces(call, dst_call, 
+				 PJSUA_XFER_NO_REQUIRE_REPLACES, 
+				 &msg_data);
+    }
+}
+
+static void ui_send_dtmf_2833()
+{
+    if (current_call == -1) {
+	PJ_LOG(3,(THIS_FILE, "No current call"));
+    } else if (!pjsua_call_has_media(current_call)) {
+	PJ_LOG(3,(THIS_FILE, "Media is not established yet!"));
+    } else {
+	pj_str_t digits;
+	int call = current_call;
+	pj_status_t status;
+	char buf[128];
+
+	if (!simple_input("DTMF strings to send (0-9*#A-B)", buf, 
+	    sizeof(buf)))
+	{
+	    return;
+	}
+
+	if (call != current_call) {
+	    puts("Call has been disconnected");
+	    return;
+	}
+
+	digits = pj_str(buf);
+	status = pjsua_call_dial_dtmf(current_call, &digits);
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror(THIS_FILE, "Unable to send DTMF", status);
+	} else {
+	    puts("DTMF digits enqueued for transmission");
+	}
+    }
+}
+
+static void ui_send_dtmf_info()
+{
+    if (current_call == -1) {
+	PJ_LOG(3,(THIS_FILE, "No current call"));
+    } else {
+	const pj_str_t SIP_INFO = pj_str("INFO");
+	pj_str_t digits;
+	int call = current_call;
+	int i;
+	pj_status_t status;
+	char buf[128];
+
+	if (!simple_input("DTMF strings to send (0-9*#A-B)", buf, 
+	    sizeof(buf)))
+	{
+	    return;
+	}
+
+	if (call != current_call) {
+	    puts("Call has been disconnected");
+	    return;
+	}
+
+	digits = pj_str(buf);
+	for (i=0; i<digits.slen; ++i) {
+	    char body[80];
+	    pjsua_msg_data msg_data;
+
+	    pjsua_msg_data_init(&msg_data);
+	    msg_data.content_type = pj_str("application/dtmf-relay");
+
+	    pj_ansi_snprintf(body, sizeof(body),
+		"Signal=%c\r\n"
+		"Duration=160",
+		buf[i]);
+	    msg_data.msg_body = pj_str(body);
+
+	    status = pjsua_call_send_request(current_call, &SIP_INFO, 
+		&msg_data);
+	    if (status != PJ_SUCCESS) {
+		return;
+	    }
+	}
+    }
+}
+
+static void ui_send_arbitrary_request()
+{
+    char text[128];
+    char buf[128];
+    char *uri;
+    input_result result;
+    pj_str_t tmp;
+
+    if (pjsua_acc_get_count() == 0) {
+	puts("Sorry, need at least one account configured");
+	return;
+    }
+
+    puts("Send arbitrary request to remote host");
+
+    /* Input METHOD */
+    if (!simple_input("Request method:",text,sizeof(text)))
+	return;
+
+    /* Input destination URI */
+    uri = NULL;
+    ui_input_url("Destination URI", buf, sizeof(buf), &result);
+    if (result.nb_result != NO_NB) {
+
+	if (result.nb_result == -1) {
+	    puts("Sorry you can't do that!");
+	    return;
+	} else if (result.nb_result == 0) {
+	    uri = NULL;
+	    if (current_call == PJSUA_INVALID_ID) {
+		puts("No current call");
+		return;
+	    }
+	} else {	    
+	    pjsua_buddy_info binfo;
+	    pjsua_buddy_get_info(result.nb_result-1, &binfo);
+	    tmp.ptr = buf;
+	    pj_strncpy_with_null(&tmp, &binfo.uri, sizeof(buf));
+	    uri = buf;
+	}
+
+    } else if (result.uri_result) {
+	uri = result.uri_result;
+    } else {
+	return;
+    }
+
+    if (uri) {
+	tmp = pj_str(uri);
+	send_request(text, &tmp);
+    } else {
+	/* If you send call control request using this method
+	* (such requests includes BYE, CANCEL, etc.), it will
+	* not go well with the call state, so don't do it
+	* unless it's for testing.
+	*/
+	pj_str_t method = pj_str(text);
+	pjsua_call_send_request(current_call, &method, NULL);
+    }	    
+}
+
+static void ui_echo(char menuin[])
+{
+    if (pj_ansi_strnicmp(menuin, "echo", 4)==0) {
+	pj_str_t tmp;
+
+	tmp.ptr = menuin+5;
+	tmp.slen = pj_ansi_strlen(menuin)-6;
+
+	if (tmp.slen < 1) {
+	    puts("Usage: echo [0|1]");
+	    return;
+	}
+	cmd_echo = *tmp.ptr != '0' || tmp.slen!=1;
+    }
+}
+
+static void ui_sleep(char menuin[])
+{
+    if (pj_ansi_strnicmp(menuin, "sleep", 5)==0) {
+	pj_str_t tmp;
+	int delay;
+
+	tmp.ptr = menuin+6;
+	tmp.slen = pj_ansi_strlen(menuin)-7;
+
+	if (tmp.slen < 1) {
+	    puts("Usage: sleep MSEC");
+	    return;
+	}
+
+	delay = pj_strtoul(&tmp);
+	if (delay < 0) delay = 0;
+	pj_thread_sleep(delay);		
+    }
+}
+
+static void ui_subscribe(char menuin[])
+{
+    char buf[128];
+    input_result result;
+
+    ui_input_url("(un)Subscribe presence of", buf, sizeof(buf), &result);
+    if (result.nb_result != NO_NB) {
+	if (result.nb_result == -1) {
+	    int i, count;
+	    count = pjsua_get_buddy_count();
+	    for (i=0; i<count; ++i)
+		pjsua_buddy_subscribe_pres(i, menuin[0]=='s');
+	} else if (result.nb_result == 0) {
+	    puts("Sorry, can only subscribe to buddy's presence, "
+		"not from existing call");
+	} else {
+	    pjsua_buddy_subscribe_pres(result.nb_result-1, (menuin[0]=='s'));
+	}
+
+    } else if (result.uri_result) {
+	puts("Sorry, can only subscribe to buddy's presence, "
+	    "not arbitrary URL (for now)");
+    }
+}
+
+static void ui_register(char menuin[])
+{
+    switch (menuin[1]) {
+    case 'r':
+	/*
+	* Re-Register.
+	*/
+	pjsua_acc_set_registration(current_acc, PJ_TRUE);
+	break;
+    case 'u':
+	/*
+	* Unregister
+	*/
+	pjsua_acc_set_registration(current_acc, PJ_FALSE);
+	break;
+    }
+}
+
+static void ui_toggle_state()
+{
+    pjsua_acc_info acc_info;
+
+    pjsua_acc_get_info(current_acc, &acc_info);
+    acc_info.online_status = !acc_info.online_status;
+    pjsua_acc_set_online_status(current_acc, acc_info.online_status);
+    printf("Setting %s online status to %s\n",
+	   acc_info.acc_uri.ptr,
+	   (acc_info.online_status?"online":"offline"));
+}
+
+/*
+ * Change extended online status.
+ */
+static void ui_change_online_status()
+{
+    char menuin[32];
+    pj_bool_t online_status;
+    pjrpid_element elem;
+    int i, choice;
+
+    enum {
+	AVAILABLE, BUSY, OTP, IDLE, AWAY, BRB, OFFLINE, OPT_MAX
+    };
+
+    struct opt {
+	int id;
+	char *name;
+    } opts[] = {
+	{ AVAILABLE, "Available" },
+	{ BUSY, "Busy"},
+	{ OTP, "On the phone"},
+	{ IDLE, "Idle"},
+	{ AWAY, "Away"},
+	{ BRB, "Be right back"},
+	{ OFFLINE, "Offline"}
+    };
+
+    printf("\n"
+	   "Choices:\n");
+    for (i=0; i<PJ_ARRAY_SIZE(opts); ++i) {
+	printf("  %d  %s\n", opts[i].id+1, opts[i].name);
+    }
+
+    if (!simple_input("Select status", menuin, sizeof(menuin)))
+	return;
+
+    choice = atoi(menuin) - 1;
+    if (choice < 0 || choice >= OPT_MAX) {
+	puts("Invalid selection");
+	return;
+    }
+
+    pj_bzero(&elem, sizeof(elem));
+    elem.type = PJRPID_ELEMENT_TYPE_PERSON;
+
+    online_status = PJ_TRUE;
+
+    switch (choice) {
+    case AVAILABLE:
+	break;
+    case BUSY:
+	elem.activity = PJRPID_ACTIVITY_BUSY;
+	elem.note = pj_str("Busy");
+	break;
+    case OTP:
+	elem.activity = PJRPID_ACTIVITY_BUSY;
+	elem.note = pj_str("On the phone");
+	break;
+    case IDLE:
+	elem.activity = PJRPID_ACTIVITY_UNKNOWN;
+	elem.note = pj_str("Idle");
+	break;
+    case AWAY:
+	elem.activity = PJRPID_ACTIVITY_AWAY;
+	elem.note = pj_str("Away");
+	break;
+    case BRB:
+	elem.activity = PJRPID_ACTIVITY_UNKNOWN;
+	elem.note = pj_str("Be right back");
+	break;
+    case OFFLINE:
+	online_status = PJ_FALSE;
+	break;
+    }
+
+    pjsua_acc_set_online_status2(current_acc, online_status, &elem);
+}
+
+/*
+ * List the ports in conference bridge
+ */
+static void ui_conf_list()
+{
+    unsigned i, count;
+    pjsua_conf_port_id id[PJSUA_MAX_CALLS];
+
+    printf("Conference ports:\n");
+
+    count = PJ_ARRAY_SIZE(id);
+    pjsua_enum_conf_ports(id, &count);
+
+    for (i=0; i<count; ++i) {
+	char txlist[PJSUA_MAX_CALLS*4+10];
+	unsigned j;
+	pjsua_conf_port_info info;
+
+	pjsua_conf_get_port_info(id[i], &info);
+
+	txlist[0] = '\0';
+	for (j=0; j<info.listener_cnt; ++j) {
+	    char s[10];
+	    pj_ansi_snprintf(s, sizeof(s), "#%d ", info.listeners[j]);
+	    pj_ansi_strcat(txlist, s);
+	}
+	printf("Port #%02d[%2dKHz/%dms/%d] %20.*s  transmitting to: %s\n", 
+	       info.slot_id, 
+	       info.clock_rate/1000,
+	       info.samples_per_frame*1000/info.channel_count/info.clock_rate,
+	       info.channel_count,
+	       (int)info.name.slen, 
+	       info.name.ptr,
+	       txlist);
+
+    }
+    puts("");
+}
+
+static void ui_conf_connect(char menuin[])
+{
+    char tmp[10], src_port[10], dst_port[10];
+    pj_status_t status;
+    int cnt;
+    const char *src_title, *dst_title;
+
+    cnt = sscanf(menuin, "%s %s %s", tmp, src_port, dst_port);
+
+    if (cnt != 3) {
+	ui_conf_list();
+
+	src_title = (menuin[1]=='c'? "Connect src port #":
+				     "Disconnect src port #");
+	dst_title = (menuin[1]=='c'? "To dst port #":"From dst port #");
+
+	if (!simple_input(src_title, src_port, sizeof(src_port)))
+	    return;
+
+	if (!simple_input(dst_title, dst_port, sizeof(dst_port)))
+	    return;
+    }
+
+    if (menuin[1]=='c') {
+	status = pjsua_conf_connect(my_atoi(src_port), my_atoi(dst_port));
+    } else {
+	status = pjsua_conf_disconnect(my_atoi(src_port), my_atoi(dst_port));
+    }
+    if (status == PJ_SUCCESS) {
+	puts("Success");
+    } else {
+	puts("ERROR!!");
+    }
+}
+
+static void ui_adjust_volume()
+{
+    char buf[128];
+    char text[128];
+    sprintf(buf, "Adjust mic level: [%4.1fx] ", app_config.mic_level);
+    if (simple_input(buf,text,sizeof(text))) {
+	char *err;
+	app_config.mic_level = (float)strtod(text, &err);
+	pjsua_conf_adjust_rx_level(0, app_config.mic_level);
+    }
+    sprintf(buf, "Adjust speaker level: [%4.1fx] ", app_config.speaker_level);
+    if (simple_input(buf,text,sizeof(text))) {
+	char *err;
+	app_config.speaker_level = (float)strtod(text, &err);
+	pjsua_conf_adjust_tx_level(0, app_config.speaker_level);
+    }
+}
+
+static void ui_dump_call_quality()
+{
+    if (current_call != PJSUA_INVALID_ID) {
+	log_call_dump(current_call);
+    } else {
+	PJ_LOG(3,(THIS_FILE, "No current call"));
+    }
+}
+
+static void ui_dump_configuration()
+{
+    char settings[2000];
+    int len;
+
+    len = write_settings(&app_config, settings, sizeof(settings));
+    if (len < 1)
+	PJ_LOG(1,(THIS_FILE, "Error: not enough buffer"));
+    else
+	PJ_LOG(3,(THIS_FILE, "Dumping configuration (%d bytes):\n%s\n",	
+		  len, settings));	
+}
+
+static void ui_write_settings()
+{
+    char settings[2000];
+    int len;
+    char buf[128];
+
+    len = write_settings(&app_config, settings, sizeof(settings));
+    if (len < 1)
+	PJ_LOG(1,(THIS_FILE, "Error: not enough buffer"));
+    else {
+	pj_oshandle_t fd;
+	pj_status_t status;
+
+	status = pj_file_open(app_config.pool, buf, PJ_O_WRONLY, &fd);
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror(THIS_FILE, "Unable to open file", status);
+	} else {
+	    pj_ssize_t size = len;
+	    pj_file_write(fd, settings, &size);
+	    pj_file_close(fd);
+
+	    printf("Settings successfully written to '%s'\n", buf);
+	}
+    }
+}
+
+/*
+ * Dump application states.
+ */
+static void ui_app_dump(pj_bool_t detail)
+{
+    pjsua_dump(detail);
+}
+
+static void ui_call_redirect(char menuin[])
+{
+    if (current_call == PJSUA_INVALID_ID) {
+	PJ_LOG(3,(THIS_FILE, "No current call"));
+    } else {
+	if (!pjsua_call_is_active(current_call)) {
+	    PJ_LOG(1,(THIS_FILE, "Call %d has gone", current_call));
+	} else if (menuin[1] == 'a') {
+	    pjsua_call_process_redirect(current_call, 
+		PJSIP_REDIRECT_ACCEPT_REPLACE);
+	} else if (menuin[1] == 'A') {
+	    pjsua_call_process_redirect(current_call, 
+		PJSIP_REDIRECT_ACCEPT);
+	} else if (menuin[1] == 'r') {
+	    pjsua_call_process_redirect(current_call,
+		PJSIP_REDIRECT_REJECT);
+	} else {
+	    pjsua_call_process_redirect(current_call,
+		PJSIP_REDIRECT_STOP);
+	}
+    }
+}
+
+/*
+ * Main "user interface" loop.
+ */
+void console_app_main(const pj_str_t *uri_to_call, pj_bool_t *app_restart)
+{    
+    char menuin[80];    
+    char buf[128];    
+
+    pjsua_call_setting_default(&call_opt);
+    call_opt.aud_cnt = app_config.aud_cnt;
+    call_opt.vid_cnt = app_config.vid.vid_cnt;
+
+    /* If user specifies URI to call, then call the URI */
+    if (uri_to_call->slen) {
+	pjsua_call_make_call( current_acc, uri_to_call, &call_opt, 
+			      NULL, NULL, NULL);
+    }
+
+    keystroke_help(current_call);
+
+    for (;;) {
+
+	printf(">>> ");
+	fflush(stdout);
+
+	if (fgets(menuin, sizeof(menuin), stdin) == NULL) {
+	    /* 
+	     * Be friendly to users who redirect commands into
+	     * program, when file ends, resume with kbd.
+	     * If exit is desired end script with q for quit
+	     */
+ 	    /* Reopen stdin/stdout/stderr to /dev/console */
+#if defined(PJ_WIN32) && PJ_WIN32!=0
+	    if (freopen ("CONIN$", "r", stdin) == NULL) {
+#else
+	    if (1) {
+#endif
+		puts("Cannot switch back to console from file redirection");
+		menuin[0] = 'q';
+		menuin[1] = '\0';
+	    } else {
+		puts("Switched back to console from file redirection");
+		continue;
+	    }
+	}
+
+	if (cmd_echo) {
+	    printf("%s", menuin);
+	}
+
+	/* Update call setting */
+	pjsua_call_setting_default(&call_opt);
+	call_opt.aud_cnt = app_config.aud_cnt;
+	call_opt.vid_cnt = app_config.vid.vid_cnt;
+
+	switch (menuin[0]) {
+
+	case 'm':
+	    /* Make call! : */
+	    ui_make_new_call();
+	    break;
+
+	case 'M':
+	    /* Make multiple calls! : */
+	    ui_make_multi_call();
+	    break;
+
+	case 'n':
+	    ui_detect_nat_type();
+	    break;
+
+	case 'i':
+	    /* Send instant messaeg */
+	    ui_send_instant_message();
+	    break;
+
+	case 'a':
+	    ui_answer_call();
+	    break;
+
+	case 'h':
+	    ui_hangup_call(menuin);
+	    break;
+
+	case ']':
+	case '[':
+	    /*
+	     * Cycle next/prev dialog.
+	     */
+	    ui_cycle_dialog(menuin);
+	    break;
+
+	case '>':
+	case '<':
+	    ui_cycle_account();
+	    break;
+
+	case '+':
+	    if (menuin[1] == 'b') {
+		ui_add_buddy();
+	    } else if (menuin[1] == 'a') {
+		ui_add_account(&app_config.rtp_cfg);
+	    } else {
+		printf("Invalid input %s\n", menuin);
+	    }
+	    break;
+
+	case '-':
+	    if (menuin[1] == 'b') {
+		ui_delete_buddy();
+	    } else if (menuin[1] == 'a') {
+		ui_delete_account();
+	    } else {
+		printf("Invalid input %s\n", menuin);
+	    }
+	    break;
+
+	case 'H':
+	    /*
+	     * Hold call.
+	     */
+	    ui_call_hold();
+	    break;
+
+	case 'v':	    
+#if PJSUA_HAS_VIDEO
+	    if (menuin[1]=='i' && menuin[2]=='d' && menuin[3]==' ') {
+		vid_handle_menu(menuin);
+	    } else
+#endif
+	    if (current_call != -1) {
+		/*
+		 * re-INVITE
+		 */
+		ui_call_reinvite();
+	    } else {
+		PJ_LOG(3,(THIS_FILE, "No current call"));
+	    }
+	    break;
+
+	case 'U':
+	    /*
+	     * Send UPDATE
+	     */
+	    ui_send_update();
+	    break;
+
+	case 'C':
+	    if (menuin[1] == 'p') {
+		ui_manage_codec_prio();
+	    }
+	    break;
+
+	case 'x':
+	    /*
+	     * Transfer call.
+	     */
+	    ui_call_transfer(app_config.no_refersub);
+	    break;
+
+	case 'X':
+	    /*
+	     * Transfer call with replaces.
+	     */
+	    ui_call_transfer_replaces(app_config.no_refersub);
+	    break;
+
+	case '#':
+	    /*
+	     * Send DTMF strings.
+	     */
+	    ui_send_dtmf_2833();
+	    break;
+
+	case '*':
+	    /* Send DTMF with INFO */
+	    ui_send_dtmf_info();
+	    break;
+
+	case 'S':
+	    /*
+	     * Send arbitrary request
+	     */
+	    ui_send_arbitrary_request();
+	    break;
+
+	case 'e':
+	    ui_echo(menuin);
+	    break;
+
+	case 's':	    
+	    ui_sleep(menuin);
+	    break;
+	    /* Continue below */
+
+	case 'u':
+	    /*
+	     * Subscribe/unsubscribe presence.
+	     */
+	    ui_subscribe(menuin);
+	    break;
+
+	case 'r':
+	    ui_register(menuin);
+	    break;
+	    
+	case 't':
+	    ui_toggle_state();
+	    break;
+
+	case 'T':
+	    ui_change_online_status();
+	    break;
+
+	case 'c':
+	    switch (menuin[1]) {
+	    case 'l':
+		ui_conf_list();
+		break;
+	    case 'c':
+	    case 'd':
+		ui_conf_connect(menuin);
+		break;
+	    }
+	    break;
+
+	case 'V':
+	    /* Adjust audio volume */	    
+	    ui_adjust_volume();
+	    break;
+
+	case 'd':
+	    if (menuin[1] == 'c') {
+		ui_dump_configuration();	
+	    } else if (menuin[1] == 'q') {
+		ui_dump_call_quality();
+	    } else {		
+		ui_app_dump(menuin[1]=='d');
+	    }
+	    break;
+
+	case 'f':
+	    if (simple_input("Enter output filename", buf, sizeof(buf))) {
+		ui_write_settings();		
+	    }
+	    break;
+
+	case 'L':   /* Restart */
+	    *app_restart = PJ_TRUE;
+	    /* Continues below */
+
+	case 'q':
+	    goto on_exit;
+
+	case 'R':	    
+	    ui_call_redirect(menuin);
+	    break;
+
+	default:
+	    if (menuin[0] != '\n' && menuin[0] != '\r') {
+		printf("Invalid input %s", menuin);
+	    }
+	    keystroke_help();
+	    break;
+	}
+    }
+
+on_exit:
+    ;
+}
Index: /pjproject/trunk/pjsip-apps/src/samples/clidemo.c
===================================================================
--- /pjproject/trunk/pjsip-apps/src/samples/clidemo.c	(revision 4440)
+++ /pjproject/trunk/pjsip-apps/src/samples/clidemo.c	(revision 4440)
@@ -0,0 +1,262 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2010 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ * Sample CLI application
+ */
+#include <pjlib-util/cli.h>
+#include <pjlib-util/cli_imp.h>
+#include <pjlib-util/cli_console.h>
+#include <pjlib-util/cli_telnet.h>
+#include <pjlib-util/errno.h>
+#include <pjlib.h>
+
+#define THIS_FILE	"clidemo.c"
+
+/* Set this to 1 if you want to let the system assign a port
+ * for the CLI telnet daemon.
+ * Default: 1
+ */
+#define USE_RANDOM_PORT 1
+
+struct cmd_xml_t {
+    char * xml;
+    pj_cli_cmd_handler handler;
+};
+
+/*
+ * Declaration of system specific main loop, which will be defined in
+ * a separate file.
+ */
+pj_status_t app_main(pj_cli_t *cli);
+
+#define print_msg(arg) \
+    do { \
+        unsigned d = pj_log_get_decor(); \
+        pj_log_set_decor(0); \
+        PJ_LOG(1, arg); \
+        pj_log_set_decor(d); \
+    } while (0)
+
+static pj_cli_t *cli = NULL;
+
+/* Handler for sayhello command */
+static pj_status_t sayhello(pj_cli_cmd_val *cval)
+{
+    print_msg(("", "Hello %.*s!\r\n", 
+              (int)cval->argv[1].slen, cval->argv[1].ptr));
+    return PJ_SUCCESS;
+}
+
+/* Handler for saybye command */
+static pj_status_t saybye(pj_cli_cmd_val *cval)
+{
+    print_msg(("", "Bye %.*s!\r\n",
+              (int)cval->argv[1].slen, cval->argv[1].ptr));
+    return PJ_SUCCESS;
+}
+
+/* Handler for say command */
+static pj_status_t say(pj_cli_cmd_val *cval)
+{
+    print_msg(("", "%.*s %.*s\r\n",
+              (int)cval->argv[1].slen, cval->argv[1].ptr,
+              (int)cval->argv[2].slen, cval->argv[2].ptr));
+    return PJ_SUCCESS;
+}
+
+static pj_status_t quit_app(pj_cli_cmd_val *cval)
+{
+    PJ_UNUSED_ARG(cval);
+    pj_cli_quit(cval->sess->fe->cli, cval->sess, PJ_FALSE);
+
+    return PJ_CLI_EEXIT;
+}
+
+static void get_codec_list(pj_cli_dyn_choice_param *param)
+{
+    if (param->arg_id == 3) {
+	param->cnt = 2;
+	pj_strdup2(param->pool, &param->choice[0].value, "iLbc");
+	pj_strdup2(param->pool, &param->choice[0].desc, "iLbc Codec");
+	pj_strdup2(param->pool, &param->choice[1].value, "g729");
+	pj_strdup2(param->pool, &param->choice[1].desc, "g729 Codec");
+    }
+}
+
+static struct cmd_xml_t cmd_xmls[] = {
+    {"<CMD name='sayhello' id='1' sc='  ,h , ,, sh  ,' desc='Will say hello'>"
+     "    <ARG name='whom' type='text' desc='Whom to say hello to'/>"
+     "</CMD>",
+     &sayhello},
+    {"<CMD name='saybye' id='2' sc='b,sb' desc='Will say bye'>"
+     "    <ARG name='whom' type='text' desc='Whom to say bye to'/>"
+     "</CMD>",
+     &saybye},
+    {"<CMD name='saymsg' id='3' sc='s' desc='Will say something'>"
+     "    <ARG name='msg' type='text' desc='Message to say'/>"
+     "    <ARG name='whom' type='text' desc='Whom to say to'/>"
+     "</CMD>",
+     &say},
+    {"<CMD name='vid' id='1' desc='Video Command'>"
+     "   <CMD name='help' id='2' desc='Show Help' />"
+     "   <CMD name='enable' id='3' desc='Enable Video' />"
+     "   <CMD name='disable' id='4' desc='Disable Video' />"
+     "   <CMD name='call' id='5' desc='Video call' >"
+     "            <CMD name='add' id='6' desc='Add Call' />"
+     "            <CMD name='cap' id='7' desc='Capture Call' >"
+     "               <ARG name='streamno' type='int' desc='Stream No' id='1'/>"
+     "               <ARG name='devid' type='int' desc='Device Id' id='2'/>"
+     "            </CMD>"
+     "   </CMD>"     
+     "</CMD>",
+     NULL},
+    {"<CMD name='disable_codec' id='8' desc='Disable codec'>"
+     "	<ARG name='codec_list' type='choice' id='3' desc='Codec list'>"
+     "	    <CHOICE value='g711' desc='G711 Codec'/>"
+     "	    <CHOICE value='g722' desc='G722 Codec'/>"
+     "	</ARG>"
+     "</CMD>",
+     NULL},
+    {"<CMD name='quit_app' id='999' sc='qa' desc='Quit the application'>"
+     "</CMD>",
+     &quit_app},
+};
+
+static void log_writer(int level, const char *buffer, int len)
+{
+    if (cli)
+        pj_cli_write_log(cli, level, buffer, len);
+}
+
+int main()
+{
+    pj_caching_pool cp;
+    pj_cli_cfg cli_cfg;
+    pj_cli_telnet_cfg tcfg;
+    pj_str_t xml;
+    pj_status_t status;
+    int i;        
+
+    pj_init();
+    pj_caching_pool_init(&cp, NULL, 0);
+    pjlib_util_init();
+
+    /*
+     * Create CLI app.
+     */
+    pj_cli_cfg_default(&cli_cfg);
+    cli_cfg.pf = &cp.factory;
+    cli_cfg.name = pj_str("mycliapp");
+    cli_cfg.title = pj_str("My CLI Application");
+
+    status = pj_cli_create(&cli_cfg, &cli);
+    if (status != PJ_SUCCESS)
+	goto on_return;
+
+    /*
+     * Register some commands.
+     */
+    for (i = 0; i < sizeof(cmd_xmls)/sizeof(cmd_xmls[0]); i++) {
+        xml = pj_str(cmd_xmls[i].xml);	
+        status = pj_cli_add_cmd_from_xml(cli, NULL, &xml, 
+	    cmd_xmls[i].handler, NULL, get_codec_list);
+        if (status != PJ_SUCCESS)
+	    goto on_return;
+    }
+
+    /*
+     * Start telnet daemon
+     */
+    pj_cli_telnet_cfg_default(&tcfg);
+//    tcfg.passwd = pj_str("pjsip");
+#if USE_RANDOM_PORT
+    tcfg.port = 0;
+#else
+    tcfg.port = 2233;
+#endif    
+    tcfg.prompt_str = pj_str("CoolWater% ");
+    status = pj_cli_telnet_create(cli, &tcfg, NULL);
+    if (status != PJ_SUCCESS)
+	goto on_return;
+
+    /*
+     * Run the system specific main loop.
+     */
+    status = app_main(cli);
+
+on_return:
+
+    /*
+     * Destroy
+     */
+    pj_cli_destroy(cli);
+    cli = NULL;
+    pj_caching_pool_destroy(&cp);
+    pj_shutdown();
+
+    return (status != PJ_SUCCESS ? 1 : 0);
+}
+
+
+/*xxxxxxxxxxxxxxxxxxxxxxxxxxxxx main_console.c xxxxxxxxxxxxxxxxxxxxxxxxxxxx */
+/*
+ * Simple implementation of app_main() for console targets
+ */
+pj_status_t app_main(pj_cli_t *cli)
+{
+    pj_status_t status;
+    pj_cli_sess *sess;
+    pj_cli_console_cfg console_cfg;
+
+    pj_cli_console_cfg_default(&console_cfg);
+    console_cfg.prompt_str = pj_str("HotWater> ");
+    
+    /*
+     * Create the console front end
+     */
+    status = pj_cli_console_create(cli, &console_cfg, &sess, NULL);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    pj_log_set_log_func(&log_writer);
+
+    /*
+     * Main loop.
+     */
+    for (;;) {
+	char cmdline[PJ_CLI_MAX_CMDBUF];
+        pj_status_t status;
+
+        status = pj_cli_console_process(sess, &cmdline[0], sizeof(cmdline));
+	if (status != PJ_SUCCESS)
+	    break;
+
+	//pj_ansi_strcpy(cmdline, "sayhello {Teluu Inc.}");	
+	if (status == PJ_CLI_EEXIT) {
+	    /* exit is called */
+	    break;
+	} else if (status != PJ_SUCCESS) {
+	    /* Something wrong with the cmdline */
+	    PJ_PERROR(1,(THIS_FILE, status, "Exec error"));
+	}
+    }
+
+    return PJ_SUCCESS;
+}
