mirror of
https://github.com/baresip/baresip
synced 2026-08-07 00:26:06 -04:00
If the calling UA receives 183 Session Progress with SDP the remote media direction has to be taken into account to limit the number of early media streams. The local media direction normally is always send-recv and thus doesn't matter.
1380 lines
27 KiB
C
1380 lines
27 KiB
C
/**
|
|
* @file menu.c Interactive menu
|
|
*
|
|
* Copyright (C) 2010 Alfred E. Heggestad
|
|
*/
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
#include <string.h>
|
|
#include <re.h>
|
|
#include <baresip.h>
|
|
#include "menu.h"
|
|
|
|
|
|
/**
|
|
* @defgroup menu menu
|
|
*
|
|
* Interactive menu
|
|
*
|
|
* This module must be loaded if you want to use the interactive menu
|
|
* to control the Baresip application.
|
|
*/
|
|
static struct menu menu;
|
|
|
|
enum {
|
|
MIN_RINGTIME = 1000,
|
|
TONE_DELAY = 20, /* Delay for starting tone in [ms] */
|
|
};
|
|
|
|
|
|
struct filter_arg {
|
|
enum call_state state;
|
|
const struct call *exclude;
|
|
const struct call *match;
|
|
struct call *call;
|
|
};
|
|
|
|
|
|
static void menu_sel_other(struct call *exclude);
|
|
static bool check_delayed_answer(struct call *call);
|
|
|
|
static int menu_set_incall(bool incall)
|
|
{
|
|
int err = 0;
|
|
|
|
/* Dynamic menus */
|
|
if (incall) {
|
|
dial_menu_unregister();
|
|
err = dynamic_menu_register();
|
|
}
|
|
else {
|
|
dynamic_menu_unregister();
|
|
err = dial_menu_register();
|
|
}
|
|
if (err) {
|
|
warning("menu: set_incall: cmd_register failed (%m)\n", err);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
static void tmrstat_handler(void *arg)
|
|
{
|
|
(void)arg;
|
|
|
|
/* the UI will only show the current current call */
|
|
if (!menu.curcall)
|
|
return;
|
|
|
|
tmr_start(&menu.tmr_stat, 100, tmrstat_handler, 0);
|
|
|
|
if (ui_isediting(baresip_uis()))
|
|
return;
|
|
|
|
if (STATMODE_OFF != menu.statmode) {
|
|
(void)re_fprintf(stderr, "%H\r", call_status, menu.curcall);
|
|
}
|
|
}
|
|
|
|
|
|
void menu_update_callstatus(bool incall)
|
|
{
|
|
/* if there are any active calls, enable the call status view */
|
|
if (incall && menu_callcur())
|
|
tmr_start(&menu.tmr_stat, 100, tmrstat_handler, 0);
|
|
else
|
|
tmr_cancel(&menu.tmr_stat);
|
|
}
|
|
|
|
|
|
static void redial_reset(void)
|
|
{
|
|
tmr_cancel(&menu.tmr_redial);
|
|
menu.current_attempts = 0;
|
|
}
|
|
|
|
|
|
static char *errorcode_fb_aufile(uint16_t scode)
|
|
{
|
|
switch (scode) {
|
|
|
|
case 404: return "notfound.wav";
|
|
case 486:
|
|
case 603: return "busy.wav";
|
|
case 487: return NULL; /* ignore */
|
|
default: return "error.wav";
|
|
}
|
|
}
|
|
|
|
|
|
static char *errorcode_key_aufile(uint16_t scode)
|
|
{
|
|
switch (scode) {
|
|
|
|
case 404: return "notfound_aufile";
|
|
case 486:
|
|
case 603:
|
|
return "busy_aufile";
|
|
case 487: return NULL; /* ignore */
|
|
default: return "error_aufile";
|
|
}
|
|
}
|
|
|
|
|
|
static void limit_earlymedia(struct call* call, void *arg)
|
|
{
|
|
enum sdp_dir rdir, ndir;
|
|
uint32_t maxcnt = 32;
|
|
bool update = false;
|
|
(void)arg;
|
|
|
|
if (!call_is_outgoing(call))
|
|
return;
|
|
|
|
rdir = sdp_media_rdir(stream_sdpmedia(audio_strm(call_audio(call))));
|
|
ndir = rdir;
|
|
conf_get_u32(conf_cur(), "menu_max_earlyaudio", &maxcnt);
|
|
|
|
if (menu.outcnt > maxcnt)
|
|
ndir = SDP_INACTIVE;
|
|
else if (menu.outcnt > 1)
|
|
ndir &= SDP_SENDONLY;
|
|
|
|
if (ndir != rdir) {
|
|
call_set_audio_ldir(call, ndir);
|
|
update = true;
|
|
}
|
|
|
|
if (!call_video(call)) {
|
|
if (update)
|
|
call_update_media(call);
|
|
|
|
return;
|
|
}
|
|
|
|
/* video */
|
|
rdir = sdp_media_rdir(stream_sdpmedia(video_strm(call_video(call))));
|
|
ndir = rdir;
|
|
|
|
maxcnt = 32;
|
|
conf_get_u32(conf_cur(), "menu_max_earlyvideo_rx", &maxcnt);
|
|
if (menu.outcnt > maxcnt)
|
|
ndir &= SDP_SENDONLY;
|
|
|
|
maxcnt = 32;
|
|
conf_get_u32(conf_cur(), "menu_max_earlyvideo_tx", &maxcnt);
|
|
if (menu.outcnt > maxcnt)
|
|
ndir &= SDP_RECVONLY;
|
|
|
|
if (ndir != rdir) {
|
|
call_set_video_ldir(call, ndir);
|
|
update = true;
|
|
}
|
|
|
|
if (update)
|
|
call_update_media(call);
|
|
}
|
|
|
|
|
|
static bool active_call_test(const struct call* call, void *arg)
|
|
{
|
|
struct filter_arg *fa = arg;
|
|
|
|
if (call == fa->exclude)
|
|
return false;
|
|
|
|
return call_state(call) == CALL_STATE_ESTABLISHED &&
|
|
!call_is_onhold(call);
|
|
}
|
|
|
|
|
|
static bool established_call_test(const struct call* call, void *arg)
|
|
{
|
|
struct filter_arg *fa = arg;
|
|
|
|
if (call == fa->exclude)
|
|
return false;
|
|
|
|
return call_state(call) == CALL_STATE_ESTABLISHED;
|
|
}
|
|
|
|
|
|
static bool outgoing_call_test(const struct call* call, void *arg)
|
|
{
|
|
struct filter_arg *fa = arg;
|
|
enum call_state st;
|
|
|
|
if (call == fa->exclude)
|
|
return false;
|
|
|
|
st = call_state(call);
|
|
return st == CALL_STATE_OUTGOING || st == CALL_STATE_RINGING ||
|
|
st == CALL_STATE_EARLY;
|
|
}
|
|
|
|
|
|
static void find_first_call(struct call *call, void *arg)
|
|
{
|
|
struct filter_arg *fa = arg;
|
|
|
|
if (!fa->call)
|
|
fa->call = call;
|
|
}
|
|
|
|
|
|
struct call *menu_find_call_state(enum call_state st)
|
|
{
|
|
struct le *le;
|
|
|
|
for (le = list_head(uag_list()); le; le = le->next) {
|
|
struct ua *ua = le->data;
|
|
struct call *call = ua_find_call_state(ua, st);
|
|
|
|
if (call)
|
|
return call;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
* Search all User-Agents for a call that matches
|
|
*
|
|
* @param matchh Optional match handler. If NULL, the last call of the first
|
|
* User-Agent is returned
|
|
* @param exclude Call to exclude
|
|
*
|
|
* @return A call that matches
|
|
*/
|
|
struct call *menu_find_call(call_match_h *matchh, const struct call *exclude)
|
|
{
|
|
struct filter_arg fa = {CALL_STATE_UNKNOWN, exclude, NULL, NULL};
|
|
|
|
uag_filter_calls(find_first_call, matchh, &fa);
|
|
return fa.call;
|
|
}
|
|
|
|
|
|
static void menu_stop_play(void)
|
|
{
|
|
menu.play = mem_deref(menu.play);
|
|
menu.ringback = false;
|
|
tmr_cancel(&menu.tmr_play);
|
|
}
|
|
|
|
|
|
static int menu_ovkey(char **key, const struct call *call, struct pl *suffix)
|
|
{
|
|
int err;
|
|
struct mbuf *mb = mbuf_alloc(32);
|
|
if (!mb)
|
|
return ENOMEM;
|
|
|
|
err = mbuf_printf(mb, "%s-%r", call_id(call), suffix);
|
|
if (err)
|
|
goto out;
|
|
|
|
mb->pos = 0;
|
|
err = mbuf_strdup(mb, key, mb->end);
|
|
out:
|
|
mem_deref(mb);
|
|
return err;
|
|
}
|
|
|
|
|
|
static int menu_ovkey_str(char **key, const struct call *call,
|
|
const char *suffix)
|
|
{
|
|
struct pl pl;
|
|
pl_set_str(&pl, suffix);
|
|
return menu_ovkey(key, call, &pl);
|
|
}
|
|
|
|
enum Device {
|
|
DEVICE_ALERT,
|
|
DEVICE_PLAYER
|
|
};
|
|
|
|
static bool menu_play(const struct call *call,
|
|
const char *ckey,
|
|
const char *fname,
|
|
int repeat,
|
|
enum Device device)
|
|
{
|
|
struct config *cfg = conf_config();
|
|
struct player *player = baresip_player();
|
|
char *ovkey;
|
|
const char *ovaukey = NULL;
|
|
struct pl pl = PL_INIT;
|
|
char *file = NULL;
|
|
int err;
|
|
const char *play_mod = cfg->audio.alert_mod;
|
|
const char *play_dev = cfg->audio.alert_dev;
|
|
|
|
if (device == DEVICE_PLAYER) {
|
|
play_mod = cfg->audio.play_mod;
|
|
play_dev = cfg->audio.play_dev;
|
|
}
|
|
|
|
if (ckey) {
|
|
err = menu_ovkey_str(&ovkey, call, ckey);
|
|
if (!err) {
|
|
ovaukey = odict_string(menu.ovaufile, ovkey);
|
|
mem_deref(ovkey);
|
|
}
|
|
|
|
if (ovaukey && !strcmp(ovaukey, "none"))
|
|
return false;
|
|
|
|
if (ovaukey)
|
|
(void)conf_get(conf_cur(), ovaukey, &pl);
|
|
|
|
if (!pl_isset(&pl))
|
|
(void)conf_get(conf_cur(), ckey, &pl);
|
|
}
|
|
|
|
if (!pl_isset(&pl))
|
|
pl_set_str(&pl, fname);
|
|
|
|
if (!pl_isset(&pl) || !pl_strcmp(&pl, "none"))
|
|
return false;
|
|
|
|
pl_strdup(&file, &pl);
|
|
menu_stop_play();
|
|
err = play_file(&menu.play, player, file, repeat,
|
|
play_mod,
|
|
play_dev);
|
|
mem_deref(file);
|
|
|
|
return err == 0;
|
|
}
|
|
|
|
|
|
static void play_incoming(const struct call *call)
|
|
{
|
|
|
|
if (call_state(call) != CALL_STATE_INCOMING)
|
|
return;
|
|
|
|
if (menu_find_call(active_call_test, call)) {
|
|
menu_play(call, "callwaiting_aufile", "callwaiting.wav", 3,
|
|
DEVICE_PLAYER);
|
|
}
|
|
else if (menu.curcall == call) {
|
|
/* Alert user */
|
|
menu_play(call, "ring_aufile", "ring.wav", -1, DEVICE_ALERT);
|
|
}
|
|
}
|
|
|
|
|
|
static void play_ringback(const struct call *call)
|
|
{
|
|
/* stop any ringtones */
|
|
menu_stop_play();
|
|
|
|
if (menu.ringback_disabled) {
|
|
info("menu: ringback disabled\n");
|
|
}
|
|
else {
|
|
menu_play(call, "ringback_aufile", "ringback.wav", -1,
|
|
DEVICE_PLAYER);
|
|
menu.ringback = true;
|
|
}
|
|
}
|
|
|
|
|
|
static void check_ringback(struct call *call)
|
|
{
|
|
enum sdp_dir adir = sdp_media_dir(stream_sdpmedia(
|
|
audio_strm(call_audio(call))));
|
|
bool ring = !(adir & SDP_RECVONLY);
|
|
|
|
if (ring && !menu.ringback &&
|
|
!menu_find_call(active_call_test, NULL)) {
|
|
play_ringback(call);
|
|
}
|
|
else if (!ring) {
|
|
menu_stop_play();
|
|
}
|
|
}
|
|
|
|
|
|
static void delayed_play(void *arg)
|
|
{
|
|
struct call *call = menu_callcur();
|
|
(void) arg;
|
|
|
|
switch (call_state(call)) {
|
|
case CALL_STATE_INCOMING:
|
|
if (!check_delayed_answer(call))
|
|
play_incoming(call);
|
|
break;
|
|
case CALL_STATE_RINGING:
|
|
case CALL_STATE_EARLY:
|
|
check_ringback(call);
|
|
break;
|
|
default:
|
|
menu_stop_play();
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static void check_registrations(void)
|
|
{
|
|
static bool ual_ready = false;
|
|
struct le *le;
|
|
uint32_t n;
|
|
|
|
if (ual_ready)
|
|
return;
|
|
|
|
for (le = list_head(uag_list()); le; le = le->next) {
|
|
struct ua *ua = le->data;
|
|
|
|
if (!ua_isregistered(ua) && !account_prio(ua_account(ua)))
|
|
return;
|
|
}
|
|
|
|
n = list_count(uag_list());
|
|
|
|
/* We are ready */
|
|
ui_output(baresip_uis(),
|
|
"\x1b[32mAll %u useragent%s registered successfully!"
|
|
" (%u ms)\x1b[;m\n",
|
|
n, n==1 ? "" : "s",
|
|
(uint32_t)(tmr_jiffies() - menu.start_ticks));
|
|
|
|
ual_ready = true;
|
|
}
|
|
|
|
|
|
static void redial_handler(void *arg)
|
|
{
|
|
char *uri = NULL;
|
|
int err;
|
|
(void)arg;
|
|
|
|
info("menu: redialing now. current_attempts=%u, max_attempts=%u\n",
|
|
menu.current_attempts,
|
|
menu.redial_attempts);
|
|
|
|
if (menu.current_attempts > menu.redial_attempts) {
|
|
|
|
info("menu: redial: too many attempts -- giving up\n");
|
|
return;
|
|
}
|
|
|
|
if (menu.dialbuf->end == 0) {
|
|
warning("menu: redial: dialbuf is empty\n");
|
|
return;
|
|
}
|
|
|
|
menu.dialbuf->pos = 0;
|
|
err = mbuf_strdup(menu.dialbuf, &uri, menu.dialbuf->end);
|
|
if (err)
|
|
return;
|
|
|
|
err = ua_connect(uag_find_aor(menu.redial_aor), NULL, NULL,
|
|
uri, VIDMODE_ON);
|
|
if (err) {
|
|
warning("menu: redial: ua_connect failed (%m)\n", err);
|
|
}
|
|
|
|
mem_deref(uri);
|
|
}
|
|
|
|
|
|
static void invite_handler(void *arg)
|
|
{
|
|
(void)arg;
|
|
|
|
const char *uri = menu.invite_uri;
|
|
|
|
if (!str_isset(uri))
|
|
return;
|
|
|
|
int err;
|
|
err = ua_connect(uag_find_requri(uri), NULL, NULL, uri, VIDMODE_ON);
|
|
if (err)
|
|
warning("menu: call to %s failed (%m)\n", menu.invite_uri,
|
|
err);
|
|
|
|
menu.invite_uri = mem_deref(menu.invite_uri);
|
|
}
|
|
|
|
|
|
static void menu_invite(const char *prm)
|
|
{
|
|
menu.invite_uri = mem_deref(menu.invite_uri);
|
|
int err = str_dup(&menu.invite_uri, prm);
|
|
if (err) {
|
|
warning("menu: call to %s failed (%m)\n", prm, err);
|
|
return;
|
|
}
|
|
|
|
tmr_start(&menu.tmr_invite, 0, invite_handler, NULL);
|
|
}
|
|
|
|
|
|
static int menu_autoanwser_call(struct call *call)
|
|
{
|
|
struct call *outgoing;
|
|
bool single = conf_config()->call.hold_other_calls;
|
|
if (single && menu_find_call(established_call_test, call))
|
|
return EINVAL;
|
|
|
|
outgoing = menu_find_call(outgoing_call_test, call);
|
|
if (outgoing) {
|
|
call_hangup(outgoing, 0, NULL);
|
|
bevent_call_emit(BEVENT_CALL_CLOSED, outgoing,
|
|
"Outgoing call cancelled due to auto answer");
|
|
mem_deref(outgoing);
|
|
}
|
|
|
|
call_start_answtmr(call, 0);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void menu_play_closed(struct call *call)
|
|
{
|
|
uint16_t scode;
|
|
const char *key;
|
|
const char *fb;
|
|
|
|
/* stop any ringtones */
|
|
menu_stop_play();
|
|
|
|
if (call_scode(call)) {
|
|
scode = call_scode(call);
|
|
key = errorcode_key_aufile(scode);
|
|
fb = errorcode_fb_aufile(scode);
|
|
|
|
menu_play(call, key, fb, 1, DEVICE_ALERT);
|
|
}
|
|
else {
|
|
menu_play(call, "hangup_aufile", "none", 0, DEVICE_PLAYER);
|
|
}
|
|
}
|
|
|
|
|
|
static void auans_play_finished(struct play *play, void *arg)
|
|
{
|
|
struct call *call = menu_callcur();
|
|
(void)arg;
|
|
(void)play;
|
|
|
|
/* the first incoming call stays the current */
|
|
if (call_state(call) != CALL_STATE_INCOMING)
|
|
return;
|
|
|
|
menu_autoanwser_call(call);
|
|
}
|
|
|
|
|
|
static bool alert_uri_supported(const char *uri)
|
|
{
|
|
if (!uri)
|
|
return false;
|
|
|
|
if (!re_regex(uri, strlen(uri), "https://"))
|
|
return true;
|
|
|
|
if (!re_regex(uri, strlen(uri), "http://"))
|
|
return true;
|
|
|
|
if (!re_regex(uri, strlen(uri), "file://") && fs_isfile(uri + 7))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
static void auto_answer_handler(void *arg)
|
|
{
|
|
struct call *call = menu_callcur();
|
|
(void)arg;
|
|
|
|
/* the first incoming call stays the current */
|
|
if (call_state(call) != CALL_STATE_INCOMING)
|
|
return;
|
|
|
|
struct account *acc = call_account(call);
|
|
const char *aluri = call_alerturi(call);
|
|
enum sipansbeep bmet = account_sipansbeep(acc);
|
|
bool beep = false;
|
|
|
|
if (bmet) {
|
|
if (bmet != SIPANSBEEP_LOCAL && aluri &&
|
|
alert_uri_supported(aluri)) {
|
|
beep = menu_play(call, NULL, aluri, 1, DEVICE_ALERT);
|
|
}
|
|
|
|
if (!beep) {
|
|
beep = menu_play(call, "sip_autoanswer_aufile",
|
|
"autoanswer.wav", 1, DEVICE_ALERT);
|
|
}
|
|
}
|
|
|
|
if (beep)
|
|
play_set_finish_handler(menu.play, auans_play_finished, NULL);
|
|
else
|
|
menu_autoanwser_call(call);
|
|
}
|
|
|
|
|
|
static void start_autoanswer(struct call *call)
|
|
{
|
|
int32_t adelay = call_answer_delay(call);
|
|
if (adelay <= -1)
|
|
return;
|
|
|
|
if (adelay >= MIN_RINGTIME)
|
|
play_incoming(call);
|
|
|
|
tmr_start(&menu.tmr_answ, adelay, auto_answer_handler, NULL);
|
|
}
|
|
|
|
|
|
static bool check_delayed_answer(struct call *call)
|
|
{
|
|
if (call != menu.curcall)
|
|
return false;
|
|
|
|
struct account *acc = call_account(call);
|
|
int32_t adelay = -1;
|
|
|
|
if (account_sip_autoanswer(acc))
|
|
adelay = call_answer_delay(call);
|
|
|
|
if (account_answerdelay(acc)) {
|
|
int32_t accdelay = account_answerdelay(acc);
|
|
|
|
if (accdelay >= 0 && (adelay < 0 || accdelay < adelay)) {
|
|
adelay = accdelay;
|
|
call_set_answer_delay(call, adelay);
|
|
}
|
|
}
|
|
|
|
if (adelay >= 0) {
|
|
start_autoanswer(call);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
static bool ovaufile_del(struct le *le, void *arg)
|
|
{
|
|
struct odict_entry *oe = le->data;
|
|
struct call *call = arg;
|
|
const char *id = call_id(call);
|
|
|
|
if (!strncmp(odict_entry_key(oe), id, str_len(id)))
|
|
mem_deref(oe);
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
static void process_module_event(struct call *call, const char *prm)
|
|
{
|
|
int err;
|
|
struct pl module, event, data;
|
|
struct pl from, to;
|
|
char *ovkey;
|
|
|
|
err = re_regex(prm, strlen(prm), "[^,]*,[^,]*,[~]*",
|
|
&module, &event, &data);
|
|
if (err)
|
|
return;
|
|
|
|
if (!pl_strcmp(&event, "override-aufile")) {
|
|
err = re_regex(data.p, data.l, "[^:]*:[~]*", &from, &to);
|
|
if (err)
|
|
return;
|
|
|
|
err = menu_ovkey(&ovkey, call, &from);
|
|
if (err)
|
|
return;
|
|
|
|
odict_entry_del(menu.ovaufile, ovkey);
|
|
odict_entry_add(menu.ovaufile, ovkey, ODICT_STRING, to.p);
|
|
mem_deref(ovkey);
|
|
}
|
|
}
|
|
|
|
|
|
static void apply_contact_mediadir(struct call *call)
|
|
{
|
|
const struct contacts *contacts = baresip_contacts();
|
|
struct contact *con = contact_find_call(contacts, call);
|
|
if (!con)
|
|
return;
|
|
|
|
enum sdp_dir caudir = SDP_SENDRECV;
|
|
enum sdp_dir cviddir = SDP_SENDRECV;
|
|
contact_get_ldir(con, &caudir, &cviddir);
|
|
|
|
enum sdp_dir estaudir = SDP_SENDRECV;
|
|
enum sdp_dir estviddir = SDP_SENDRECV;
|
|
call_get_media_estdir(call, &estaudir, &estviddir);
|
|
|
|
enum sdp_dir audir = estaudir & caudir;
|
|
enum sdp_dir viddir = estviddir & cviddir;
|
|
if (audir == estaudir && viddir == estviddir)
|
|
return;
|
|
|
|
debug("menu: apply contact media direction audio=%s video=%s\n",
|
|
sdp_dir_name(audir), sdp_dir_name(viddir));
|
|
call_set_media_direction(call, audir, viddir);
|
|
}
|
|
|
|
|
|
static void event_handler(enum bevent_ev ev, struct bevent *event, void *arg)
|
|
{
|
|
struct call *call2 = NULL;
|
|
bool incall;
|
|
enum sdp_dir ardir, vrdir;
|
|
uint32_t count;
|
|
struct pl val;
|
|
const char *uri;
|
|
const char *prm = bevent_get_text(event);
|
|
struct call *call = bevent_get_call(event);
|
|
struct ua *ua = bevent_get_ua(event);
|
|
const struct sip_msg *msg = bevent_get_msg(event);
|
|
struct account *acc = ua_account(bevent_get_ua(event));
|
|
int err;
|
|
(void)arg;
|
|
|
|
#if 0
|
|
debug("menu: [ ua=%s call=%s ] event: %s (%s)\n",
|
|
account_aor(acc), call_id(call), bevent_str(ev), prm);
|
|
#endif
|
|
|
|
|
|
ardir =sdp_media_rdir(
|
|
stream_sdpmedia(audio_strm(call_audio(call))));
|
|
count = uag_call_count();
|
|
|
|
switch (ev) {
|
|
|
|
case BEVENT_SIPSESS_CONN:
|
|
|
|
if (menu.dnd) {
|
|
const uint16_t scode = 480;
|
|
const char *reason = "Temporarily Unavailable";
|
|
|
|
(void)sip_treply(NULL, uag_sip(), msg, scode, reason);
|
|
|
|
info("menu: incoming call from %r <%r> rejected: "
|
|
"%u %s\n",
|
|
&msg->from.dname, &msg->from.auri, scode, reason);
|
|
bevent_sip_msg_emit(BEVENT_MODULE, msg,
|
|
"menu,rejected,%u %s", scode, reason);
|
|
bevent_stop(event);
|
|
break;
|
|
}
|
|
|
|
ua = uag_find_msg(msg);
|
|
err = ua_accept(ua, msg);
|
|
if (err) {
|
|
warning("menu: could not accept incoming call (%m)\n",
|
|
err);
|
|
return;
|
|
}
|
|
|
|
bevent_stop(event);
|
|
return;
|
|
|
|
case BEVENT_CALL_INCOMING:
|
|
|
|
apply_contact_mediadir(call);
|
|
if (call_state(call) != CALL_STATE_INCOMING)
|
|
return;
|
|
|
|
if (account_answermode(acc) == ANSWERMODE_AUTO) {
|
|
if (!menu_autoanwser_call(call))
|
|
return;
|
|
}
|
|
|
|
/* the new incoming call should not change the current call */
|
|
if (!menu.curcall)
|
|
menu_selcall(call);
|
|
else
|
|
menu_selcall(menu.curcall);
|
|
|
|
vrdir = sdp_media_rdir(
|
|
stream_sdpmedia(video_strm(call_video(call))));
|
|
if (!call_has_video(call))
|
|
vrdir = SDP_INACTIVE;
|
|
|
|
info("menu: %s: Incoming call from: %s %s - audio-video: %s-%s"
|
|
" - (press 'a' to accept)\n",
|
|
account_aor(acc), call_peername(call), call_peeruri(call),
|
|
sdp_dir_name(ardir), sdp_dir_name(vrdir));
|
|
|
|
if (!check_delayed_answer(call))
|
|
play_incoming(call);
|
|
|
|
break;
|
|
|
|
case BEVENT_CALL_OUTGOING:
|
|
apply_contact_mediadir(call);
|
|
++menu.outcnt;
|
|
break;
|
|
|
|
case BEVENT_CALL_LOCAL_SDP:
|
|
if (call_state(call) == CALL_STATE_OUTGOING)
|
|
menu_selcall(call);
|
|
break;
|
|
|
|
case BEVENT_CALL_RINGING:
|
|
menu_selcall(call);
|
|
if (!menu.ringback && !menu_find_call(active_call_test, call))
|
|
play_ringback(call);
|
|
break;
|
|
|
|
case BEVENT_CALL_PROGRESS:
|
|
menu_selcall(call);
|
|
uag_filter_calls(limit_earlymedia, NULL, NULL);
|
|
|
|
tmr_start(&menu.tmr_play, TONE_DELAY, delayed_play, NULL);
|
|
break;
|
|
|
|
case BEVENT_CALL_ANSWERED:
|
|
menu_stop_play();
|
|
break;
|
|
|
|
case BEVENT_CALL_ESTABLISHED:
|
|
menu_selcall(call);
|
|
/* stop any ringtones */
|
|
menu_stop_play();
|
|
|
|
/* We must stop the re-dialing if the call was established */
|
|
redial_reset();
|
|
uag_hold_others(call);
|
|
break;
|
|
|
|
case BEVENT_CALL_CLOSED:
|
|
/* Activate the re-dialing if:
|
|
*
|
|
* - redial_attempts must be enabled in config
|
|
* - the closed call must be of outgoing direction
|
|
* - the closed call must fail with special code 701
|
|
*/
|
|
if (menu.redial_attempts) {
|
|
|
|
if (menu.current_attempts
|
|
||
|
|
(call_is_outgoing(call) &&
|
|
call_scode(call) == 701)) {
|
|
|
|
info("menu: call closed"
|
|
" -- redialing in %u seconds\n",
|
|
menu.redial_delay);
|
|
|
|
++menu.current_attempts;
|
|
|
|
str_ncpy(menu.redial_aor, account_aor(acc),
|
|
sizeof(menu.redial_aor));
|
|
|
|
tmr_start(&menu.tmr_redial,
|
|
menu.redial_delay*1000,
|
|
redial_handler, NULL);
|
|
}
|
|
else {
|
|
info("menu: call closed -- not redialing\n");
|
|
}
|
|
}
|
|
|
|
if (menu.xfer_call == call || menu.xfer_targ == call) {
|
|
call_hold(menu.xfer_call, false);
|
|
menu.xfer_call = NULL;
|
|
menu.xfer_targ = NULL;
|
|
}
|
|
|
|
if (call == menu.curcall) {
|
|
menu.curcall = NULL;
|
|
if (count == 1) {
|
|
menu_play_closed(call);
|
|
}
|
|
else {
|
|
menu_sel_other(call);
|
|
tmr_start(&menu.tmr_play, 0,
|
|
delayed_play, NULL);
|
|
}
|
|
}
|
|
else if (call_state(call) == CALL_STATE_ESTABLISHED) {
|
|
tmr_start(&menu.tmr_play, 0,
|
|
delayed_play, NULL);
|
|
}
|
|
|
|
hash_apply(menu.ovaufile->ht, ovaufile_del, call);
|
|
if (call_is_outgoing(call))
|
|
--menu.outcnt;
|
|
|
|
break;
|
|
|
|
case BEVENT_CALL_REMOTE_SDP:
|
|
if (!str_cmp(prm, "answer") &&
|
|
call_state(call) == CALL_STATE_ESTABLISHED)
|
|
menu_selcall(call);
|
|
|
|
if (call_state(call) == CALL_STATE_EARLY)
|
|
tmr_start(&menu.tmr_play, TONE_DELAY, delayed_play,
|
|
NULL);
|
|
|
|
break;
|
|
|
|
case BEVENT_CALL_TRANSFER:
|
|
/*
|
|
* Create a new call to transfer target.
|
|
*
|
|
* NOTE: we will automatically connect a new call to the
|
|
* transfer target
|
|
*/
|
|
|
|
info("menu: transferring call %s to '%s'\n",
|
|
call_id(call), prm);
|
|
|
|
err = ua_call_alloc(&call2, ua, VIDMODE_ON, NULL, call,
|
|
call_localuri(call), true);
|
|
if (!err) {
|
|
call_set_user_data(call2, call_user_data(call));
|
|
|
|
struct pl pl;
|
|
pl_set_str(&pl, prm);
|
|
err = call_connect(call2, &pl);
|
|
if (err) {
|
|
warning("menu: transfer: connect error: %m\n",
|
|
err);
|
|
}
|
|
else {
|
|
module_event("menu", "transfer", ua, call,
|
|
"target %s", call_id(call2));
|
|
}
|
|
}
|
|
|
|
if (err) {
|
|
(void)call_notify_sipfrag(call, 500, "Call Error");
|
|
mem_deref(call2);
|
|
}
|
|
break;
|
|
|
|
case BEVENT_CALL_TRANSFER_FAILED:
|
|
info("menu: transfer failure: %s\n", prm);
|
|
menu_stop_play();
|
|
call_hold(call, false);
|
|
menu_selcall(call);
|
|
break;
|
|
|
|
case BEVENT_CALL_REDIRECT:
|
|
uri = strchr(prm, ',');
|
|
if (!uri)
|
|
break;
|
|
|
|
++uri;
|
|
if (account_sip_autoredirect(ua_account(ua))) {
|
|
info("menu: redirecting call to %s\n", uri);
|
|
menu_invite(uri);
|
|
}
|
|
else {
|
|
info("menu: redirect call to %s\n", uri);
|
|
}
|
|
break;
|
|
|
|
case BEVENT_REFER:
|
|
val = pl_null;
|
|
if (!re_regex(prm, strlen(prm), "sip:"))
|
|
pl_set_str(&val, "invite");
|
|
|
|
(void)cparam_decode(prm, "method", &val);
|
|
if (!pl_strcmp(&val, "invite")) {
|
|
info("menu: incoming REFER to %s\n", prm);
|
|
menu_invite(prm);
|
|
}
|
|
|
|
break;
|
|
|
|
case BEVENT_REGISTER_OK:
|
|
check_registrations();
|
|
break;
|
|
|
|
case BEVENT_UNREGISTERING:
|
|
return;
|
|
|
|
case BEVENT_MWI_NOTIFY:
|
|
info("menu: ----- MWI for %s -----\n", account_aor(acc));
|
|
info("%s\n", prm);
|
|
break;
|
|
|
|
case BEVENT_AUDIO_ERROR:
|
|
info("menu: audio error (%s)\n", prm);
|
|
break;
|
|
|
|
case BEVENT_MODULE:
|
|
process_module_event(call, prm);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
incall = ev == BEVENT_CALL_CLOSED ? count > 1 : count;
|
|
menu_set_incall(incall);
|
|
menu_update_callstatus(incall);
|
|
}
|
|
|
|
|
|
static void message_handler(struct ua *ua, const struct pl *peer,
|
|
const struct pl *ctype,
|
|
struct mbuf *body, void *arg)
|
|
{
|
|
struct config *cfg;
|
|
(void)ua;
|
|
(void)ctype;
|
|
(void)arg;
|
|
|
|
cfg = conf_config();
|
|
|
|
ui_output(baresip_uis(), "\r%r: \"%b\"\n",
|
|
peer, mbuf_buf(body), mbuf_get_left(body));
|
|
|
|
if (menu.message_tone) {
|
|
(void)play_file(NULL, baresip_player(), "message.wav", 0,
|
|
cfg->audio.alert_mod, cfg->audio.alert_dev);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Get the menu object
|
|
*
|
|
* @return ptr to menu object
|
|
*/
|
|
struct menu *menu_get(void)
|
|
{
|
|
return &menu;
|
|
}
|
|
|
|
|
|
static bool filter_call(const struct call *call, void *arg)
|
|
{
|
|
struct filter_arg *fa = arg;
|
|
|
|
if (fa->state != CALL_STATE_UNKNOWN && call_state(call) != fa->state)
|
|
return false;
|
|
|
|
if (call == fa->exclude)
|
|
return false;
|
|
|
|
if (fa->match && call != fa->match)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Selects the given call to be the current call.
|
|
*
|
|
* @param call The call
|
|
*/
|
|
void menu_selcall(struct call *call)
|
|
{
|
|
menu.curcall = call;
|
|
call_set_current(ua_calls(call_get_ua(call)), call);
|
|
}
|
|
|
|
|
|
/**
|
|
* Chooses a new current call.
|
|
* Prefer call state established before early, ringing, outgoing and incoming
|
|
*/
|
|
static void menu_sel_other(struct call *exclude)
|
|
{
|
|
int i;
|
|
struct filter_arg fa = {CALL_STATE_UNKNOWN, exclude, NULL, NULL};
|
|
enum call_state state[] = {
|
|
CALL_STATE_INCOMING,
|
|
CALL_STATE_OUTGOING,
|
|
CALL_STATE_RINGING,
|
|
CALL_STATE_EARLY,
|
|
CALL_STATE_ESTABLISHED,
|
|
};
|
|
|
|
/* select another call */
|
|
for (i = RE_ARRAY_SIZE(state)-1; i >= 0; --i) {
|
|
fa.state = state[i];
|
|
uag_filter_calls(find_first_call, filter_call, &fa);
|
|
|
|
if (fa.call)
|
|
break;
|
|
}
|
|
|
|
menu_selcall(fa.call);
|
|
}
|
|
|
|
|
|
/**
|
|
* Gets the active call.
|
|
*
|
|
* @return The active call.
|
|
*/
|
|
struct call *menu_callcur(void)
|
|
{
|
|
struct filter_arg fa = {CALL_STATE_UNKNOWN, NULL, menu.curcall, NULL};
|
|
|
|
if (!menu.curcall)
|
|
return NULL;
|
|
|
|
uag_filter_calls(find_first_call, filter_call, &fa);
|
|
return fa.call;
|
|
}
|
|
|
|
|
|
/**
|
|
* Get UA of active call
|
|
*
|
|
* @return ptr to UA object
|
|
*/
|
|
struct ua *menu_uacur(void)
|
|
{
|
|
return call_get_ua(menu_callcur());
|
|
}
|
|
|
|
|
|
/**
|
|
* Manual selection of the UA via command parameter
|
|
* - carg->data has highest priority
|
|
* - otherwise second word in carg->prm is checked for an UA index
|
|
*
|
|
* @param pf Print backend
|
|
* @param carg Command argument
|
|
* @param word1 First word
|
|
* @param word2 Second word
|
|
*
|
|
* @return The UA if found, NULL otherwise.
|
|
*/
|
|
struct ua *menu_ua_carg(struct re_printf *pf, const struct cmd_arg *carg,
|
|
struct pl *word1, struct pl *word2)
|
|
{
|
|
int err;
|
|
struct le *le;
|
|
uint32_t i;
|
|
struct ua *ua = carg->data;
|
|
|
|
if (ua) {
|
|
pl_set_str(word1, carg->prm);
|
|
return ua;
|
|
}
|
|
|
|
err = re_regex(carg->prm, str_len(carg->prm), "[^ ]+ [^ ]+", word1,
|
|
word2);
|
|
if (err)
|
|
return NULL;
|
|
|
|
i = pl_u32(word2);
|
|
|
|
le = uag_list()->head;
|
|
while (le && i--)
|
|
le = le->next;
|
|
|
|
if (le) {
|
|
ua = le->data;
|
|
info("menu: %s: selected for request\n",
|
|
account_aor(ua_account(ua)));
|
|
}
|
|
else {
|
|
re_hprintf(pf, "no User-Agent at pos %r\n", word2);
|
|
}
|
|
|
|
return ua;
|
|
}
|
|
|
|
|
|
/**
|
|
* Find ua and call from command arguments.
|
|
*
|
|
* Assumes that the first argument passed in carg->prm is a valid call-id
|
|
* (if passed at all). If carg->data is set, it MUST be a pointer to a
|
|
* struct ua. If no call-id is passed in carg->prm, the currently active call
|
|
* is returned, if there is an active call.
|
|
*
|
|
* @param pf Print backend
|
|
* @param carg Command arguments
|
|
* @param uap Pointer-pointer to ua. Set on successful return.
|
|
* @param callp Pointer-pointer to call. Set on successful return.
|
|
*
|
|
* @return 0 for success, otherwise errorcode
|
|
*/
|
|
int menu_get_call_ua(struct re_printf *pf, const struct cmd_arg *carg,
|
|
struct ua **uap, struct call **callp)
|
|
{
|
|
int err = 0;
|
|
struct ua *ua;
|
|
struct call *call;
|
|
const char *eq;
|
|
char *cid = NULL;
|
|
struct pl pl = PL_INIT;
|
|
|
|
if (!carg || !uap || !callp)
|
|
return EINVAL;
|
|
|
|
/* fallback */
|
|
ua = carg->data ? carg->data : menu_uacur();
|
|
call = ua_call(ua);
|
|
|
|
if (re_regex(carg->prm, str_len(carg->prm), "[^ ]+", &pl))
|
|
goto out;
|
|
|
|
/* A call-id MUST NOT contain an '='. See RFC 3261 section 25.1. */
|
|
eq = pl_strchr(&pl, '=');
|
|
if (eq)
|
|
goto out;
|
|
|
|
err = pl_strdup(&cid, &pl);
|
|
if (err)
|
|
return err;
|
|
|
|
call = uag_call_find(cid);
|
|
if (!call) {
|
|
(void)re_hprintf(pf, "call %s not found\n", cid);
|
|
err = EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
ua = call_get_ua(call);
|
|
|
|
out:
|
|
if (!call && !err) {
|
|
(void)re_hprintf(pf, "no active call\n");
|
|
err = ENOENT;
|
|
}
|
|
|
|
if (!err) {
|
|
*uap = ua;
|
|
*callp = call;
|
|
}
|
|
|
|
mem_deref(cid);
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
static int module_init(void)
|
|
{
|
|
struct pl val;
|
|
int err;
|
|
|
|
memset(&menu, 0, sizeof(menu));
|
|
menu.redial_attempts = 0;
|
|
menu.redial_delay = 5;
|
|
menu.ringback_disabled = false;
|
|
menu.statmode = STATMODE_CALL;
|
|
menu.clean_number = false;
|
|
menu.play = NULL;
|
|
menu.adelay = -1;
|
|
menu.message_tone = true;
|
|
err = odict_alloc(&menu.ovaufile, 8);
|
|
if (err)
|
|
return err;
|
|
|
|
/*
|
|
* Read the config values
|
|
*/
|
|
conf_get_bool(conf_cur(), "ringback_disabled",
|
|
&menu.ringback_disabled);
|
|
conf_get_bool(conf_cur(), "menu_clean_number", &menu.clean_number);
|
|
conf_get_bool(conf_cur(), "menu_message_tone", &menu.message_tone);
|
|
|
|
if (0 == conf_get(conf_cur(), "redial_attempts", &val) &&
|
|
0 == pl_strcasecmp(&val, "inf")) {
|
|
menu.redial_attempts = (uint32_t)-1;
|
|
}
|
|
else {
|
|
conf_get_u32(conf_cur(), "redial_attempts",
|
|
&menu.redial_attempts);
|
|
}
|
|
conf_get_u32(conf_cur(), "redial_delay", &menu.redial_delay);
|
|
|
|
if (menu.redial_attempts) {
|
|
info("menu: redial enabled with %u attempts and"
|
|
" %u seconds delay\n",
|
|
menu.redial_attempts,
|
|
menu.redial_delay);
|
|
}
|
|
|
|
menu.dialbuf = mbuf_alloc(64);
|
|
if (!menu.dialbuf)
|
|
return ENOMEM;
|
|
|
|
menu.start_ticks = tmr_jiffies();
|
|
|
|
if (0 == conf_get(conf_cur(), "statmode_default", &val) &&
|
|
0 == pl_strcasecmp(&val, "off")) {
|
|
menu.statmode = STATMODE_OFF;
|
|
}
|
|
else {
|
|
menu.statmode = STATMODE_CALL;
|
|
}
|
|
|
|
err = static_menu_register();
|
|
err |= dial_menu_register();
|
|
if (err)
|
|
return err;
|
|
|
|
err = bevent_register(event_handler, NULL);
|
|
if (err)
|
|
return err;
|
|
|
|
err = message_listen(baresip_message(),
|
|
message_handler, NULL);
|
|
if (err)
|
|
return err;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int module_close(void)
|
|
{
|
|
debug("menu: close (redial current_attempts=%d)\n",
|
|
menu.current_attempts);
|
|
|
|
message_unlisten(baresip_message(), message_handler);
|
|
|
|
bevent_unregister(event_handler);
|
|
static_menu_unregister();
|
|
dial_menu_unregister();
|
|
dynamic_menu_unregister();
|
|
|
|
tmr_cancel(&menu.tmr_stat);
|
|
tmr_cancel(&menu.tmr_play);
|
|
tmr_cancel(&menu.tmr_answ);
|
|
menu.dialbuf = mem_deref(menu.dialbuf);
|
|
menu.invite_uri = mem_deref(menu.invite_uri);
|
|
menu.ovaufile = mem_deref(menu.ovaufile);
|
|
menu.ansval = mem_deref(menu.ansval);
|
|
menu_stop_play();
|
|
|
|
tmr_cancel(&menu.tmr_redial);
|
|
tmr_cancel(&menu.tmr_invite);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
const struct mod_export DECL_EXPORTS(menu) = {
|
|
"menu",
|
|
"application",
|
|
module_init,
|
|
module_close
|
|
};
|