#include "precomp.h"


//
// CONTROL.CPP
// Control by us, control of us
//
// Copyright(c) Microsoft 1997-
//

#define MLZ_FILE_ZONE  ZONE_CORE




//
// CA_ReceivedPacket()
//
void  ASShare::CA_ReceivedPacket
(
    ASPerson *      pasFrom,
    PS20DATAPACKET  pPacket
)
{
    PCAPACKET       pCAPacket;

    DebugEntry(ASShare::CA_ReceivedPacket);

    ValidatePerson(pasFrom);

    pCAPacket = (PCAPACKET)pPacket;

    switch (pCAPacket->msg)
    {
        case CA_MSG_NOTIFY_STATE:
            if (pasFrom->cpcCaps.general.version < CAPS_VERSION_30)
            {
                ERROR_OUT(("Ignoring CA_MSG_NOTIFY_STATE from 2.x node [%d]",
                    pasFrom->mcsID));
            }
            else
            {
                CAHandleNewState(pasFrom, (PCANOTPACKET)pPacket);
            }
            break;

        case CA_OLDMSG_DETACH:
        case CA_OLDMSG_COOPERATE:
            // Set "cooperating", and map it to allow/disallow control
            CA2xCooperateChange(pasFrom, (pCAPacket->msg == CA_OLDMSG_COOPERATE));
            break;

        case CA_OLDMSG_REQUEST_CONTROL:
            CA2xRequestControl(pasFrom, pCAPacket);
            break;

        case CA_OLDMSG_GRANTED_CONTROL:
            CA2xGrantedControl(pasFrom, pCAPacket);
            break;

        default:
            // Ignore for now -- old 2.x messages
            break;
    }

    DebugExitVOID(ASShare::CA_ReceivedPacket);
}



//
// CA30_ReceivedPacket()
//
void ASShare::CA30_ReceivedPacket
(
    ASPerson *      pasFrom,
    PS20DATAPACKET  pPacket
)
{
    LPBYTE          pCAPacket;

    DebugEntry(ASShare::CA30_ReceivedPacket);

    pCAPacket = (LPBYTE)pPacket + sizeof(CA30PACKETHEADER);

    if (pasFrom->cpcCaps.general.version < CAPS_VERSION_30)
    {
        ERROR_OUT(("Ignoring CA30 packet %d from 2.x node [%d]",
            ((PCA30PACKETHEADER)pPacket)->msg, pasFrom->mcsID));
        DC_QUIT;
    }

    switch (((PCA30PACKETHEADER)pPacket)->msg)
    {
        // From VIEWER (remote) to HOST (us)
        case CA_REQUEST_TAKECONTROL:
        {
            CAHandleRequestTakeControl(pasFrom, (PCA_RTC_PACKET)pCAPacket);
            break;
        }

        // From HOST (remote) to VIEWER (us)
        case CA_REPLY_REQUEST_TAKECONTROL:
        {
            CAHandleReplyRequestTakeControl(pasFrom, (PCA_REPLY_RTC_PACKET)pCAPacket);
            break;
        }

        // From HOST (remote) to VIEWER (us)
        case CA_REQUEST_GIVECONTROL:
        {
            CAHandleRequestGiveControl(pasFrom, (PCA_RGC_PACKET)pCAPacket);
            break;
        }

        // From VIEWER (remote) to HOST (us)
        case CA_REPLY_REQUEST_GIVECONTROL:
        {
            CAHandleReplyRequestGiveControl(pasFrom, (PCA_REPLY_RGC_PACKET)pCAPacket);
            break;
        }

        // From CONTROLLER (remote) to HOST (us)
        case CA_PREFER_PASSCONTROL:
        {
            CAHandlePreferPassControl(pasFrom, (PCA_PPC_PACKET)pCAPacket);
            break;
        }



        // From CONTROLLER (remote) to HOST (us)
        case CA_INFORM_RELEASEDCONTROL:
        {
            CAHandleInformReleasedControl(pasFrom, (PCA_INFORM_PACKET)pCAPacket);
            break;
        }

        // From HOST (remote) to CONTROLLER (us)
        case CA_INFORM_REVOKEDCONTROL:
        {
            CAHandleInformRevokedControl(pasFrom, (PCA_INFORM_PACKET)pCAPacket);
            break;
        }

        // From HOST (remote) to CONTROLLER (us)
        case CA_INFORM_PAUSEDCONTROL:
        {
            CAHandleInformPausedControl(pasFrom, (PCA_INFORM_PACKET)pCAPacket);
            break;
        }

        // From HOST (remote) to CONTROLLER (us)
        case CA_INFORM_UNPAUSEDCONTROL:
        {
            CAHandleInformUnpausedControl(pasFrom, (PCA_INFORM_PACKET)pCAPacket);
            break;
        }

        default:
        {
            WARNING_OUT(("CA30_ReceivedPacket: unrecognized message %d",
                ((PCA30PACKETHEADER)pPacket)->msg));
            break;
        }
    }

DC_EXIT_POINT:
    DebugExitVOID(ASShare::CA30_ReceivedPacket);
}



//
// CANewRequestID()
//
// Returns a new token.  It uses the current value, fills in the new one, and
// also returns the new one.  We wrap around if necessary.  ZERO is never
// valid.  Note that this is a unique identifier only to us.
//
// It is a stamp for the control operation.  Since you can't be controlling
// and controlled at the same time, we have one stamp for all ops.
//
UINT ASShare::CANewRequestID(void)
{
    DebugEntry(ASShare::CANewRequestID);

    ++(m_pasLocal->m_caControlID);
    if (m_pasLocal->m_caControlID == 0)
    {
        ++(m_pasLocal->m_caControlID);
    }

    DebugExitDWORD(ASShare::CANewRequestID, m_pasLocal->m_caControlID);
    return(m_pasLocal->m_caControlID);
}



//
// CA_ViewStarting()
// Called when a REMOTE starts hosting
//
// We only do anything if it's a 2.x node since they could be cooperating
// but not hosting.
//
BOOL ASShare::CA_ViewStarting(ASPerson * pasPerson)
{
    DebugEntry(ASShare::CA_ViewStarting);

    //
    // If this isn't a back level system, ignore it.
    //
    if (pasPerson->cpcCaps.general.version >= CAPS_VERSION_30)
    {
        DC_QUIT;
    }

    //
    // See if AllowControl should now be on.
    //
    if (pasPerson->m_ca2xCooperating)
    {
        //
        // Yes, it should.  2.x node is cooperating, now they are hosting,
        // and we can take control of them.
        //
        ASSERT(!pasPerson->m_caAllowControl);
        pasPerson->m_caAllowControl = TRUE;
        VIEW_HostStateChange(pasPerson);
    }

DC_EXIT_POINT:
    DebugExitBOOL(ASShare::CA_ViewStarting, TRUE);
    return(TRUE);
}


//
// CA_ViewEnded()
// Called when a REMOTE stopped hosting
//
void ASShare::CA_ViewEnded(ASPerson * pasPerson)
{
    PCAREQUEST  pRequest;
    PCAREQUEST  pNext;

    DebugEntry(ASShare::CA_ViewEnded);

    //
    // Clear any control stuff we are a part of where they are the host
    //
    CA_ClearLocalState(CACLEAR_VIEW, pasPerson, FALSE);

    //
    // Clear any control stuff involving remotes
    //
    if (pasPerson->m_caControlledBy)
    {
        ASSERT(pasPerson->m_caControlledBy != m_pasLocal);

        CAClearHostState(pasPerson, NULL);
        ASSERT(!pasPerson->m_caControlledBy);
    }

    pasPerson->m_caAllowControl = FALSE;

    //
    // Clean up outstanding control packets to this person
    //
    pRequest = (PCAREQUEST)COM_BasedListFirst(&m_caQueuedMsgs, FIELD_OFFSET(CAREQUEST, chain));
    while (pRequest)
    {
        pNext = (PCAREQUEST)COM_BasedListNext(&m_caQueuedMsgs, pRequest,
            FIELD_OFFSET(CAREQUEST, chain));

        if (pRequest->destID == pasPerson->mcsID)
        {
            if (pRequest->type == REQUEST_30)
            {
                //
                // Delete messages sent by us to this person who is hosting
                //
                switch (pRequest->msg)
                {
                    case CA_REQUEST_TAKECONTROL:
                    case CA_PREFER_PASSCONTROL:
                    case CA_REPLY_REQUEST_GIVECONTROL:
                        WARNING_OUT(("Deleting viewer control message %d, person [%d] stopped hosting",
                            pRequest->msg, pasPerson->mcsID));
                        COM_BasedListRemove(&pRequest->chain);
                        delete pRequest;
                        break;
                }
            }
            else
            {
                ASSERT(pRequest->type == REQUEST_2X);

                // Change GRANTED_CONTROL packets to this host to DETACH
                if (pRequest->msg == CA_OLDMSG_GRANTED_CONTROL)
                {
                    //
                    // For 2.x messages, destID is only non-zero when we are
                    // attempting to control a particular node.  It allows us
                    // to undo/cancel control, to map our one-to-one model
                    // into the global 2.x collaboration model.
                    //

                    //
                    // Make this a DETACH, that way we don't have to worry if
                    // part of the COOPERATE/GRANTED_CONTROL sequence got out
                    // but part was left in the queue.
                    //
                    WARNING_OUT(("Changing GRANTED_CONTROL to 2.x host [%d] into DETATCH",
                        pasPerson->mcsID));

                    pRequest->destID            = 0;
                    pRequest->msg               = CA_OLDMSG_DETACH;
                    pRequest->req.req2x.data1   = 0;
                    pRequest->req.req2x.data2   = 0;
                }
            }
        }

        pRequest = pNext;
    }

    DebugExitVOID(ASView::CA_ViewEnded);
}

//
// CA_PartyLeftShare()
//
void ASShare::CA_PartyLeftShare(ASPerson * pasPerson)
{
    DebugEntry(ASShare::CA_PartyLeftShare);

    ValidatePerson(pasPerson);

    //
    // Clean up 2.x control stuff
    //
    if (pasPerson == m_ca2xControlTokenOwner)
    {
        m_ca2xControlTokenOwner = NULL;
    }

    //
    // We must have cleaned up hosting info for this person already.
    // So it can't be controlled or controllable.
    //
    ASSERT(!pasPerson->m_caAllowControl);
    ASSERT(!pasPerson->m_caControlledBy);

    if (pasPerson != m_pasLocal)
    {
        PCAREQUEST  pRequest;
        PCAREQUEST  pNext;

        //
        // Clear any control stuff we are a part of where they are the
        // viewer.
        //
        CA_ClearLocalState(CACLEAR_HOST, pasPerson, FALSE);

        //
        // Clear any control stuff involving remotes
        //
        if (pasPerson->m_caInControlOf)
        {
            ASSERT(pasPerson->m_caInControlOf != m_pasLocal);
            CAClearHostState(pasPerson->m_caInControlOf, NULL);
        }

        //
        // Clean up outgoing packets meant for this person.
        //
        pRequest = (PCAREQUEST)COM_BasedListFirst(&m_caQueuedMsgs, FIELD_OFFSET(CAREQUEST, chain));
        while (pRequest)
        {
            pNext = (PCAREQUEST)COM_BasedListNext(&m_caQueuedMsgs, pRequest,
                FIELD_OFFSET(CAREQUEST, chain));

            //
            // This doesn't need to know if it's a 2.x or 3.0 request,
            // simply remove queued packets intended for somebody leaving.
            //
            // Only GRANTED_CONTROL requests will have non-zero destIDs of
            // the 2.x packets.
            //
            if (pRequest->destID == pasPerson->mcsID)
            {
                WARNING_OUT(("Freeing outgoing RESPONSE to node [%d]", pasPerson->mcsID));

                COM_BasedListRemove(&(pRequest->chain));
                delete pRequest;
            }

            pRequest = pNext;
        }

        ASSERT(m_caWaitingForReplyFrom != pasPerson);
    }
    else
    {
        //
        // When our waiting for/controlled dude stopped sharing, we should
        // have cleaned this goop up.
        //
        ASSERT(!pasPerson->m_caInControlOf);
        ASSERT(!pasPerson->m_caControlledBy);
        ASSERT(!m_caWaitingForReplyFrom);
        ASSERT(!m_caWaitingForReplyMsg);

        //
        // There should be NO outgoing control requests
        //
        ASSERT(COM_BasedListIsEmpty(&(m_caQueuedMsgs)));
    }

    DebugExitVOID(ASShare::CA_PartyLeftShare);
}



//
// CA_Periodic() -> SHARE STUFF
//
void  ASShare::CA_Periodic(void)
{
    DebugEntry(ASShare::CA_Periodic);

    //
    // Flush as many queued outgoing messages as we can
    //
    CAFlushOutgoingPackets();

    DebugExitVOID(ASShare::CA_Periodic);
}



//
// CA_SyncAlreadyHosting()
//
void ASHost::CA_SyncAlreadyHosting(void)
{
    DebugEntry(ASHost::CA_SyncAlreadyHosting);

    m_caRetrySendState          = TRUE;

    DebugExitVOID(ASHost::CA_SyncAlreadyHosting);
}


