1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
21 MtaOleClipb.cxx - documentation
23 This class setup a single threaded apartment (sta) thread to deal with
24 the ole clipboard, which runs only in an sta thread.
25 The consequence is that callback from the ole clipboard are in the
26 context of this sta thread. In the soffice applications this may lead
27 to problems because they all use the one and only mutex called
29 In order to transfer clipboard requests to our sta thread we use a
30 hidden window an forward these requests via window messages.
34 #pragma warning( disable : 4786 ) // identifier was truncated to 'number'
35 // characters in the debug information
39 #include <osl/diagnose.h>
41 #include "MtaOleClipb.hxx"
42 #include <osl/conditn.hxx>
43 #include <osl/thread.h>
48 #include <systools/win32/comtools.hxx>
53 #define __uuidof(I) IID_##I
56 // namespace directives
60 using osl::MutexGuard
;
61 using osl::ClearableMutexGuard
;
63 namespace /* private */
65 char CLIPSRV_DLL_NAME
[] = "sysdtrans.dll";
66 char g_szWndClsName
[] = "MtaOleReqWnd###";
70 const sal_uInt32 MSG_SETCLIPBOARD
= WM_USER
+ 0x0001;
71 const sal_uInt32 MSG_GETCLIPBOARD
= WM_USER
+ 0x0002;
72 const sal_uInt32 MSG_REGCLIPVIEWER
= WM_USER
+ 0x0003;
73 const sal_uInt32 MSG_FLUSHCLIPBOARD
= WM_USER
+ 0x0004;
74 const sal_uInt32 MSG_SHUTDOWN
= WM_USER
+ 0x0005;
76 const sal_uInt32 MAX_WAITTIME
= 10000; // msec
77 const sal_uInt32 MAX_WAIT_SHUTDOWN
= 10000; // msec
78 const sal_uInt32 MAX_CLIPEVENT_PROCESSING_TIME
= 5000; // msec
80 const sal_Bool MANUAL_RESET
= sal_True
;
81 const sal_Bool AUTO_RESET
= sal_False
;
82 const sal_Bool INIT_NONSIGNALED
= sal_False
;
84 /* Cannot use osl conditions because they are blocking
85 without waking up on messages sent by another thread
86 this leads to deadlocks because we are blocking the
87 communication between inter-thread marshalled COM
89 COM Proxy-Stub communication uses SendMessages for
90 synchronization purposes.
98 m_hEvent
= CreateEvent(
100 true, /* manual reset */
101 false, /* initial state not signaled */
102 0); /* automatic name */
108 CloseHandle(m_hEvent
);
111 // wait infinite for event be signaled
112 // leave messages sent through
118 MsgWaitForMultipleObjects(1, &m_hEvent
, FALSE
, INFINITE
, QS_SENDMESSAGE
);
125 case WAIT_OBJECT_0
+ 1:
127 /* PeekMessage processes all messages in the SendMessage
128 queue that's what we want, messages from the PostMessage
129 queue stay untouched */
131 PeekMessage(&msg
, NULL
, 0, 0, PM_NOREMOVE
);
148 // prevent copy/assignment
150 Win32Condition(const Win32Condition
&);
151 Win32Condition
& operator=(const Win32Condition
&);
154 // we use one condition for every request
158 Win32Condition aCondition
;
162 } /* namespace private */
164 // static member initialization
166 CMtaOleClipboard
* CMtaOleClipboard::s_theMtaOleClipboardInst
= NULL
;
168 // marshal an IDataObject
171 HRESULT
MarshalIDataObjectInStream( IDataObject
* pIDataObject
, LPSTREAM
* ppStream
)
173 OSL_ASSERT( NULL
!= pIDataObject
);
174 OSL_ASSERT( NULL
!= ppStream
);
177 return CoMarshalInterThreadInterfaceInStream(
178 __uuidof(IDataObject
), //The IID of interface to be marshaled
179 pIDataObject
, //The interface pointer
180 ppStream
//IStream pointer
184 // unmarshal an IDataObject
187 HRESULT
UnmarshalIDataObjectAndReleaseStream( LPSTREAM lpStream
, IDataObject
** ppIDataObject
)
189 OSL_ASSERT( NULL
!= lpStream
);
190 OSL_ASSERT( NULL
!= ppIDataObject
);
192 *ppIDataObject
= NULL
;
193 return CoGetInterfaceAndReleaseStream(
195 __uuidof(IDataObject
),
196 reinterpret_cast<LPVOID
*>(ppIDataObject
));
199 // helper class to ensure that the calling thread has com initialized
207 to be safe we call CoInitialize
208 although it is not necessary if
209 the calling thread was created
210 using osl_CreateThread because
211 this function calls CoInitialize
212 for every thread it creates
214 m_hResult
= CoInitialize( NULL
);
216 if ( S_OK
== m_hResult
)
218 "com was not yet initialzed, the thread was not created using osl_createThread" );
219 else if ( FAILED( m_hResult
) && !( RPC_E_CHANGED_MODE
== m_hResult
) )
221 "com could not be initialized, maybe the thread was not created using osl_createThread" );
227 we only call CoUninitialize when
228 CoInitailize returned S_FALSE, what
229 means that com was already initialize
230 for that thread so we keep the balance
231 if CoInitialize returned S_OK what means
232 com was not yet initialized we better
233 let com initialized or we may run into
234 the realm of undefined behaviour
236 if ( m_hResult
== S_FALSE
)
246 CMtaOleClipboard::CMtaOleClipboard( ) :
247 m_hOleThread( NULL
),
249 m_hEvtThrdReady( NULL
),
250 m_hwndMtaOleReqWnd( NULL
),
251 m_MtaOleReqWndClassAtom( 0 ),
252 m_hwndNextClipViewer( NULL
),
253 m_pfncClipViewerCallback( NULL
),
254 m_bRunClipboardNotifierThread( true ),
255 m_hClipboardChangedEvent( m_hClipboardChangedNotifierEvents
[0] ),
256 m_hTerminateClipboardChangedNotifierEvent( m_hClipboardChangedNotifierEvents
[1] ),
257 m_ClipboardChangedEventCount( 0 )
259 // signals that the thread was successfully setup
260 m_hEvtThrdReady
= CreateEventA( 0, MANUAL_RESET
, INIT_NONSIGNALED
, NULL
);
262 OSL_ASSERT( NULL
!= m_hEvtThrdReady
);
264 s_theMtaOleClipboardInst
= this;
266 m_hOleThread
= (HANDLE
)_beginthreadex(
267 NULL
, 0, CMtaOleClipboard::oleThreadProc
, this, 0, &m_uOleThreadId
);
268 OSL_ASSERT( NULL
!= m_hOleThread
);
270 // setup the clipboard changed notifier thread
272 m_hClipboardChangedNotifierEvents
[0] = CreateEventA( 0, MANUAL_RESET
, INIT_NONSIGNALED
, NULL
);
273 OSL_ASSERT( NULL
!= m_hClipboardChangedNotifierEvents
[0] );
275 m_hClipboardChangedNotifierEvents
[1] = CreateEventA( 0, MANUAL_RESET
, INIT_NONSIGNALED
, NULL
);
276 OSL_ASSERT( NULL
!= m_hClipboardChangedNotifierEvents
[1] );
279 m_hClipboardChangedNotifierThread
= (HANDLE
)_beginthreadex(
280 NULL
, 0, CMtaOleClipboard::clipboardChangedNotifierThreadProc
, this, 0, &uThreadId
);
282 OSL_ASSERT( NULL
!= m_hClipboardChangedNotifierThread
);
287 CMtaOleClipboard::~CMtaOleClipboard( )
289 // block calling threads out
290 if ( NULL
!= m_hEvtThrdReady
)
291 ResetEvent( m_hEvtThrdReady
);
293 // terminate the clipboard changed notifier thread
294 m_bRunClipboardNotifierThread
= false;
295 SetEvent( m_hTerminateClipboardChangedNotifierEvent
);
297 sal_uInt32 dwResult
= WaitForSingleObject(
298 m_hClipboardChangedNotifierThread
, MAX_WAIT_SHUTDOWN
);
301 OSL_ENSURE( dwResult
== WAIT_OBJECT_0
, "clipboard notifier thread could not terminate" );
303 if ( NULL
!= m_hClipboardChangedNotifierThread
)
304 CloseHandle( m_hClipboardChangedNotifierThread
);
306 if ( NULL
!= m_hClipboardChangedNotifierEvents
[0] )
307 CloseHandle( m_hClipboardChangedNotifierEvents
[0] );
309 if ( NULL
!= m_hClipboardChangedNotifierEvents
[1] )
310 CloseHandle( m_hClipboardChangedNotifierEvents
[1] );
313 // because DestroyWindow can only be called
314 // from within the thread that created the window
315 sendMessage( MSG_SHUTDOWN
,
316 static_cast< WPARAM
>( 0 ),
317 static_cast< LPARAM
>( 0 ) );
319 // wait for thread shutdown
320 dwResult
= WaitForSingleObject( m_hOleThread
, MAX_WAIT_SHUTDOWN
);
321 OSL_ENSURE( dwResult
== WAIT_OBJECT_0
, "OleThread could not terminate" );
323 if ( NULL
!= m_hOleThread
)
324 CloseHandle( m_hOleThread
);
326 if ( NULL
!= m_hEvtThrdReady
)
327 CloseHandle( m_hEvtThrdReady
);
329 if ( m_MtaOleReqWndClassAtom
)
330 UnregisterClassA( g_szWndClsName
, NULL
);
332 OSL_ENSURE( ( NULL
== m_pfncClipViewerCallback
) &&
333 !IsWindow( m_hwndNextClipViewer
), \
334 "Clipboard viewer not properly unregistered" );
337 HRESULT
CMtaOleClipboard::flushClipboard( )
339 if ( !WaitForThreadReady( ) )
341 OSL_FAIL( "clipboard sta thread not ready" );
345 OSL_ENSURE( GetCurrentThreadId( ) != m_uOleThreadId
, \
346 "flushClipboard from within clipboard sta thread called" );
350 postMessage( MSG_FLUSHCLIPBOARD
,
351 static_cast< WPARAM
>( 0 ),
352 reinterpret_cast< LPARAM
>( &aMsgCtx
) );
354 aMsgCtx
.aCondition
.wait( /* infinite */ );
359 HRESULT
CMtaOleClipboard::getClipboard( IDataObject
** ppIDataObject
)
361 OSL_PRECOND( NULL
!= ppIDataObject
, "invalid parameter" );
362 OSL_PRECOND( GetCurrentThreadId( ) != m_uOleThreadId
, "getClipboard from within clipboard sta thread called" );
364 if ( !WaitForThreadReady( ) )
366 OSL_FAIL( "clipboard sta thread not ready" );
370 CAutoComInit comAutoInit
;
374 *ppIDataObject
= NULL
;
378 postMessage( MSG_GETCLIPBOARD
,
379 reinterpret_cast< WPARAM
>( &lpStream
),
380 reinterpret_cast< LPARAM
>( &aMsgCtx
) );
382 aMsgCtx
.aCondition
.wait( /* infinite */ );
384 HRESULT hr
= aMsgCtx
.hr
;
386 if ( SUCCEEDED( hr
) )
388 hr
= UnmarshalIDataObjectAndReleaseStream( lpStream
, ppIDataObject
);
389 OSL_ENSURE( SUCCEEDED( hr
), "unmarshalling clipboard data object failed" );
395 // this is an asynchronous method that's why we don't wait until the
396 // request is completed
398 HRESULT
CMtaOleClipboard::setClipboard( IDataObject
* pIDataObject
)
400 if ( !WaitForThreadReady( ) )
402 OSL_FAIL( "clipboard sta thread not ready" );
406 CAutoComInit comAutoInit
;
408 OSL_ENSURE( GetCurrentThreadId( ) != m_uOleThreadId
, "setClipboard from within the clipboard sta thread called" );
410 // because we marshall this request
411 // into the sta thread we better
412 // acquire the interface here so
413 // that the object will not be
414 // destroyed before the ole clipboard
416 // remember: pIDataObject may be NULL
417 // which is an request to clear the
418 // current clipboard content
420 pIDataObject
->AddRef( );
424 reinterpret_cast< WPARAM
>( pIDataObject
),
427 // because this is an asynchronous function
428 // the return value is useless
432 // register a clipboard viewer
434 bool CMtaOleClipboard::registerClipViewer( LPFNC_CLIPVIEWER_CALLBACK_t pfncClipViewerCallback
)
436 if ( !WaitForThreadReady( ) )
438 OSL_FAIL( "clipboard sta thread not ready" );
444 OSL_ENSURE( GetCurrentThreadId( ) != m_uOleThreadId
, "registerClipViewer from within the OleThread called" );
448 postMessage( MSG_REGCLIPVIEWER
,
449 reinterpret_cast<WPARAM
>( pfncClipViewerCallback
),
450 reinterpret_cast<LPARAM
>( &aMsgCtx
) );
452 aMsgCtx
.aCondition
.wait( /* infinite */ );
457 // register a clipboard viewer
459 bool CMtaOleClipboard::onRegisterClipViewer( LPFNC_CLIPVIEWER_CALLBACK_t pfncClipViewerCallback
)
463 // we need exclusive access because the clipboard changed notifier
464 // thread also accesses this variable
465 MutexGuard
aGuard( m_pfncClipViewerCallbackMutex
);
467 // register if not yet done
468 if ( ( NULL
!= pfncClipViewerCallback
) && ( NULL
== m_pfncClipViewerCallback
) )
470 // SetClipboardViewer sends a WM_DRAWCLIPBOARD message we ignore
471 // this message if we register ourself as clip viewer
472 m_bInRegisterClipViewer
= true;
473 m_hwndNextClipViewer
= SetClipboardViewer( m_hwndMtaOleReqWnd
);
474 m_bInRegisterClipViewer
= false;
476 // if there is no other cb-viewer the
477 // return value is NULL!!!
478 bRet
= IsWindow( m_hwndNextClipViewer
) ? true : false;
480 // save the new callback function
481 m_pfncClipViewerCallback
= pfncClipViewerCallback
;
483 else if ( ( NULL
== pfncClipViewerCallback
) && ( NULL
!= m_pfncClipViewerCallback
) )
485 m_pfncClipViewerCallback
= NULL
;
487 // unregister if input parameter is NULL and we previously registered
488 // as clipboard viewer
489 ChangeClipboardChain( m_hwndMtaOleReqWnd
, m_hwndNextClipViewer
);
490 m_hwndNextClipViewer
= NULL
;
496 LRESULT
CMtaOleClipboard::onSetClipboard( IDataObject
* pIDataObject
)
498 return static_cast<LRESULT
>( OleSetClipboard( pIDataObject
) );
501 LRESULT
CMtaOleClipboard::onGetClipboard( LPSTREAM
* ppStream
)
503 OSL_ASSERT(NULL
!= ppStream
);
505 IDataObjectPtr pIDataObject
;
507 // forward the request to the OleClipboard
508 HRESULT hr
= OleGetClipboard( &pIDataObject
);
509 if ( SUCCEEDED( hr
) )
511 hr
= MarshalIDataObjectInStream(pIDataObject
.get(), ppStream
);
512 OSL_ENSURE(SUCCEEDED(hr
), "marshalling cliboard data object failed");
514 return static_cast<LRESULT
>(hr
);
517 // flush the ole-clipboard
519 LRESULT
CMtaOleClipboard::onFlushClipboard( )
521 return static_cast<LRESULT
>( OleFlushClipboard( ) );
524 // handle clipboard chain change event
526 LRESULT
CMtaOleClipboard::onChangeCBChain( HWND hWndRemove
, HWND hWndNext
)
528 if ( hWndRemove
== m_hwndNextClipViewer
)
529 m_hwndNextClipViewer
= hWndNext
;
530 else if ( IsWindow( m_hwndNextClipViewer
) )
532 // forward the message to the next one
535 m_hwndNextClipViewer
,
537 reinterpret_cast<WPARAM
>(hWndRemove
),
538 reinterpret_cast<LPARAM
>(hWndNext
),
540 MAX_CLIPEVENT_PROCESSING_TIME
,
547 // handle draw clipboard event
549 LRESULT
CMtaOleClipboard::onDrawClipboard( )
551 // we don't send a notification if we are
552 // registering ourself as clipboard
553 if ( !m_bInRegisterClipViewer
)
555 ClearableMutexGuard
aGuard( m_ClipboardChangedEventCountMutex
);
557 m_ClipboardChangedEventCount
++;
558 SetEvent( m_hClipboardChangedEvent
);
563 // forward the message to the next viewer in the chain
564 if ( IsWindow( m_hwndNextClipViewer
) )
568 m_hwndNextClipViewer
,
570 static_cast< WPARAM
>( 0 ),
571 static_cast< LPARAM
>( 0 ),
573 MAX_CLIPEVENT_PROCESSING_TIME
,
580 // SendMessage so we don't need to supply the HWND if we send
581 // something to our wrapped window
583 LRESULT
CMtaOleClipboard::sendMessage( UINT msg
, WPARAM wParam
, LPARAM lParam
)
585 return ::SendMessageA( m_hwndMtaOleReqWnd
, msg
, wParam
, lParam
);
588 // PostMessage so we don't need to supply the HWND if we send
589 // something to our wrapped window
591 bool CMtaOleClipboard::postMessage( UINT msg
, WPARAM wParam
, LPARAM lParam
)
593 return PostMessageA( m_hwndMtaOleReqWnd
, msg
, wParam
, lParam
) ? true : false;
598 LRESULT CALLBACK
CMtaOleClipboard::mtaOleReqWndProc( HWND hWnd
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
602 // get a connection to the class-instance via the static member
603 CMtaOleClipboard
* pImpl
= CMtaOleClipboard::s_theMtaOleClipboardInst
;
604 OSL_ASSERT( NULL
!= pImpl
);
608 case MSG_SETCLIPBOARD
:
610 IDataObject
* pIDataObject
= reinterpret_cast< IDataObject
* >( wParam
);
611 pImpl
->onSetClipboard( pIDataObject
);
613 // in setClipboard we did acquire the
614 // interface pointer in order to prevent
615 // destruction of the object before the
616 // ole clipboard can acquire the interface
617 // now we release the interface so that
618 // our lostOwnership mechanism works
619 // remember: pIDataObject may be NULL
621 pIDataObject
->Release( );
625 case MSG_GETCLIPBOARD
:
627 MsgCtx
* aMsgCtx
= reinterpret_cast< MsgCtx
* >( lParam
);
628 OSL_ASSERT( aMsgCtx
);
630 aMsgCtx
->hr
= pImpl
->onGetClipboard( reinterpret_cast< LPSTREAM
* >(wParam
) );
631 aMsgCtx
->aCondition
.set( );
635 case MSG_FLUSHCLIPBOARD
:
637 MsgCtx
* aMsgCtx
= reinterpret_cast< MsgCtx
* >( lParam
);
638 OSL_ASSERT( aMsgCtx
);
640 aMsgCtx
->hr
= pImpl
->onFlushClipboard( );
641 aMsgCtx
->aCondition
.set( );
645 case MSG_REGCLIPVIEWER
:
647 MsgCtx
* aMsgCtx
= reinterpret_cast< MsgCtx
* >( lParam
);
648 OSL_ASSERT( aMsgCtx
);
650 pImpl
->onRegisterClipViewer( reinterpret_cast<CMtaOleClipboard::LPFNC_CLIPVIEWER_CALLBACK_t
>(wParam
) );
651 aMsgCtx
->aCondition
.set( );
655 case WM_CHANGECBCHAIN
:
656 lResult
= pImpl
->onChangeCBChain(
657 reinterpret_cast< HWND
>( wParam
), reinterpret_cast< HWND
>( lParam
) );
660 case WM_DRAWCLIPBOARD
:
661 lResult
= pImpl
->onDrawClipboard( );
665 DestroyWindow( pImpl
->m_hwndMtaOleReqWnd
);
668 // force the sta thread to end
670 PostQuitMessage( 0 );
674 lResult
= DefWindowProcA( hWnd
, uMsg
, wParam
, lParam
);
681 void CMtaOleClipboard::createMtaOleReqWnd( )
685 HINSTANCE hInst
= GetModuleHandleA( CLIPSRV_DLL_NAME
);
686 OSL_ENSURE( NULL
!= hInst
, "The name of the clipboard service dll must have changed" );
688 ZeroMemory( &wcex
, sizeof( WNDCLASSEXA
) );
690 wcex
.cbSize
= sizeof(WNDCLASSEXA
);
692 wcex
.lpfnWndProc
= static_cast< WNDPROC
>( CMtaOleClipboard::mtaOleReqWndProc
);
695 wcex
.hInstance
= hInst
;
698 wcex
.hbrBackground
= NULL
;
699 wcex
.lpszMenuName
= NULL
;
700 wcex
.lpszClassName
= g_szWndClsName
;
703 m_MtaOleReqWndClassAtom
= RegisterClassExA( &wcex
);
705 if ( 0 != m_MtaOleReqWndClassAtom
)
706 m_hwndMtaOleReqWnd
= CreateWindowA(
707 g_szWndClsName
, NULL
, 0, 0, 0, 0, 0, NULL
, NULL
, hInst
, NULL
);
710 unsigned int CMtaOleClipboard::run( )
712 #if OSL_DEBUG_LEVEL > 0
715 OleInitialize( NULL
);
716 OSL_ASSERT( SUCCEEDED( hr
) );
718 createMtaOleReqWnd( );
722 if ( IsWindow( m_hwndMtaOleReqWnd
) )
724 if ( NULL
!= m_hEvtThrdReady
)
725 SetEvent( m_hEvtThrdReady
);
729 while( GetMessageA( &msg
, NULL
, 0, 0 ) )
730 DispatchMessageA( &msg
);
742 unsigned int WINAPI
CMtaOleClipboard::oleThreadProc( LPVOID pParam
)
744 osl_setThreadName("CMtaOleClipboard::run()");
746 CMtaOleClipboard
* pInst
=
747 reinterpret_cast<CMtaOleClipboard
*>( pParam
);
748 OSL_ASSERT( NULL
!= pInst
);
750 return pInst
->run( );
753 unsigned int WINAPI
CMtaOleClipboard::clipboardChangedNotifierThreadProc( LPVOID pParam
)
755 osl_setThreadName("CMtaOleClipboard::clipboardChangedNotifierThreadProc()");
756 CMtaOleClipboard
* pInst
= reinterpret_cast< CMtaOleClipboard
* >( pParam
);
757 OSL_ASSERT( NULL
!= pInst
);
759 CoInitialize( NULL
);
761 // assuming we don't need a lock for
762 // a boolean variable like m_bRun...
763 while ( pInst
->m_bRunClipboardNotifierThread
)
765 // wait for clipboard changed or terminate event
766 WaitForMultipleObjects( 2, pInst
->m_hClipboardChangedNotifierEvents
, false, INFINITE
);
768 ClearableMutexGuard
aGuard( pInst
->m_ClipboardChangedEventCountMutex
);
770 if ( pInst
->m_ClipboardChangedEventCount
> 0 )
772 pInst
->m_ClipboardChangedEventCount
--;
773 if ( 0 == pInst
->m_ClipboardChangedEventCount
)
774 ResetEvent( pInst
->m_hClipboardChangedEvent
);
778 // nobody should touch m_pfncClipViewerCallback while we do
779 MutexGuard
aClipViewerGuard( pInst
->m_pfncClipViewerCallbackMutex
);
781 // notify all clipboard listener
782 if ( pInst
->m_pfncClipViewerCallback
)
783 pInst
->m_pfncClipViewerCallback( );
794 bool CMtaOleClipboard::WaitForThreadReady( ) const
798 if ( NULL
!= m_hEvtThrdReady
)
800 DWORD dwResult
= WaitForSingleObject(
801 m_hEvtThrdReady
, MAX_WAITTIME
);
802 bRet
= ( dwResult
== WAIT_OBJECT_0
);
808 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */