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 .
20 #include <sal/config.h>
22 #include <o3tl/test_info.hxx>
23 #include <osl/diagnose.h>
24 #include <comphelper/diagnose_ex.hxx>
25 #include <com/sun/star/datatransfer/clipboard/ClipboardEvent.hpp>
26 #include <com/sun/star/lang/DisposedException.hpp>
27 #include <com/sun/star/lang/IllegalArgumentException.hpp>
28 #include <com/sun/star/uno/XComponentContext.hpp>
29 #include <cppuhelper/supportsservice.hxx>
30 #include <cppuhelper/weak.hxx>
31 #include <vcl/svapp.hxx>
33 #include <salinst.hxx>
35 #include <com/sun/star/datatransfer/clipboard/RenderingCapabilities.hpp>
36 #include "XNotifyingDataObject.hxx"
38 #include <systools/win32/comtools.hxx>
39 #include "DtObjFactory.hxx"
40 #include "APNDataObject.hxx"
41 #include "DOTransferable.hxx"
42 #include "WinClipboard.hxx"
44 #if !defined WIN32_LEAN_AND_MEAN
45 #define WIN32_LEAN_AND_MEAN
51 using namespace com::sun::star
;
55 CWinClipboard
* s_pCWinClipbImpl
= nullptr;
56 std::mutex s_aClipboardSingletonMutex
;
58 unsigned __stdcall
releaseAsyncProc(void* p
)
60 static_cast<css::datatransfer::XTransferable
*>(p
)->release();
64 void releaseAsync(css::uno::Reference
<css::datatransfer::XTransferable
>& ref
)
68 auto pInterface
= ref
.get();
69 pInterface
->acquire();
70 ref
.clear(); // before starting the thread, to avoid race
71 if (auto handle
= _beginthreadex(nullptr, 0, releaseAsyncProc
, pInterface
, 0, nullptr))
72 CloseHandle(reinterpret_cast<HANDLE
>(handle
));
77 CWinClipboard::CWinClipboard(const uno::Reference
<uno::XComponentContext
>& rxContext
,
78 const OUString
& aClipboardName
)
79 : m_xContext(rxContext
)
80 , m_itsName(aClipboardName
)
82 // necessary to reassociate from
83 // the static callback function
85 std::unique_lock
aGuard(s_aClipboardSingletonMutex
);
86 s_pCWinClipbImpl
= this;
89 registerClipboardViewer();
92 CWinClipboard::~CWinClipboard()
95 assert(!s_pCWinClipbImpl
);
98 void CWinClipboard::disposing(std::unique_lock
<std::mutex
>& mutex
)
101 std::unique_lock
aGuard(s_aClipboardSingletonMutex
);
102 s_pCWinClipbImpl
= nullptr;
105 unregisterClipboardViewer();
107 WeakComponentImplHelper::disposing(mutex
);
112 CXNotifyingDataObject
* CWinClipboard::getOwnClipContent() const
114 assert(!m_pCurrentOwnClipContent
|| !m_pNewOwnClipContent
); // Both can be null, or only one set
115 return m_pCurrentOwnClipContent
? m_pCurrentOwnClipContent
: m_pNewOwnClipContent
;
118 // to avoid unnecessary traffic we check first if there is a clipboard
119 // content which was set via setContent, in this case we don't need
120 // to query the content from the clipboard, create a new wrapper object
121 // and so on, we simply return the original XTransferable instead of our
124 uno::Reference
<datatransfer::XTransferable
> SAL_CALL
CWinClipboard::getContents()
126 std::unique_lock
aGuard(m_aMutex
);
127 return getContents_noLock();
130 css::uno::Reference
<css::datatransfer::XTransferable
> CWinClipboard::getContents_noLock()
133 throw lang::DisposedException("object is already disposed",
134 static_cast<XClipboardEx
*>(this));
136 assert(!getOwnClipContent() || !m_foreignContent
); // Both can be null, or only one set
138 // use the shortcut or create a transferable from
140 if (auto pOwnClipContent
= getOwnClipContent())
141 return pOwnClipContent
->m_XTransferable
;
144 if (m_foreignContent
.is())
145 return m_foreignContent
;
147 uno::Reference
<datatransfer::XTransferable
> rClipContent
;
149 // get the current format list from clipboard
150 if (UINT nFormats
; !GetUpdatedClipboardFormats(nullptr, 0, &nFormats
)
151 && GetLastError() == ERROR_INSUFFICIENT_BUFFER
)
153 std::vector
<UINT
> aUINTFormats(nFormats
);
154 if (GetUpdatedClipboardFormats(aUINTFormats
.data(), nFormats
, &nFormats
))
156 std::vector
<sal_uInt32
> aFormats(aUINTFormats
.begin(), aUINTFormats
.end());
157 rClipContent
= new CDOTransferable(m_xContext
, this, aFormats
);
159 m_foreignContent
= rClipContent
;
166 IDataObjectPtr
CWinClipboard::getIDataObject()
169 std::unique_lock
aGuard(m_aMutex
);
172 throw lang::DisposedException("object is already disposed",
173 static_cast<XClipboardEx
*>(this));
175 // get the current dataobject from clipboard
176 IDataObjectPtr pIDataObject
;
177 HRESULT hr
= m_MtaOleClipboard
.getClipboard(&pIDataObject
);
181 // create an apartment neutral dataobject and initialize it with a
182 // com smart pointer to the IDataObject from clipboard
183 pIDataObject
= new CAPNDataObject(pIDataObject
);
189 void SAL_CALL
CWinClipboard::setContents(
190 const uno::Reference
<datatransfer::XTransferable
>& xTransferable
,
191 const uno::Reference
<datatransfer::clipboard::XClipboardOwner
>& xClipboardOwner
)
193 std::unique_lock
aGuard(m_aMutex
);
196 throw lang::DisposedException("object is already disposed",
197 static_cast<XClipboardEx
*>(this));
199 IDataObjectPtr pIDataObj
;
201 // The object must be destroyed only outside of the mutex lock, and in a different thread,
202 // because it may call CWinClipboard::onReleaseDataObject, or try to lock solar mutex, in
203 // another thread of this process synchronously
204 releaseAsync(m_foreignContent
); // clear m_foreignContent
205 assert(!m_foreignContent
.is());
206 m_pCurrentOwnClipContent
= nullptr;
208 if (xTransferable
.is())
210 // Store the new object's pointer to temporary m_pNewOwnClipContent, to be moved to
211 // m_pCurrentOwnClipContent in handleClipboardContentChanged.
212 m_pNewOwnClipContent
= new CXNotifyingDataObject(
213 CDTransObjFactory::createDataObjFromTransferable(m_xContext
, xTransferable
),
214 xTransferable
, xClipboardOwner
, this);
216 pIDataObj
= IDataObjectPtr(m_pNewOwnClipContent
);
220 m_pNewOwnClipContent
= nullptr;
223 m_MtaOleClipboard
.setClipboard(pIDataObj
.get());
226 OUString SAL_CALL
CWinClipboard::getName()
228 std::unique_lock
aGuard(m_aMutex
);
230 throw lang::DisposedException("object is already disposed",
231 static_cast<XClipboardEx
*>(this));
236 // XFlushableClipboard
238 void SAL_CALL
CWinClipboard::flushClipboard()
240 std::unique_lock
aGuard(m_aMutex
);
243 throw lang::DisposedException("object is already disposed",
244 static_cast<XClipboardEx
*>(this));
246 // FlushClipboard does a callback and frees DataObject, which calls onReleaseDataObject and
247 // locks mutex. FlushClipboard has to be synchron in order to prevent shutdown until all
248 // clipboard-formats are rendered. The request is needed to prevent flushing if we are not
249 // clipboard owner (it is not known what happens if we flush but aren't clipboard owner).
250 // It may be possible to move the request to the clipboard STA thread by saving the
251 // DataObject and call OleIsCurrentClipboard before flushing.
253 if (getOwnClipContent())
256 m_MtaOleClipboard
.flushClipboard();
262 sal_Int8 SAL_CALL
CWinClipboard::getRenderingCapabilities()
265 throw lang::DisposedException("object is already disposed",
266 static_cast<XClipboardEx
*>(this));
268 using namespace datatransfer::clipboard::RenderingCapabilities
;
269 return (Delayed
| Persistent
);
272 // XClipboardNotifier
274 void SAL_CALL
CWinClipboard::addClipboardListener(
275 const uno::Reference
<datatransfer::clipboard::XClipboardListener
>& listener
)
277 std::unique_lock
aGuard(m_aMutex
);
279 throw lang::DisposedException("object is already disposed",
280 static_cast<XClipboardEx
*>(this));
282 // check input parameter
284 throw lang::IllegalArgumentException("empty reference", static_cast<XClipboardEx
*>(this),
287 maClipboardListeners
.addInterface(aGuard
, listener
);
290 void SAL_CALL
CWinClipboard::removeClipboardListener(
291 const uno::Reference
<datatransfer::clipboard::XClipboardListener
>& listener
)
293 std::unique_lock
aGuard(m_aMutex
);
295 throw lang::DisposedException("object is already disposed",
296 static_cast<XClipboardEx
*>(this));
298 // check input parameter
300 throw lang::IllegalArgumentException("empty reference", static_cast<XClipboardEx
*>(this),
303 maClipboardListeners
.removeInterface(aGuard
, listener
);
306 void CWinClipboard::handleClipboardContentChanged()
308 std::unique_lock
aGuard(m_aMutex
);
312 // The object must be destroyed only outside of the mutex lock, and in a different thread,
313 // because it may call CWinClipboard::onReleaseDataObject, or try to lock solar mutex, in
314 // another thread of this process synchronously
315 releaseAsync(m_foreignContent
); // clear m_foreignContent
316 assert(!m_foreignContent
.is());
317 // If new own content assignment is pending, do it; otherwise, clear it.
318 // This makes sure that there will be no stuck clipboard content.
319 m_pCurrentOwnClipContent
= std::exchange(m_pNewOwnClipContent
, nullptr);
321 if (!maClipboardListeners
.getLength(aGuard
))
326 uno::Reference
<datatransfer::XTransferable
> rXTransf(getContents_noLock());
327 datatransfer::clipboard::ClipboardEvent
aClipbEvent(static_cast<XClipboard
*>(this),
329 maClipboardListeners
.notifyEach(
330 aGuard
, &datatransfer::clipboard::XClipboardListener::changedContents
, aClipbEvent
);
331 aGuard
.unlock(); // for XTransferable dtor, that may delegate to another thread
333 catch (const lang::DisposedException
&)
335 OSL_FAIL("Service Manager disposed");
336 if (aGuard
.owns_lock())
338 // no further clipboard changed notifications
339 unregisterClipboardViewer();
345 OUString SAL_CALL
CWinClipboard::getImplementationName()
347 return "com.sun.star.datatransfer.clipboard.ClipboardW32";
350 sal_Bool SAL_CALL
CWinClipboard::supportsService(const OUString
& ServiceName
)
352 return cppu::supportsService(this, ServiceName
);
355 uno::Sequence
<OUString
> SAL_CALL
CWinClipboard::getSupportedServiceNames()
357 return { "com.sun.star.datatransfer.clipboard.SystemClipboard" };
360 extern "C" SAL_DLLPUBLIC_EXPORT
css::uno::XInterface
*
361 dtrans_CWinClipboard_get_implementation(css::uno::XComponentContext
* context
,
362 css::uno::Sequence
<css::uno::Any
> const& args
)
364 // We run unit tests in parallel, which is a problem when touching a shared resource
365 // like the system clipboard, so rather use the dummy GenericClipboard.
366 static const bool bRunningUnitTest
= o3tl::IsRunningUnitTest() || o3tl::IsRunningUITest();
368 if (bRunningUnitTest
)
370 SolarMutexGuard aGuard
;
371 auto xClipboard
= ImplGetSVData()->mpDefInst
->CreateClipboard(args
);
373 xClipboard
->acquire();
374 return xClipboard
.get();
378 return cppu::acquire(new CWinClipboard(context
, ""));
382 void CWinClipboard::onReleaseDataObject(CXNotifyingDataObject
& theCaller
)
384 theCaller
.lostOwnership();
386 // if the current caller is the one we currently hold, then set it to NULL
387 // because an external source must be the clipboardowner now
388 std::unique_lock
aGuard(m_aMutex
);
390 if (getOwnClipContent() == &theCaller
)
391 m_pCurrentOwnClipContent
= m_pNewOwnClipContent
= nullptr;
394 void CWinClipboard::registerClipboardViewer()
396 m_MtaOleClipboard
.registerClipViewer(CWinClipboard::onClipboardContentChanged
);
399 void CWinClipboard::unregisterClipboardViewer() { m_MtaOleClipboard
.registerClipViewer(nullptr); }
401 void WINAPI
CWinClipboard::onClipboardContentChanged()
403 rtl::Reference
<CWinClipboard
> pWinClipboard
;
405 // Only hold the mutex to obtain a safe reference to the impl, to avoid deadlock
406 std::unique_lock
aGuard(s_aClipboardSingletonMutex
);
407 pWinClipboard
.set(s_pCWinClipbImpl
);
411 pWinClipboard
->handleClipboardContentChanged();
414 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */