implement transfer functionality in chan_skinny
authorMichiel van Baak <michiel@vanbaak.info>
Wed, 25 Jun 2008 19:37:40 +0000 (19:37 +0000)
committerMichiel van Baak <michiel@vanbaak.info>
Wed, 25 Jun 2008 19:37:40 +0000 (19:37 +0000)
(closes issue #9939)
Reported by: wedhorn
Patches:
      transfer_v6.diff uploaded by wedhorn (license 30)
      chan_skinny-transfer-trunk-v10.txt uploaded by DEA (license 3)
      chan_skinny-transfer-trunk-v12.txt uploaded by mvanbaak (license 7)
Tested by: DEA, wedhorn, mvanbaak

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@125096 65c4cc65-6c06-0410-ace0-fbb531ad65f3

channels/chan_skinny.c

index 5758a27..a7ebf8c 100644 (file)
@@ -67,6 +67,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/threadstorage.h"
 #include "asterisk/devicestate.h"
 #include "asterisk/event.h"
+#include "asterisk/indications.h"
 
 /*************************************
  * Skinny/Asterisk Protocol Settings *
@@ -202,14 +203,14 @@ struct stimulus_message {
 
 #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_ringout[] = {
 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 @@ struct display_prompt_status_message {
        char promptMessage[32];
        uint32_t lineInstance;
        uint32_t callReference;
+       uint32_t space[3];
 };
 
 #define CLEAR_PROMPT_MESSAGE  0x0113
@@ -1139,8 +1142,12 @@ struct skinny_subchannel {
        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 skinny_line {
 
        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 @@ static struct skinny_device {
        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 const struct ast_channel_tech skinny_tech = {
 };
 
 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 @@ static void transmit_dialednumber(struct skinnysession *s, const char *text, int
        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;
@@ -3160,6 +3218,9 @@ static struct skinny_device *build_device(const char *cat, struct ast_variable *
                                        l->nat = nat;
                                        l->canreinvite = canreinvite;
 
+                                       if (!d->lines) {
+                                               d->activeline = l;
+                                       }
                                        l->next = d->lines;
                                        d->lines = l;
                                }
@@ -3394,7 +3455,7 @@ static int skinny_call(struct ast_channel *ast, char *dest, int timeout)
                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);
@@ -3409,7 +3470,7 @@ static int skinny_call(struct ast_channel *ast, char *dest, int timeout)
 
 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;
@@ -3421,22 +3482,88 @@ static int skinny_hangup(struct ast_channel *ast)
        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;
@@ -3462,6 +3589,15 @@ static int skinny_answer(struct ast_channel *ast)
 
        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);
@@ -3477,10 +3613,11 @@ static int skinny_answer(struct ast_channel *ast)
           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;
 }
 
@@ -3678,6 +3815,68 @@ static char *control2str(int ind) {
        }
 }
 
+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)
 {
@@ -3698,12 +3897,18 @@ static int skinny_indicate(struct ast_channel *ast, int ind, const void *data, s
                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 */
@@ -3719,7 +3924,7 @@ static int skinny_indicate(struct ast_channel *ast, int ind, const void *data, s
                        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) {
@@ -3732,7 +3937,7 @@ static int skinny_indicate(struct ast_channel *ast, int ind, const void *data, s
                        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) {
@@ -3745,7 +3950,7 @@ static int skinny_indicate(struct ast_channel *ast, int ind, const void *data, s
                        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;
@@ -3805,9 +4010,14 @@ static struct ast_channel *skinny_new(struct skinny_line *l, int state)
                        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;
@@ -3880,7 +4090,6 @@ static int skinny_hold(struct skinny_subchannel *sub)
        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)
@@ -3894,26 +4103,11 @@ static int skinny_hold(struct skinny_subchannel *sub)
                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;
-
-       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_activatecallplane(s, l);
+       transmit_closereceivechannel(s,sub);
+       transmit_stopmediatransmission(s,sub);
 
+       transmit_callstateonly(s, sub, SKINNY_HOLD);
        transmit_lamp_indication(s, STIMULUS_LINE, l->instance, SKINNY_LAMP_WINK);
        sub->onhold = 1;
        return 1;
@@ -3924,7 +4118,6 @@ static int skinny_unhold(struct skinny_subchannel *sub)
        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)
@@ -3936,18 +4129,98 @@ static int skinny_unhold(struct skinny_subchannel *sub)
 
        ast_queue_control(sub->owner, AST_CONTROL_UNHOLD);
 
-       if (!(req = req_alloc(sizeof(struct activate_call_plane_message), ACTIVATE_CALL_PLANE_MESSAGE)))
-               return 0;
-
-       req->data.activatecallplane.lineInstance = htolel(l->instance);
-       transmit_response(s, req);
+       transmit_activatecallplane(s, l);
 
        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)))
@@ -4084,7 +4357,8 @@ static int handle_keypad_button_message(struct skinny_req *req, struct skinnyses
        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;
@@ -4234,20 +4508,15 @@ static int handle_stimulus_message(struct skinny_req *req, struct skinnysession
        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)
@@ -4406,6 +4675,8 @@ static int handle_stimulus_message(struct skinny_req *req, struct skinnysession
                        return 0;
                }
 
+               d->activeline = l;
+
                /* turn the speaker on */
                transmit_speaker_mode(s, SKINNY_SPEAKERON);
                transmit_ringer_mode(s, SKINNY_RING_OFF);
@@ -4420,7 +4691,7 @@ static int handle_stimulus_message(struct skinny_req *req, struct skinnysession
                        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);
@@ -4466,22 +4737,40 @@ static int handle_offhook_message(struct skinny_req *req, struct skinnysession *
        struct skinny_line *l;
        struct skinny_subchannel *sub;
        struct ast_channel *c;
+       struct skinny_line *tmp;
        pthread_t t;
-       int unknown1;
-       int unknown2;
-
-       unknown1 = letohl(req->data.offhook.unknown1);
-       unknown2 = letohl(req->data.offhook.unknown2);
+       int instance;
+       int reference;
 
-       sub = find_subchannel_by_instance_reference(d, d->lastlineinstance, d->lastcallreference);
+       /* 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 */
 
-       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);
@@ -4498,7 +4787,7 @@ static int handle_offhook_message(struct skinny_req *req, struct skinnysession *
        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);
@@ -4535,25 +4824,29 @@ static int handle_onhook_message(struct skinny_req *req, struct skinnysession *s
 {
        struct skinny_device *d = s->device;
        struct skinny_line *l;
-       struct skinny_subchannel *sub;
-       int unknown1;
-       int unknown2;
-
-       unknown1 = letohl(req->data.onhook.unknown1);
-       unknown2 = letohl(req->data.onhook.unknown2);
+       struct skinny_subchannel *sub, *tmpsub;
+       int instance;
+       int reference;
+       int onlysub = 0;
 
-       sub = find_subchannel_by_instance_reference(d, d->lastlineinstance, d->lastcallreference);
+       instance = letohl(req->data.onhook.instance);
+       reference = letohl(req->data.onhook.reference);
 
-       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);
 
@@ -4561,28 +4854,39 @@ static int handle_onhook_message(struct skinny_req *req, struct skinnysession *s
                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);
@@ -5171,20 +5475,16 @@ static int handle_soft_key_event_message(struct skinny_req *req, struct skinnyse
        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)
@@ -5269,29 +5569,44 @@ static int handle_soft_key_event_message(struct skinny_req *req, struct skinnyse
                        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);
@@ -5308,6 +5623,17 @@ static int handle_soft_key_event_message(struct skinny_req *req, struct skinnyse
        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)
@@ -5323,7 +5649,7 @@ static int handle_soft_key_event_message(struct skinny_req *req, struct skinnyse
                        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);
@@ -5454,9 +5780,13 @@ static int handle_message(struct skinny_req *req, struct skinnysession *s)
                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);