//
// CA_Periodic() -> HOSTING STUFF
//
void ASHost::CA_Periodic(void)
{
    DebugEntry(ASHost::CA_Periodic);

    if (m_caRetrySendState)
    {
        PCANOTPACKET  pPacket;
#ifdef _DEBUG
        UINT            sentSize;
#endif // _DEBUG

        pPacket = (PCANOTPACKET)m_pShare->SC_AllocPkt(PROT_STR_MISC, g_s20BroadcastID,
            sizeof(*pPacket));
        if (!pPacket)
        {
            WARNING_OUT(("CA_Periodic: couldn't broadcast new state"));
        }
        else
        {
            pPacket->header.data.dataType   = DT_CA;
            pPacket->msg                    = CA_MSG_NOTIFY_STATE;

            pPacket->state                  = 0;
            if (m_pShare->m_pasLocal->m_caAllowControl)
                pPacket->state              |= CASTATE_ALLOWCONTROL;

            if (m_pShare->m_pasLocal->m_caControlledBy)
                pPacket->controllerID       = m_pShare->m_pasLocal->m_caControlledBy->mcsID;
            else
                pPacket->controllerID       = 0;

#ifdef _DEBUG
            sentSize =
#endif // _DEBUG
            m_pShare->DCS_CompressAndSendPacket(PROT_STR_MISC, g_s20BroadcastID,
                &(pPacket->header), sizeof(*pPacket));

            m_caRetrySendState = FALSE;
        }
    }

    DebugExitVOID(ASHost::CA_Periodic);
}



//
// CAFlushOutgoingPackets()
//
// This tries to send private packets (not broadcast notifications) that
// we have accumulated.  It returns TRUE if the outgoing queue is empty.
//
BOOL ASShare::CAFlushOutgoingPackets(void)
{
    BOOL            fEmpty = TRUE;
    PCAREQUEST      pRequest;

    //
    // If we're hosting and haven't yet flushed the HET or CA state,
    // force queueing.
    //
    if (m_hetRetrySendState || (m_pHost && m_pHost->m_caRetrySendState))
    {
        TRACE_OUT(("CAFlushOutgoingPackets:  force queuing, pending HET/CA state broadcast"));
        fEmpty = FALSE;
        DC_QUIT;
    }

    while (pRequest = (PCAREQUEST)COM_BasedListFirst(&m_caQueuedMsgs,
        FIELD_OFFSET(CAREQUEST, chain)))
    {
        //
        // Allocate/send packet
        //
        if (pRequest->type == REQUEST_30)
        {
            if (!CASendPacket(pRequest->destID, pRequest->msg,
                &pRequest->req.req30.packet))
            {
                WARNING_OUT(("CAFlushOutgoingPackets: couldn't send request"));
                fEmpty = FALSE;
                break;
            }
        }
        else
        {
            ASSERT(pRequest->type == REQUEST_2X);

            if (!CA2xSendMsg(pRequest->destID, pRequest->msg,
                pRequest->req.req2x.data1, pRequest->req.req2x.data2))
            {
                WARNING_OUT(("CAFlushOutgoingmsgs: couldn't send request"));
                fEmpty = FALSE;
                break;
            }
        }

        //
        // Do we do state transitions here or when things are added to queue?
        // requestID, results are calculated when put on queue.  Results can
        // change though based on a future action.
        //

        COM_BasedListRemove(&(pRequest->chain));
        delete pRequest;
    }

DC_EXIT_POINT:
    DebugExitBOOL(CAFlushOutgoingPackets, fEmpty);
    return(fEmpty);
}


//
// CASendPacket()
// This sends a private message (request or response) to the destination.
// If there are queued private messages in front of this one, or we can't
// send it, we add it to the pending queue.
//
// This TRUE if sent.
//
// It's up to the caller to change state info appropriately.
//
BOOL  ASShare::CASendPacket
(
    UINT_PTR            destID,
    UINT            msg,
    PCA30P          pData
)
{
    BOOL                fSent = FALSE;
    PCA30PACKETHEADER   pPacket;
#ifdef _DEBUG
    UINT                sentSize;
#endif // _DEBUG

    DebugEntry(ASShare::CASendPacket);

    //
    // Note that CA30P does not include size of header.
    //
    pPacket = (PCA30PACKETHEADER)SC_AllocPkt(PROT_STR_INPUT, destID,
        sizeof(CA30PACKETHEADER) + sizeof(*pData));
    if (!pPacket)
    {
        WARNING_OUT(("CASendPacket: no memory to send %d packet to [%d]",
            msg, destID));
        DC_QUIT;
    }

    pPacket->header.data.dataType   = DT_CA30;
    pPacket->msg                    = msg;
    memcpy(pPacket+1, pData, sizeof(*pData));

#ifdef _DEBUG
    sentSize =
#endif // _DEBUG
    DCS_CompressAndSendPacket(PROT_STR_INPUT, destID,
            &(pPacket->header), sizeof(*pPacket));
    TRACE_OUT(("CA30 request packet size: %08d, sent %08d", sizeof(*pPacket), sentSize));

    fSent = TRUE;

DC_EXIT_POINT:

    DebugExitBOOL(ASShare::CASendPacket, fSent);
    return(fSent);
}




//
// CAQueueSendPacket()
// This flushes pending queued requests if there are any, then tries to
// send this one.  If it can't, we add it to the queue.  If there's not any
// memory even for that, we return an error about it.
//
BOOL ASShare::CAQueueSendPacket
(
    UINT_PTR            destID,
    UINT            msg,
    PCA30P          pPacketSend
)
{
    BOOL            rc = TRUE;
    PCAREQUEST      pCARequest;

    DebugEntry(ASShare::CAQueueSendPacket);

    //
    // These must go out in order.  So if any queued messages are still
    // present, those must be sent first.
    //
    if (!CAFlushOutgoingPackets() ||
        !CASendPacket(destID, msg, pPacketSend))
    {
        //
        // We must queue this.
        //
        TRACE_OUT(("CAQueueSendPacket: queuing request for send later"));

        pCARequest = new CAREQUEST;
        if (!pCARequest)
        {
            ERROR_OUT(("CAQueueSendPacket: can't even allocate memory to queue request; must fail"));
            rc = FALSE;
        }
        else
        {
            SET_STAMP(pCARequest, CAREQUEST);

            pCARequest->type                    = REQUEST_30;
            pCARequest->destID                  = destID;
            pCARequest->msg                     = msg;
            pCARequest->req.req30.packet        = *pPacketSend;

            //
            // Stick this at the end of the queue
            //
            COM_BasedListInsertBefore(&(m_caQueuedMsgs), &(pCARequest->chain));
        }
    }

    DebugExitBOOL(ASShare::CAQueueSendPacket, rc);
    return(rc);
}



//
// CALangToggle()
//
// This temporarily turns off the keyboard language toggle key, so that a
// remote controlling us doesn't inadvertently change it.  When we stop being
// controlled, we put it back.
//
void  ASShare::CALangToggle(BOOL fBackOn)
{
    //
    // Local Variables
    //
    LONG        rc;
    HKEY        hkeyToggle;
    BYTE        regValue[2];
    DWORD       cbRegValue;
    DWORD       dwType;
    LPCSTR      szValue;

    DebugEntry(ASShare::CALangToggle);

    szValue = (g_asWin95) ? NULL : LANGUAGE_TOGGLE_KEY_VAL;

    if (fBackOn)
    {
        //
        // We are gaining control of our local keyboard again - we restore the
        // language togging functionality.
        //
        // We must directly access the registry to accomplish this.
        //
        if (m_caToggle != LANGUAGE_TOGGLE_NOT_PRESENT)
        {
            rc = RegOpenKey(HKEY_CURRENT_USER, LANGUAGE_TOGGLE_KEY,
                        &hkeyToggle);

            if (rc == ERROR_SUCCESS)
            {
                //
                // Clear the value for this key.
                //
                regValue[0] = m_caToggle;
                regValue[1] = '\0';                  // ensure NUL termination

                //
                // Restore the value.
                //
                RegSetValueEx(hkeyToggle, szValue, 0, REG_SZ,
                    regValue, sizeof(regValue));

                //
                // We need to inform the system about this change.  We do not
                // tell any other apps about this (ie do not set any of the
                // notification flags as the last parm)
                //
                SystemParametersInfo(SPI_SETLANGTOGGLE, 0, 0, 0);
            }

            RegCloseKey(hkeyToggle);
        }
    }
    else
    {
        //
        // We are losing control of our keyboard - ensure that remote key
        // events will not change our local keyboard settings by disabling the
        // keyboard language toggle.
        //
        // We must directly access the registry to accomplish this.
        //
        rc = RegOpenKey(HKEY_CURRENT_USER, LANGUAGE_TOGGLE_KEY,
                    &hkeyToggle);

        if (rc == ERROR_SUCCESS)
        {
            cbRegValue = sizeof(regValue);

            rc = RegQueryValueEx(hkeyToggle, szValue, NULL,
                &dwType, regValue, &cbRegValue);

            if (rc == ERROR_SUCCESS)
            {
                m_caToggle = regValue[0];

                //
                // Clear the value for this key.
                //
                regValue[0] = '3';
                regValue[1] = '\0';                  // ensure NUL termination

                //
                // Clear the value.
                //
                RegSetValueEx(hkeyToggle, szValue, 0, REG_SZ,
                    regValue, sizeof(regValue));

                //
                // We need to inform the system about this change.  We do not
                // tell any other apps about this (ie do not set any of the
                // notification flags as the last parm)
                //
                SystemParametersInfo(SPI_SETLANGTOGGLE, 0, 0, 0);
            }
            else
            {
                m_caToggle = LANGUAGE_TOGGLE_NOT_PRESENT;
            }

            RegCloseKey(hkeyToggle);
        }
    }

    DebugExitVOID(ASShare::CALangToggle);
}



//
// CAStartControlled()
//
void ASShare::CAStartControlled
(
    ASPerson *  pasInControl,
    UINT        controlID
)
{
    DebugEntry(ASShare::CAStartControlled);

    ValidatePerson(pasInControl);

    //
    // Undo last known state of remote
    //
    CAClearRemoteState(pasInControl);

    //
    // Get any VIEW frame UI out of the way
    //
    SendMessage(g_asSession.hwndHostUI, HOST_MSG_CONTROLLED, TRUE, 0);
    VIEWStartControlled(TRUE);

    ASSERT(!m_pasLocal->m_caControlledBy);
    m_pasLocal->m_caControlledBy = pasInControl;

    ASSERT(!pasInControl->m_caInControlOf);
    pasInControl->m_caInControlOf = m_pasLocal;

    ASSERT(!pasInControl->m_caControlID);
    ASSERT(controlID);
    pasInControl->m_caControlID = controlID;

    //
    // Notify IM.
    //
    IM_Controlled(pasInControl);

    //
    // Disable language toggling.
    //
    CALangToggle(FALSE);

    ASSERT(m_pHost);
    m_pHost->CM_Controlled(pasInControl);

    //
    // Notify the UI.  Pass GCCID of controller
    //
    DCS_NotifyUI(SH_EVT_STARTCONTROLLED, pasInControl->cpcCaps.share.gccID, 0);

    //
    // Broadcast new state
    //
    m_pHost->m_caRetrySendState = TRUE;
    m_pHost->CA_Periodic();

    DebugExitVOID(ASShare::CAStartControlled);
}



//
// CAStopControlled()
//
void ASShare::CAStopControlled(void)
{
    ASPerson *  pasControlledBy;

    DebugEntry(ASShare::CAStopControlled);

    pasControlledBy = m_pasLocal->m_caControlledBy;
    ValidatePerson(pasControlledBy);

    //
    // If control is paused, unpause it.
    //
    if (m_pasLocal->m_caControlPaused)
    {
        CA_PauseControl(pasControlledBy, FALSE, FALSE);
    }

    m_pasLocal->m_caControlledBy        = NULL;

    ASSERT(pasControlledBy->m_caInControlOf == m_pasLocal);
    pasControlledBy->m_caInControlOf    = NULL;

    ASSERT(pasControlledBy->m_caControlID);
    pasControlledBy->m_caControlID      = 0;

    //
    // Notify IM.
    //
    IM_Controlled(NULL);

    //
    // Restore language toggling functionality.
    //
    CALangToggle(TRUE);

    ASSERT(m_pHost);
    m_pHost->CM_Controlled(NULL);

    VIEWStartControlled(FALSE);
    ASSERT(IsWindow(g_asSession.hwndHostUI));
    SendMessage(g_asSession.hwndHostUI, HOST_MSG_CONTROLLED, FALSE, 0);


    //
    // Notify the UI
    //
    DCS_NotifyUI(SH_EVT_STOPCONTROLLED, pasControlledBy->cpcCaps.share.gccID, 0);

    //
    // Broadcast the new state
    //
    m_pHost->m_caRetrySendState = TRUE;
    m_pHost->CA_Periodic();

    DebugExitVOID(ASShare::CAStopControlled);
}


