mirror of
https://github.com/asterisk/asterisk.git
synced 2025-10-06 21:09:47 +00:00
Merged revisions 183126 via svnmerge from
https://origsvn.digium.com/svn/asterisk/branches/1.4 ........ r183126 | dvossel | 2009-03-19 11:15:16 -0500 (Thu, 19 Mar 2009) | 17 lines Allow disconnect feature before a call is bridged feature.conf has a disconnect option. By default this option is set to '*', but it could be anything. If a user wishes to disconnect a call before the other side answers, only '*' will work, regardless if the disconnect option is set to something else. This is because features are unavailable until bridging takes place. The default disconnect option, '*', was hardcoded in app_dial, which doesn't make any sense from a user perspective since they may expect it to be something different. This patch allows features to be detected from outside of the bridge, but not operated on. In this case, the disconnect feature can be detected before briding and handled outside of features.c. (closes issue #11583) Reported by: sobomax Patches: patch-apps__app_dial.c uploaded by sobomax (license 359) 11583.latest-patch uploaded by murf (license 17) detect_disconnect.diff uploaded by dvossel (license 671) Tested by: sobomax, dvossel Review: http://reviewboard.digium.com/r/195/ ........ git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@183172 65c4cc65-6c06-0410-ace0-fbb531ad65f3
This commit is contained in:
@@ -560,6 +560,7 @@ struct chanlist {
|
|||||||
uint64_t flags;
|
uint64_t flags;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static int detect_disconnect(struct ast_channel *chan, char code, struct ast_str *featurecode);
|
||||||
|
|
||||||
static void hanguptree(struct chanlist *outgoing, struct ast_channel *exception, int answered_elsewhere)
|
static void hanguptree(struct chanlist *outgoing, struct ast_channel *exception, int answered_elsewhere)
|
||||||
{
|
{
|
||||||
@@ -809,7 +810,7 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
|
|||||||
#ifdef HAVE_EPOLL
|
#ifdef HAVE_EPOLL
|
||||||
struct chanlist *epollo;
|
struct chanlist *epollo;
|
||||||
#endif
|
#endif
|
||||||
|
struct ast_str *featurecode = ast_str_alloca(FEATURE_MAX_LEN + 1);
|
||||||
if (single) {
|
if (single) {
|
||||||
/* Turn off hold music, etc */
|
/* Turn off hold music, etc */
|
||||||
ast_deactivate_generator(in);
|
ast_deactivate_generator(in);
|
||||||
@@ -1058,8 +1059,8 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ast_test_flag64(peerflags, OPT_CALLER_HANGUP) &&
|
if (ast_test_flag64(peerflags, OPT_CALLER_HANGUP) &&
|
||||||
(f->subclass == '*')) { /* hmm it it not guaranteed to be '*' anymore. */
|
detect_disconnect(in, f->subclass, featurecode)) {
|
||||||
ast_verb(3, "User hit %c to disconnect call.\n", f->subclass);
|
ast_verb(3, "User requested call disconnect.\n");
|
||||||
*to = 0;
|
*to = 0;
|
||||||
strcpy(pa->status, "CANCEL");
|
strcpy(pa->status, "CANCEL");
|
||||||
ast_cdr_noanswer(in->cdr);
|
ast_cdr_noanswer(in->cdr);
|
||||||
@@ -1103,6 +1104,26 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
|
|||||||
return peer;
|
return peer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int detect_disconnect(struct ast_channel *chan, char code, struct ast_str *featurecode)
|
||||||
|
{
|
||||||
|
struct ast_flags features = { AST_FEATURE_DISCONNECT }; /* only concerned with disconnect feature */
|
||||||
|
struct ast_call_feature feature;
|
||||||
|
int res;
|
||||||
|
|
||||||
|
ast_str_append(&featurecode, 1, "%c", code);
|
||||||
|
|
||||||
|
res = ast_feature_detect(chan, &features, ast_str_buffer(featurecode), &feature);
|
||||||
|
|
||||||
|
if (res != AST_FEATURE_RETURN_STOREDIGITS) {
|
||||||
|
ast_str_reset(featurecode);
|
||||||
|
}
|
||||||
|
if (feature.feature_mask & AST_FEATURE_DISCONNECT) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static void replace_macro_delimiter(char *s)
|
static void replace_macro_delimiter(char *s)
|
||||||
{
|
{
|
||||||
for (; *s; s++)
|
for (; *s; s++)
|
||||||
@@ -1597,7 +1618,7 @@ static int dial_exec_full(struct ast_channel *chan, void *data, struct ast_flags
|
|||||||
res = -1; /* reset default */
|
res = -1; /* reset default */
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ast_test_flag64(&opts, OPT_DTMF_EXIT)) {
|
if (ast_test_flag64(&opts, OPT_DTMF_EXIT) || ast_test_flag64(&opts, OPT_CALLER_HANGUP)) {
|
||||||
__ast_answer(chan, 0, 0);
|
__ast_answer(chan, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -36,6 +36,21 @@
|
|||||||
|
|
||||||
#define PARK_APP_NAME "Park"
|
#define PARK_APP_NAME "Park"
|
||||||
|
|
||||||
|
#define AST_FEATURE_RETURN_HANGUP -1
|
||||||
|
#define AST_FEATURE_RETURN_SUCCESSBREAK 0
|
||||||
|
#define AST_FEATURE_RETURN_PBX_KEEPALIVE AST_PBX_KEEPALIVE
|
||||||
|
#define AST_FEATURE_RETURN_NO_HANGUP_PEER AST_PBX_NO_HANGUP_PEER
|
||||||
|
#define AST_FEATURE_RETURN_PASSDIGITS 21
|
||||||
|
#define AST_FEATURE_RETURN_STOREDIGITS 22
|
||||||
|
#define AST_FEATURE_RETURN_SUCCESS 23
|
||||||
|
#define AST_FEATURE_RETURN_KEEPTRYING 24
|
||||||
|
#define AST_FEATURE_RETURN_PARKFAILED 25
|
||||||
|
|
||||||
|
#define FEATURE_SENSE_CHAN (1 << 0)
|
||||||
|
#define FEATURE_SENSE_PEER (1 << 1)
|
||||||
|
|
||||||
|
typedef int (*ast_feature_operation)(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense, void *data);
|
||||||
|
|
||||||
/*! \brief main call feature structure */
|
/*! \brief main call feature structure */
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
@@ -53,7 +68,7 @@ struct ast_call_feature {
|
|||||||
char sname[FEATURE_SNAME_LEN];
|
char sname[FEATURE_SNAME_LEN];
|
||||||
char exten[FEATURE_MAX_LEN];
|
char exten[FEATURE_MAX_LEN];
|
||||||
char default_exten[FEATURE_MAX_LEN];
|
char default_exten[FEATURE_MAX_LEN];
|
||||||
int (*operation)(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense, void *data);
|
ast_feature_operation operation;
|
||||||
unsigned int flags;
|
unsigned int flags;
|
||||||
char app[FEATURE_APP_LEN];
|
char app[FEATURE_APP_LEN];
|
||||||
char app_args[FEATURE_APP_ARGS_LEN];
|
char app_args[FEATURE_APP_ARGS_LEN];
|
||||||
@@ -61,14 +76,6 @@ struct ast_call_feature {
|
|||||||
AST_LIST_ENTRY(ast_call_feature) feature_entry;
|
AST_LIST_ENTRY(ast_call_feature) feature_entry;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define AST_FEATURE_RETURN_HANGUP -1
|
|
||||||
#define AST_FEATURE_RETURN_SUCCESSBREAK 0
|
|
||||||
#define AST_FEATURE_RETURN_PASSDIGITS 21
|
|
||||||
#define AST_FEATURE_RETURN_STOREDIGITS 22
|
|
||||||
#define AST_FEATURE_RETURN_SUCCESS 23
|
|
||||||
#define AST_FEATURE_RETURN_KEEPTRYING 24
|
|
||||||
#define AST_FEATURE_RETURN_PARKFAILED 25
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Park a call and read back parked location
|
* \brief Park a call and read back parked location
|
||||||
* \param chan the channel to actually be parked
|
* \param chan the channel to actually be parked
|
||||||
@@ -123,6 +130,10 @@ void ast_register_feature(struct ast_call_feature *feature);
|
|||||||
\param feature the ast_call_feature object which was registered before*/
|
\param feature the ast_call_feature object which was registered before*/
|
||||||
void ast_unregister_feature(struct ast_call_feature *feature);
|
void ast_unregister_feature(struct ast_call_feature *feature);
|
||||||
|
|
||||||
|
/*! \brief detect a feature before bridging
|
||||||
|
\para chan, ast_flags ptr, code, ast_call_feature ptr to be set if found */
|
||||||
|
int ast_feature_detect(struct ast_channel *chan, struct ast_flags *features, char *code, struct ast_call_feature *feature);
|
||||||
|
|
||||||
/*! \brief look for a call feature entry by its sname
|
/*! \brief look for a call feature entry by its sname
|
||||||
\param name a string ptr, should match "automon", "blindxfer", "atxfer", etc. */
|
\param name a string ptr, should match "automon", "blindxfer", "atxfer", etc. */
|
||||||
struct ast_call_feature *ast_find_call_feature(const char *name);
|
struct ast_call_feature *ast_find_call_feature(const char *name);
|
||||||
|
193
main/features.c
193
main/features.c
@@ -889,9 +889,6 @@ static int masq_park_call_announce(struct ast_channel *rchan, struct ast_channel
|
|||||||
return masq_park_call(rchan, peer, timeout, extout, 1, NULL);
|
return masq_park_call(rchan, peer, timeout, extout, 1, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define FEATURE_SENSE_CHAN (1 << 0)
|
|
||||||
#define FEATURE_SENSE_PEER (1 << 1)
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief set caller and callee according to the direction
|
* \brief set caller and callee according to the direction
|
||||||
* \param caller, callee, peer, chan, sense
|
* \param caller, callee, peer, chan, sense
|
||||||
@@ -1969,26 +1966,125 @@ static int remap_feature(const char *name, const char *value)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Check the dynamic features
|
* \brief Helper function for feature_interpret and ast_feature_detect
|
||||||
* \param chan,peer,config,code,sense
|
* \param chan,peer,config,code,sense,dynamic_features char buf,feature flags,operation,feature
|
||||||
*
|
*
|
||||||
* Lock features list, browse for code, unlock list
|
* Lock features list, browse for code, unlock list
|
||||||
|
* If a feature is found and the operation variable is set, that feature's
|
||||||
|
* operation is executed. The first feature found is copied to the feature parameter.
|
||||||
* \retval res on success.
|
* \retval res on success.
|
||||||
* \retval -1 on failure.
|
* \retval -1 on failure.
|
||||||
*/
|
*/
|
||||||
static int feature_interpret(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense)
|
static int feature_interpret_helper(struct ast_channel *chan, struct ast_channel *peer,
|
||||||
|
struct ast_bridge_config *config, char *code, int sense, char *dynamic_features_buf,
|
||||||
|
struct ast_flags *features, int operation, struct ast_call_feature *feature)
|
||||||
{
|
{
|
||||||
int x;
|
int x;
|
||||||
struct ast_flags features;
|
|
||||||
struct ast_call_feature *feature;
|
|
||||||
struct feature_group *fg = NULL;
|
struct feature_group *fg = NULL;
|
||||||
struct feature_group_exten *fge;
|
struct feature_group_exten *fge;
|
||||||
const char *peer_dynamic_features, *chan_dynamic_features;
|
struct ast_call_feature *tmpfeature;
|
||||||
char dynamic_features_buf[128];
|
|
||||||
char *tmp, *tok;
|
char *tmp, *tok;
|
||||||
int res = AST_FEATURE_RETURN_PASSDIGITS;
|
int res = AST_FEATURE_RETURN_PASSDIGITS;
|
||||||
int feature_detected = 0;
|
int feature_detected = 0;
|
||||||
|
|
||||||
|
if (!(peer && chan && config) && operation) {
|
||||||
|
return -1; /* can not run feature operation */
|
||||||
|
}
|
||||||
|
|
||||||
|
ast_rwlock_rdlock(&features_lock);
|
||||||
|
for (x = 0; x < FEATURES_COUNT; x++) {
|
||||||
|
if ((ast_test_flag(features, builtin_features[x].feature_mask)) &&
|
||||||
|
!ast_strlen_zero(builtin_features[x].exten)) {
|
||||||
|
/* Feature is up for consideration */
|
||||||
|
if (!strcmp(builtin_features[x].exten, code)) {
|
||||||
|
ast_debug(3, "Feature detected: fname=%s sname=%s exten=%s\n", builtin_features[x].fname, builtin_features[x].sname, builtin_features[x].exten);
|
||||||
|
if (operation) {
|
||||||
|
res = builtin_features[x].operation(chan, peer, config, code, sense, NULL);
|
||||||
|
}
|
||||||
|
memcpy(feature, &builtin_features[x], sizeof(feature));
|
||||||
|
feature_detected = 1;
|
||||||
|
break;
|
||||||
|
} else if (!strncmp(builtin_features[x].exten, code, strlen(code))) {
|
||||||
|
if (res == AST_FEATURE_RETURN_PASSDIGITS)
|
||||||
|
res = AST_FEATURE_RETURN_STOREDIGITS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ast_rwlock_unlock(&features_lock);
|
||||||
|
|
||||||
|
if (ast_strlen_zero(dynamic_features_buf) || feature_detected) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp = dynamic_features_buf;
|
||||||
|
|
||||||
|
while ((tok = strsep(&tmp, "#"))) {
|
||||||
|
AST_RWLIST_RDLOCK(&feature_groups);
|
||||||
|
|
||||||
|
fg = find_group(tok);
|
||||||
|
|
||||||
|
if (fg) {
|
||||||
|
AST_LIST_TRAVERSE(&fg->features, fge, entry) {
|
||||||
|
if (strcasecmp(fge->exten, code))
|
||||||
|
continue;
|
||||||
|
if (operation) {
|
||||||
|
res = fge->feature->operation(chan, peer, config, code, sense, fge->feature);
|
||||||
|
}
|
||||||
|
memcpy(feature, fge->feature, sizeof(feature));
|
||||||
|
if (res != AST_FEATURE_RETURN_KEEPTRYING) {
|
||||||
|
AST_RWLIST_UNLOCK(&feature_groups);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
res = AST_FEATURE_RETURN_PASSDIGITS;
|
||||||
|
}
|
||||||
|
if (fge)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
AST_RWLIST_UNLOCK(&feature_groups);
|
||||||
|
|
||||||
|
AST_RWLIST_RDLOCK(&feature_list);
|
||||||
|
|
||||||
|
if (!(tmpfeature = find_dynamic_feature(tok))) {
|
||||||
|
AST_RWLIST_UNLOCK(&feature_list);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Feature is up for consideration */
|
||||||
|
if (!strcmp(tmpfeature->exten, code)) {
|
||||||
|
ast_verb(3, " Feature Found: %s exten: %s\n",tmpfeature->sname, tok);
|
||||||
|
if (operation) {
|
||||||
|
res = tmpfeature->operation(chan, peer, config, code, sense, tmpfeature);
|
||||||
|
}
|
||||||
|
memcpy(feature, tmpfeature, sizeof(feature));
|
||||||
|
if (res != AST_FEATURE_RETURN_KEEPTRYING) {
|
||||||
|
AST_RWLIST_UNLOCK(&feature_list);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
res = AST_FEATURE_RETURN_PASSDIGITS;
|
||||||
|
} else if (!strncmp(tmpfeature->exten, code, strlen(code)))
|
||||||
|
res = AST_FEATURE_RETURN_STOREDIGITS;
|
||||||
|
|
||||||
|
AST_RWLIST_UNLOCK(&feature_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Check the dynamic features
|
||||||
|
* \param chan,peer,config,code,sense
|
||||||
|
*
|
||||||
|
* \retval res on success.
|
||||||
|
* \retval -1 on failure.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static int feature_interpret(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense) {
|
||||||
|
|
||||||
|
char dynamic_features_buf[128];
|
||||||
|
const char *peer_dynamic_features, *chan_dynamic_features;
|
||||||
|
struct ast_flags features;
|
||||||
|
struct ast_call_feature feature;
|
||||||
if (sense == FEATURE_SENSE_CHAN) {
|
if (sense == FEATURE_SENSE_CHAN) {
|
||||||
ast_copy_flags(&features, &(config->features_caller), AST_FLAGS_ALL);
|
ast_copy_flags(&features, &(config->features_caller), AST_FLAGS_ALL);
|
||||||
}
|
}
|
||||||
@@ -2008,81 +2104,24 @@ static int feature_interpret(struct ast_channel *chan, struct ast_channel *peer,
|
|||||||
|
|
||||||
ast_debug(3, "Feature interpret: chan=%s, peer=%s, code=%s, sense=%d, features=%d, dynamic=%s\n", chan->name, peer->name, code, sense, features.flags, dynamic_features_buf);
|
ast_debug(3, "Feature interpret: chan=%s, peer=%s, code=%s, sense=%d, features=%d, dynamic=%s\n", chan->name, peer->name, code, sense, features.flags, dynamic_features_buf);
|
||||||
|
|
||||||
ast_rwlock_rdlock(&features_lock);
|
return feature_interpret_helper(chan, peer, config, code, sense, dynamic_features_buf, &features, 1, &feature);
|
||||||
for (x = 0; x < FEATURES_COUNT; x++) {
|
}
|
||||||
if ((ast_test_flag(&features, builtin_features[x].feature_mask)) &&
|
|
||||||
!ast_strlen_zero(builtin_features[x].exten)) {
|
|
||||||
/* Feature is up for consideration */
|
|
||||||
if (!strcmp(builtin_features[x].exten, code)) {
|
|
||||||
ast_debug(3, "Feature detected: fname=%s sname=%s exten=%s\n", builtin_features[x].fname, builtin_features[x].sname, builtin_features[x].exten);
|
|
||||||
res = builtin_features[x].operation(chan, peer, config, code, sense, NULL);
|
|
||||||
feature_detected = 1;
|
|
||||||
break;
|
|
||||||
} else if (!strncmp(builtin_features[x].exten, code, strlen(code))) {
|
|
||||||
if (res == AST_FEATURE_RETURN_PASSDIGITS)
|
|
||||||
res = AST_FEATURE_RETURN_STOREDIGITS;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ast_rwlock_unlock(&features_lock);
|
|
||||||
|
|
||||||
if (ast_strlen_zero(dynamic_features_buf) || feature_detected)
|
|
||||||
return res;
|
|
||||||
|
|
||||||
tmp = dynamic_features_buf;
|
int ast_feature_detect(struct ast_channel *chan, struct ast_flags *features, char *code, struct ast_call_feature *feature) {
|
||||||
|
|
||||||
while ((tok = strsep(&tmp, "#"))) {
|
char *dynamic_features;
|
||||||
AST_RWLIST_RDLOCK(&feature_groups);
|
ast_channel_lock(chan);
|
||||||
|
dynamic_features = ast_strdupa(S_OR(pbx_builtin_getvar_helper(chan, "DYNAMIC_FEATURES"),""));
|
||||||
|
ast_channel_unlock(chan);
|
||||||
|
|
||||||
fg = find_group(tok);
|
return feature_interpret_helper(chan, NULL, NULL, code, 0, dynamic_features, features, 0, feature);
|
||||||
|
|
||||||
if (fg) {
|
|
||||||
AST_LIST_TRAVERSE(&fg->features, fge, entry) {
|
|
||||||
if (strcasecmp(fge->exten, code))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
res = fge->feature->operation(chan, peer, config, code, sense, fge->feature);
|
|
||||||
if (res != AST_FEATURE_RETURN_KEEPTRYING) {
|
|
||||||
AST_RWLIST_UNLOCK(&feature_groups);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
res = AST_FEATURE_RETURN_PASSDIGITS;
|
|
||||||
}
|
|
||||||
if (fge)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
AST_RWLIST_UNLOCK(&feature_groups);
|
|
||||||
|
|
||||||
AST_RWLIST_RDLOCK(&feature_list);
|
|
||||||
|
|
||||||
if (!(feature = find_dynamic_feature(tok))) {
|
|
||||||
AST_RWLIST_UNLOCK(&feature_list);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Feature is up for consideration */
|
|
||||||
if (!strcmp(feature->exten, code)) {
|
|
||||||
ast_verb(3, " Feature Found: %s exten: %s\n",feature->sname, tok);
|
|
||||||
res = feature->operation(chan, peer, config, code, sense, feature);
|
|
||||||
if (res != AST_FEATURE_RETURN_KEEPTRYING) {
|
|
||||||
AST_RWLIST_UNLOCK(&feature_list);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
res = AST_FEATURE_RETURN_PASSDIGITS;
|
|
||||||
} else if (!strncmp(feature->exten, code, strlen(code)))
|
|
||||||
res = AST_FEATURE_RETURN_STOREDIGITS;
|
|
||||||
|
|
||||||
AST_RWLIST_UNLOCK(&feature_list);
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void set_config_flags(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config)
|
static void set_config_flags(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config)
|
||||||
{
|
{
|
||||||
int x;
|
int x;
|
||||||
|
|
||||||
ast_clear_flag(config, AST_FLAGS_ALL);
|
ast_clear_flag(config, AST_FLAGS_ALL);
|
||||||
|
|
||||||
ast_rwlock_rdlock(&features_lock);
|
ast_rwlock_rdlock(&features_lock);
|
||||||
@@ -2097,7 +2136,7 @@ static void set_config_flags(struct ast_channel *chan, struct ast_channel *peer,
|
|||||||
ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_1);
|
ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_1);
|
||||||
}
|
}
|
||||||
ast_rwlock_unlock(&features_lock);
|
ast_rwlock_unlock(&features_lock);
|
||||||
|
|
||||||
if (chan && peer && !(ast_test_flag(config, AST_BRIDGE_DTMF_CHANNEL_0) && ast_test_flag(config, AST_BRIDGE_DTMF_CHANNEL_1))) {
|
if (chan && peer && !(ast_test_flag(config, AST_BRIDGE_DTMF_CHANNEL_0) && ast_test_flag(config, AST_BRIDGE_DTMF_CHANNEL_1))) {
|
||||||
const char *dynamic_features = pbx_builtin_getvar_helper(chan, "DYNAMIC_FEATURES");
|
const char *dynamic_features = pbx_builtin_getvar_helper(chan, "DYNAMIC_FEATURES");
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user