Index: channels/chan_skinny.c =================================================================== --- channels/chan_skinny.c (revision 121854) +++ channels/chan_skinny.c (working copy) @@ -67,6 +67,7 @@ #include "asterisk/threadstorage.h" #include "asterisk/devicestate.h" #include "asterisk/event.h" +#include "asterisk/indications.h" /************************************* * Skinny/Asterisk Protocol Settings * @@ -202,14 +203,14 @@ #define OFFHOOK_MESSAGE 0x0006 struct offhook_message { - uint32_t unknown1; - uint32_t unknown2; + uint32_t instance; + uint32_t reference; }; #define ONHOOK_MESSAGE 0x0007 struct onhook_message { - uint32_t unknown1; - uint32_t unknown2; + uint32_t instance; + uint32_t reference; }; #define CAPABILITIES_RES_MESSAGE 0x0010 @@ -797,6 +798,7 @@ static const uint8_t soft_key_default_offhookwithfeat[] = { SOFTKEY_REDIAL, SOFTKEY_ENDCALL, + SOFTKEY_TRNSFER, }; static const uint8_t soft_key_default_unknown[] = { @@ -861,6 +863,7 @@ char promptMessage[32]; uint32_t lineInstance; uint32_t callReference; + uint32_t space[3]; }; #define CLEAR_PROMPT_MESSAGE 0x0113 @@ -1139,8 +1142,12 @@ int nat; int outgoing; int alreadygone; + int blindxfer; + int xferor; + struct skinny_subchannel *next; + struct skinny_subchannel *related; struct skinny_line *parent; }; @@ -1197,6 +1204,7 @@ struct ast_codec_pref prefs; struct skinny_subchannel *sub; + struct skinny_subchannel *activesub; struct skinny_line *next; struct skinny_device *parent; struct ast_variable *chanvars; /*!< Channel variables to set for inbound call */ @@ -1245,6 +1253,7 @@ struct ast_ha *ha; struct skinnysession *session; struct skinny_device *next; + struct skinny_line *activeline; } *devices = NULL; struct skinny_paging_device { @@ -1298,6 +1307,7 @@ }; static int skinny_extensionstate_cb(char *context, char* exten, int state, void *data); +static int skinny_transfer(struct skinny_subchannel *sub); static void *get_button_template(struct skinnysession *s, struct button_definition_template *btn) { @@ -2096,6 +2106,54 @@ transmit_response(s, req); } +static void transmit_closereceivechannel(struct skinnysession *s, struct skinny_subchannel *sub) +{ + struct skinny_req *req; + + if (!(req = req_alloc(sizeof(struct close_receive_channel_message), CLOSE_RECEIVE_CHANNEL_MESSAGE))) + return; + + req->data.closereceivechannel.conferenceId = htolel(0); + req->data.closereceivechannel.partyId = htolel(sub->callid); + transmit_response(s, req); +} + +static void transmit_stopmediatransmission(struct skinnysession *s, struct skinny_subchannel *sub) +{ + struct skinny_req *req; + + if (!(req = req_alloc(sizeof(struct stop_media_transmission_message), STOP_MEDIA_TRANSMISSION_MESSAGE))) + return; + + req->data.stopmedia.conferenceId = htolel(0); + req->data.stopmedia.passThruPartyId = htolel(sub->callid); + transmit_response(s, req); +} + +static void transmit_activatecallplane(struct skinnysession *s, struct skinny_line *l) +{ + struct skinny_req *req; + + if (!(req = req_alloc(sizeof(struct activate_call_plane_message), ACTIVATE_CALL_PLANE_MESSAGE))) + return; + + req->data.activatecallplane.lineInstance = htolel(l->instance); + transmit_response(s, req); +} + +static void transmit_callstateonly(struct skinnysession *s, struct skinny_subchannel *sub, int state) +{ + struct skinny_req *req; + + if (!(req = req_alloc(sizeof(struct call_state_message), CALL_STATE_MESSAGE))) + return; + + req->data.callstate.callState = htolel(state); + req->data.callstate.lineInstance = htolel(sub->parent->instance); + req->data.callstate.callReference = htolel(sub->callid); + transmit_response(s, req); +} + static void transmit_callstate(struct skinnysession *s, int instance, int state, unsigned callid) { struct skinny_req *req; @@ -3156,6 +3214,9 @@ l->nat = nat; l->canreinvite = canreinvite; + if (!d->lines) { + d->activeline = l; + } l->next = d->lines; d->lines = l; } @@ -3390,7 +3451,7 @@ break; } - transmit_callstate(s, l->instance, SKINNY_RINGIN, sub->callid); + transmit_callstateonly(s, sub, SKINNY_RINGIN); transmit_selectsoftkeys(s, l->instance, sub->callid, KEYDEF_RINGIN); transmit_displaypromptstatus(s, "Ring-In", 0, l->instance, sub->callid); transmit_callinfo(s, ast->cid.cid_name, ast->cid.cid_num, l->cid_name, l->cid_num, l->instance, sub->callid, 1); @@ -3405,7 +3466,7 @@ static int skinny_hangup(struct ast_channel *ast) { - struct skinny_subchannel *sub = ast->tech_pvt; + struct skinny_subchannel *sub = ast->tech_pvt, *tmpsub; struct skinny_line *l; struct skinny_device *d; struct skinnysession *s; @@ -3417,22 +3478,88 @@ l = sub->parent; d = l->parent; s = d->session; - if (skinnydebug) - ast_verb(1, "skinny_hangup(%s) on %s@%s\n", ast->name, l->name, d->name); + if (l->sub == sub) { + l->sub = l->sub->next; + } else { + tmpsub = l->sub; + while (tmpsub->next) { + if (tmpsub->next == sub) { + tmpsub->next = tmpsub->next->next; + break; + } + } + } + if (d->registered) { - if ((l->type = TYPE_LINE) && (l->hookstate == SKINNY_OFFHOOK)) { + /* Ignoring l->type, doesn't seem relevant and previous code + assigned rather than tested, ie always true */ + if (l->sub) { + if (sub->related) { + sub->related->related = NULL; + + } + if (sub == l->activesub) { /* we are killing the active sub, but there are other subs on the line*/ + if (sub->related) { + l->activesub = sub->related; + } else { + if (sub->next) { + l->activesub = sub->next; + } else { + l->activesub = l->sub; + } + } + transmit_activatecallplane(s, l); + transmit_closereceivechannel(s,sub); + transmit_stopmediatransmission(s,sub); + transmit_tone(s, SKINNY_CALLWAITTONE, l->instance, l->activesub->callid); + transmit_lamp_indication(s, STIMULUS_LINE, l->instance, SKINNY_LAMP_BLINK); + } else { /* we are killing a background sub on the line with other subs*/ + if (l->sub->next) { + transmit_lamp_indication(s, STIMULUS_LINE, l->instance, SKINNY_LAMP_BLINK); + } else { + transmit_lamp_indication(s, STIMULUS_LINE, l->instance, SKINNY_LAMP_ON); + } + } + } else { /* no more subs on line so make idle */ + l->hookstate = SKINNY_ONHOOK; transmit_callstate(s, l->instance, SKINNY_ONHOOK, sub->callid); + l->activesub = NULL; transmit_lamp_indication(s, STIMULUS_LINE, l->instance, SKINNY_LAMP_OFF); - transmit_speaker_mode(s, SKINNY_SPEAKEROFF); + if (sub->parent == d->activeline) { + transmit_activatecallplane(s, l); + transmit_closereceivechannel(s,sub); + transmit_stopmediatransmission(s,sub); + transmit_speaker_mode(s, SKINNY_SPEAKEROFF); + transmit_ringer_mode(s, SKINNY_RING_OFF); + /* we should check to see if we can start the ringer if another line is ringing */ + } + } + + /* if ((l->type = TYPE_LINE) && (l->hookstate == SKINNY_OFFHOOK)) { + transmit_callstate(s, l->instance, SKINNY_ONHOOK, sub->callid); + if (onlysub){ + if (skinnydebug) + ast_debug(1, "skinny_hangup(%s) on %s@%s is not the only call on this device\n", ast->name, l->name, d->name); + l->hookstate = SKINNY_ONHOOK; + transmit_lamp_indication(s, STIMULUS_LINE, l->instance, SKINNY_LAMP_OFF); + transmit_speaker_mode(s, SKINNY_SPEAKEROFF); + transmit_ringer_mode(s, SKINNY_RING_OFF); + } else { + transmit_ringer_mode(s, SKINNY_RING_OFF); + if (skinnydebug) + ast_debug(1, "skinny_hangup(%s) on %s@%s \n", ast->name, l->name, d->name); + } + /ends + } else if ((l->type = TYPE_LINE) && (l->hookstate == SKINNY_ONHOOK)) { transmit_tone(s, SKINNY_SILENCE, l->instance, sub->callid); transmit_callstate(s, l->instance, SKINNY_ONHOOK, sub->callid); transmit_ringer_mode(s, SKINNY_RING_OFF); transmit_lamp_indication(s, STIMULUS_LINE, l->instance, SKINNY_LAMP_OFF); do_housekeeping(s); - } + } */ } ast_mutex_lock(&sub->lock); sub->owner = NULL; @@ -3458,6 +3585,15 @@ ast_copy_string(exten, S_OR(ast->macroexten, ast->exten), sizeof(exten)); + if (sub->blindxfer) { + if (skinnydebug) + ast_debug(1, "skinny_answer(%s) on %s@%s-%d with BlindXFER, transferring\n", + ast->name, l->name, d->name, sub->callid); + ast_setstate(ast, AST_STATE_UP); + skinny_transfer(sub); + return 0; + } + sub->cxmode = SKINNY_CX_SENDRECV; if (!sub->rtp) { start_rtp(sub); @@ -3473,10 +3609,11 @@ for some reason, transmit_callinfo must be before transmit_callstate, or you won't get keypad messages in some situations. */ transmit_callinfo(s, ast->cid.cid_name, ast->cid.cid_num, exten, exten, l->instance, sub->callid, 2); - transmit_callstate(s, l->instance, SKINNY_CONNECTED, sub->callid); + transmit_callstateonly(s, sub, SKINNY_CONNECTED); transmit_selectsoftkeys(s, l->instance, sub->callid, KEYDEF_CONNECTED); transmit_dialednumber(s, exten, l->instance, sub->callid); transmit_displaypromptstatus(s, "Connected", 0, l->instance, sub->callid); + l->activesub = sub; return res; } @@ -3674,6 +3811,68 @@ } } +static int skinny_transfer(struct skinny_subchannel *sub) +{ + struct skinny_subchannel *xferor; /* the sub doing the transferring */ + struct skinny_subchannel *xferee; /* the sub being transferred */ + const struct ind_tone_zone_sound *ts = NULL; + + if (ast_bridged_channel(sub->owner) || ast_bridged_channel(sub->related->owner)) { + if (sub->xferor) { + xferor = sub; + xferee = sub->related; + } else { + xferor = sub; + xferee = sub->related; + } + + if (skinnydebug) { + ast_debug(1, "Transferee channels (local/remote): %s and %s\n", + xferee->owner->name, ast_bridged_channel(xferee->owner)?ast_bridged_channel(xferee->owner)->name:""); + ast_debug(1, "Transferor channels (local/remote): %s and %s\n", + xferor->owner->name, ast_bridged_channel(xferor->owner)?ast_bridged_channel(xferor->owner)->name:""); + } + if (ast_bridged_channel(xferor->owner)) { + if (ast_bridged_channel(xferee->owner)) { + ast_queue_control(xferee->owner, AST_CONTROL_UNHOLD); + } + if (xferor->owner->_state == AST_STATE_RING) { + /* play ringing inband */ + ts = ast_get_indication_tone(xferor->owner->zone, "ring"); + ast_playtones_start(xferor->owner,0,ts->data, 1); + } + if (skinnydebug) + ast_debug(1, "Transfer Masquerading %s to %s\n", + xferee->owner->name, ast_bridged_channel(xferor->owner)?ast_bridged_channel(xferor->owner)->name:""); + if (ast_channel_masquerade(xferee->owner, ast_bridged_channel(xferor->owner))) { + ast_log(LOG_WARNING, "Unable to masquerade %s as %s\n", + ast_bridged_channel(xferor->owner)->name, xferee->owner->name); + return -1; + } + } else if (ast_bridged_channel(xferee->owner)) { + ast_queue_control(xferee->owner, AST_CONTROL_UNHOLD); + if (xferor->owner->_state == AST_STATE_RING) { + /* play ringing inband */ + ts = ast_get_indication_tone(xferor->owner->zone, "ring"); + ast_playtones_start(xferor->owner,0,ts->data, 1); + } + if (skinnydebug) + ast_debug(1, "Transfer Masquerading %s to %s\n", + xferor->owner->name, ast_bridged_channel(xferee->owner)?ast_bridged_channel(xferee->owner)->name:""); + if (ast_channel_masquerade(xferor->owner, ast_bridged_channel(xferee->owner))) { + ast_log(LOG_WARNING, "Unable to masquerade %s as %s\n", + ast_bridged_channel(xferee->owner)->name, xferor->owner->name); + return -1; + } + return 0; + } else { + if (option_debug) + ast_log(LOG_DEBUG, "Neither %s nor %s are in a bridge, nothing to transfer\n", + xferor->owner->name, xferee->owner->name); + } + } + return 0; +} static int skinny_indicate(struct ast_channel *ast, int ind, const void *data, size_t datalen) { @@ -3694,12 +3893,18 @@ ast_verb(3, "Asked to indicate '%s' condition on channel %s\n", control2str(ind), ast->name); switch(ind) { case AST_CONTROL_RINGING: + if (sub->blindxfer) { + if (skinnydebug) + ast_debug(1, "Channel %s set up for Blind Xfer, so Xfer rather than ring device\n", ast->name); + skinny_transfer(sub); + break; + } if (ast->_state != AST_STATE_UP) { if (!sub->progress) { if (!d->earlyrtp) { transmit_tone(s, SKINNY_ALERT, l->instance, sub->callid); } - transmit_callstate(s, l->instance, SKINNY_RINGOUT, sub->callid); + transmit_callstateonly(s, sub, SKINNY_RINGOUT); transmit_dialednumber(s, exten, l->instance, sub->callid); transmit_displaypromptstatus(s, "Ring Out", 0, l->instance, sub->callid); transmit_callinfo(s, ast->cid.cid_name, ast->cid.cid_num, exten, exten, l->instance, sub->callid, 2); /* 2 = outgoing from phone */ @@ -3715,7 +3920,7 @@ if (!d->earlyrtp) { transmit_tone(s, SKINNY_BUSYTONE, l->instance, sub->callid); } - transmit_callstate(s, l->instance, SKINNY_BUSY, sub->callid); + transmit_callstateonly(s, sub, SKINNY_BUSY); sub->alreadygone = 1; ast_softhangup_nolock(ast, AST_SOFTHANGUP_DEV); if (!d->earlyrtp) { @@ -3728,7 +3933,7 @@ if (!d->earlyrtp) { transmit_tone(s, SKINNY_REORDER, l->instance, sub->callid); } - transmit_callstate(s, l->instance, SKINNY_CONGESTION, sub->callid); + transmit_callstateonly(s, sub, SKINNY_CONGESTION); sub->alreadygone = 1; ast_softhangup_nolock(ast, AST_SOFTHANGUP_DEV); if (!d->earlyrtp) { @@ -3741,7 +3946,7 @@ if (!d->earlyrtp) { transmit_tone(s, SKINNY_ALERT, l->instance, sub->callid); } - transmit_callstate(s, l->instance, SKINNY_PROGRESS, sub->callid); + transmit_callstateonly(s, sub, SKINNY_PROGRESS); transmit_displaypromptstatus(s, "Call Progress", 0, l->instance, sub->callid); transmit_callinfo(s, ast->cid.cid_name, ast->cid.cid_num, exten, exten, l->instance, sub->callid, 2); /* 2 = outgoing from phone */ sub->progress = 1; @@ -3801,9 +4006,14 @@ sub->nat = l->nat; sub->parent = l; sub->onhold = 0; + sub->blindxfer = 0; + sub->xferor = 0; + sub->related = NULL; + sub->next = l->sub; l->sub = sub; + l->activesub = sub; } tmp->tech = &skinny_tech; tmp->tech_pvt = sub; @@ -3876,7 +4086,6 @@ struct skinny_line *l = sub->parent; struct skinny_device *d = l->parent; struct skinnysession *s = d->session; - struct skinny_req *req; /* Don't try to hold a channel that doesn't exist */ if (!sub || !sub->owner) @@ -3890,26 +4099,11 @@ S_OR(l->mohsuggest, NULL), !ast_strlen_zero(l->mohsuggest) ? strlen(l->mohsuggest) + 1 : 0); - if (!(req = req_alloc(sizeof(struct activate_call_plane_message), ACTIVATE_CALL_PLANE_MESSAGE))) - return 0; + transmit_activatecallplane(s, l); + transmit_closereceivechannel(s,sub); + transmit_stopmediatransmission(s,sub); - req->data.activatecallplane.lineInstance = htolel(l->instance); - transmit_response(s, req); - - if (!(req = req_alloc(sizeof(struct close_receive_channel_message), CLOSE_RECEIVE_CHANNEL_MESSAGE))) - return 0; - - req->data.closereceivechannel.conferenceId = htolel(sub->callid); - req->data.closereceivechannel.partyId = htolel(sub->callid); - transmit_response(s, req); - - if (!(req = req_alloc(sizeof(struct stop_media_transmission_message), STOP_MEDIA_TRANSMISSION_MESSAGE))) - return 0; - - req->data.stopmedia.conferenceId = htolel(sub->callid); - req->data.stopmedia.passThruPartyId = htolel(sub->callid); - transmit_response(s, req); - + transmit_callstateonly(s, sub, SKINNY_HOLD); transmit_lamp_indication(s, STIMULUS_LINE, l->instance, SKINNY_LAMP_WINK); sub->onhold = 1; return 1; @@ -3920,7 +4114,6 @@ struct skinny_line *l = sub->parent; struct skinny_device *d = l->parent; struct skinnysession *s = d->session; - struct skinny_req *req; /* Don't try to unhold a channel that doesn't exist */ if (!sub || !sub->owner) @@ -3932,18 +4125,98 @@ ast_queue_control(sub->owner, AST_CONTROL_UNHOLD); - if (!(req = req_alloc(sizeof(struct activate_call_plane_message), ACTIVATE_CALL_PLANE_MESSAGE))) - return 0; + transmit_activatecallplane(s, l); - req->data.activatecallplane.lineInstance = htolel(l->instance); - transmit_response(s, req); - transmit_connect(s, sub); + transmit_callstateonly(s, sub, SKINNY_CONNECTED); transmit_lamp_indication(s, STIMULUS_LINE, l->instance, SKINNY_LAMP_ON); + l->hookstate = SKINNY_OFFHOOK; sub->onhold = 0; return 1; } +static int handle_hold_button(struct skinny_subchannel *sub) +{ + if (!sub) + return -1; + if (sub->related) { + skinny_hold(sub); + skinny_unhold(sub->related); + sub->parent->activesub = sub->related; + } else { + if (sub->onhold) { + skinny_unhold(sub); + transmit_selectsoftkeys(sub->parent->parent->session, sub->parent->instance, sub->callid, KEYDEF_CONNECTED); + } else { + skinny_hold(sub); + transmit_selectsoftkeys(sub->parent->parent->session, sub->parent->instance, sub->callid, KEYDEF_ONHOLD); + } + } + return 1; +} + +static int handle_transfer_button(struct skinny_subchannel *sub) +{ + struct skinny_line *l = sub->parent; + struct skinny_device *d = l->parent; + struct skinnysession *s = d->session; + struct skinny_subchannel *newsub; + struct ast_channel *c; + pthread_t t; + + if (!sub) { + ast_verbose("Transfer: No subchannel to transfer\n"); + return -1; + } + if (!sub->related) { + /* Another sub has not been created so this must be first XFER press */ + if (!sub->onhold) { + skinny_hold(sub); + } + c = skinny_new(l, AST_STATE_DOWN); + if (c) { + newsub = c->tech_pvt; + /* point the sub and newsub at each other so we know they are related */ + newsub->related = sub; + sub->related = newsub; + newsub->xferor = 1; + l->activesub = newsub; + transmit_callstate(s, l->instance, SKINNY_OFFHOOK, newsub->callid); + if (skinnydebug) + ast_debug(1, "Attempting to Clear display on Skinny %s@%s\n", l->name, d->name); + transmit_displaymessage(s, NULL, l->instance, newsub->callid); /* clear display */ + transmit_tone(s, SKINNY_DIALTONE, l->instance, newsub->callid); + transmit_selectsoftkeys(s, l->instance, newsub->callid, KEYDEF_OFFHOOKWITHFEAT); + /* start the switch thread */ + if (ast_pthread_create(&t, NULL, skinny_ss, c)) { + ast_log(LOG_WARNING, "Unable to create switch thread: %s\n", strerror(errno)); + ast_hangup(c); + } + } else { + ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name); + } + } else { + /* We already have a related sub so we can either complete XFER or go into BLINDXFER (or cancel BLINDXFER */ + if (sub->blindxfer) { + /* toggle blindxfer off */ + sub->blindxfer = 0; + sub->related->blindxfer = 0; + /* we really need some indications */ + } else { + /* We were doing attended transfer */ + if (sub->owner->_state == AST_STATE_DOWN || sub->related->owner->_state == AST_STATE_DOWN) { + /* one of the subs so we cant transfer yet, toggle blindxfer on */ + sub->blindxfer = 1; + sub->related->blindxfer = 1; + } else { + /* big assumption we have two channels, lets transfer */ + skinny_transfer(sub); + } + } + } + return 0; +} + static int handle_keep_alive_message(struct skinny_req *req, struct skinnysession *s) { if (!(req = req_alloc(0, KEEP_ALIVE_ACK_MESSAGE))) @@ -4080,7 +4353,8 @@ if (lineInstance && callReference) sub = find_subchannel_by_instance_reference(d, lineInstance, callReference); else - sub = find_subchannel_by_instance_reference(d, d->lastlineinstance, d->lastcallreference); + sub = d->activeline->activesub; + //sub = find_subchannel_by_instance_reference(d, d->lastlineinstance, d->lastcallreference); if (!sub) return 0; @@ -4230,20 +4504,15 @@ case STIMULUS_HOLD: if (skinnydebug) ast_verb(1, "Received Stimulus: Hold(%d/%d)\n", instance, callreference); - - if (!sub) - break; - - if (sub->onhold) { - skinny_unhold(sub); - } else { - skinny_hold(sub); - } + handle_hold_button(sub); break; case STIMULUS_TRANSFER: if (skinnydebug) ast_verb(1, "Received Stimulus: Transfer(%d/%d)\n", instance, callreference); - /* XXX figure out how to transfer */ + if (l->transfer) + handle_transfer_button(sub); + else + transmit_displaynotify(s, "Transfer disabled", 10); break; case STIMULUS_CONFERENCE: if (skinnydebug) @@ -4386,6 +4655,8 @@ return 0; } + d->activeline = l; + /* turn the speaker on */ transmit_speaker_mode(s, SKINNY_SPEAKERON); transmit_ringer_mode(s, SKINNY_RING_OFF); @@ -4400,7 +4671,7 @@ ast_queue_control(sub->owner, AST_CONTROL_ANSWER); transmit_callstate(s, l->instance, SKINNY_OFFHOOK, sub->callid); transmit_tone(s, SKINNY_SILENCE, l->instance, sub->callid); - transmit_callstate(s, l->instance, SKINNY_CONNECTED, sub->callid); + transmit_callstateonly(s, sub, SKINNY_CONNECTED); transmit_displaypromptstatus(s, "Connected", 0, l->instance, sub->callid); transmit_selectsoftkeys(s, l->instance, sub->callid, KEYDEF_CONNECTED); start_rtp(sub); @@ -4446,22 +4717,40 @@ struct skinny_line *l; struct skinny_subchannel *sub; struct ast_channel *c; + struct skinny_line *tmp; pthread_t t; - int unknown1; - int unknown2; + int instance; + int reference; - unknown1 = letohl(req->data.offhook.unknown1); - unknown2 = letohl(req->data.offhook.unknown2); + /* if any line on a device is offhook, than the device must be offhook, + unless we have shared lines CCM seems that it would never get here, + but asterisk does, so we may need to do more work. Ugly, we should + probably move hookstate from line to device, afterall, it's actually + a device that changes hookstates */ - sub = find_subchannel_by_instance_reference(d, d->lastlineinstance, d->lastcallreference); - - if (!sub) { - l = find_line_by_instance(d, d->lastlineinstance); - if (!l) { + for (tmp = d->lines; tmp; tmp = tmp->next) { + if (tmp->hookstate == SKINNY_OFFHOOK) { + ast_verbose(VERBOSE_PREFIX_3 "Got offhook message when device (%s@%s) already offhook\n", tmp->name, d->name); return 0; } + } + + instance = letohl(req->data.offhook.instance); + reference = letohl(req->data.offhook.reference); + + if (instance) { + sub = find_subchannel_by_instance_reference(d, d->lastlineinstance, d->lastcallreference); + if (!sub) { + l = find_line_by_instance(d, d->lastlineinstance); + if (!l) { + return 0; + } + } else { + l = sub->parent; + } } else { - l = sub->parent; + l = d->activeline; + sub = l->activesub; } transmit_ringer_mode(s, SKINNY_RING_OFF); @@ -4478,7 +4767,7 @@ if (sub && sub->outgoing) { /* We're answering a ringing call */ ast_queue_control(sub->owner, AST_CONTROL_ANSWER); - transmit_callstate(s, l->instance, SKINNY_OFFHOOK, sub->callid); + transmit_callstateonly(s, sub, SKINNY_CONNECTED); transmit_tone(s, SKINNY_SILENCE, l->instance, sub->callid); transmit_callstate(s, l->instance, SKINNY_CONNECTED, sub->callid); transmit_selectsoftkeys(s, l->instance, sub->callid, KEYDEF_CONNECTED); @@ -4515,25 +4804,29 @@ { struct skinny_device *d = s->device; struct skinny_line *l; - struct skinny_subchannel *sub; - int unknown1; - int unknown2; + struct skinny_subchannel *sub, *tmpsub; + int instance; + int reference; + int onlysub = 0; - unknown1 = letohl(req->data.onhook.unknown1); - unknown2 = letohl(req->data.onhook.unknown2); + instance = letohl(req->data.onhook.instance); + reference = letohl(req->data.onhook.reference); - sub = find_subchannel_by_instance_reference(d, d->lastlineinstance, d->lastcallreference); - - if (!sub) { - return 0; + if (instance && reference) { + sub = find_subchannel_by_instance_reference(d, instance, reference); + if (!sub) { + return 0; + } + l = sub->parent; + } else { + l = d->activeline; + sub = l->activesub; } - l = sub->parent; if (l->hookstate == SKINNY_ONHOOK) { /* Something else already put us back on hook */ return 0; } - l->hookstate = SKINNY_ONHOOK; ast_device_state_changed("Skinny/%s@%s", l->name, d->name); @@ -4541,28 +4834,39 @@ return 0; } + if (!l->sub->next) { + onlysub = 1; + } else { + tmpsub = l->sub; + while (tmpsub->next){ + if ((sub == tmpsub->next) && sub->next) { + tmpsub->next = sub->next; + break; + } + tmpsub = tmpsub->next; + } + } + sub->cxmode = SKINNY_CX_RECVONLY; + if (onlysub || sub->xferor){ /* is this the only call to this device? */ + l->hookstate = SKINNY_ONHOOK; + if (skinnydebug) + ast_debug(1, "Skinny %s@%s-%d went on hook\n", l->name, d->name, reference); + } + transmit_callstate(s, l->instance, l->hookstate, sub->callid); - if (skinnydebug) - ast_verb(1, "Skinny %s@%s went on hook\n", l->name, d->name); - if (l->transfer && (sub->owner && sub->next && sub->next->owner) && ((!sub->outgoing) || (sub->next && !sub->next->outgoing))) { + if (l->transfer && sub->xferor && sub->owner->_state >= AST_STATE_RING) { /* We're allowed to transfer, we have two active calls and we made at least one of the calls. Let's try and transfer */ - -#if 0 - if ((res = attempt_transfer(p)) < 0) { - if (sub->next && sub->next->owner) { - sub->next->alreadygone = 1; - ast_queue_hangup(sub->next->owner); - } - } else if (res) { - ast_log(LOG_WARNING, "Transfer attempt failed\n"); - return 0; - } -#endif + handle_transfer_button(sub); } else { /* Hangup the current call */ /* If there is another active call, skinny_hangup will ring the phone with the other call */ + if (sub->xferor && sub->related){ + sub->related->related = NULL; + sub->related->blindxfer = 0; + } + if (sub->owner) { sub->alreadygone = 1; ast_queue_hangup(sub->owner); @@ -5151,20 +5455,16 @@ case SOFTKEY_HOLD: if (skinnydebug) ast_verb(1, "Received Softkey Event: Hold(%d/%d)\n", instance, callreference); - - if (sub) { - if (sub->onhold) { - skinny_unhold(sub); - } else { - skinny_hold(sub); - } - } - + handle_hold_button(sub); break; case SOFTKEY_TRNSFER: if (skinnydebug) ast_verb(1, "Received Softkey Event: Transfer(%d/%d)\n", instance, callreference); - /* XXX figure out how to transfer */ + if (l->transfer) + handle_transfer_button(sub); + else + transmit_displaynotify(s, "Transfer disabled", 10); + break; case SOFTKEY_DND: if (skinnydebug) @@ -5249,29 +5549,44 @@ break; } if (sub) { + int onlysub = 0; + struct skinny_subchannel *tmpsub; + + if (!l->sub->next) { + onlysub = 1; + } else { + tmpsub = l->sub; + while (tmpsub->next){ + if ((sub == tmpsub->next) && sub->next) { + tmpsub->next = sub->next; + break; + } + tmpsub = tmpsub->next; + } + } + sub->cxmode = SKINNY_CX_RECVONLY; - l->hookstate = SKINNY_ONHOOK; + if (onlysub || sub->xferor){ /*Are there other calls to this device */ + l->hookstate = SKINNY_ONHOOK; + if (skinnydebug) + ast_debug(1, "Skinny %s@%s-%d went on hook\n", l->name, d->name, callreference); + } + transmit_callstate(s, l->instance, l->hookstate, sub->callid); if (skinnydebug) ast_verb(1, "Skinny %s@%s went on hook\n", l->name, d->name); - if (l->transfer && (sub->owner && sub->next && sub->next->owner) && ((!sub->outgoing) || (sub->next && !sub->next->outgoing))) { + if (l->transfer && sub->xferor && sub->owner->_state >= AST_STATE_RING) { /* We're allowed to transfer, we have two active calls and we made at least one of the calls. Let's try and transfer */ - -#if 0 - if ((res = attempt_transfer(p)) < 0) { - if (sub->next && sub->next->owner) { - sub->next->alreadygone = 1; - ast_queue_hangup(sub->next->owner); - } - } else if (res) { - ast_log(LOG_WARNING, "Transfer attempt failed\n"); - break; - } -#endif + handle_transfer_button(sub); } else { /* Hangup the current call */ /* If there is another active call, skinny_hangup will ring the phone with the other call */ + if (sub->xferor && sub->related){ + sub->related->related = NULL; + sub->related->blindxfer = 0; + } + if (sub->owner) { sub->alreadygone = 1; ast_queue_hangup(sub->owner); @@ -5288,6 +5603,17 @@ case SOFTKEY_RESUME: if (skinnydebug) ast_verb(1, "Received Softkey Event: Resume(%d/%d)\n", instance, callreference); + + if (sub) { + if (sub->onhold) { + skinny_unhold(sub); + transmit_selectsoftkeys(s, l->instance, sub->callid, KEYDEF_CONNECTED); + } else { + skinny_hold(sub); + transmit_selectsoftkeys(s, l->instance, sub->callid, KEYDEF_ONHOLD); + } + } + break; case SOFTKEY_ANSWER: if (skinnydebug) @@ -5303,7 +5629,7 @@ ast_queue_control(sub->owner, AST_CONTROL_ANSWER); transmit_callstate(s, l->instance, SKINNY_OFFHOOK, sub->callid); transmit_tone(s, SKINNY_SILENCE, l->instance, sub->callid); - transmit_callstate(s, l->instance, SKINNY_CONNECTED, sub->callid); + transmit_callstateonly(s, sub, SKINNY_CONNECTED); transmit_selectsoftkeys(s, l->instance, sub->callid, KEYDEF_CONNECTED); start_rtp(sub); ast_setstate(sub->owner, AST_STATE_UP); @@ -5418,9 +5744,13 @@ lineInstance = letohl(req->data.keypad.lineInstance); callReference = letohl(req->data.keypad.callReference); - sub = find_subchannel_by_instance_reference(d, lineInstance, callReference); + if (lineInstance) { + sub = find_subchannel_by_instance_reference(d, lineInstance, callReference); + } else { + sub = d->activeline->activesub; + } - if (sub && (sub->owner && sub->owner->_state < AST_STATE_UP)) { + if (sub && ((sub->owner && sub->owner->_state < AST_STATE_UP) || sub->onhold)) { char dgt; int digit = letohl(req->data.keypad.button);