//
// CAStartInControl()
//
void ASShare::CAStartInControl
(
    ASPerson *  pasControlled,
    UINT        controlID
)
{
    DebugEntry(ASShare::CAStartInControl);

    ValidatePerson(pasControlled);

    //
    // Undo last known state of host
    //
    CAClearRemoteState(pasControlled);

    ASSERT(!m_pasLocal->m_caInControlOf);
    m_pasLocal->m_caInControlOf = pasControlled;

    ASSERT(!pasControlled->m_caControlledBy);
    pasControlled->m_caControlledBy = m_pasLocal;

    ASSERT(!pasControlled->m_caControlID);
    ASSERT(controlID);
    pasControlled->m_caControlID = controlID;

    ASSERT(!g_lpimSharedData->imControlled);
    IM_InControl(pasControlled);

    VIEW_InControl(pasControlled, TRUE);

    //
    // Pass GCC ID of node we're controlling
    //
    DCS_NotifyUI(SH_EVT_STARTINCONTROL, pasControlled->cpcCaps.share.gccID, 0);

    DebugExitVOID(ASShare::CAStartInControl);
}


//
// CAStopInControl()
//
void ASShare::CAStopInControl(void)
{
    ASPerson *  pasInControlOf;

    DebugEntry(ASShare::CAStopInControl);

    pasInControlOf = m_pasLocal->m_caInControlOf;
    ValidatePerson(pasInControlOf);

    if (pasInControlOf->m_caControlPaused)
    {
        pasInControlOf->m_caControlPaused = FALSE;
    }

    m_pasLocal->m_caInControlOf         = NULL;

    ASSERT(pasInControlOf->m_caControlledBy == m_pasLocal);
    pasInControlOf->m_caControlledBy    = NULL;

    ASSERT(pasInControlOf->m_caControlID);
    pasInControlOf->m_caControlID       = 0;

    ASSERT(!g_lpimSharedData->imControlled);
    IM_InControl(NULL);

    VIEW_InControl(pasInControlOf, FALSE);

    DCS_NotifyUI(SH_EVT_STOPINCONTROL, pasInControlOf->cpcCaps.share.gccID, 0);

    DebugExitVOID(ASShare::CAStopInControl);
}


//
// CA_AllowControl()
// Allows/disallows remotes from controlling us.
//
void ASShare::CA_AllowControl(BOOL fAllow)
{
    DebugEntry(ASShare::CA_AllowControl);

    if (!m_pHost)
    {
        WARNING_OUT(("CA_AllowControl: ignoring, we aren't hosting"));
        DC_QUIT;
    }

    if (fAllow != m_pasLocal->m_caAllowControl)
    {
        if (!fAllow)
        {
            // Undo pending control/control queries/being controlled stuff
            CA_ClearLocalState(CACLEAR_HOST, NULL, TRUE);
        }

        m_pasLocal->m_caAllowControl = fAllow;

        ASSERT(IsWindow(g_asSession.hwndHostUI));
        SendMessage(g_asSession.hwndHostUI, HOST_MSG_ALLOWCONTROL, fAllow, 0);

        DCS_NotifyUI(SH_EVT_CONTROLLABLE, fAllow, 0);

        m_pHost->m_caRetrySendState = TRUE;
    }

DC_EXIT_POINT:
    DebugExitVOID(ASShare::CA_AllowControl);
}





//
// CA_HostEnded()
//
// When we stop hosting, we do not need to flush queued control
// responses.  But we need to delete them!
//
void ASHost::CA_HostEnded(void)
{
    PCAREQUEST  pCARequest;
    PCAREQUEST  pCANext;

    DebugEntry(ASHost::CA_HostEnded);

    m_pShare->CA_ClearLocalState(CACLEAR_HOST, NULL, FALSE);

    //
    // Delete now obsolete messages originating from us as host.
    //
    pCARequest = (PCAREQUEST)COM_BasedListFirst(&m_pShare->m_caQueuedMsgs,
        FIELD_OFFSET(CAREQUEST, chain));
    while (pCARequest)
    {
        pCANext = (PCAREQUEST)COM_BasedListNext(&m_pShare->m_caQueuedMsgs, pCARequest,
            FIELD_OFFSET(CAREQUEST, chain));

        if (pCARequest->type == REQUEST_30)
        {
            switch (pCARequest->msg)
            {
                //
                // Delete messages sent by us when we are hosting.
                //
                case CA_INFORM_PAUSEDCONTROL:
                case CA_INFORM_UNPAUSEDCONTROL:
                case CA_REPLY_REQUEST_TAKECONTROL:
                case CA_REQUEST_GIVECONTROL:
                    WARNING_OUT(("Deleting host control message %d, we stopped hosting",
                        pCARequest->msg));
                    COM_BasedListRemove(&pCARequest->chain);
                    delete pCARequest;
                    break;
            }
        }

        pCARequest = pCANext;
    }

    if (m_pShare->m_pasLocal->m_caAllowControl)
    {
        m_pShare->m_pasLocal->m_caAllowControl = FALSE;

        ASSERT(IsWindow(g_asSession.hwndHostUI));
        SendMessage(g_asSession.hwndHostUI, HOST_MSG_ALLOWCONTROL, FALSE, 0);

        DCS_NotifyUI(SH_EVT_CONTROLLABLE, FALSE, 0);
    }

    DebugExitVOID(ASHost::CA_HostEnded);
}



//
// CA_TakeControl()
//
// Called by viewer to ask to take control of host.  Note parallels to
// CA_GiveControl(), which is called by host to get same result.
//
void ASShare::CA_TakeControl(ASPerson *  pasHost)
{
    DebugEntry(ASShare::CA_TakeControl);

    ValidatePerson(pasHost);
    ASSERT(pasHost != m_pasLocal);

    //
    // If this person isn't hosting or controllable, fail.
    //
    if (!pasHost->m_pView)
    {
        WARNING_OUT(("CA_TakeControl: failing, person [%d] not hosting",
            pasHost->mcsID));
        DC_QUIT;
    }

    if (!pasHost->m_caAllowControl)
    {
        WARNING_OUT(("CA_TakeControl: failing, host [%d] not controllable",
            pasHost->mcsID));
        DC_QUIT;
    }

    //
    // Undo current state.
    //
    CA_ClearLocalState(CACLEAR_ALL, NULL, TRUE);

    //
    // Now take control.
    //
    if (pasHost->cpcCaps.general.version >= CAPS_VERSION_30)
    {
        //
        // 3.0 host
        //
        CA30P   packetSend;

        ZeroMemory(&packetSend, sizeof(packetSend));
        packetSend.rtc.viewerControlID = CANewRequestID();

        if (CAQueueSendPacket(pasHost->mcsID, CA_REQUEST_TAKECONTROL, &packetSend))
        {
            //
            // Now we're in waiting state.
            //
            CAStartWaiting(pasHost, CA_REPLY_REQUEST_TAKECONTROL);
            VIEW_UpdateStatus(pasHost, IDS_STATUS_WAITINGFORCONTROL);
        }
        else
        {
            WARNING_OUT(("CA_TakeControl of [%d]: failing, out of memory", pasHost->mcsID));
        }
    }
    else
    {
        CA2xTakeControl(pasHost);
    }

DC_EXIT_POINT:
    DebugExitVOID(ASShare::CA_TakeControl);
}



//
// CA_CancelTakeControl()
//
void ASShare::CA_CancelTakeControl
(
    ASPerson *  pasHost,
    BOOL        fPacket
)
{
    DebugEntry(ASShare::CA_CancelTakeControl);

    ValidatePerson(pasHost);
    ASSERT(pasHost != m_pasLocal);

    if ((m_caWaitingForReplyFrom        != pasHost) ||
        (m_caWaitingForReplyMsg         != CA_REPLY_REQUEST_TAKECONTROL))
    {
        // We're not waiting for control of this host.
        WARNING_OUT(("CA_CancelTakeControl failing; not waiting to take control of [%d]",
            pasHost->mcsID));
        DC_QUIT;
    }

    ASSERT(pasHost->cpcCaps.general.version >= CAPS_VERSION_30);
    ASSERT(pasHost->m_caControlID == 0);

    if (fPacket)
    {
        CA30P   packetSend;

        ZeroMemory(&packetSend, sizeof(packetSend));
        packetSend.inform.viewerControlID   = m_pasLocal->m_caControlID;
        packetSend.inform.hostControlID     = pasHost->m_caControlID;

        if (!CAQueueSendPacket(pasHost->mcsID, CA_INFORM_RELEASEDCONTROL,
            &packetSend))
        {
            WARNING_OUT(("Couldn't tell node [%d] we're no longer waiting for control",
                pasHost->mcsID));
        }
    }

    m_caWaitingForReplyFrom     = NULL;
    m_caWaitingForReplyMsg      = 0;

    VIEW_UpdateStatus(pasHost, IDS_STATUS_NONE);

DC_EXIT_POINT:
    DebugExitVOID(ASShare::CA_CancelTakeControl);
}



//
// CA_ReleaseControl()
//
void ASShare::CA_ReleaseControl
(
    ASPerson *  pasHost,
    BOOL        fPacket
)
{
    DebugEntry(ASShare::CA_ReleaseControl);

    ValidatePerson(pasHost);
    ASSERT(pasHost != m_pasLocal);

    if (pasHost->m_caControlledBy != m_pasLocal)
    {
        // We're not in control of this dude, nothing to do.
        WARNING_OUT(("CA_ReleaseControl failing; not in control of [%d]",
            pasHost->mcsID));
        DC_QUIT;
    }

    ASSERT(!m_caWaitingForReplyFrom);
    ASSERT(!m_caWaitingForReplyMsg);

    if (fPacket)
    {
        if (pasHost->cpcCaps.general.version >= CAPS_VERSION_30)
        {
            CA30P   packetSend;

            ZeroMemory(&packetSend, sizeof(packetSend));
            packetSend.inform.viewerControlID   = m_pasLocal->m_caControlID;
            packetSend.inform.hostControlID     = pasHost->m_caControlID;

            if (!CAQueueSendPacket(pasHost->mcsID, CA_INFORM_RELEASEDCONTROL,
                &packetSend))
            {
                WARNING_OUT(("Couldn't tell node [%d] they're no longer controlled",
                    pasHost->mcsID));
            }
        }
        else
        {
            if (!CA2xQueueSendMsg(0, CA_OLDMSG_DETACH, 0, 0))
            {
                WARNING_OUT(("Couldn't tell 2.x node [%d] they're no longer controlled",
                    pasHost->mcsID));
            }
        }
    }

    CAStopInControl();

DC_EXIT_POINT:
    DebugExitVOID(ASShare::CA_ReleaseControl);
}



//
// CA_PassControl()
//
void ASShare::CA_PassControl(ASPerson *  pasHost, ASPerson *  pasViewer)
{
    CA30P       packetSend;

    DebugEntry(ASShare::CA_PassControl);

    ValidatePerson(pasHost);
    ValidatePerson(pasViewer);
    ASSERT(pasHost != pasViewer);
    ASSERT(pasHost != m_pasLocal);
    ASSERT(pasViewer != m_pasLocal);

    if (pasHost->m_caControlledBy != m_pasLocal)
    {
        WARNING_OUT(("CA_PassControl: failing, we're not in control of [%d]",
            pasHost->mcsID));
        DC_QUIT;
    }

    ASSERT(!m_caWaitingForReplyFrom);
    ASSERT(!m_caWaitingForReplyMsg);

    //
    // No 2.x nodes, neither host nor controller, allowed
    //
    if ((pasHost->cpcCaps.general.version < CAPS_VERSION_30) ||
        (pasViewer->cpcCaps.general.version < CAPS_VERSION_30))
    {
        WARNING_OUT(("CA_PassControl: failing, we can't pass control with 2.x nodes"));
        DC_QUIT;
    }

    ZeroMemory(&packetSend, sizeof(packetSend));
    packetSend.ppc.viewerControlID  = m_pasLocal->m_caControlID;
    packetSend.ppc.hostControlID    = pasHost->m_caControlID;
    packetSend.ppc.mcsPassTo        = pasViewer->mcsID;

    if (CAQueueSendPacket(pasHost->mcsID, CA_PREFER_PASSCONTROL, &packetSend))
    {
        CAStopInControl();
    }
    else
    {
        WARNING_OUT(("Couldn't tell node [%d] we want them to pass control to [%d]",
            pasHost->mcsID, pasViewer->mcsID));
    }

DC_EXIT_POINT:
    DebugExitVOID(ASShare::CA_PassControl);
}




//
// CA_GiveControl()
//
// Called by host to ask to grant control to viewer.  Note parallels to
// CA_TakeControl(), which is called by viewer to get same result.
//
void ASShare::CA_GiveControl(ASPerson * pasTo)
{
    CA30P       packetSend;

    DebugEntry(ASShare::CA_GiveControl);

    ValidatePerson(pasTo);
    ASSERT(pasTo != m_pasLocal);

    //
    // If we aren't hosting or controllable, fail.
    //
    if (!m_pHost)
    {
        WARNING_OUT(("CA_GiveControl: failing, we're not hosting"));
        DC_QUIT;
    }

    if (!m_pasLocal->m_caAllowControl)
    {
        WARNING_OUT(("CA_GiveControl: failing, we're not controllable"));
        DC_QUIT;
    }

    if (pasTo->cpcCaps.general.version < CAPS_VERSION_30)
    {
        //
        // Can't do this with 2.x node.
        //
        WARNING_OUT(("CA_GiveControl: failing, can't invite 2.x node [%d]",
            pasTo->mcsID));
        DC_QUIT;
    }

    //
    // Undo our control state.
    //
    CA_ClearLocalState(CACLEAR_ALL, NULL, TRUE);

    //
    // Now invite control.
    //
    ZeroMemory(&packetSend, sizeof(packetSend));
    packetSend.rgc.hostControlID    = CANewRequestID();
    packetSend.rgc.mcsPassFrom      = 0;

    if (CAQueueSendPacket(pasTo->mcsID, CA_REQUEST_GIVECONTROL, &packetSend))
    {
        //
        // Now we're in waiting state.
        //
        CAStartWaiting(pasTo, CA_REPLY_REQUEST_GIVECONTROL);
    }
    else
    {
        WARNING_OUT(("CA_GiveControl of [%d]: failing, out of memory", pasTo->mcsID));
    }

DC_EXIT_POINT:
    DebugExitVOID(ASShare::CA_GiveControl);
}



//
// CA_CancelGiveControl()
// Cancels an invite TAKE or PASS request.
//
void ASShare::CA_CancelGiveControl
(
    ASPerson *  pasTo,
    BOOL        fPacket
)
{
    DebugEntry(ASShare::CA_CancelGiveControl);

    ValidatePerson(pasTo);
    ASSERT(pasTo != m_pasLocal);

    //
    // Have we invited this person, and are we now waiting for a response?
    //
    if ((m_caWaitingForReplyFrom        != pasTo)   ||
        (m_caWaitingForReplyMsg         != CA_REPLY_REQUEST_GIVECONTROL))
    {
        // We're not waiting to be controlled by this viewer.
        WARNING_OUT(("CA_CancelGiveControl failing; not waiting to give control to [%d]",
            pasTo->mcsID));
        DC_QUIT;
    }

    ASSERT(pasTo->cpcCaps.general.version >= CAPS_VERSION_30);
    ASSERT(!pasTo->m_caControlID);

    if (fPacket)
    {
        CA30P   packetSend;

        ZeroMemory(&packetSend, sizeof(packetSend));
        packetSend.inform.viewerControlID   = pasTo->m_caControlID;
        packetSend.inform.hostControlID     = m_pasLocal->m_caControlID;

        if (!CAQueueSendPacket(pasTo->mcsID, CA_INFORM_REVOKEDCONTROL,
            &packetSend))
        {
            WARNING_OUT(("Couldn't tell node [%d] they're no longer invited to control us",
               pasTo->mcsID));
        }
    }

    m_caWaitingForReplyFrom     = NULL;
    m_caWaitingForReplyMsg      = 0;

DC_EXIT_POINT:
    DebugExitVOID(ASShare::CA_CancelGiveControl);
}




//
// CA_RevokeControl()
// Takes control back.  If we're cleaning up (we've stopped hosting or
//
//
void ASShare::CA_RevokeControl
(
    ASPerson *  pasInControl,
    BOOL        fPacket
)
{
    CA30P       packetSend;
    PCAREQUEST  pRequest;

    DebugEntry(ASShare::CA_RevokeControl);

    //
    // If the response to pasController is still queued, simply delete it.
    // There should NOT be any CARESULT_CONFIRMED responses left.
    //
    // Otherwise, if it wasn't found, we must send a packet.
    //
    ValidatePerson(pasInControl);
    ASSERT(pasInControl != m_pasLocal);

    if (pasInControl != m_pasLocal->m_caControlledBy)
    {
        WARNING_OUT(("CA_RevokeControl: node [%d] not in control of us",
            pasInControl->mcsID));
        DC_QUIT;
    }

    //
    // Take control back if we're being controlled
    //
    if (fPacket)
    {
        //
        // Regardless of whether we can queue or not, we get control back!
        // Note that we use the controller's request ID, so he knows if
        // this is still applicable.
        //
        ZeroMemory(&packetSend, sizeof(packetSend));
        packetSend.inform.viewerControlID  = pasInControl->m_caControlID;
        packetSend.inform.hostControlID    = m_pasLocal->m_caControlID;

        if (!CAQueueSendPacket(pasInControl->mcsID, CA_INFORM_REVOKEDCONTROL,
            &packetSend))

        {
            WARNING_OUT(("Couldn't tell node [%d] they're no longer in control",
                pasInControl->mcsID));
        }
    }

    CAStopControlled();

DC_EXIT_POINT:
    DebugExitVOID(ASShare::CA_RevokeControl);
}




//
// CA_PauseControl()
//
void ASShare::CA_PauseControl
(
    ASPerson *  pasControlledBy,
    BOOL        fPause,
    BOOL        fPacket
)
{
    DebugEntry(ASShare::CA_PauseControl);

    ValidatePerson(pasControlledBy);
    ASSERT(pasControlledBy != m_pasLocal);

    //
    // If we aren't a controlled host, this doesn't do anything.
    //
    if (pasControlledBy != m_pasLocal->m_caControlledBy)
    {
        WARNING_OUT(("CA_PauseControl failing; not controlled by [%d]", pasControlledBy->mcsID));
        DC_QUIT;
    }

    ASSERT(m_pHost);
    ASSERT(m_pasLocal->m_caAllowControl);

    if (m_pasLocal->m_caControlPaused == (fPause != FALSE))
    {
        WARNING_OUT(("CA_PauseControl failing; already in requested state"));
        DC_QUIT;
    }

    if (fPacket)
    {
        CA30P       packetSend;

        ZeroMemory(&packetSend, sizeof(packetSend));
        packetSend.inform.viewerControlID   = m_pasLocal->m_caControlledBy->m_caControlID;
        packetSend.inform.hostControlID     = m_pasLocal->m_caControlID;

        if (!CAQueueSendPacket(m_pasLocal->m_caControlledBy->mcsID,
            (fPause ? CA_INFORM_PAUSEDCONTROL : CA_INFORM_UNPAUSEDCONTROL),
            &packetSend))
        {
            WARNING_OUT(("CA_PauseControl: out of memory, can't notify [%d]",
                m_pasLocal->m_caControlledBy->mcsID));
        }
    }

    // Do pause
    m_pasLocal->m_caControlPaused   = (fPause != FALSE);
    g_lpimSharedData->imPaused      = (fPause != FALSE);

    DCS_NotifyUI((fPause ? SH_EVT_PAUSEDCONTROLLED : SH_EVT_UNPAUSEDCONTROLLED),
        pasControlledBy->cpcCaps.share.gccID, 0);

DC_EXIT_POINT:
    DebugExitVOID(ASShare::CA_PauseControl);
}




//
// CAHandleRequestTakeControl()
//      WE are HOST, REMOTE is VIEWER
// Handles incoming take control request.  If our state is good, we accept.
//
void ASShare::CAHandleRequestTakeControl
(
    ASPerson *      pasViewer,
    PCA_RTC_PACKET  pPacketRecv
)
{
    UINT            result = CARESULT_CONFIRMED;

    DebugEntry(ASShare::CAHandleRequestTakeControl);

    ValidatePerson(pasViewer);

    //
    // If we aren't hosting, or haven't turned allow control on, we're
    // not controllable.
    //
    if (!m_pHost || !m_pasLocal->m_caAllowControl)
    {
        result = CARESULT_DENIED_WRONGSTATE;
        goto RESPOND_PACKET;
    }

    //
    // Are we doing something else right now?  Waiting to hear back about
    // something?
    //

    if (m_caWaitingForReplyFrom)
    {
        result = CARESULT_DENIED_BUSY;
        goto RESPOND_PACKET;
    }

    if (m_caQueryDlg)
    {
        result = CARESULT_DENIED_BUSY;
        goto RESPOND_PACKET;
    }

    //
    // LAURABU TEMPORARY:
    // In a bit, if we're controlled when a new control request comes in,
    // pause control then allow host to handle it.
    //
    if (m_pasLocal->m_caControlledBy)
    {
        result = CARESULT_DENIED_BUSY;
        goto RESPOND_PACKET;
    }


    //
    // Try to put up query dialog
    //
    if (!CAStartQuery(pasViewer, CA_REQUEST_TAKECONTROL, (PCA30P)pPacketRecv))
    {
        result = CARESULT_DENIED;
    }

RESPOND_PACKET:
    if (result != CARESULT_CONFIRMED)
    {
        // Instant failure.
        CACompleteRequestTakeControl(pasViewer, pPacketRecv, result);
    }
    else
    {
        //
        // We're in a waiting state.  CACompleteRequestTakeControl() will
        // complete later or the request will just go away.
        //
    }

    DebugExitVOID(ASShare::CAHandleRequestTakeControl);
}



//
// CACompleteRequestTakeControl()
//      WE are HOST, REMOTE is VIEWER
// Completes the take control request.
//
void ASShare::CACompleteRequestTakeControl
(
    ASPerson *      pasFrom,
    PCA_RTC_PACKET  pPacketRecv,
    UINT            result
)
{
    CA30P           packetSend;

    DebugEntry(ASShare::CACompleteRequestTakeControl);

    ValidatePerson(pasFrom);

    ZeroMemory(&packetSend, sizeof(packetSend));
    packetSend.rrtc.viewerControlID     = pPacketRecv->viewerControlID;
    packetSend.rrtc.result              = result;

    if (result == CARESULT_CONFIRMED)
    {
        packetSend.rrtc.hostControlID   = CANewRequestID();
    }

    if (CAQueueSendPacket(pasFrom->mcsID, CA_REPLY_REQUEST_TAKECONTROL, &packetSend))
    {
        if (result == CARESULT_CONFIRMED)
        {
            // Clear current state, whatever that is.
            CA_ClearLocalState(CACLEAR_ALL, NULL, TRUE);

            // We are now controlled by the sender.
            CAStartControlled(pasFrom, pPacketRecv->viewerControlID);
        }
        else
        {
            WARNING_OUT(("Denying REQUEST TAKE CONTROL from [%d] with reason %d",
                pasFrom->mcsID, result));
        }
    }
    else
    {
        WARNING_OUT(("Reply to REQUEST TAKE CONTROL from [%d] failing, out of memory",
            pasFrom->mcsID));
    }

    DebugExitVOID(ASShare::CACompleteRequestTakeControl);
}



//
// CAHandleReplyRequestTakeControl()
//      WE are VIEWER, REMOTE is HOST
// Handles reply to previous take control request.
//
void ASShare::CAHandleReplyRequestTakeControl
(
    ASPerson *              pasHost,
    PCA_REPLY_RTC_PACKET    pPacketRecv
)
{
    DebugEntry(ASShare::CAHandleReplyRequestTakeControl);

    ValidatePerson(pasHost);

    if (pPacketRecv->result == CARESULT_CONFIRMED)
    {
        // On success, should have valid op ID.
        ASSERT(pPacketRecv->hostControlID);
    }
    else
    {
        // On failure, should have invalid op ID.
        ASSERT(!pPacketRecv->hostControlID);
    }

    //
    // Is this response for the current control op?
    //
    if ((m_caWaitingForReplyFrom        != pasHost) ||
        (m_caWaitingForReplyMsg         != CA_REPLY_REQUEST_TAKECONTROL))
    {
        WARNING_OUT(("Ignoring TAKE CONTROL REPLY from [%d], not waiting for one",
            pasHost->mcsID));
        DC_QUIT;
    }

    if (pPacketRecv->viewerControlID    != m_pasLocal->m_caControlID)
    {
        WARNING_OUT(("Ignoring TAKE CONTROL REPLY from [%d], request %d is out of date",
            pasHost->mcsID, pPacketRecv->viewerControlID));
        DC_QUIT;

    }

    ASSERT(!m_caQueryDlg);

    //
    // Cleanup waiting state (for both failure & success)
    //
    CA_CancelTakeControl(pasHost, FALSE);
    ASSERT(!m_caWaitingForReplyFrom);
    ASSERT(!m_caWaitingForReplyMsg);

    if (pPacketRecv->result == CARESULT_CONFIRMED)
    {
        // Success!  We're now in control of the host.

        // Make sure our own state is OK
        ASSERT(!m_pasLocal->m_caControlledBy);
        ASSERT(!m_pasLocal->m_caInControlOf);

        CAStartInControl(pasHost, pPacketRecv->hostControlID);
    }
    else
    {
        UINT        ids;

        WARNING_OUT(("TAKE CONTROL REPLY from host [%d] is failure %d", pasHost->mcsID,
            pPacketRecv->result));

        ids = IDS_ERR_TAKECONTROL_MIN + pPacketRecv->result;
        if ((ids < IDS_ERR_TAKECONTROL_FIRST) || (ids > IDS_ERR_TAKECONTROL_LAST))
            ids = IDS_ERR_TAKECONTROL_LAST;

        VIEW_Message(pasHost, ids);
    }

DC_EXIT_POINT:
    DebugExitVOID(ASShare::CAHandleReplyRequestTakeControl);
}




//
// CAHandleRequestGiveControl()
//      WE are VIEWER, REMOTE is HOST
// Handles incoming take control invite.  If our state is good, we accept.
//
// NOTE how similar this routine is to CAHandleRequestTakeControl().  They
// are inverses of each other.  With RequestTake/Reply sequence, viewer
// initiates, host finishes.  With RequestGive/Reply sequence, host initiates,
// viewer finishes.  Both end up with viewer in control of host when
// completed successfully.
//
void ASShare::CAHandleRequestGiveControl
(
    ASPerson *      pasHost,
    PCA_RGC_PACKET  pPacketRecv
)
{
    UINT            result = CARESULT_CONFIRMED;

    DebugEntry(ASShare::CAHandleRequestGiveControl);

    ValidatePerson(pasHost);

    //
    // Is this node hosting as far as we know.  If not, or has not turned
    // on allow control, we can't do it.
    //
    if (!pasHost->m_pView)
    {
        WARNING_OUT(("GIVE CONTROL went ahead of HOSTING, that's bad"));
        result = CARESULT_DENIED_WRONGSTATE;
        goto RESPOND_PACKET;
    }

    if (!pasHost->m_caAllowControl)
    {
        //
        // We haven't got an AllowControl notification yet, this info is
        // more up to-date.  Make use of it.
        //
        WARNING_OUT(("GIVE CONTROL went ahead of ALLOW CONTROL, that's kind of bad"));
        result = CARESULT_DENIED_WRONGSTATE;
        goto RESPOND_PACKET;
    }


    //
    // Are we doing something else right now?  Waiting to hear back about
    // something?
    //
    if (m_caWaitingForReplyFrom)
    {
        result = CARESULT_DENIED_BUSY;
        goto RESPOND_PACKET;
    }

    if (m_caQueryDlg)
    {
        result = CARESULT_DENIED_BUSY;
        goto RESPOND_PACKET;
    }

    //
    // LAURABU TEMPORARY:
    // In a bit, if we're controlled when a new control request comes in,
    // pause control then allow host to handle it.
    //
    if (m_pasLocal->m_caControlledBy)
    {
        result = CARESULT_DENIED_BUSY;
        goto RESPOND_PACKET;
    }

    //
    // Try to put up query dialog
    //
    if (!CAStartQuery(pasHost, CA_REQUEST_GIVECONTROL, (PCA30P)pPacketRecv))
    {
        result = CARESULT_DENIED;
    }

RESPOND_PACKET:
    if (result != CARESULT_CONFIRMED)
    {
        // Instant failure.
        CACompleteRequestGiveControl(pasHost, pPacketRecv, result);
    }
    else
    {
        //
        // We're in a waiting state.  CACompleteRequestGiveControl() will
        // complete later or the request will just go away.
        //
    }

    DebugExitVOID(ASShare::CAHandleRequestGiveControl);
}



//
// CACompleteRequestGiveControl()
//      WE are VIEWER, REMOTE is HOST
// Completes the invite control request.
//
void ASShare::CACompleteRequestGiveControl
(
    ASPerson *      pasFrom,
    PCA_RGC_PACKET  pPacketRecv,
    UINT            result
)
{
    CA30P           packetSend;

    DebugEntry(ASShare::CACompleteRequestGiveControl);

    ValidatePerson(pasFrom);

    ZeroMemory(&packetSend, sizeof(packetSend));
    packetSend.rrgc.hostControlID       = pPacketRecv->hostControlID;
    packetSend.rrgc.result              = result;

    if (result == CARESULT_CONFIRMED)
    {
        packetSend.rrgc.viewerControlID     = CANewRequestID();
    }

    if (CAQueueSendPacket(pasFrom->mcsID, CA_REPLY_REQUEST_GIVECONTROL, &packetSend))
    {
        //
        // If this is successful, change our state.  We're now in control.
        //
        if (result == CARESULT_CONFIRMED)
        {
            // Clear current state, whatever that is.
            CA_ClearLocalState(CACLEAR_ALL, NULL, TRUE);

            CAStartInControl(pasFrom, pPacketRecv->hostControlID);
        }
        else
        {
            WARNING_OUT(("Denying GIVE CONTROL from [%d] with reason %d",
                pasFrom->mcsID, result));
        }
    }
    else
    {
        WARNING_OUT(("Reply to GIVE CONTROL from [%d] failing, out of memory",
            pasFrom->mcsID));
    }

    DebugExitVOID(ASShare::CACompleteRequestGiveControl);
}




//
// CAHandleReplyRequestGiveControl()
//      WE are HOST, REMOTE is VIEWER
// Handles reply to previous take control invite.
//
void ASShare::CAHandleReplyRequestGiveControl
(
    ASPerson *              pasViewer,
    PCA_REPLY_RGC_PACKET    pPacketRecv
)
{
    DebugEntry(ASShare::CAHandleReplyRequestGiveControl);

    ValidatePerson(pasViewer);

    if (pPacketRecv->result == CARESULT_CONFIRMED)
    {
        // On success, should have valid op ID.
        ASSERT(pPacketRecv->viewerControlID);
    }
    else
    {
        // On failure, should have invalid op ID.
        ASSERT(!pPacketRecv->viewerControlID);
    }

    //
    // Is this response for the latest control op?
    //
    if ((m_caWaitingForReplyFrom        != pasViewer) ||
        (m_caWaitingForReplyMsg         != CA_REPLY_REQUEST_GIVECONTROL))
    {
        WARNING_OUT(("Ignoring GIVE CONTROL REPLY from [%d], not waiting for one",
            pasViewer->mcsID));
        DC_QUIT;
    }

    if (pPacketRecv->hostControlID     != m_pasLocal->m_caControlID)
    {
        WARNING_OUT(("Ignoring GIVE CONTROL REPLY from [%d], request %d is out of date",
            pasViewer->mcsID, pPacketRecv->hostControlID));
        DC_QUIT;
    }

    ASSERT(!m_caQueryDlg);
    ASSERT(m_pHost);
    ASSERT(m_pasLocal->m_caAllowControl);

    //
    // Cleanup waiting state (for both failure & success)
    //
    CA_CancelGiveControl(pasViewer, FALSE);
    ASSERT(!m_caWaitingForReplyFrom);
    ASSERT(!m_caWaitingForReplyMsg);

    if (pPacketRecv->result == CARESULT_CONFIRMED)
    {
        // Success!  We are now controlled by the viewer

        // Make sure our own state is OK
        ASSERT(!m_pasLocal->m_caControlledBy);
        ASSERT(!m_pasLocal->m_caInControlOf);

        CAStartControlled(pasViewer, pPacketRecv->viewerControlID);
    }
    else
    {
        WARNING_OUT(("GIVE CONTROL to viewer [%d] was denied", pasViewer->mcsID));
    }

DC_EXIT_POINT:
    DebugExitVOID(ASShare::CAHandleReplyRequestGiveControl);
}




//
// CAHandlePreferPassControl()
//      WE are HOST, REMOTE is CONTROLLER
// Handles incoming pass control request.  If we are controlled by the
// remote, and end user is cool with it, accept.
//
void ASShare::CAHandlePreferPassControl
(
    ASPerson *      pasController,
    PCA_PPC_PACKET  pPacketRecv
)
{
    ASPerson *      pasNewController;

    DebugEntry(ASShare::CAHandlePreferPassControl);

    ValidatePerson(pasController);

    //
    // If we're not controlled by the requester, ignore it.
    //
    if (m_pasLocal->m_caControlledBy    != pasController)
    {
        WARNING_OUT(("Ignoring PASS CONTROL from [%d], not controlled by him",
            pasController->mcsID));
        DC_QUIT;
    }

    if ((pPacketRecv->viewerControlID   != pasController->m_caControlID) ||
        (pPacketRecv->hostControlID     != m_pasLocal->m_caControlID))
    {
        WARNING_OUT(("Ignoring PASS CONTROL from [%d], request %d %d out of date",
            pasController->mcsID, pPacketRecv->viewerControlID, pPacketRecv->hostControlID));
        DC_QUIT;
    }

    ASSERT(!m_caQueryDlg);
    ASSERT(!m_caWaitingForReplyFrom);
    ASSERT(!m_caWaitingForReplyMsg);

    //
    // OK, the sender is not in control of us anymore.
    //
    CA_RevokeControl(pasController, FALSE);

    // Is the pass to person specified valid?
    pasNewController = SC_PersonFromNetID(pPacketRecv->mcsPassTo);
    if (!pasNewController                       ||
        (pasNewController == pasController)     ||
        (pasNewController == m_pasLocal)        ||
        (pasNewController->cpcCaps.general.version < CAPS_VERSION_30))
    {
        WARNING_OUT(("PASS CONTROL to [%d] failing, not valid person to pass to",
            pPacketRecv->mcsPassTo));
        DC_QUIT;
    }

    //
    // Try to put up query dialog
    //
    if (!CAStartQuery(pasController, CA_PREFER_PASSCONTROL, (PCA30P)pPacketRecv))
    {
        // Instant failure.  In this case, no packet.
        WARNING_OUT(("Denying PREFER PASS CONTROL from [%d], out of memory",
            pasController->mcsID));
    }
    else
    {
        //
        // We're in a waiting state.  CACompletePreferPassControl() will
        // complete later or the request will just go away.
        //
    }

DC_EXIT_POINT:
    DebugExitVOID(ASShare::CAHandlePreferPassControl);
}



//
// CACompletePreferPassControl()
//      WE are HOST, REMOTE is new potential CONTROLLER
// Completes the prefer pass control request.
//
void ASShare::CACompletePreferPassControl
(
    ASPerson *      pasTo,
    UINT_PTR            mcsOrg,
    PCA_PPC_PACKET  pPacketRecv,
    UINT            result
)
{
    CA30P           packetSend;

    DebugEntry(ASShare::CACompletePreferPassControl);

    ValidatePerson(pasTo);

    if (result == CARESULT_CONFIRMED)
    {
        ZeroMemory(&packetSend, sizeof(packetSend));
        packetSend.rgc.hostControlID = CANewRequestID();
        packetSend.rgc.mcsPassFrom   = mcsOrg;

        if (CAQueueSendPacket(pasTo->mcsID, CA_REQUEST_GIVECONTROL,
                &packetSend))
        {
            CA_ClearLocalState(CACLEAR_HOST, NULL, TRUE);

            CAStartWaiting(pasTo, CA_REPLY_REQUEST_GIVECONTROL);
        }
        else
        {
            WARNING_OUT(("Reply to PREFER PASS CONTROL from [%d] to [%d] failing, out of memory",
                mcsOrg, pasTo->mcsID));
        }
    }
    else
    {
        WARNING_OUT(("Denying PREFER PASS CONTROL from [%d] to [%d] with reason %d",
            mcsOrg, pasTo->mcsID, result));
    }

    DebugExitVOID(ASShare::CACompletePreferPassControl);
}




//
// CAHandleInformReleasedControl()
//      WE are HOST, REMOTE is CONTROLLER
//
void ASShare::CAHandleInformReleasedControl
(
    ASPerson *              pasController,
    PCA_INFORM_PACKET       pPacketRecv
)
{
    DebugEntry(ASShare::CAHandleInformReleasedControl);

    ValidatePerson(pasController);

    //
    // Do we currently have a TakeControl dialog up for this request?  If so,
    // take it down but don't send a packet.
    //
    if (m_caQueryDlg                            &&
        (m_caQuery.pasReplyTo    == pasController)   &&
        (m_caQuery.msg      == CA_REQUEST_TAKECONTROL)  &&
        (m_caQuery.request.rtc.viewerControlID  == pPacketRecv->viewerControlID))
    {
        ASSERT(!pPacketRecv->hostControlID);
        CACancelQuery(pasController, FALSE);
        DC_QUIT;
    }

    //
    // If this person isn't in control of us or the control op referred to
    // isn't the current one, ignore.  NULL hostControlID means the person
    // cancelled a request before they heard back from us.
    //

    if (pasController->m_caInControlOf  != m_pasLocal)
    {
        WARNING_OUT(("Ignoring RELEASE CONTROL from [%d], we're not controlled by them",
            pasController->mcsID));
        DC_QUIT;
    }

    if (pPacketRecv->viewerControlID    != pasController->m_caControlID)
    {
        WARNING_OUT(("Ignoring RELEASE CONTROL from [%d], viewer ID out of date",
            pasController->mcsID, pPacketRecv->viewerControlID));
        DC_QUIT;
    }

    if (pPacketRecv->hostControlID && (pPacketRecv->hostControlID != m_pasLocal->m_caControlID))
    {
        WARNING_OUT(("Ignoring RELEASE CONTROL from [%d], host ID out of date",
            pasController->mcsID, pPacketRecv->hostControlID));
        DC_QUIT;
    }


    // Undo control, but no packet gets sent, we're just cleaning up.
    CA_RevokeControl(pasController, FALSE);

DC_EXIT_POINT:
    DebugExitVOID(ASShare::CAHandleInformReleasedControl);
}




//
// CAHandleInformRevokedControl()
//      WE are CONTROLLER, REMOTE is HOST
//
void ASShare::CAHandleInformRevokedControl
(
    ASPerson *              pasHost,
    PCA_INFORM_PACKET       pPacketRecv
)
{
    DebugEntry(ASShare::CAHandleInformRevokedControl);

    ValidatePerson(pasHost);

    //
    // Do we currently have a GiveControl dialog up for this request?  If so,
    // take it down but don't send a packet.
    //

    if (m_caQueryDlg                            &&
        (m_caQuery.pasReplyTo        == pasHost)     &&
        (m_caQuery.msg          == CA_REQUEST_GIVECONTROL)   &&
        (m_caQuery.request.rgc.hostControlID == pPacketRecv->hostControlID))
    {
        ASSERT(!pPacketRecv->viewerControlID);
        CACancelQuery(pasHost, FALSE);
        DC_QUIT;
    }

    //
    // If this person isn't controlled by us or the control op referred to
    // isn't the current one, ignore.
    //
    if (pasHost->m_caControlledBy       != m_pasLocal)
    {
        WARNING_OUT(("Ignoring REVOKE CONTROL from [%d], not in control of them",
            pasHost->mcsID));
        DC_QUIT;
    }

    if (pPacketRecv->hostControlID     != pasHost->m_caControlID)
    {
        WARNING_OUT(("Ignoring REVOKE CONTROL from [%d], host ID out of date",
            pasHost->mcsID, pPacketRecv->hostControlID));
        DC_QUIT;
    }

    if (pPacketRecv->viewerControlID && (pPacketRecv->viewerControlID != m_pasLocal->m_caControlID))
    {
        WARNING_OUT(("Ignoring REVOKE CONTROL from [%d], viewer ID out of date",
            pasHost->mcsID, pPacketRecv->viewerControlID));
        DC_QUIT;
    }


    // Undo control, but no packet gets sent, we're just cleaning up.
    CA_ReleaseControl(pasHost, FALSE);

DC_EXIT_POINT:
    DebugExitVOID(ASShare::CAHandleInformRevokedControl);
}



//
// CAHandleInformPausedControl()
//      WE are CONTROLLER, REMOTE is HOST
//
void ASShare::CAHandleInformPausedControl
(
    ASPerson *              pasHost,
    PCA_INFORM_PACKET       pPacketRecv
)
{
    DebugEntry(ASShare::CAHandleInformPausedControl);

    ValidatePerson(pasHost);

    if (pasHost->m_caControlledBy != m_pasLocal)
    {
        WARNING_OUT(("Ignoring control paused from [%d], not controlled by us",
            pasHost->mcsID));
        DC_QUIT;
    }

    if (pasHost->m_caControlPaused)
    {
        WARNING_OUT(("Ignoring control paused from [%d], already paused",
            pasHost->mcsID));
        DC_QUIT;
    }

    pasHost->m_caControlPaused = TRUE;
    VIEW_PausedInControl(pasHost, TRUE);

    DCS_NotifyUI(SH_EVT_PAUSEDINCONTROL, pasHost->cpcCaps.share.gccID, 0);

DC_EXIT_POINT:
    DebugExitVOID(ASShare::CAHandleInformPausedControl);
}




//
// CAHandleInformUnpausedControl()
//      WE are CONTROLLER, REMOTE is HOST
//
void ASShare::CAHandleInformUnpausedControl
(
    ASPerson *              pasHost,
    PCA_INFORM_PACKET       pPacketRecv
)
{
    DebugEntry(ASShare::CAHandleInformUnpausedControl);

    ValidatePerson(pasHost);

    if (pasHost->m_caControlledBy != m_pasLocal)
    {
        WARNING_OUT(("Ignoring control unpaused from [%d], not controlled by us",
            pasHost->mcsID));
        DC_QUIT;
    }

    if (!pasHost->m_caControlPaused)
    {
        WARNING_OUT(("Ignoring control unpaused from [%d], not paused",
            pasHost->mcsID));
        DC_QUIT;
    }

    pasHost->m_caControlPaused = FALSE;
    VIEW_PausedInControl(pasHost, FALSE);

    DCS_NotifyUI(SH_EVT_UNPAUSEDINCONTROL, pasHost->cpcCaps.share.gccID, 0);

DC_EXIT_POINT:
    DebugExitVOID(ASShare::CAHandleInformUnpausedControl);
}




void ASShare::CAHandleNewState
(
    ASPerson *      pasHost,
    PCANOTPACKET    pPacket
)
{
    BOOL            caOldAllowControl;
    BOOL            caNewAllowControl;
    ASPerson *      pasController;

    DebugEntry(ASShare::CAHandleNewState);

    //
    // If this node isn't hosting, ignore this.
    //
    ValidatePerson(pasHost);
    ASSERT(pasHost->cpcCaps.general.version >= CAPS_VERSION_30);
    ASSERT(pasHost->hetCount);

    //
    // Update controllable state FIRST, so view window changes will
    // reflect it.
    //
    caOldAllowControl           = pasHost->m_caAllowControl;
    caNewAllowControl           = ((pPacket->state & CASTATE_ALLOWCONTROL) != 0);

    if (!caNewAllowControl && (pasHost->m_caControlledBy == m_pasLocal))
    {
        //
        // Fix up bogus notification
        //
        ERROR_OUT(("CA_STATE notification error!  We're in control of [%d] but he says he's not controllable.",
            pasHost->mcsID));
        CA_ReleaseControl(pasHost, FALSE);
    }

    pasHost->m_caAllowControl   = caNewAllowControl;


    // Update/clear controller
    if (!pPacket->controllerID)
    {
        pasController = NULL;
    }
    else
    {
        pasController = SC_PersonFromNetID(pPacket->controllerID);
        if (pasController == pasHost)
        {
            ERROR_OUT(("Bogus controller, same as host [%d]", pPacket->controllerID));
            pasController = NULL;
        }
    }

    if (!CAClearHostState(pasHost, pasController))
    {
        // This failed.  Put back old controllable state.
        pasHost->m_caAllowControl = caOldAllowControl;
    }

    // Force a state change if the allow state has altered
    if (caOldAllowControl != pasHost->m_caAllowControl)
    {
        VIEW_HostStateChange(pasHost);
    }

    DebugExitVOID(ASShare::CAHandleNewState);
}



//
// CAStartWaiting()
// Sets up vars for waiting state.
//
void ASShare::CAStartWaiting
(
    ASPerson *  pasWaitForReplyFrom,
    UINT        msgWaitForReplyFrom
)
{
    DebugEntry(ASShare::CAStartWaiting);

    ValidatePerson(pasWaitForReplyFrom);
    ASSERT(msgWaitForReplyFrom);

    ASSERT(!m_caWaitingForReplyFrom);
    ASSERT(!m_caWaitingForReplyMsg);

    m_caWaitingForReplyFrom    = pasWaitForReplyFrom;
    m_caWaitingForReplyMsg     = msgWaitForReplyFrom;

    DebugExitVOID(ASShare::CAStartWaiting);
}


//
// CA_ClearLocalState()
//
// Called to reset control state for LOCAL dude.
//
void ASShare::CA_ClearLocalState
(
    UINT        flags,
    ASPerson *  pasRemote,
    BOOL        fPacket
)
{
    DebugEntry(ASShare::CA_ClearLocalState);

    //
    // Clear HOST stuff
    //
    if (flags & CACLEAR_HOST)
    {
        if (m_caWaitingForReplyMsg == CA_REPLY_REQUEST_GIVECONTROL)
        {
            if (!pasRemote || (pasRemote == m_caWaitingForReplyFrom))
            {
                // Kill the outstanding invitation to the remote
                CA_CancelGiveControl(m_caWaitingForReplyFrom, fPacket);
            }
        }

        if (m_caQueryDlg &&
            ((m_caQuery.msg == CA_REQUEST_TAKECONTROL) ||
             (m_caQuery.msg == CA_PREFER_PASSCONTROL)))
        {
            if (!pasRemote || (pasRemote == m_caQuery.pasReplyTo))
            {
                // Kill the user query dialog that's up
                CACancelQuery(m_caQuery.pasReplyTo, fPacket);
            }
        }

        if (m_pasLocal->m_caControlledBy)
        {
            if (!pasRemote || (pasRemote == m_pasLocal->m_caControlledBy))
            {
                CA_RevokeControl(m_pasLocal->m_caControlledBy, fPacket);
                ASSERT(!m_pasLocal->m_caControlledBy);
            }
        }
    }

    //
    // Clear VIEW stuff
    //
    if (flags & CACLEAR_VIEW)
    {
        if (m_caWaitingForReplyMsg == CA_REPLY_REQUEST_TAKECONTROL)
        {
            if (!pasRemote || (pasRemote == m_caWaitingForReplyFrom))
            {
                CA_CancelTakeControl(m_caWaitingForReplyFrom, fPacket);
            }
        }

        if (m_caQueryDlg && (m_caQuery.msg == CA_REQUEST_GIVECONTROL))
        {
            if (!pasRemote || (pasRemote == m_caQuery.pasReplyTo))
            {
                // Kill the user query dialog that's up
                CACancelQuery(m_caQuery.pasReplyTo, fPacket);
            }
        }

        if (m_pasLocal->m_caInControlOf)
        {
            if (!pasRemote || (pasRemote == m_pasLocal->m_caInControlOf))
            {
                CA_ReleaseControl(m_pasLocal->m_caInControlOf, fPacket);
                ASSERT(!m_pasLocal->m_caInControlOf);
            }
        }
    }

    DebugExitVOID(ASShare::CA_ClearLocalState);
}


//
// CAClearRemoteState()
//
// Called to reset all control state for a REMOTE node
//
void ASShare::CAClearRemoteState(ASPerson * pasClear)
{
    DebugEntry(ASShare::CAClearRemoteState);

    if (pasClear->m_caInControlOf)
    {
        CAClearHostState(pasClear->m_caInControlOf, NULL);
        ASSERT(!pasClear->m_caInControlOf);
        ASSERT(!pasClear->m_caControlledBy);
    }
    else if (pasClear->m_caControlledBy)
    {
        CAClearHostState(pasClear, NULL);
        ASSERT(!pasClear->m_caControlledBy);
        ASSERT(!pasClear->m_caInControlOf);
    }

    DebugExitVOID(ASShare:CAClearRemoteState);
}


//
// CAClearHostState()
//
// Called to clean up the mutual pointers when undoing a node's host state.
// We need to undo the previous states:
//      * Clear the previous controller of the host
//      * Clear the previous controller of the controller
//      * Clear the previous controllee of the controller
//
// This may be recursive.
//
// It returns TRUE if the change takes effect, FALSE if it's ignored because
// it involves us and we have more recent information.
//
BOOL ASShare::CAClearHostState
(
    ASPerson *  pasHost,
    ASPerson *  pasController
)
{
    BOOL        rc = FALSE;
    UINT        gccID;

    DebugEntry(ASShare::CAClearHostState);

    ValidatePerson(pasHost);

    //
    // If nothing is changing, do nothing
    //
    if (pasHost->m_caControlledBy == pasController)
    {
        TRACE_OUT(("Ignoring control change; nothing's changing"));
        rc = TRUE;
        DC_QUIT;
    }

    //
    // If the host is us, ignore.
    // Also, if the host isn't hosting yet we got an in control change,
    // ignore it too.
    //
    if ((pasHost == m_pasLocal) ||
        (pasController && !pasHost->hetCount))
    {
        WARNING_OUT(("Ignoring control change; host is us or not sharing"));
        DC_QUIT;
    }

    //
    // UNDO any old state of the controller
    //
    if (pasController)
    {
        if (pasController == m_pasLocal)
        {
            TRACE_OUT(("Ignoring control with us as controller"));
            DC_QUIT;
        }
        else if (pasController->m_caInControlOf)
        {
            ASSERT(!pasController->m_caControlledBy);
            ASSERT(pasController->m_caInControlOf->m_caControlledBy == pasController);
            rc = CAClearHostState(pasController->m_caInControlOf, NULL);
            if (!rc)
            {
                DC_QUIT;
            }
            ASSERT(!pasController->m_caInControlOf);
        }
        else if (pasController->m_caControlledBy)
        {
            ASSERT(!pasController->m_caInControlOf);
            ASSERT(pasController->m_caControlledBy->m_caInControlOf == pasController);
            rc = CAClearHostState(pasController, NULL);
            if (!rc)
            {
                DC_QUIT;
            }
            ASSERT(!pasController->m_caControlledBy);
        }
    }

    //
    // UNDO any old IN CONTROL state of the host
    //
    if (pasHost->m_caInControlOf)
    {
        ASSERT(!pasHost->m_caControlledBy);
        ASSERT(pasHost->m_caInControlOf->m_caControlledBy == pasHost);
        rc = CAClearHostState(pasHost->m_caInControlOf, NULL);
        if (!rc)
        {
            DC_QUIT;
        }
        ASSERT(!pasHost->m_caInControlOf);
    }

    //
    // FINALLY!  Update CONTROLLED BY state of the host
    //

    // Clear OLD ControlledBy
    if (pasHost->m_caControlledBy)
    {
        ASSERT(pasHost->m_caControlledBy->m_caInControlOf == pasHost);
        pasHost->m_caControlledBy->m_caInControlOf = NULL;
    }

    // Set NEW ControlledBy
    pasHost->m_caControlledBy = pasController;
    if (pasController)
    {
        pasController->m_caInControlOf = pasHost;
        gccID = pasController->cpcCaps.share.gccID;
    }
    else
    {
        gccID = 0;
    }

    VIEW_HostStateChange(pasHost);

    //
    // The hosts' controller has changed.  Repaint the shadow cursor with/wo
    // the new initials.
    //
    CM_UpdateShadowCursor(pasHost, pasHost->cmShadowOff, pasHost->cmPos.x,
        pasHost->cmPos.y, pasHost->cmHotSpot.x, pasHost->cmHotSpot.y);

    rc = TRUE;

DC_EXIT_POINT:
    DebugExitBOOL(ASShare::CAClearHostState, rc);
    return(rc);
}



