bump product version to 6.3.0.0.beta1
[LibreOffice.git] / extensions / source / scanner / scanwin.cxx
blob11e2e7345df4ff4e2406c789c4a0d0ae8c5927f7
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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 <com/sun/star/uno/Reference.hxx>
21 #include <com/sun/star/frame/XFrame.hpp>
22 #include <com/sun/star/frame/Desktop.hpp>
24 #include "twain32shim.hxx"
26 #include <comphelper/processfactory.hxx>
27 #include <comphelper/scopeguard.hxx>
28 #include <config_folders.h>
29 #include <o3tl/char16_t2wchar_t.hxx>
30 #include <osl/conditn.hxx>
31 #include <osl/file.hxx>
32 #include <osl/mutex.hxx>
33 #include <rtl/bootstrap.hxx>
34 #include <salhelper/thread.hxx>
35 #include <toolkit/helper/vclunohelper.hxx>
36 #include <tools/stream.hxx>
37 #include <tools/helpers.hxx>
38 #include <vcl/svapp.hxx>
39 #include <vcl/window.hxx>
40 #include "scanner.hxx"
42 namespace
45 enum TwainState
47 TWAIN_STATE_NONE = 0,
48 TWAIN_STATE_SCANNING = 1,
49 TWAIN_STATE_DONE = 2,
50 TWAIN_STATE_CANCELED = 3
53 struct HANDLEDeleter
55 using pointer = HANDLE;
56 void operator()(HANDLE h) { CloseHandle(h); }
59 using ScopedHANDLE = std::unique_ptr<HANDLE, HANDLEDeleter>;
61 class Twain
63 public:
64 Twain();
65 ~Twain();
67 bool SelectSource(ScannerManager& rMgr, const VclPtr<vcl::Window>& xTopWindow);
68 bool PerformTransfer(ScannerManager& rMgr,
69 const css::uno::Reference<css::lang::XEventListener>& rxListener,
70 const VclPtr<vcl::Window>& xTopWindow);
71 void WaitReadyForNextTask();
73 TwainState GetState() const { return meState; }
75 private:
76 friend class ShimListenerThread;
77 class ShimListenerThread : public salhelper::Thread
79 public:
80 ShimListenerThread(const VclPtr<vcl::Window>& xTopWindow);
81 ~ShimListenerThread() override;
82 void execute() override;
83 const OUString& getError() { return msErrorReported; }
85 // These methods are executed outside of own thread
86 bool WaitInitialization();
87 bool WaitRequestResult();
88 void DontNotify() { mbDontNotify = true; }
89 void RequestDestroy();
90 bool RequestSelectSource();
91 bool RequestInitXfer();
93 private:
94 VclPtr<vcl::Window> mxTopWindow; // the window that we made modal
95 bool mbDontNotify = false;
96 HWND mhWndShim = nullptr; // shim main window handle
97 OUString msErrorReported;
98 osl::Condition mcInitCompleted; // initially not set
99 bool mbInitSucceeded = false;
100 osl::Condition mcGotRequestResult;
101 bool mbRequestResult = false;
103 void SendShimRequest(WPARAM nRequest);
104 bool SendShimRequestWithResult(WPARAM nRequest);
105 void NotificationHdl(WPARAM nEvent, LPARAM lParam);
106 void NotifyOwner(WPARAM nEvent);
107 void NotifyXFerOwner(LPARAM nHandle);
109 css::uno::Reference<css::lang::XEventListener> mxListener;
110 css::uno::Reference<css::scanner::XScannerManager> mxMgr;
111 ScannerManager* mpCurMgr = nullptr;
112 TwainState meState = TWAIN_STATE_NONE;
113 rtl::Reference<ShimListenerThread> mpThread;
114 osl::Mutex maMutex;
116 DECL_LINK(ImpNotifyHdl, void*, void);
117 DECL_LINK(ImpNotifyXferHdl, void*, void);
118 void Notify(WPARAM nEvent); // called by shim communication thread to notify me
119 void NotifyXFer(LPARAM nHandle); // called by shim communication thread to notify me
121 bool InitializeNewShim(ScannerManager& rMgr, const VclPtr<vcl::Window>& xTopWindow);
123 void Reset(); // cleanup thread and manager
126 static Twain aTwain;
128 Twain::ShimListenerThread::ShimListenerThread(const VclPtr<vcl::Window>& xTopWindow)
129 : salhelper::Thread("TWAINShimListenerThread")
130 , mxTopWindow(xTopWindow)
132 if (mxTopWindow)
134 mxTopWindow->IncModalCount(); // the operation is modal to the frame
138 Twain::ShimListenerThread::~ShimListenerThread()
140 if (mxTopWindow)
142 mxTopWindow->DecModalCount(); // unblock the frame
146 bool Twain::ShimListenerThread::WaitInitialization()
148 mcInitCompleted.wait();
149 return mbInitSucceeded;
152 bool Twain::ShimListenerThread::WaitRequestResult()
154 mcGotRequestResult.wait();
155 return mbRequestResult;
158 void Twain::ShimListenerThread::SendShimRequest(WPARAM nRequest)
160 if (mhWndShim)
161 PostMessageW(mhWndShim, WM_TWAIN_REQUEST, nRequest, 0);
164 bool Twain::ShimListenerThread::SendShimRequestWithResult(WPARAM nRequest)
166 mcGotRequestResult.reset();
167 mbRequestResult = false;
168 SendShimRequest(nRequest);
169 return WaitRequestResult();
172 void Twain::ShimListenerThread::RequestDestroy() { SendShimRequest(TWAIN_REQUEST_QUIT); }
174 bool Twain::ShimListenerThread::RequestSelectSource()
176 assert(mbInitSucceeded);
177 return SendShimRequestWithResult(TWAIN_REQUEST_SELECTSOURCE);
180 bool Twain::ShimListenerThread::RequestInitXfer()
182 assert(mbInitSucceeded);
183 return SendShimRequestWithResult(TWAIN_REQUEST_INITXFER);
186 void Twain::ShimListenerThread::NotifyOwner(WPARAM nEvent)
188 if (!mbDontNotify)
189 aTwain.Notify(nEvent);
192 void Twain::ShimListenerThread::NotifyXFerOwner(LPARAM nHandle)
194 if (!mbDontNotify)
195 aTwain.NotifyXFer(nHandle);
198 // May only be called from the own thread, so no threading issues modifying self
199 void Twain::ShimListenerThread::NotificationHdl(WPARAM nEvent, LPARAM lParam)
201 switch (nEvent)
203 case TWAIN_EVENT_NOTIFYHWND: // shim reported its main HWND for communications
204 if (!mcInitCompleted.check()) // only if not yet initialized!
206 // Owner is still waiting mcInitCompleted in its Twain::InitializeNewShim,
207 // holding its access mutex
208 mhWndShim = reinterpret_cast<HWND>(lParam);
210 mbInitSucceeded = lParam != 0;
211 mcInitCompleted.set();
213 break;
214 case TWAIN_EVENT_SCANNING:
215 NotifyOwner(nEvent);
216 break;
217 case TWAIN_EVENT_XFER:
218 NotifyXFerOwner(lParam);
219 break;
220 case TWAIN_EVENT_REQUESTRESULT:
221 mbRequestResult = lParam;
222 mcGotRequestResult.set();
223 break;
224 // We don't handle TWAIN_EVENT_QUIT notification from shim, because we send it ourselves
225 // in the end of execute()
229 // Spawn a separate 32-bit process to use TWAIN on Windows, and listen for its notifications
230 void Twain::ShimListenerThread::execute()
232 MSG msg;
233 // Initialize thread message queue before launching shim process
234 PeekMessageW(&msg, nullptr, 0, 0, PM_NOREMOVE);
238 ScopedHANDLE hShimProcess;
240 // Determine twain32shim executable URL:
241 OUString shimURL("$BRAND_BASE_DIR/" LIBO_BIN_FOLDER "/twain32shim.exe");
242 rtl::Bootstrap::expandMacros(shimURL);
244 OUString sCmdLine;
245 if (osl::FileBase::getSystemPathFromFileURL(shimURL, sCmdLine) != osl::FileBase::E_None)
246 throw std::exception("getSystemPathFromFileURL failed!");
248 HANDLE hDup;
249 if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(),
250 &hDup, SYNCHRONIZE | THREAD_QUERY_LIMITED_INFORMATION, TRUE, 0))
251 ThrowLastError("DuplicateHandle");
252 // we will not need our copy as soon as shim has its own inherited one
253 ScopedHANDLE hScopedDup(hDup);
254 DWORD nDup = reinterpret_cast<DWORD>(hDup);
255 if (reinterpret_cast<HANDLE>(nDup) != hDup)
256 throw std::exception("HANDLE does not fit to 32 bit - cannot pass to shim!");
258 // Send this thread handle as the first parameter
259 sCmdLine = "\"" + sCmdLine + "\" " + OUString::number(nDup);
261 // We need a WinAPI HANDLE of the process to be able to wait on it and detect the process
262 // termination; so use WinAPI to start the process, not osl_executeProcess.
264 STARTUPINFOW si{ sizeof(si) }; // null-initialize all but cb
265 PROCESS_INFORMATION pi;
267 if (!CreateProcessW(nullptr, const_cast<LPWSTR>(o3tl::toW(sCmdLine.getStr())), nullptr,
268 nullptr, TRUE, CREATE_NO_WINDOW, nullptr, nullptr, &si, &pi))
269 ThrowLastError("CreateProcessW");
271 CloseHandle(pi.hThread);
272 hShimProcess.reset(pi.hProcess);
274 HANDLE h = hShimProcess.get();
275 while (true)
277 DWORD nWaitResult = MsgWaitForMultipleObjects(1, &h, FALSE, INFINITE, QS_POSTMESSAGE);
278 // Process any messages in queue before checking if we need to break, to not loose
279 // possible pending notifications
280 while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE))
282 // process it here
283 if (msg.message == WM_TWAIN_EVENT)
285 NotificationHdl(msg.wParam, msg.lParam);
288 if (nWaitResult == WAIT_OBJECT_0)
290 // shim process exited - return
291 break;
293 if (nWaitResult == WAIT_FAILED)
295 // Some Win32 error - report and return
296 ThrowLastError("MsgWaitForMultipleObjects");
300 catch (const std::exception& e)
302 msErrorReported = OUString(e.what(), strlen(e.what()), RTL_TEXTENCODING_UTF8);
303 // allow owner to resume (in case the condition isn't set yet)
304 mcInitCompleted.set(); // let mbInitSucceeded keep its (maybe false) value!
306 // allow owner to resume (in case the conditions isn't set yet)
307 mcGotRequestResult.set();
308 NotifyOwner(TWAIN_EVENT_QUIT);
311 Twain::Twain() {}
313 Twain::~Twain()
315 osl::MutexGuard aGuard(maMutex);
316 if (mpThread)
318 mpThread->DontNotify();
319 mpThread->RequestDestroy();
320 mpThread->join();
321 mpThread.clear();
325 void Twain::Reset()
327 mpThread->join();
328 if (!mpThread->getError().isEmpty())
329 SAL_WARN("extensions.scanner", mpThread->getError());
330 mpThread.clear();
331 mpCurMgr = nullptr;
332 mxMgr.clear();
335 bool Twain::InitializeNewShim(ScannerManager& rMgr, const VclPtr<vcl::Window>& xTopWindow)
337 osl::MutexGuard aGuard(maMutex);
338 if (mpThread)
339 return false; // Have a shim for another task already!
341 // hold reference to ScannerManager, to prevent premature death
342 mxMgr.set(static_cast<OWeakObject*>(mpCurMgr = &rMgr),
343 css::uno::UNO_QUERY);
345 mpThread.set(new ShimListenerThread(xTopWindow));
346 mpThread->launch();
347 const bool bSuccess = mpThread->WaitInitialization();
348 if (!bSuccess)
349 Reset();
351 return bSuccess;
354 void Twain::Notify(WPARAM nEvent)
356 Application::PostUserEvent(LINK(this, Twain, ImpNotifyHdl), reinterpret_cast<void*>(nEvent));
359 void Twain::NotifyXFer(LPARAM nHandle)
361 Application::PostUserEvent(LINK(this, Twain, ImpNotifyXferHdl),
362 reinterpret_cast<void*>(nHandle));
365 bool Twain::SelectSource(ScannerManager& rMgr, const VclPtr<vcl::Window>& xTopWindow)
367 osl::MutexGuard aGuard(maMutex);
368 bool bRet = false;
370 if (InitializeNewShim(rMgr, xTopWindow))
372 meState = TWAIN_STATE_NONE;
373 bRet = mpThread->RequestSelectSource();
376 return bRet;
379 bool Twain::PerformTransfer(ScannerManager& rMgr,
380 const css::uno::Reference<css::lang::XEventListener>& rxListener,
381 const VclPtr<vcl::Window>& xTopWindow)
383 osl::MutexGuard aGuard(maMutex);
384 bool bRet = false;
386 if (InitializeNewShim(rMgr, xTopWindow))
388 mxListener = rxListener;
389 meState = TWAIN_STATE_NONE;
390 bRet = mpThread->RequestInitXfer();
393 return bRet;
396 void Twain::WaitReadyForNextTask()
398 while ([&]() {
399 osl::MutexGuard aGuard(maMutex);
400 return bool(mpThread);
401 }())
403 Application::Reschedule(true);
407 IMPL_LINK(Twain, ImpNotifyHdl, void*, pParam, void)
409 osl::MutexGuard aGuard(maMutex);
410 WPARAM nEvent = reinterpret_cast<WPARAM>(pParam);
411 switch (nEvent)
413 case TWAIN_EVENT_SCANNING:
414 meState = TWAIN_STATE_SCANNING;
415 break;
417 case TWAIN_EVENT_QUIT:
419 if (meState != TWAIN_STATE_DONE)
420 meState = TWAIN_STATE_CANCELED;
422 css::lang::EventObject event(mxMgr); // mxMgr will be cleared below
424 if (mpThread)
425 Reset();
427 if (mxListener.is())
429 mxListener->disposing(event);
430 mxListener.clear();
433 break;
435 default:
436 break;
440 IMPL_LINK(Twain, ImpNotifyXferHdl, void*, pParam, void)
442 osl::MutexGuard aGuard(maMutex);
443 if (mpThread)
445 mpCurMgr->SetData(pParam);
446 meState = pParam ? TWAIN_STATE_DONE : TWAIN_STATE_CANCELED;
448 css::lang::EventObject event(mxMgr); // mxMgr will be cleared below
450 Reset();
452 if (mxListener.is())
453 mxListener->disposing(css::lang::EventObject(mxMgr));
456 mxListener.clear();
459 VclPtr<vcl::Window> ImplGetActiveFrameWindow()
463 // query desktop instance
464 css::uno::Reference<css::frame::XDesktop2> xDesktop
465 = css::frame::Desktop::create(comphelper::getProcessComponentContext());
466 if (css::uno::Reference<css::frame::XFrame> xActiveFrame = xDesktop->getActiveFrame())
467 return VCLUnoHelper::GetWindow(xActiveFrame->getComponentWindow());
469 catch (const css::uno::Exception&)
472 SAL_WARN("extensions.scanner", "ImplGetActiveFrame: Could not determine active frame!");
473 return nullptr;
476 } // namespace
478 void ScannerManager::AcquireData() {}
480 void ScannerManager::ReleaseData()
482 if (mpData)
484 CloseHandle(static_cast<HANDLE>(mpData));
485 mpData = nullptr;
489 css::awt::Size ScannerManager::getSize()
491 css::awt::Size aRet;
493 if (mpData)
495 HANDLE hMap = static_cast<HANDLE>(mpData);
496 // map full size
497 const sal_Int8* pMap = static_cast<sal_Int8*>(MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0));
498 if (pMap)
500 const BITMAPINFOHEADER* pBIH = reinterpret_cast<const BITMAPINFOHEADER*>(pMap + 4);
501 aRet.Width = pBIH->biWidth;
502 aRet.Height = pBIH->biHeight;
504 UnmapViewOfFile(pMap);
508 return aRet;
511 css::uno::Sequence<sal_Int8> ScannerManager::getDIB()
513 css::uno::Sequence<sal_Int8> aRet;
515 if (mpData)
517 HANDLE hMap = static_cast<HANDLE>(mpData);
518 // map full size
519 const sal_Int8* pMap = static_cast<sal_Int8*>(MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0));
520 if (pMap)
522 DWORD nDIBSize;
523 memcpy(&nDIBSize, pMap, 4); // size of the following DIB
525 const BITMAPINFOHEADER* pBIH = reinterpret_cast<const BITMAPINFOHEADER*>(pMap + 4);
527 sal_uInt32 nColEntries = 0;
529 switch (pBIH->biBitCount)
531 case 1:
532 case 4:
533 case 8:
534 nColEntries = pBIH->biClrUsed ? pBIH->biClrUsed : (1 << pBIH->biBitCount);
535 break;
537 case 24:
538 nColEntries = pBIH->biClrUsed ? pBIH->biClrUsed : 0;
539 break;
541 case 16:
542 case 32:
543 nColEntries = pBIH->biClrUsed;
544 if (pBIH->biCompression == BI_BITFIELDS)
545 nColEntries += 3;
546 break;
549 aRet = css::uno::Sequence<sal_Int8>(sizeof(BITMAPFILEHEADER) + nDIBSize);
551 sal_Int8* pBuf = aRet.getArray();
552 SvMemoryStream* pMemStm
553 = new SvMemoryStream(pBuf, sizeof(BITMAPFILEHEADER), StreamMode::WRITE);
555 pMemStm->WriteChar('B').WriteChar('M').WriteUInt32(0).WriteUInt32(0);
556 pMemStm->WriteUInt32(sizeof(BITMAPFILEHEADER) + pBIH->biSize
557 + (nColEntries * sizeof(RGBQUAD)));
559 delete pMemStm;
560 memcpy(pBuf + sizeof(BITMAPFILEHEADER), pBIH, nDIBSize);
562 UnmapViewOfFile(pMap);
565 ReleaseData();
568 return aRet;
571 css::uno::Sequence<ScannerContext> SAL_CALL ScannerManager::getAvailableScanners()
573 osl::MutexGuard aGuard(maProtector);
574 css::uno::Sequence<ScannerContext> aRet(1);
576 aRet.getArray()[0].ScannerName = "TWAIN";
577 aRet.getArray()[0].InternalData = 0;
579 return aRet;
582 sal_Bool SAL_CALL ScannerManager::configureScannerAndScan(
583 ScannerContext& rContext, const css::uno::Reference<css::lang::XEventListener>& rxListener)
585 osl::MutexGuard aGuard(maProtector);
586 css::uno::Reference<XScannerManager> xThis(this);
588 if (rContext.InternalData != 0 || rContext.ScannerName != "TWAIN")
589 throw ScannerException("Scanner does not exist", xThis, ScanError_InvalidContext);
591 ReleaseData();
593 VclPtr<vcl::Window> xTopWindow = ImplGetActiveFrameWindow();
594 if (xTopWindow)
595 xTopWindow->IncModalCount(); // to avoid changes between the two operations that each
596 // block the window
597 comphelper::ScopeGuard aModalGuard([xTopWindow]() {
598 if (xTopWindow)
599 xTopWindow->DecModalCount();
602 const bool bSelected = aTwain.SelectSource(*this, xTopWindow);
603 if (bSelected)
605 aTwain.WaitReadyForNextTask();
606 aTwain.PerformTransfer(*this, rxListener, xTopWindow);
608 return bSelected;
611 void SAL_CALL
612 ScannerManager::startScan(const ScannerContext& rContext,
613 const css::uno::Reference<css::lang::XEventListener>& rxListener)
615 osl::MutexGuard aGuard(maProtector);
616 css::uno::Reference<XScannerManager> xThis(this);
618 if (rContext.InternalData != 0 || rContext.ScannerName != "TWAIN")
619 throw ScannerException("Scanner does not exist", xThis, ScanError_InvalidContext);
621 ReleaseData();
622 aTwain.PerformTransfer(*this, rxListener, ImplGetActiveFrameWindow());
625 ScanError SAL_CALL ScannerManager::getError(const ScannerContext& rContext)
627 osl::MutexGuard aGuard(maProtector);
628 css::uno::Reference<XScannerManager> xThis(this);
630 if (rContext.InternalData != 0 || rContext.ScannerName != "TWAIN")
631 throw ScannerException("Scanner does not exist", xThis, ScanError_InvalidContext);
633 return ((aTwain.GetState() == TWAIN_STATE_CANCELED) ? ScanError_ScanCanceled
634 : ScanError_ScanErrorNone);
637 css::uno::Reference<css::awt::XBitmap>
638 SAL_CALL ScannerManager::getBitmap(const ScannerContext& /*rContext*/)
640 osl::MutexGuard aGuard(maProtector);
641 return css::uno::Reference<css::awt::XBitmap>(this);
644 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */