diff -r 000000000000 -r d54f32e146dd tactilefeedback/tactilefeedbackclient/src/touchfeedbackclient.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tactilefeedback/tactilefeedbackclient/src/touchfeedbackclient.cpp Thu Dec 17 08:53:38 2009 +0200 @@ -0,0 +1,665 @@ +/* +* Copyright (c) 2007-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: Communication with server side implementation of +* Area Registry. +* Part of: Tactile Feedback. +* +*/ + +#include +#include + +#include +#include + +#include "touchfeedbackclient.h" +#include "touchfeedbackimpl.h" +#include "touchfeedbackregistry.h" +#include "OstTraceDefinitions.h" +#ifdef OST_TRACE_COMPILER_IN_USE +#include "touchfeedbackclientTraces.h" +#endif + +// Minimun and maximum sized of one shared memory chunk. +const TInt KTactileChunkInitialSize = 4096; // 4kB +const TInt KTactileChunkMaxSize = 262144; // 256kB +const TUid KTactileClickPluginUid = { 0x2000B493 }; +// ======== MEMBER FUNCTIONS ======== + +// --------------------------------------------------------------------------- +// +// --------------------------------------------------------------------------- +// +CTouchFeedbackClient::CTouchFeedbackClient( CTouchFeedbackImpl& aFeedback ): + iClickPlugin ( CEikonEnv::Static()->WsSession()), + iFeedback( aFeedback ) + { + } + +// --------------------------------------------------------------------------- +// We do all the initializations in the construction phase. Other option +// would be to postpone these until the first registry entry has been added. +// +// #1 Connect to our ClickPlugin +// #2 Create a shared chunk, which is used for storing this application +// process' area registry, so that it can be accessed by the window +// server. +// #3 Set window count to zero in the new chunk +// #4 Try to create a global semaphore to have mutual exclusion on chunks +// If semaphore already exists, then try to open it. +// #5 Put our chunk name to package buffer, and send it to click plugin +// so that it can open a handle to our chunk and thus access the shared +// memory. +// #6 Create a CIdle object that is used for updating the registry in shared +// memory. +// +// Notice that we will leave in case anything fails. This has to be handled +// on higher level, so that whole application launch will not fail in case +// touch feedback does not work. +// --------------------------------------------------------------------------- +// +void CTouchFeedbackClient::ConstructL() + { + TRACE("CTouchFeedbackClient::ConstructL - Begin"); + + // #1 Connect to click plugin + User::LeaveIfError( iClickPlugin.Construct( KTactileClickPluginUid ) ); + + TBool changable = EFalse; + + TBool loaded = iClickPlugin.IsLoaded( changable ); + + if ( !loaded ) + { + User::Leave( KErrNotSupported ); + } + + // #2 Create shared chunk + RThread me; + + TTactileFeedbackConnectData data; + + // Set window group identifier + data.iWindowGroupId = CCoeEnv::Static()->RootWin().Identifier(); + + // We use our own thread name as name for the chunk + data.iChunkName.Copy( me.Name().Right( KMaxKernelName ) ); + + // Now create the chunk + TInt err = iChunk.CreateGlobal( data.iChunkName, + KTactileChunkInitialSize, + KTactileChunkMaxSize ); + + // We have to take into account, that the chunk may already exist. + // This may happen in case we have been started (and crashed) + // once already. + if ( err == KErrAlreadyExists ) + { + User::LeaveIfError( iChunk.OpenGlobal( data.iChunkName, EFalse ) ); + } + + // #3 Set window count to zero + TInt* chunkPtr = reinterpret_cast( iChunk.Base() ); + + *chunkPtr = 0; // No entries in the chunk in the beginning + + // #4 Try to create/open global semaphore + err = iSemaphore.CreateGlobal( KTouchFeedbackSemaphore, 1 ); + + if ( err == KErrAlreadyExists ) + { + User::LeaveIfError( iSemaphore.OpenGlobal( KTouchFeedbackSemaphore ) ); + } + + // #5 Send chunk name to window server + TPckgC dataPkg ( data ); + + User::LeaveIfError( iClickPlugin.CommandReply( ETactileOpCodeConnect, + dataPkg ) ); + iConnected = ETrue; + + iFeedbackTimer = CPeriodic::NewL( CActive::EPriorityStandard ); + + User::LeaveIfError( iFbClient.Connect() ); + + // #6 We use Standard priority so that we won't disturb the normal + // functionality of the application, but can get registry updated + // fastly enough in case the application does very plenty of + // background prosessing with active objects. + iIdle = CIdle::NewL( CActive::EPriorityStandard ); + + TRACE("CTouchFeedbackClient::ConstructL - End"); + } + +// --------------------------------------------------------------------------- +// +// --------------------------------------------------------------------------- +// +CTouchFeedbackClient* CTouchFeedbackClient::NewL( CTouchFeedbackImpl& aFeedback ) + { + CTouchFeedbackClient* self = + new( ELeave ) CTouchFeedbackClient ( aFeedback ); + CleanupStack::PushL( self ); + self->ConstructL(); + CleanupStack::Pop( self ); + return self; + } + +// --------------------------------------------------------------------------- +// +// --------------------------------------------------------------------------- +// +CTouchFeedbackClient::~CTouchFeedbackClient() + { + iChunk.Close(); + iSemaphore.Close(); + + if ( iFeedbackTimer ) + { + iFeedbackTimer->Cancel(); + delete iFeedbackTimer; + } + + iFbClient.Close(); + + if ( iConnected ) + { + TTactileFeedbackDisconnectData data; + + data.iWindowGroupId = CCoeEnv::Static()->RootWin().Identifier(); + + TPckgC dataPkg ( data ); + + iClickPlugin.CommandReply( ETactileOpCodeDisconnect, dataPkg ); + } + + iClickPlugin.Close(); + + delete iIdle; + } + + +// --------------------------------------------------------------------------- +// In this function we just activate CIdle. The motivation is that this +// way we avoid updating the registry multiple times. For e.g. Calculator +// application launch would cause around 20 registry updates in case we would +// always make changes to shared memory immediately after registry update. +// --------------------------------------------------------------------------- +// +void CTouchFeedbackClient::RegistryChanged( ) + { + if ( iIdle && !iIdle->IsActive() ) + { + iIdle->Start( TCallBack( IdleCallbackL, this ) ); + } + } + +// --------------------------------------------------------------------------- +// Callback for CIdle. +// +// We check the current enabled/disabled status of tactile feedback for +// this application, and either UpdateRegistryToChunkL for updating whole +// registry to shared chunk, or ClearWholeChunkL for clearing all feedback +// areas from the shared chunk. +// --------------------------------------------------------------------------- +// +TInt CTouchFeedbackClient::IdleCallbackL( TAny* aPtr ) + { + if ( aPtr ) + { + CTouchFeedbackClient* self = + static_cast ( aPtr ); + + if ( self->iFeedback.FeedbackEnabledForThisApp() ) + { + self->UpdateRegistryToChunkL(); + } + else + { + self->ClearWholeChunkL(); + } + } + + return KErrNone; + } + +// --------------------------------------------------------------------------- +// Area registry is written to shared memory in following format +// +// + Header (four bytes) = Number of windows as TInt +// + Window entries +// + Area registry entries +// +// Window entries: For each window: +// [four bytes] = Window handle +// [four bytes] = Number of registry entries in this window +// [four bytes] = Offset of registry data from beginning of chunk +// Area registry entries: +// - Each entry written as TFeedbackChunkAreaEntry straight into memory +// +// +// Steps taken in this function: +// +// #1 Calculate the size needed for area registry data +// #2 Mutual exclusion with global semaphore +// #3 Increase Chunk size in case necessary +// #4 Update Chunk contents. This is now done simply by writing the whole +// area registry into the shared memory. +// #5 Release semaphore +// --------------------------------------------------------------------------- +// +void CTouchFeedbackClient::UpdateRegistryToChunkL() + { + TRACE( "CTouchFeedbackClient::UpdateRegistryToChunkL - Begin" ); + + RPointerArray* registry = + iFeedback.RegistryArray(); + + // Do cleanup so that empty registry instances are not hanging anymore + TInt windowIndex = 0; + TInt cleanupAreaCount = 0; + TInt cleanupWindowCount = 0; + // value is returned in parameter variable + iFeedback.GetAreaCount( cleanupAreaCount, cleanupWindowCount ); + + while ( windowIndex < cleanupWindowCount ) + { + TBool emptyWindow = ( (*registry)[windowIndex]->AreaCount() == 0 ); + if ( emptyWindow ) + { + // Remove empty/possibly destroyed registry instance that possibly + // may refer to destroyed control/window. + CTouchFeedbackRegistry* feedbackRegistry = (*registry)[windowIndex]; + registry->Remove( windowIndex ); + delete feedbackRegistry; + cleanupWindowCount--; + } + else + { + windowIndex++; // to next window + } + } + + // #1 Calculate the size needed + TInt windowCount(0); + TInt areaCount(0); + iFeedback.GetAreaCount( areaCount, windowCount ); + + // Header contains only number of windows in this registry + TInt headerSize = sizeof( TInt ); + + // There is a list of windows in this registry in the beginning, with + // data offsets in the chunk + TInt windowItemSize = + sizeof( TInt ) + // Window handle + sizeof( TInt ) + // Area count + sizeof( TInt ); // Offset to area registry entries + + // Size of one area registry entry + TInt areaItemSize = sizeof( TFeedbackChunkAreaEntry ); + + TInt sizeNeeded = + headerSize + + windowCount * windowItemSize + + areaCount * areaItemSize; + + // #2 Mutual exclusion + iSemaphore.Wait(); + + // We use cleanup item for making sure that semaphore will not stay + // reserved in case there is a leave. + CleanupStack::PushL( TCleanupItem( CleanupSemaphore, &iSemaphore ) ); + + // #3 Increase chunk (committed) size in case needed + if ( iChunk.Size() < sizeNeeded ) + { + TInt err = iChunk.Adjust( sizeNeeded ); + + if ( err != KErrNone ) + { + // In case the current registry does not fit to the chunk, then + // we'll set window count to zero, in case that is possible + // (it is not possible if chunk size is zero). + // This means that there won't be any area registry based + // feedback for this application on the moment. + if ( iChunk.Size() > 0 ) + { + TInt* chunkPtr = reinterpret_cast( iChunk.Base() ); + + // Set window count to zero + *chunkPtr = 0; + } + + // We will leave anyway + User::Leave( err ); + } + } + + + // #4 Update chunk contents + TInt* chunkPtr = reinterpret_cast( iChunk.Base() ); + + // Set window count in the beginning of chunk + *chunkPtr = windowCount; + chunkPtr++; + + TInt areaOffset = headerSize + windowCount * windowItemSize; + + // This is pointer to the first area registry entry in the chunk + TFeedbackChunkAreaEntry* chunkEntryPtr + = reinterpret_cast ( iChunk.Base() + areaOffset ); + + // Check if audio or vibra is disabled for this application. + TBool audioEnabled = iFeedback.FeedbackEnabledForThisApp( ETouchFeedbackAudio ); + TBool vibraEnabled = iFeedback.FeedbackEnabledForThisApp( ETouchFeedbackVibra ); + + // One loop round for each window where we have area registry entries. + for ( TInt windowIndex = 0; windowIndex < windowCount; windowIndex++ ) + { + RArray* windowRegistry = + (*registry)[windowIndex]->WindowRegistry(); + + // Write handle of this window + *chunkPtr = (*registry)[windowIndex]->WindowHandle(); + chunkPtr++; + // Store the address where we shall write count later + // (We don't know the amount of areas yet, as we shall + // only add areas of visible controls). + TInt* countPointer = chunkPtr; + chunkPtr++; + // Write offset of the area registry entries of this window + *chunkPtr = ((TInt)chunkEntryPtr-(TInt)iChunk.Base()); + chunkPtr++; + + // Write all areas of visible controls in this window + // to the shared memory. Invisible controls' areas are left out so + // that they won't mess up the feedback for overlapping visible + // controls. + TInt visibleAreaCount = 0; + for ( TInt areaIndex = windowRegistry->Count()-1; areaIndex >= 0; areaIndex-- ) + { + TFeedbackEntry entry = (*windowRegistry)[areaIndex]; + + if ( entry.iVisible ) + { + chunkEntryPtr->iRect = entry.iRect; + + TInt feedback = ( entry.iFeedbackTypeUp << 10 ); + feedback |= entry.iFeedbackTypeDown; + + // Add audio and vibra information to feedback type + if ( entry.iVibraEnabled && vibraEnabled ) + { + //feedback |= KTactileVibraBit; + if ( entry.iFeedbackDown & ETouchFeedbackVibra ) + { + feedback |= KTactileVibraBitDown; + } + if ( entry.iFeedbackUp & ETouchFeedbackVibra ) + { + feedback |= KTactileVibraBitUp; + } + + } + if ( entry.iAudioEnabled && audioEnabled ) + { + //feedback |= KTactileAudioBit; + if ( entry.iFeedbackDown & ETouchFeedbackAudio ) + { + feedback |= KTactileAudioBitDown; + } + if ( entry.iFeedbackUp & ETouchFeedbackAudio ) + { + feedback |= KTactileAudioBitUp; + } + } + + chunkEntryPtr->iFeedbackType = static_cast( feedback ); + + chunkEntryPtr->iEventType = entry.iEventType; + + chunkEntryPtr++; + visibleAreaCount++; + } + } + // Now store area count as we know the amount of visible areas + // for this window. + *countPointer = visibleAreaCount; + } + + // #5 Release semaphore so that other processes can access the chunks again + // This calls "Signal" on the semaphore + CleanupStack::PopAndDestroy(&iSemaphore); + + TRACE( "CTouchFeedbackClient::UpdateRegistryToChunkL - End" ); + } + +// --------------------------------------------------------------------------- +// Despite the function name, we don't actually clear whole chunk here +// as that is not necessary. It is enough to set the window count to zero +// at beginning of chunk, as then server will consider this chunk empty. +// +// #1 Mutual exclusion +// #2 Make sure chunk is not of zero -size +// #3 Set window count to zero at beginning of chunk +// #4 Release mutual exclusion +// --------------------------------------------------------------------------- +// +void CTouchFeedbackClient::ClearWholeChunkL() + { + TRACE( "CTouchFeedbackClient::ClearWholeChunkL - Begin" ); + + // #1 Mutual exclusion + iSemaphore.Wait(); + + // We use cleanup item for making sure that semaphore will not stay + // reserved in case there is a leave. + CleanupStack::PushL( TCleanupItem( CleanupSemaphore, &iSemaphore ) ); + + // We only need four bytes for the data, as we are only going to store + // one number to the chunk. But we count the space just in case anyway, + // because at some point chunks initial size may be change so that it is + // zero. + TInt sizeNeeded = sizeof( TInt ); + + // #2 Increase chunk (committed) size in case needed + if ( iChunk.Size() < sizeNeeded ) + { + User::LeaveIfError( iChunk.Adjust( sizeNeeded ) ); + } + + // #3 Update chunk contents + TInt* chunkPtr = reinterpret_cast( iChunk.Base() ); + + // Set window count to zero (this is enough for the server to think + // that there are no feedback areas at all). + *chunkPtr = 0; + + // #4 Release semaphore + CleanupStack::PopAndDestroy(&iSemaphore); + + TRACE( "CTouchFeedbackClient::ClearWholeChunkL - End" ); + } + +// --------------------------------------------------------------------------- +// This is a cleanup function for releasing a semaphore in case there will +// be a leave after calling RSemaphore::Wait (otherwise there could easily +// be a deadlock in the system). +// --------------------------------------------------------------------------- +// +void CTouchFeedbackClient::CleanupSemaphore( TAny* aPtr ) + { + TRACE( "CTouchFeedbackClient::CleanupSemaphore" ); + + if ( aPtr ) + { + RSemaphore* sem = static_cast ( aPtr ); + + sem->Signal(); + } + } + +// --------------------------------------------------------------------------- +// Here we just send the logical feedback type to server. +// --------------------------------------------------------------------------- +// +void CTouchFeedbackClient::ImmediateFeedback( TTouchLogicalFeedback aType, + TBool aVibraOn, TBool aAudioOn ) + { + OstTrace0( TACTILE_PERFORMANCE, TACTILE_CLIENT_INSTANT_FEEDBACK_1, "e_TACTILE_CLIENT_INSTANT_FEEDBACK 1"); + + if ( aVibraOn || aAudioOn ) + { + iFbClient.PlayFeedback( aType, aVibraOn, aAudioOn ); + } + + OstTrace0( TACTILE_PERFORMANCE, TACTILE_CLIENT_INSTANT_FEEDBACK_0, "e_TACTILE_CLIENT_INSTANT_FEEDBACK 0"); + } + +// --------------------------------------------------------------------------- +// Check that we really have something to update, and then do the update. +// --------------------------------------------------------------------------- +// +void CTouchFeedbackClient::FlushUpdates() + { + if ( iIdle && iIdle->IsActive() ) + { + // Do the updates + TRAP_IGNORE( IdleCallbackL( this ) ); + + // Cancel pending update request (no need to keep it active since + // registry is now up-to-date). + iIdle->Cancel(); + } + } + +// ---------------------------------------------------------------------------- +// +// ---------------------------------------------------------------------------- +// +void CTouchFeedbackClient::StartFeedback( TUint32 aClientHandle, + TTouchContinuousFeedback aType, + TInt aIntensity, + TTimeIntervalMicroSeconds32 aTimeout ) + { + TRACE("CTouchFeedbackClient::StartFeedback - Begin"); + + if ( !iFeedbackTimer->IsActive() ) + { + iPreviousIntensity = aIntensity; + iFbClient.StartFeedback( aType, aIntensity ); + iClientHandle = aClientHandle; // Control, which started the feedback. + } + if ( aTimeout != TTimeIntervalMicroSeconds32(0) ) + { + StartFeedbackTimer( aTimeout ); + + if ( aIntensity != iPreviousIntensity ) + { + ModifyFeedback( aClientHandle, aIntensity ); + } + } + + TRACE("CTouchFeedbackClient::StartFeedback - End"); + } + +// ---------------------------------------------------------------------------- +// +// ---------------------------------------------------------------------------- +// +void CTouchFeedbackClient::ModifyFeedback( TUint32 aClientHandle, + TInt aIntensity ) + { + // Modification is allowed only for the same control, which started + // the feedback. + if ( aClientHandle == iClientHandle && + aIntensity != iPreviousIntensity ) + { + iPreviousIntensity = aIntensity; + iFbClient.ModifyFeedback( aIntensity ); + } + } + +// ---------------------------------------------------------------------------- +// +// ---------------------------------------------------------------------------- +// +void CTouchFeedbackClient::StopFeedback( const TUint32 aClientHandle ) + { + TRACE("CTouchFeedbackClient::StopFeedback - Begin"); + if ( aClientHandle == iClientHandle ) + { + iFbClient.StopFeedback(); + iFeedbackTimer->Cancel(); + // Clear also client handle to indicate there's no ongoing feedback. + iClientHandle = 0; + } + TRACE("CTouchFeedbackClient::StopFeedback - End"); + } + +// --------------------------------------------------------------------------- +// +// --------------------------------------------------------------------------- +// +TInt CTouchFeedbackClient::SetFeedbackEnabledForDevice( TTouchFeedbackType aFeedbackType ) + { + return iFbClient.SetFeedbackEnabledForDevice( aFeedbackType ); + } + +// ---------------------------------------------------------------------------- +// +// ---------------------------------------------------------------------------- +// +TTouchFeedbackType CTouchFeedbackClient::FeedbackEnabledForDevice() + { + TTouchFeedbackType feedbackEnabled; + iFbClient.FeedbackEnabledForDevice( feedbackEnabled ); + return feedbackEnabled; + } + +// ---------------------------------------------------------------------------- +// +// ---------------------------------------------------------------------------- +// +void CTouchFeedbackClient::StartFeedbackTimer( TTimeIntervalMicroSeconds32 aTimeout ) + { + iFeedbackTimer->Cancel(); + TCallBack callback( StopFeedbackCallback, this ); + iFeedbackTimer->Start( aTimeout, 0, callback ); + } + +// ---------------------------------------------------------------------------- +// +// ---------------------------------------------------------------------------- +// +void CTouchFeedbackClient::StopFeedbackByTimeout() + { + iFeedbackTimer->Cancel(); + iFbClient.StopFeedback(); + iClientHandle = 0; + } + +// ---------------------------------------------------------------------------- +// CAknSlider::StopFeedbackCallback +// ---------------------------------------------------------------------------- +// +TInt CTouchFeedbackClient::StopFeedbackCallback( TAny* aThis ) + { + TRACE("CTouchFeedbackClient::StopFeedbackCallback - Begin"); + static_cast( aThis )->StopFeedbackByTimeout(); + TRACE("CTouchFeedbackClient::StopFeedbackCallback - End"); + return KErrNone; + } + +// End of File