//
// 2.X COMPATIBILITY STUFF
// This is so that we can do a decent job of reflecting old 2.x control
// stuff, and allow a 3.0 node to take control of a 2.x system.
//


//
// CA2xCooperateChange()
//
// This is called when a 2.x node is cooperating or not.  When a 2.x node
// is a host and cooperating, he is "controllable" by 3.0 standards.  So
// when he starts/stops hosting or starts/stops cooperating we must
// recalculate "AllowControl"
//
void ASShare::CA2xCooperateChange
(
    ASPerson *      pasPerson,
    BOOL            fCooperating
)
{
    BOOL            fAllowControl;

    DebugEntry(ASShare::CA2xCooperateChange);

    ValidatePerson(pasPerson);

    //
    // If this isn't a back level system, ignore it.
    //
    if (pasPerson->cpcCaps.general.version >= CAPS_VERSION_30)
    {
        WARNING_OUT(("Received old CA cooperate message from 3.0 node [%d]",
            pasPerson->mcsID));
        DC_QUIT;
    }

    //
    // Update the cooperating state.
    //
    pasPerson->m_ca2xCooperating = fCooperating;

    //
    // If cooperating & this person owns the control token, this person
    // is now in control of all 2.x cooperating nodes.  If we were
    // controlling a 2.x host, act like we've been bounced.  But we MUST
    // send a packet.
    //
    if (fCooperating)
    {
        if (pasPerson == m_ca2xControlTokenOwner)
        {
            //
            // This person is now "in control" of the 2.x cooperating nodes.
            // If we were in control of a 2.x host, we've basically been
            // bounced and another 2.x node is running the show.  With 3.0,
            // it doesn't matter and we don't need to find out what's going
            // on with a 3.0 node in control of 2.x dudes.
            //
            if (m_pasLocal->m_caInControlOf &&
                (m_pasLocal->m_caInControlOf->cpcCaps.general.version < CAPS_VERSION_30))
            {
                CA_ReleaseControl(pasPerson, TRUE);
            }
        }
    }

    //
    // Figure out whether we need to set/clear AllowControl
    //
    fAllowControl = (fCooperating && pasPerson->m_pView);

    if (pasPerson->m_caAllowControl != fAllowControl)
    {
        if (pasPerson->m_pView && !fAllowControl)
        {
            //
            // This 2.x node is hosting, and no longer is cooperating.
            // Cleanup the controller
            //
            if (pasPerson->m_caControlledBy == m_pasLocal)
            {
                CA_ReleaseControl(pasPerson, TRUE);
            }
            else
            {
                CAClearHostState(pasPerson, NULL);
            }
        }

        pasPerson->m_caAllowControl = fAllowControl;

        // This will do nothing if this person isn't hosting.
        VIEW_HostStateChange(pasPerson);
    }

DC_EXIT_POINT:
    DebugExitVOID(ASShare::CA2xCooperateChange);
}



//
// CA2xRequestControl()
//
// Called when a 2.x node requests control.
//
void ASShare::CA2xRequestControl
(
    ASPerson *      pasPerson,
    PCAPACKET       pCAPacket
)
{
    DebugEntry(ASShare::CA2xRequestControl);

    //
    // A 2.x node has sent this.  3.0 hosts never request, they simply
    // grab control.
    //
    ValidatePerson(pasPerson);

    //
    // If it's from a 3.0 node, it's an error.
    //
    if (pasPerson->cpcCaps.general.version >= CAPS_VERSION_30)
    {
        ERROR_OUT(("Received CA_OLDMSG_REQUEST_CONTROL from 3.0 node [%d]",
            pasPerson->mcsID));
        DC_QUIT;
    }

    //
    // If we have the token, grant it.  We must release control of a host if
    // that person is 2.x.
    //
    if (m_ca2xControlTokenOwner == m_pasLocal)
    {
        //
        // In this case, we do NOT want a dest ID.  This isn't us trying to
        // take control of a 2.x host.  It is simply granting control to
        // a 2.x dude.
        //
        if (CA2xQueueSendMsg(0, CA_OLDMSG_GRANTED_CONTROL,
            pasPerson->mcsID, m_ca2xControlGeneration))
        {
            m_ca2xControlTokenOwner = pasPerson;

            // Release control of 2.x host.
            if (m_pasLocal->m_caInControlOf &&
                (m_pasLocal->m_caInControlOf->cpcCaps.general.version < CAPS_VERSION_30))
            {
                CA_ReleaseControl(m_pasLocal->m_caInControlOf, TRUE);
            }
        }
        else
        {
            ERROR_OUT(("CA2xRequestControl:  Unable to respond GRANTED to node [%d]",
                pasPerson->mcsID));
        }
    }

DC_EXIT_POINT:
    DebugExitVOID(ASShare::CA2xRequestControl);
}



//
// CA2xGrantedControl()
//
// Called when any node (2.x or 3.0 controlling 2.x) broadcasts granted
// control.  If we are controlling a 2.x host, it is now nuked.
//
void ASShare::CA2xGrantedControl
(
    ASPerson *  pasPerson,
    PCAPACKET   pCAPacket
)
{
    DebugEntry(ASShare::CA2xGrantedControl);

    ValidatePerson(pasPerson);

    if ((pCAPacket->data2 >= m_ca2xControlGeneration) ||
        ((m_ca2xControlGeneration - pCAPacket->data2) > 0x80000000))
    {
        ASPerson * pas2xNewTokenOwner;

        //
        // This dude is now the controller of 2.x nodes.  Remember it for
        // later COOPERATE msgs.  If nothing has changed (this is a sync
        // broadcast for example, do nothing ourselvs).
        //
        pas2xNewTokenOwner = SC_PersonFromNetID(pCAPacket->data1);
        if (pas2xNewTokenOwner != m_ca2xControlTokenOwner)
        {
            m_ca2xControlTokenOwner = pas2xNewTokenOwner;
            m_ca2xControlGeneration = pCAPacket->data2;

            //
            // Are we in control of a 2.x node?  If so, undo it.
            //
            if (m_pasLocal->m_caInControlOf &&
                (m_pasLocal->m_caInControlOf->cpcCaps.general.version < CAPS_VERSION_30))
            {
                CA_ReleaseControl(m_pasLocal->m_caInControlOf, TRUE);
            }
        }
    }

    DebugExitVOID(ASShare::CA2xGrantedControl);
}



//
// CA2xTakeControl()
//
// This fakes up packets to take control of a 2.x node.  We don't broadcast,
// we send them privately just to the individual node so we don't control
// any other host but him.
//
// We do this by sending COOPERATE then GRANTED_CONTROL.  If there's a
// collision, we'll see a GRANTED_CONTROL from somebody else that outdates
// ours.
//
void ASShare::CA2xTakeControl(ASPerson * pasHost)
{
    UINT_PTR    caNew2xControlGeneration;

    DebugEntry(ASShare::CA2xTakeControl);

    ValidateView(pasHost);

    caNew2xControlGeneration = m_ca2xControlGeneration + m_pasLocal->mcsID;

    if (CA2xQueueSendMsg(0, CA_OLDMSG_COOPERATE, 0, 0))
    {
        if (!CA2xQueueSendMsg(pasHost->mcsID, CA_OLDMSG_GRANTED_CONTROL,
            m_pasLocal->mcsID, caNew2xControlGeneration))
        {
            //
            // Failure.  Best we can do is follow it with a DETACH
            //
            ERROR_OUT(("CA2xTakeControl:  Can't take control of [%d]", pasHost->mcsID));
            CA2xQueueSendMsg(0, CA_OLDMSG_DETACH, 0, 0);
        }
        else
        {
            m_ca2xControlGeneration = caNew2xControlGeneration;
            m_ca2xControlTokenOwner = m_pasLocal;

            CANewRequestID();
            CAStartInControl(pasHost, 1);
        }
    }
    else
    {
        ERROR_OUT(("CA2xTakeControl:  Can't take control of [%d]", pasHost->mcsID));
    }

    DebugExitVOID(ASShare::CA2xTakeControl);
}




//
// CA2xSendMsg()
// This sends a 2.x node CA message.  It returns FALSE if it can't alloc
// a packet.
//
BOOL ASShare::CA2xSendMsg
(
    UINT_PTR            destID,
    UINT            msg,
    UINT_PTR            data1,
    UINT_PTR            data2
)
{
    BOOL            fSent = FALSE;
    PCAPACKET       pPacket;
#ifdef _DEBUG
    UINT            sentSize;
#endif // _DEBUG

    DebugEntry(ASShare::CASendPacket);

    //
    // For cooperate/detach, there's no target.  We broadcast them no
    // matter what so everybody knows what state we're in.
    //
    if (msg != CA_OLDMSG_GRANTED_CONTROL)
    {
        ASSERT(!destID);
    }

    //
    // WE MUST USE PROT_STR_MISC!  Backlevel nodes will uncompress it
    // using that prot dictionary.  And note that we must broadcast 2.x
    // CA packets so everybody knows what's going on.
    //
    pPacket = (PCAPACKET)SC_AllocPkt(PROT_STR_MISC, g_s20BroadcastID,
        sizeof(*pPacket));
    if (!pPacket)
    {
        WARNING_OUT(("CA2xSendMsg: can't get packet to send"));
        WARNING_OUT(("  msg             0x%08x",    msg));
        WARNING_OUT(("  data1           0x%08x",    data1));
        WARNING_OUT(("  data2           0x%08x",    data2));

        DC_QUIT;
    }

    pPacket->header.data.dataType   = DT_CA;
    pPacket->msg                    = (TSHR_UINT16)msg;
    pPacket->data1                  = (TSHR_UINT16)data1;
    pPacket->data2                  = data2;

#ifdef _DEBUG
    sentSize =
#endif
    DCS_CompressAndSendPacket(PROT_STR_MISC, g_s20BroadcastID,
            &(pPacket->header), sizeof(*pPacket));
    TRACE_OUT(("CA request packet size: %08d, sent %08d", sizeof(*pPacket), sentSize));

    fSent = TRUE;

DC_EXIT_POINT:

    DebugExitBOOL(ASShare::CA2xSendMsg, fSent);
    return(fSent);
}


//
// CA2xQueueSendMsg()
// This sends (or queues if failure) a 2.x node CA message.  It has different
// fields, hence a different routine.
//
BOOL ASShare::CA2xQueueSendMsg
(
    UINT_PTR        destID,
    UINT            msg,
    UINT_PTR        data1,
    UINT_PTR        data2
)
{
    BOOL            rc = TRUE;
    PCAREQUEST      pCARequest;

    DebugEntry(ASShare::CA2xQueueSendMsg);

    if (msg != CA_OLDMSG_GRANTED_CONTROL)
    {
        ASSERT(!destID);
    }

    //
    // A DETACH message will cancel out a pending GRANTED_CONTROL message.
    // So look for that first.  If we find one (and there can only be at
    // most one), replace it.
    //
    if (msg == CA_OLDMSG_DETACH)
    {
        pCARequest = (PCAREQUEST)COM_BasedListFirst(&m_caQueuedMsgs,
            FIELD_OFFSET(CAREQUEST, chain));
        while (pCARequest)
        {
            if ((pCARequest->type       == REQUEST_2X)   &&
                (pCARequest->destID     == destID)      &&
                (pCARequest->msg        == CA_OLDMSG_GRANTED_CONTROL))
            {
                // Replace it
                WARNING_OUT(("Replacing cancelled GRANTED_CONTROL msg to 2.x host"));

                pCARequest->destID              = 0;
                pCARequest->msg                 = CA_OLDMSG_DETACH;
                pCARequest->req.req2x.data1     = 0;
                pCARequest->req.req2x.data2     = 0;

                // We're done.
                DC_QUIT;
            }

            pCARequest = (PCAREQUEST)COM_BasedListNext(&m_caQueuedMsgs, pCARequest,
                FIELD_OFFSET(CAREQUEST, chain));
        }
    }
    //
    // The messages must go out in order.  So we must flush pending
    // queued messages first.
    //
    if (!CAFlushOutgoingPackets() ||
        !CA2xSendMsg(destID, msg, data1, data2))
    {
        //
        // We must queue this.
        //
        WARNING_OUT(("CA2xQueueSendMsg: queueing request for send later"));

        pCARequest = new CAREQUEST;
        if (!pCARequest)
        {
            ERROR_OUT(("CA2xQueueSendMsg: can't even allocate memory to queue request; must fail"));
            rc = FALSE;
        }
        else
        {
            SET_STAMP(pCARequest, CAREQUEST);

            pCARequest->type                    = REQUEST_2X;
            pCARequest->destID                  = destID;
            pCARequest->msg                     = msg;
            pCARequest->req.req2x.data1         = data1;
            pCARequest->req.req2x.data2         = data2;

            //
            // Stick this at the end of the queue
            //
            COM_BasedListInsertBefore(&(m_caQueuedMsgs),
                &(pCARequest->chain));
        }
    }

DC_EXIT_POINT:
    DebugExitBOOL(ASShare::CA2xQueueSendMsg, rc);
    return(rc);
}



