// Copyright (c) 2000-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
//
// Initial Contributors:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
// Client side classes for handling direct screen access
// 
//

#include <e32std.h>
#include <e32base.h>
#include "../SERVER/w32cmd.h"
#include "CLIENT.H"
#include "w32comm.h"
#include <e32msgqueue.h>


NONSHARABLE_CLASS(CDsaMsgQueue) : public CActive
{
	public:
		CDsaMsgQueue();
		~CDsaMsgQueue();
		void Request(TRequestStatus* aClientRequest);
		TBool Started() { return iStarted;}
		TBool Completed();
		void OpenRecQueue(TInt aHandle);
		void OpenSendQueue(TInt aHandle);
		TInt Send(TInt aData);
		RMsgQueueBase& SendQueue() {return iSendQueue; }
		RMsgQueueBase& Queue() { return iRecQueue; }
		TRequestStatus* Status() { return &iStatus; }
		TBool RequestStarted() { return iStarted;}
	private:
		void DoCancel();
		void RunL();
		void Listen();
		
	private:
		RMsgQueueBase iRecQueue;
		RMsgQueueBase iSendQueue;
		TRequestStatus* iClientRequest;
		TBool iStarted;
		RThread* iServer;
};

//
CDsaMsgQueue::CDsaMsgQueue() : CActive(RDirectScreenAccess::EPriorityVeryHigh)
	{
	CActiveScheduler::Add(this);
	}

CDsaMsgQueue::~CDsaMsgQueue()
	{
	Cancel();
	iRecQueue.Close();
	iSendQueue.Close();
	}

TInt CDsaMsgQueue::Send(TInt aData)
	{
	return iSendQueue.Send(&aData,sizeof(TInt));
	}

void CDsaMsgQueue::OpenRecQueue(TInt aHandle)
	{
	iRecQueue.SetHandle(aHandle);
// With RmessagePtr2 compelete using an RHandle the returned handle is already duplicated
	}

void CDsaMsgQueue::OpenSendQueue(TInt aHandle)
	{
	iSendQueue.SetHandle(aHandle);
// With RmessagePtr2 compelete using an RHandle the returned handle is already duplicated
	}

void CDsaMsgQueue::DoCancel()
	{
	iRecQueue.CancelDataAvailable();
	TInt ret = KErrNone;
	do
		{
		TInt data = 0;
		ret = iRecQueue.Receive(&data,sizeof(TInt));
		}while(ret == KErrNone);
	if(iClientRequest)
		{
		RThread().RequestComplete(iClientRequest,KErrCancel);
		}
	}
	
void CDsaMsgQueue::RunL()
	{
	// get the data from the msg queue
	TInt reason = 0;
	iRecQueue.Receive(&reason,sizeof(TInt));
	
	if(iClientRequest)
		{
		// if there is an outstanding client request, complete and pass on the abort reason
		User::RequestComplete(iClientRequest,reason);
		iClientRequest = NULL;
		}
	}

void CDsaMsgQueue::Listen()
	{
	if(!IsActive())
		{
		SetActive();	
		iRecQueue.NotifyDataAvailable(iStatus);
		}
	}

void CDsaMsgQueue::Request(TRequestStatus* aClientRequest)
	{
	__ASSERT_ALWAYS(!IsActive(),User::Invariant());
	iClientRequest = aClientRequest;
	iStarted = ETrue;
	Listen();
	}

TBool CDsaMsgQueue::Completed()
	{
	if(iStarted)
		{
		Send(KErrNone);
		iStarted = EFalse;
		return ETrue;
		}
	return EFalse;
	}


LOCAL_C inline TDeviceOrientation Graphics2DeviceOrientation(CFbsBitGc::TGraphicsOrientation aGraphicsOrientation)
    {
    return ((TDeviceOrientation)(1 << aGraphicsOrientation));
    }

//
// RDirectScreenAccess
//

