#include "precomp.h"


//
// AWC.CPP
// Active Window Coordinator
//
// Copyright(c) Microsoft 1997-
//
#define MLZ_FILE_ZONE  ZONE_CORE


//
// The AWC code does three things:
//      * Notifies everybody in the share what the current active window
//          is when sharing (either a shared window or something else)
//      * When in control, requests to restore/activate a remote's shared
//          window
//      * When being controlled, handles request to restore/activate
//          a local hosted window
//

//
// For the purposes of this strategy the AWC packets can be split into two
// categories.
//
// 1.  Immediate - these are the packets which are generated when a shadow
// window is controlled by some means other than direct keyboard or mouse
// input to the shadow window (which is all sent to the host system and
// handled there).  Examples include the Task List, window switching
// (Alt-TAB, Alt-Esc etc), minimising or closing another app which may pass
// activation on to a shadow window etc.  The packets in this category are:
//
//  AWC_MSG_ACTIVATE_WINDOW
//  AWC_MSG_RESTORE_WINDOW
//
// These packets can be (and are) sent immediately that the event happens.
// This is because they always refer to real windows on the host system.
//
// 2.  Periodic - these are the packets sent when the AWC detects that the
// active window has changed locally and it should inform the remote AWC.
// This packet is sent when AWC_Periodic is called.  The packets in this
// category are:
//
//  AWC_MSG_ACTIVE_CHANGE_SHARED
//  AWC_MSG_ACTIVE_CHANGE_LOCAL
//  AWC_MSG_ACTIVE_CHANGE_INVISIBLE
//
// These are only sent when AWC_Periodic is called because they may refer
// to shadow windows and therefore we avoid sending it until we know that
// the SWL has succesfully sent a window structure which includes the
// window referenced in the message.
//
// For packets in the first category we will queue up to two packets and at
// the point where we have three packets we cannot send we will discard
// packets from the front of the queue so that a users most recent actions
// have priority over any previous actions.  We will try to send all queued
// packets whenever a new category 1 packet is generated and on the
// AWC_Periodic call.
//
// For packets in the second category we will drop the packets if we cannot
// send them but remember that we have failed to send a packet and retry on
// the next call to AWC_Periodic.  This is not the same as queueing as the
// active window may change between us dropping a packet and being able to
// send the next packet from AWC_Periodic.  Queuing the dropped packet
// would have been pointless as it would now be out of date.
//
// All AWC packets go on the same stream (the updates stream) so that they
// are guaranteed to arrive in the same order as they are generated to
// prevent a AWC_MSG_ACTIVE_CHANGE_XXX being overtaken by an
// AWC_MSG_ACTIVATE_WINDOW and therefore the effect of
// AWC_MSG_ACTIVATE_WINDOW being overridden by the
// AWC_MSG_ACTIVE_CHANGE_XXX.
//




//
// AWC_ReceivedPacket()
//
void  ASShare::AWC_ReceivedPacket
(
    ASPerson *      pasPerson,
    PS20DATAPACKET  pPacket
)
{
    PAWCPACKET      pAWCPacket;
    UINT            activateWhat;
    HWND            hwnd;

    DebugEntry(ASShare::AWC_ReceivedPacket);

    ValidatePerson(pasPerson);

    pAWCPacket = (PAWCPACKET)pPacket;

    //
    // We trace the person ID out here so we don't bother to do it
    // elsewhere in this function on TRACE lines.
    //
    TRACE_OUT(("AWC_ReceivedPacket from [%d] - msg %x token %u data 0x%08x",
                 pasPerson->mcsID,
                 pAWCPacket->msg,
                 pAWCPacket->token,
                 pAWCPacket->data1));

    if (AWC_IS_INDICATION(pAWCPacket->msg))
    {
        //
        // We should simply change the view of the remote.
        //
        if (pasPerson->awcActiveWinID != pAWCPacket->data1)
        {
            pasPerson->awcActiveWinID = pAWCPacket->data1;

            if (pasPerson->m_pView)
            {
                // Update the pressed item on the window bar.
                VIEW_WindowBarChangedActiveWindow(pasPerson);
            }
        }
    }
    else if (AWC_MSG_SAS == pAWCPacket->msg)
    {
        //
        // Cause Ctrl+Alt+Del to be injected if we're in a service app,
        // we're hosting, and we're controlled by the sender.
        //
        if ((g_asOptions & AS_SERVICE) && (pasPerson->m_caInControlOf == m_pasLocal))
        {
            ASSERT(m_pHost);
            OSI_InjectCtrlAltDel();
        }
    }
    else
    {
        hwnd = (HWND)pAWCPacket->data1;

        //
        // Only accept requests if we're being controlled currently by
        // this person.  We might get renegade packets from remotes that
        // don't yet they aren't in control, or from back-level systems.
        //
        if (pasPerson->m_caInControlOf != m_pasLocal)
        {
            // We're not controlled by this person
            DC_QUIT;
        }

        ASSERT(m_pHost);

        if ((pAWCPacket->msg == AWC_MSG_ACTIVATE_WINDOW) &&
            IsWindow(hwnd)                               &&
            IsWindowEnabled(hwnd))
        {
            // Ony get owned window if enabled and we're activating.
            hwnd = GetLastActivePopup(hwnd);
        }

        if (IsWindow(hwnd) &&
            HET_WindowIsHosted(hwnd) &&
            IsWindowEnabled(hwnd))
        {
            switch (pAWCPacket->msg)
            {
                case AWC_MSG_ACTIVATE_WINDOW:
                    //
                    // Activate the window.
                    //
                    TRACE_OUT(("Received AWC_MSG_ACTIVATE_WINDOW for hwnd 0x%08x from [%d]",
                        hwnd, pasPerson->mcsID));
                    m_pHost->AWC_ActivateWindow(hwnd);
                    break;

                case AWC_MSG_RESTORE_WINDOW:
                    //
                    // Restore the window
                    //
                    TRACE_OUT(("Received AWC_MSG_RESTORE_WINDOW for hwnd 0x%08x from [%d]",
                        hwnd, pasPerson->mcsID));
                    if (IsIconic(hwnd))
                    {
                        PostMessage(hwnd, WM_SYSCOMMAND, SC_RESTORE, 0);
                    }
                    break;

                default:
                    WARNING_OUT(("Received invalid msg %d from [%d]",
                        pAWCPacket->msg, pasPerson->mcsID));
                    break;
            }
        }
    }

DC_EXIT_POINT:
    DebugExitVOID(ASShare::AWC_ReceivedPacket);
}


//
// AWC_Periodic()
//
void  ASHost::AWC_Periodic(void)
{
    HWND            currentActiveWindow;
    HWND            sendActiveWindow;
    TSHR_UINT16     sendMsg;

    DebugEntry(ASHost::AWC_Periodic);

    //
    // If we are hosting the desktop, skip this.
    //
    if (m_pShare->m_pasLocal->hetCount == HET_DESKTOPSHARED)
    {
        // Skip.
        DC_QUIT;
    }

    //
    // Find the current active window.
    //
    if (SWL_IsOurDesktopActive())
    {
        currentActiveWindow = GetForegroundWindow();
    }
    else
    {
        // Another desktop is up.
        currentActiveWindow = NULL;
    }

    if (m_pShare->HET_WindowIsHosted(currentActiveWindow))
    {
        //
        // A window which belongs to shared application is active -
        // find out if it is visible.
        //
        if (IsWindowVisible(currentActiveWindow))
        {
            //
            // The active window is also visible - this means the
            // remote system will know about it as it will have
            // been sent in a preceding SWL message.
            //
            sendMsg = AWC_MSG_ACTIVE_CHANGE_SHARED;
            sendActiveWindow = SWL_GetSharedIDFromLocalID(currentActiveWindow);
        }
        else
        {
            //
            // The active window is invisible - this means that
            // although it is shared the remote system will not
            // know about it.  Send a message to inform the remote
            // system about this.
            //
            sendMsg = AWC_MSG_ACTIVE_CHANGE_INVISIBLE;
            sendActiveWindow = 0;
        }
    }
    else
    {
        //
        // A local application has been activated send
        // AWC_ACTIVE_WINDOW_LOCAL.
        //
        sendMsg = AWC_MSG_ACTIVE_CHANGE_LOCAL;
        sendActiveWindow = 0;
    }

    //
    // Now send the packet if it's not the same as the last packet we
    // sent.  NOTE that for local unshared windows, we don't care if
    // we've deactivated one and activated another, they are generic.  So
    // we send a message if we
    //      * change activation from a shared window
    //      * change activation to a shared window
    //
    if ((sendActiveWindow   != m_awcLastActiveWindow) ||
        (sendMsg            != m_awcLastActiveMsg))
    {
        //
        // Note that this packet is sent on the updates stream so that it
        // cannot overtake a SWL packet containing the newly active window.
        //
        TRACE_OUT(("Broadcasting AWC change msg %x, hwnd 0x%08x",
            sendMsg, sendActiveWindow));
        if (m_pShare->AWC_SendMsg(g_s20BroadcastID, sendMsg, HandleToUlong(sendActiveWindow), 0))
        {
            //
            // The packet was sent succesfully - remember which window we
            // sent.
            //
            m_awcLastActiveWindow = sendActiveWindow;
            m_awcLastActiveMsg    = sendMsg;
        }
        else
        {
            //
            // The packet could not be sent for some reason - set
            // m_awcLastActiveWindow to invalid so that we will try again
            // on the next call to AWC_Periodic.
            //
            m_awcLastActiveWindow = AWC_INVALID_HWND;
            m_awcLastActiveMsg    = AWC_MSG_INVALID;
        }
    }

DC_EXIT_POINT:
    DebugExitVOID(ASHost::AWC_Periodic);
}



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

    //
    // Ensure that we resend an indication message as soon as possible
    //
    m_awcLastActiveWindow = AWC_INVALID_HWND;
    m_awcLastActiveMsg    = AWC_MSG_INVALID;

    DebugExitVOID(ASHost::AWC_SyncOutgoing);
}


//
// FUNCTION: AWC_SendMsg
//
// DESCRIPTION:
//
// Sends a AWC message to remote system
//      * Requests to activate are just to one host
//      * Notifications of activation are to everyone
//
// RETURNS: TRUE or FALSE - success or failure
//
//
BOOL  ASShare::AWC_SendMsg
(
    UINT_PTR            nodeID,
    UINT            msg,
    UINT_PTR            data1,
    UINT_PTR            data2
)
{

    PAWCPACKET      pAWCPacket;
    BOOL            rc = FALSE;
#ifdef _DEBUG
    UINT            sentSize;
#endif

    DebugEntry(ASShare::AWC_SendMsg);

    //
    // Allocate correct sized packet.
    //
    pAWCPacket = (PAWCPACKET)SC_AllocPkt(PROT_STR_UPDATES, nodeID, sizeof(AWCPACKET));
    if (!pAWCPacket)
    {
        WARNING_OUT(("Failed to alloc AWC packet"));
        DC_QUIT;
    }

    //
    // Set up the data header for an AWC message.
    //
    pAWCPacket->header.data.dataType = DT_AWC;

    //
    // Now set up the AWC fields.  By passing AWC_SYNC_MSG_TOKEN in the
    // token field, we ensure that back-level remotes will never drop our
    // packets.
    //
    pAWCPacket->msg     = (TSHR_UINT16)msg;
    pAWCPacket->data1   = data1;
    pAWCPacket->data2   = data2;
    pAWCPacket->token   = AWC_SYNC_MSG_TOKEN;

    //
    // Send the packet.
    //
    if (m_scfViewSelf)
        AWC_ReceivedPacket(m_pasLocal, &(pAWCPacket->header));

#ifdef _DEBUG
    sentSize =
#endif // _DEBUG
    DCS_CompressAndSendPacket(PROT_STR_UPDATES, nodeID,
        &(pAWCPacket->header), sizeof(*pAWCPacket));

    TRACE_OUT(("AWC packet size: %08d, sent: %08d", sizeof(*pAWCPacket), sentSize));

    rc = TRUE;

DC_EXIT_POINT:
    DebugExitDWORD(ASShare::AWC_SendMsg, rc);
    return(rc);
}




//
// AWC_ActivateWindow()
//
// Activates a shared window via a remote controller's request.
//
void ASHost::AWC_ActivateWindow(HWND window)
{
    BOOL    rcSFW;
    HWND    hwndForeground;

    DebugEntry(ASHost::AWC_ActivateWindow);

    if (!IsWindow(window))
    {
        WARNING_OUT(( "Trying to activate invalid window %08x", window));
        DC_QUIT;
    }

    //
    // SetForegroundWindow appears to be asynchronous.  That is, it may
    // return successfully here but immediately querying the active
    // window does not always reveal the newly set foreground window to
    // be active.
    //
    rcSFW = SetForegroundWindow(window);
    hwndForeground = GetForegroundWindow();

    if (hwndForeground != window)
    {
        //
        // If a Screen Saver is active then it always refuses to let us
        // activate another window.  Trace an alert if the call to
        // SetForegroundWindow, above, failed.
        //
        if (OSI_IsWindowScreenSaver(hwndForeground) ||
            (m_swlCurrentDesktop != DESKTOP_OURS))
        {
            WARNING_OUT(("Screen Saver or other desktop is up, failed to activate window 0x%08x",
                window));
        }
        else if ( !rcSFW )
        {
            //
            // The active window is not the one we set because
            // SetForegroundWindow failed.
            //
            WARNING_OUT(("Failed to activate window 0x%08x", window));
        }

        //
        // We have apparently failed to set the active window, but
        // SetForegroundWindow succeeded. This is probably just a lag in
        // Windows getting up to date, so continue as if all is normal.
        //
    }

    //
    // Whether we succeeded or failed, make sure we broadcast the current
    // active window.
    //
    m_awcLastActiveWindow = AWC_INVALID_HWND;
    m_awcLastActiveMsg    = AWC_MSG_INVALID;

DC_EXIT_POINT:
    DebugExitVOID(ASHost::AWC_ActivateWindow);
}