//
// CAStartQuery()
//
// This puts up the modeless dialog to query the user about a control
// request.  It will timeout if not handled.
//
BOOL ASShare::CAStartQuery
(
    ASPerson *  pasFrom,
    UINT        msg,
    PCA30P      pReq
)
{
    BOOL        rc = FALSE;

    DebugEntry(ASShare::CAStartQuery);

    ValidatePerson(pasFrom);

    //
    // We have no stacked queries.  If another comes in while the current
    // one is up, it gets an immediate failure busy.
    //
    ASSERT(!m_caQueryDlg);
    ASSERT(!m_caQuery.pasReplyTo);
    ASSERT(!m_caQuery.msg);

    //
    // Setup for new query
    //
    if (msg == CA_PREFER_PASSCONTROL)
    {
        //
        // With forwarding, the person we're going to send a packet to
        // if accepted is not the person who sent us the request.  It's the
        // person we're forwarding to.
        //
        m_caQuery.pasReplyTo = SC_PersonFromNetID(pReq->ppc.mcsPassTo);
        ValidatePerson(m_caQuery.pasReplyTo);
    }
    else
    {
        m_caQuery.pasReplyTo = pasFrom;
    }
    m_caQuery.mcsOrg    = pasFrom->mcsID;
    m_caQuery.msg       = msg;
    m_caQuery.request   = *pReq;

    //
    // If we are unattended, or the requester is unattended, instantly
    // confirm.  That's why we show the window after creating the dialog.
    //
    if ((m_pasLocal->cpcCaps.general.typeFlags & AS_UNATTENDED) ||
        (pasFrom->cpcCaps.general.typeFlags & AS_UNATTENDED))
    {
        CAFinishQuery(CARESULT_CONFIRMED);
        rc = TRUE;
    }
    else
    {
        //
        // If this is a request to us && we're hosting, check auto-accept/
        // auto-reject settings.
        //
        if (m_pHost &&
            ((msg == CA_REQUEST_TAKECONTROL) || (msg == CA_PREFER_PASSCONTROL)))
        {
            if (m_pHost->m_caTempRejectRequests)
            {
                CAFinishQuery(CARESULT_DENIED_BUSY);
                rc = TRUE;
                DC_QUIT;
            }
            else if (m_pHost->m_caAutoAcceptRequests)
            {
                CAFinishQuery(CARESULT_CONFIRMED);
                rc = TRUE;
                DC_QUIT;
            }
        }

        m_caQueryDlg    = CreateDialogParam(g_asInstance,
            MAKEINTRESOURCE(IDD_QUERY), NULL, CAQueryDlgProc, 0);
        if (!m_caQueryDlg)
        {
            ERROR_OUT(("Failed to create query message box from [%d]",
                pasFrom->mcsID));

            m_caQuery.pasReplyTo     = NULL;
            m_caQuery.mcsOrg    = 0;
            m_caQuery.msg       = 0;
        }
        else
        {
            // Success
            rc = TRUE;
        }
    }

DC_EXIT_POINT:
    DebugExitBOOL(ASShare::CAStartQuery, rc);
    return(rc);
}



//
// CAFinishQuery()
//
// Called to finish the query we started, either because of UI or because
// we or the remote are unattended.
//
void ASShare::CAFinishQuery(UINT result)
{
    CA30PENDING     request;

    DebugEntry(ASShare::CAFinishQuery);

    ValidatePerson(m_caQuery.pasReplyTo);

    // Make a copy of our request
    request         = m_caQuery;

    //
    // If we have a dialog up, destroy it NOW.  Completing the request
    // may cause us to be controlled or whatever.  So get the dialog
    // out of the way immediately.
    //
    // Note that destroying ourself will clear the request vars, hence the
    // copy above.
    //
    if (m_caQueryDlg)
    {
        DestroyWindow(m_caQueryDlg);
    }
    else
    {
        m_caQuery.pasReplyTo     = NULL;
        m_caQuery.mcsOrg    = 0;
        m_caQuery.msg       = 0;
    }

    switch (request.msg)
    {
        case CA_REQUEST_TAKECONTROL:
        {
            CACompleteRequestTakeControl(request.pasReplyTo,
                &request.request.rtc, result);
            break;
        }

        case CA_REQUEST_GIVECONTROL:
        {
            CACompleteRequestGiveControl(request.pasReplyTo,
                &request.request.rgc, result);
            break;
        }

        case CA_PREFER_PASSCONTROL:
        {
            CACompletePreferPassControl(request.pasReplyTo,
                request.mcsOrg, &request.request.ppc, result);
            break;
        }

        default:
        {
            ERROR_OUT(("Unrecognized query msg %d", request.msg));
            break;
        }
    }

    DebugExitVOID(ASShare::CAFinishQuery);
}



//
// CA_QueryDlgProc()
//
// Handles querying user dialog
//
INT_PTR CALLBACK CAQueryDlgProc
(
    HWND        hwnd,
    UINT        message,
    WPARAM      wParam,
    LPARAM      lParam
)
{
    return(g_asSession.pShare->CA_QueryDlgProc(hwnd, message, wParam, lParam));
}



BOOL ASShare::CA_QueryDlgProc
(
    HWND        hwnd,
    UINT        message,
    WPARAM      wParam,
    LPARAM      lParam
)
{
    BOOL        rc = TRUE;

    DebugEntry(CA_QueryDlgProc);

    switch (message)
    {
        case WM_INITDIALOG:
        {
            char    szT[256];
            char    szRes[512];
            char    szShared[64];
            UINT    idsTitle;
            ASPerson *  pasT;
            HDC     hdc;
            HFONT   hfn;
            RECT    rc;
            RECT    rcOwner;

            ValidatePerson(m_caQuery.pasReplyTo);

            pasT = NULL;

            // Set title.
            ASSERT(m_caQuery.msg);
            switch (m_caQuery.msg)
            {
                case CA_REQUEST_TAKECONTROL:
                {
                    idsTitle    = IDS_TITLE_QUERY_TAKECONTROL;

                    if (m_pasLocal->hetCount == HET_DESKTOPSHARED)
                        LoadString(g_asInstance, IDS_DESKTOP_LOWER, szShared, sizeof(szShared));
                    else
                        LoadString(g_asInstance, IDS_PROGRAMS_LOWER, szShared, sizeof(szShared));

                    LoadString(g_asInstance, IDS_MSG_QUERY_TAKECONTROL, szT, sizeof(szT));

                    wsprintf(szRes, szT, m_caQuery.pasReplyTo->scName, szShared);
                    break;
                }

                case CA_REQUEST_GIVECONTROL:
                {
                    if (m_caQuery.pasReplyTo->hetCount == HET_DESKTOPSHARED)
                        LoadString(g_asInstance, IDS_DESKTOP_LOWER, szShared, sizeof(szShared));
                    else
                        LoadString(g_asInstance, IDS_PROGRAMS_LOWER, szShared, sizeof(szShared));

                    if (m_caQuery.request.rgc.mcsPassFrom)
                    {
                        pasT = SC_PersonFromNetID(m_caQuery.request.rgc.mcsPassFrom);
                    }

                    if (pasT)
                    {
                        idsTitle    = IDS_TITLE_QUERY_YIELDCONTROL;

                        LoadString(g_asInstance, IDS_MSG_QUERY_YIELDCONTROL,
                            szT, sizeof(szT));

                        wsprintf(szRes, szT, pasT->scName, m_caQuery.pasReplyTo->scName, szShared);
                    }
                    else
                    {
                        idsTitle    = IDS_TITLE_QUERY_GIVECONTROL;

                        LoadString(g_asInstance, IDS_MSG_QUERY_GIVECONTROL,
                            szT, sizeof(szT));

                        wsprintf(szRes, szT, m_caQuery.pasReplyTo->scName, szShared);
                    }

                    break;
                }

                case CA_PREFER_PASSCONTROL:
                {
                    pasT = SC_PersonFromNetID(m_caQuery.mcsOrg);
                    ValidatePerson(pasT);

                    idsTitle    = IDS_TITLE_QUERY_FORWARDCONTROL;

                    if (m_pasLocal->hetCount == HET_DESKTOPSHARED)
                        LoadString(g_asInstance, IDS_DESKTOP_LOWER, szShared, sizeof(szShared));
                    else
                        LoadString(g_asInstance, IDS_PROGRAMS_LOWER, szShared, sizeof(szShared));

                    LoadString(g_asInstance, IDS_MSG_QUERY_FORWARDCONTROL, szT, sizeof(szT));

                    wsprintf(szRes, szT, pasT->scName, szShared, m_caQuery.pasReplyTo->scName);

                    break;
                }

                default:
                {
                    ERROR_OUT(("Bogus m_caQuery.msg %d", m_caQuery.msg));
                    break;
                }
            }

            LoadString(g_asInstance, idsTitle, szT, sizeof(szT));
            SetWindowText(hwnd, szT);

            // Set message.
            SetDlgItemText(hwnd, CTRL_QUERY, szRes);

            // Center the message vertically
            GetWindowRect(GetDlgItem(hwnd, CTRL_QUERY), &rcOwner);
            MapWindowPoints(NULL, hwnd, (LPPOINT)&rcOwner, 2);

            rc = rcOwner;

            hdc = GetDC(hwnd);
            hfn = (HFONT)SendDlgItemMessage(hwnd, CTRL_QUERY, WM_GETFONT, 0, 0);
            hfn = SelectFont(hdc, hfn);

            DrawText(hdc, szRes, -1, &rc, DT_NOCLIP | DT_EXPANDTABS |
                DT_NOPREFIX | DT_WORDBREAK | DT_CALCRECT);

            SelectFont(hdc, hfn);
            ReleaseDC(hwnd, hdc);

            ASSERT((rc.bottom - rc.top) <= (rcOwner.bottom - rcOwner.top));

            SetWindowPos(GetDlgItem(hwnd, CTRL_QUERY), NULL,
                rcOwner.left,
                ((rcOwner.top + rcOwner.bottom) - (rc.bottom - rc.top)) / 2,
                (rcOwner.right - rcOwner.left),
                rc.bottom - rc.top,
                SWP_NOACTIVATE | SWP_NOZORDER);

            SetTimer(hwnd, IDT_CAQUERY, PERIOD_CAQUERY, 0);

            //
            // Show window, the user will handle
            //
            ShowWindow(hwnd, SW_SHOWNORMAL);
            SetForegroundWindow(hwnd);
            UpdateWindow(hwnd);

            break;
        }

        case WM_COMMAND:
        {
            switch (GET_WM_COMMAND_ID(wParam, lParam))
            {
                case IDOK:
                {
                    CAFinishQuery(CARESULT_CONFIRMED);
                    break;
                }

                case IDCANCEL:
                {
                    CAFinishQuery(CARESULT_DENIED_USER);
                    break;
                }
            }
            break;
        }

        case WM_TIMER:
        {
            if (wParam != IDT_CAQUERY)
            {
                rc = FALSE;
            }
            else
            {
                KillTimer(hwnd, IDT_CAQUERY);

                // Timed out failure.
                CAFinishQuery(CARESULT_DENIED_TIMEDOUT);
            }
            break;
        }

        case WM_DESTROY:
        {
            //
            // Clear pending info
            //
            m_caQueryDlg        = NULL;
            m_caQuery.pasReplyTo     = NULL;
            m_caQuery.mcsOrg    = 0;
            m_caQuery.msg       = 0;
            break;
        }

        default:
        {
            rc = FALSE;
            break;
        }
    }

    DebugExitBOOL(CA_QueryDlgProc, rc);
    return(rc);
}



//
// CACancelQuery()
//
// If a dialog is up for a take control request, it hasn't been handled yet,
// and we get a cancel notification from the viewer, we need to take the
// dialog down WITHOUT generating a response packet.
//
void ASShare::CACancelQuery
(
    ASPerson *  pasFrom,
    BOOL        fPacket
)
{
    DebugEntry(ASShare::CACancelQuery);

    ASSERT(m_caQueryDlg);
    ASSERT(m_caQuery.pasReplyTo == pasFrom);

    if (fPacket)
    {
        // This will send a packet then destroy the dialog
        CAFinishQuery(CARESULT_DENIED);
    }
    else
    {
        // Destroy the dialog
        DestroyWindow(m_caQueryDlg);
    }

    ASSERT(!m_caQueryDlg);
    ASSERT(!m_caQuery.pasReplyTo);
    ASSERT(!m_caQuery.msg);

    DebugExitVOID(ASShare::CACancelQuery);
}