EXPORT_C RDirectScreenAccess::RDirectScreenAccess()
/** Default constructor.

Developers should use the other constructor overload instead. */
	{
	}

EXPORT_C RDirectScreenAccess::RDirectScreenAccess(RWsSession& aWs) : MWsClientClass(aWs.iBuffer), iWs(&aWs), iMsgQueue(NULL)
/** C++ constructor with a connected window server session.

Construct() must be called to complete construction.

@param aWs Connected session with the window server. */
	{
	}

EXPORT_C TInt RDirectScreenAccess::Construct()
/** Second phase constructor.

Creates the server side resource and initialises the client's handle to it.

This function always causes a flush of the window server buffer.

@return KErrNone if successful, otherwise one of the system wide error codes. 
@panic TW32Panic 17 in debug builds if called on an already constructed object.*/
	{
	__ASSERT_DEBUG(iWsHandle == KNullHandle, Panic(EW32PanicGraphicDoubleConstruction));
	return Construct(EFalse);
	}

EXPORT_C TInt RDirectScreenAccess::Construct(TBool aRegionTrackingOnly)
/** Second phase constructor.

Creates the server side resource and initialises the client's handle to it.

This function always causes a flush of the window server buffer.

@param aRegionTrackingOnly ETrue if the DSA is intended to be used for region tracking purposes only,
EFalse if the DSA will be used to perform actual drawing.
@return KErrNone if successful, otherwise one of the system wide error codes. 
@panic TW32Panic 17 in debug builds if called on an already constructed object.*/
	{
	__ASSERT_DEBUG(iWsHandle == KNullHandle, Panic(EW32PanicGraphicDoubleConstruction));
	TInt ret = KErrNone;
	TWsClientOpcodes requestedOpCode = aRegionTrackingOnly? EWsClOpCreateDirectScreenAccessRegionTrackingOnly : EWsClOpCreateDirectScreenAccess;
	
	if ((ret = iBuffer->WriteReplyWs(requestedOpCode)) >= 0)
		{
		iWsHandle = ret;
		TRAP(ret,iMsgQueue = new (ELeave)CDsaMsgQueue);
		if(ret == KErrNone)
			{
			// the servers send queue is the client receive queue
			TInt h = WriteReply(EWsDirectOpGetSendQueue);
			iMsgQueue->OpenRecQueue(h);	
		
			// servers receive queue is the clients send queue
			h = WriteReply(EWsDirectOpGetRecQueue);
			iMsgQueue->OpenSendQueue(h);	
			}
		else
			{
			Close();
			}
		}
	return(ret);
	}

EXPORT_C TInt RDirectScreenAccess::Request(RRegion*& aRegion,TRequestStatus& aStatus,const RWindowBase& aWindow)
/** Issues a request to the window server for permission to perform direct screen 
access on a window.

Direct access to the screen may be refused due to lack of memory or if the 
target window is completely obscured.

If direct access is allowed, the function passes back a clipping region which 
is the part of the screen the caller can draw to. 

When direct screen access must stop, for instance because a dialog is to be 
displayed in front of the region where direct screen access is taking place, 
the window server completes the request. The recommended way to check for 
this is for aStatus to be the request status of an active object that will 
be run when the request completes, i.e. if Request() returns KErrNone, call 
SetActive(), and in the object's RunL(), you should immediately abort direct 
screen access.

While the DSA is in operation, it is strongly advised that the client should 
not make any call to WSERV that will affect the visible area of the window in 
which the DSA is taking place. 

When WSERV tells the client that it needs to abort its DSA, it waits to receive
the acknowledgment from the client that it has done so. However, it doesn't wait 
for ever, since the client may have entered some long running calculation or even
an infinite loop. So WSERV also waits on a timer: if the timer expires before the
client acknowledges, then WSERV continues; if, later on, WSERV gets notification
from the client that it has aborted the DSA, then WSERV will invalidate the region
in which the DSA was taking place, just in case there had been a conflict between
the DSA and another client.


This function always causes a flush of the window server buffer.

@param aRegion On return, the clipping region that the caller can draw to. 
NULL if the function was not successful.
If the target window is invisible or completely covered by other windows
then the region will be empty.
@param aStatus A request status that is set to a completion code by the window 
server when direct screen access must stop.
@param aWindow The window that you want to perform the direct screen access 
on.
@return KErrNone if the request was successful, KErrNone with empty region if 
none of the window is currently visible, otherwise one of the system wide error codes,
e.g. KErrNoMemory if out of memory. */
	{
	__ASSERT_ALWAYS(iMsgQueue,Panic(EW32PanicDirectMisuse));

	aRegion = NULL;

	// Allocate the memory for the RRegion here so it is simple to back out
	// in case of failure
	TAny* regionMem = User::Alloc (sizeof (RRegion));
	if (!regionMem)
		{
		return KErrNoMemory;
		}

	TInt ret = WriteReplyInt(aWindow.WsHandle(),EWsDirectOpRequest);
	if (ret<KErrNone)
		{
		User::Free (regionMem);
		return ret;
		}
	TRect* rectList = NULL;
	TRect* newRectList;
	TInt numRect;

	do
		{
		numRect = ret;
		newRectList = STATIC_CAST(TRect*,User::ReAlloc(rectList,numRect*sizeof(TRect)));
		if (!newRectList)
			{
			Write(EWsDirectOpInitFailed);
			User::Free (regionMem);
			delete rectList;
			return KErrNoMemory;
			}
		rectList = newRectList;
		TPtr8 ptr(REINTERPRET_CAST(TUint8*,rectList),ret*sizeof(TRect));
		ret = WriteReplyIntP(ret,&ptr,EWsDirectOpGetRegion);
		} while(ret >=0 && ret != KMaxTInt);
	if (ret<0)
		{
		User::Free (regionMem);
		delete rectList;
		return ret;
		}

	aRegion = new (regionMem) RRegion (numRect, rectList);
	aStatus = KRequestPending;
	iMsgQueue->Request(&aStatus);
	iWs->DirectAcessActivation(ETrue);
	return KErrNone;
	}

EXPORT_C void RDirectScreenAccess::Completed()
/** Indicates to the window server that you have responded to the completion of 
the request status passed to Request(), by stopping direct screen access. */
	{
	__ASSERT_ALWAYS(iMsgQueue->Started(),Panic(EW32PanicDirectMisuse));
	if(iMsgQueue->Completed())
		{
		iWs->DirectAcessActivation(EFalse);
		}
	}

EXPORT_C void RDirectScreenAccess::Cancel()
/** Indicates to the window server that you have finished performing direct screen 
access. */
	{
	if(iMsgQueue->Started())
		{
		Completed();
		}
	TInt ret = WriteReply(EWsDirectOpCancel);
	if(ret != 0) // the server is sending us some data.
		{
		iMsgQueue->Queue().CancelDataAvailable();
		TInt data = 0;
		iMsgQueue->Queue().ReceiveBlocking(&data,sizeof(TInt));
		}
	iMsgQueue->Cancel();
	}

EXPORT_C void RDirectScreenAccess::Close()
/** Calls Completed() then deletes the server side resource and sets the client's 
handle to it to NULL. */
	{
	if (iBuffer && iWsHandle)
		{
		if(iMsgQueue && iMsgQueue->Started())
			{
			Completed();
			}
		Write(EWsDirectOpFree);
		delete iMsgQueue;
		iMsgQueue = NULL;
		}
	iWsHandle = NULL;
	}

//
// CDirectScreenAccess
//

EXPORT_C CDirectScreenAccess* CDirectScreenAccess::NewL(RWsSession& aWs,CWsScreenDevice& aScreenDevice,RWindowBase& aWin,MDirectScreenAccess& aAbort)
/** Allocates and constructs the object and adds it to the current active scheduler.

This function always causes a flush of the window server buffer.

@param aWs A session with the window server.
@param aScreenDevice Specifies the characteristics of the screen device to 
draw to.
@param aWin The window to draw to directly.
@param aAbort Defines an AbortNow() and a Restart() function which are both 
called on aborting, as part of the RunL(). Restart() is called from an idle 
time active object (CIdle).
@return The newly constructed object. */
	{
	return CDirectScreenAccess::NewL(aWs,aScreenDevice,aWin,aAbort,EFalse);
	}

EXPORT_C CDirectScreenAccess* CDirectScreenAccess::NewL(RWsSession& aWs,CWsScreenDevice& aScreenDevice,RWindowBase& aWin,MDirectScreenAccess& aAbort,TBool aRegionTrackingOnly)
/** Allocates and constructs the object and adds it to the current active scheduler.

This function always causes a flush of the window server buffer.

@param aWs A session with the window server.
@param aScreenDevice Specifies the characteristics of the screen device to 
draw to.
@param aWin The window to draw to directly.
@param aAbort Defines an AbortNow() and a Restart() function which are both 
called on aborting, as part of the RunL(). Restart() is called from an idle 
time active object (CIdle).
@param aRegionTrackingOnly The screen device and GC are allocated if this is EFalse,
but not if it is ETrue. Only the DSA region data and updates to that are
available in the latter case. Creating the screen device will trigger the dsa
buffer allocationand it is an operation that could fail, should this happen
the function will leave.
@return The newly constructed object. */
	{
	CDirectScreenAccess* self = new(ELeave) CDirectScreenAccess(aWs,&aScreenDevice,aWin,aAbort);
	CleanupStack::PushL(self);
	self->ConstructL(aWs,aRegionTrackingOnly);
	CleanupStack::Pop(self);
	return self;
	}

CDirectScreenAccess::~CDirectScreenAccess()
	{
	__ASSERT_ALWAYS(!iAborting,Panic(EW32PanicDirectMisuse));
	Cancel();
	delete iGc;
	delete iScreenDevice;
	if (iDrawingRegion)
		iDrawingRegion->Destroy();
	iDirectAccess.Close();
	delete iRestart;
	}

void CDirectScreenAccess::ConstructL(RWsSession& aWs,TBool aRegionTrackingOnly)
	{
	iScreenNumber = iWsScreenDevice->GetScreenNumber();
	
	if(aRegionTrackingOnly)
		{
		iFlags |= EDirectRegionTrackingOnly;
		}
	User::LeaveIfError(iDirectAccess.Construct(aRegionTrackingOnly));
	
	iRestart = CIdle::NewL(RDirectScreenAccess::EPriorityVeryHigh-5);
	CActiveScheduler::Add(this);
	if (aWs.GetColorModeList(NULL)>1)
		iFlags |= EDirectCheckModeChange;
	if (iWsScreenDevice->NumScreenModes() == 1)
		{
		if ((iWsScreenDevice->GetRotationsList(0,NULL) == 1) && !aRegionTrackingOnly)
			{
			return;
			}
		}
	iFlags |= EDirectCheckSizeModeChange;
	}

void CDirectScreenAccess::CreateScreenObjectsL(TDisplayMode aCurrentMode)
	{
	__ASSERT_DEBUG(!(iFlags&EDirectRegionTrackingOnly),Panic(EW32PanicDirectMisuse));
	delete iScreenDevice;
	iScreenDevice = NULL;
	
	iScreenDevice = CFbsScreenDevice::NewL(iScreenNumber,aCurrentMode);
	
	if (iGc)
		{
		iGc->Activate(iScreenDevice);
		}
	else
		{
		User::LeaveIfError(iScreenDevice->CreateContext(iGc));
		if (!(iFlags&EDirectCheckSizeModeChange))
			UpdateSizeAndRotation(iGc);
		}
	}

EXPORT_C void CDirectScreenAccess::StartL()
/** Informs the window server that you are going to start direct screen access 
and sets up a graphics context with which you can draw to the screen.

It should also be called to restart direct screen access after Cancel() has 
been called to stop it. 

While the DSA is in operation, it is strongly advised that the client should 
not make any call to WSERV that will affect the visible area of the window in 
which the DSA is taking place. 

When WSERV tells the client that it needs to abort its DSA, it waits to receive
the acknowledgment from the client that it has done so. However, it doesn't wait
for ever, since the client may have entered some long running calculation or even
an infinite loop. So WSERV also waits on a timer: if the timer expires before the
client acknowledges, then WSERV continues; if, later on, WSERV gets notification
from the client that it has aborted the DSA, then WSERV will invalidate the region
in which the DSA was taking place, just in case there had been a conflict between
the DSA and another client.


This function always causes a flush of the window server buffer. */
	{
	if (iDrawingRegion)
		iDrawingRegion->Destroy();
	User::LeaveIfError(iDirectAccess.Request(iDrawingRegion,iStatus,iWindow));
	SetActive();
	if(!(iFlags&EDirectRegionTrackingOnly))
		{
		if((iFlags&EDirectCheckModeChange) || iScreenDevice == NULL)
			{
			TDisplayMode currentDisplayMode = iWsScreenDevice->DisplayMode();
			if (iScreenDevice == NULL || currentDisplayMode != iScreenDevice->DisplayMode())
				{
				TRAPD(err,CreateScreenObjectsL(currentDisplayMode));
				if (err != KErrNone)
					{
					Cancel();
					User::Leave(err);
					}
				}
			}
		if (iFlags&EDirectCheckSizeModeChange)
			{
			UpdateSizeAndRotation(iGc);
			}
		iGc->SetOrigin(iWindow.AbsPosition());
		}
	iDrawingRegion->ClipRect(iScreenSize);
	if(!(iFlags&EDirectRegionTrackingOnly))
			{
			iGc->SetClippingRegion(iDrawingRegion);
			}
	}

TInt CDirectScreenAccess::Restart(TAny* aDirect)		//static
	{
	STATIC_CAST(CDirectScreenAccess*,aDirect)->Restart();
	return(KErrNone);
	}

void CDirectScreenAccess::Restart()
	{
	iAbort.Restart(iReason);
	}

void CDirectScreenAccess::UpdateSizeAndRotation(CFbsBitGc* aGc)
	{
	TPixelsAndRotation sizeAndRotation;
	iWsScreenDevice->GetDefaultScreenSizeAndRotation(sizeAndRotation);
	iScreenSize = sizeAndRotation.iPixelSize;
	__ASSERT_ALWAYS(iScreenDevice,Panic(EW32PanicDirectMisuse));
	iScreenDevice->SetDeviceOrientation(Graphics2DeviceOrientation(sizeAndRotation.iRotation));
	MDisplayMapping* interface = static_cast<MDisplayMapping*>
				(iWsScreenDevice->GetInterface(MDisplayMapping::ETypeId));
	
	if(interface)
		{
		TRect appAreaInDsa;
		interface->MapCoordinates(EApplicationSpace, iScreenSize, EDirectScreenAccessSpace, appAreaInDsa);
		if(!iDrawingRegion->BoundingRect().IsEmpty())
			{
			//no point to set draw origin if draw region is empty
			//this also indicates the place to draw might be outside DSA buffer
			iScreenDevice->SetDrawDeviceOffset(appAreaInDsa.iTl);
			}
		}
	
	aGc->Activate(iScreenDevice);
	}

void CDirectScreenAccess::RunL()
	{
	iAborting = ETrue;
	iReason = REINTERPRET_CAST(RDirectScreenAccess::TTerminationReasons&,iStatus);
	iAbort.AbortNow(iReason);
	iAborting = EFalse;
	iDirectAccess.Completed();
	iRestart->Start(TCallBack(CDirectScreenAccess::Restart,this));
	}

void CDirectScreenAccess::DoCancel()
	{
	iDirectAccess.Cancel();
	}
