summaryrefslogtreecommitdiff
path: root/extensions
diff options
context:
space:
mode:
authorMike Kaganski <mike.kaganski@collabora.com>2018-12-28 20:23:02 +0300
committerMike Kaganski <mike.kaganski@collabora.com>2018-12-29 09:58:09 +0100
commit648f24f5d1dae3f0fd5b132d68a5e39066f2572d (patch)
tree68a56efb90d57d8e33af2a190d09fbd1f570e413 /extensions
parentc3c2ac6afad6b154d83dbe555f5b276e5909718b (diff)
tdf#114635: reimplement TWAIN-based scan using 32-bit shim on Windows
Since TWAIN is only actually available as 32-bit component on Windows, to use it in a 64-bit program, we need a 32-bit shim program that does all actual communication with TWAIN subsystem. This change reimplements TWAIN implementation to be a separate 32-bit process. Image is transfered from the shim to main program using file mapping API. This reverts most of commit 585d9806961342e95f7318fb947bd31e9f86dee0. 64-bit LibreOffice doesn't bundle TWAIN DSM library now. TWAIN DSM source code is still used for TWAIN headers. Change-Id: I46f178ad36acd97a9eff156624b99036fcbb83f8 Reviewed-on: https://gerrit.libreoffice.org/65688 Tested-by: Jenkins Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
Diffstat (limited to 'extensions')
-rw-r--r--extensions/Executable_twain32shim.mk33
-rw-r--r--extensions/Library_scn.mk15
-rw-r--r--extensions/Module_extensions.mk5
-rw-r--r--extensions/source/scanner/scanwin.cxx1010
-rw-r--r--extensions/source/scanner/twain32shim.cxx598
-rw-r--r--extensions/source/scanner/twain32shim.hxx70
6 files changed, 1047 insertions, 684 deletions
diff --git a/extensions/Executable_twain32shim.mk b/extensions/Executable_twain32shim.mk
new file mode 100644
index 000000000000..29a6e6427198
--- /dev/null
+++ b/extensions/Executable_twain32shim.mk
@@ -0,0 +1,33 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_Executable_Executable,twain32shim))
+
+$(eval $(call gb_Executable_set_targettype_gui,twain32shim,YES))
+
+$(eval $(call gb_Executable_set_x86,twain32shim,YES))
+
+$(eval $(call gb_Executable_use_externals,twain32shim,\
+ sane_headers \
+))
+
+$(eval $(call gb_Executable_set_include,twain32shim,\
+ -I$(SRCDIR)/extensions/inc \
+ $$(INCLUDE) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,twain32shim,\
+ extensions/source/scanner/twain32shim \
+))
+
+$(eval $(call gb_Executable_use_system_win32_libs,twain32shim,\
+ shell32 \
+))
+
+# vim:set noet sw=4 ts=4:
diff --git a/extensions/Library_scn.mk b/extensions/Library_scn.mk
index 94fbaf0eddb4..71051e112624 100644
--- a/extensions/Library_scn.mk
+++ b/extensions/Library_scn.mk
@@ -13,7 +13,7 @@ $(eval $(call gb_Library_Library,scn))
$(eval $(call gb_Library_use_externals,scn,\
boost_headers \
- sane_headers \
+ $(if $(filter-out WNT,$(OS)),sane_headers) \
))
$(eval $(call gb_Library_set_include,scn,\
@@ -26,15 +26,16 @@ $(eval $(call gb_Library_set_componentfile,scn,extensions/source/scanner/scn))
$(eval $(call gb_Library_use_sdk_api,scn))
$(eval $(call gb_Library_use_libraries,scn,\
- svt \
- vcl \
- tl \
- utl \
comphelper \
- cppuhelper \
cppu \
- sal \
+ cppuhelper \
i18nlangtag \
+ sal \
+ $(if $(filter WNT,$(OS)),salhelper) \
+ svt \
+ tl \
+ utl \
+ vcl \
))
$(eval $(call gb_Library_add_exception_objects,scn,\
diff --git a/extensions/Module_extensions.mk b/extensions/Module_extensions.mk
index f86235abc224..f84a9ca4fbec 100644
--- a/extensions/Module_extensions.mk
+++ b/extensions/Module_extensions.mk
@@ -17,9 +17,12 @@ ifneq ($(filter-out iOS ANDROID,$(OS)),)
$(eval $(call gb_Module_add_targets,extensions,\
Library_abp \
Library_ldapbe2 \
- $(if $(filter WNT,$(OS)),Library_WinUserInfoBe) \
Library_log \
Library_scn \
+ $(if $(filter WNT,$(OS)), \
+ Library_WinUserInfoBe \
+ $(if $(filter TRUE,$(BUILD_X86)),Executable_twain32shim) \
+ ) \
UIConfig_sabpilot \
UIConfig_scanner \
))
diff --git a/extensions/source/scanner/scanwin.cxx b/extensions/source/scanner/scanwin.cxx
index dada5ebea5fd..fce8f72577ba 100644
--- a/extensions/source/scanner/scanwin.cxx
+++ b/extensions/source/scanner/scanwin.cxx
@@ -18,50 +18,23 @@
*/
#include <com/sun/star/uno/Reference.hxx>
-#include <com/sun/star/util/CloseVetoException.hpp>
-#include <com/sun/star/util/XCloseable.hpp>
-#include <com/sun/star/util/XCloseBroadcaster.hpp>
-#include <com/sun/star/util/XCloseListener.hpp>
-#include <com/sun/star/frame/XFrame.hpp>
-#include <com/sun/star/frame/Desktop.hpp>
-#include <com/sun/star/beans/XPropertySet.hpp>
-#include <cppuhelper/implbase.hxx>
-#include <comphelper/processfactory.hxx>
-
-#include <prewin.h>
-#include <postwin.h>
-#include <math.h>
+
+#include "twain32shim.hxx"
+
+#include <config_folders.h>
+#include <o3tl/char16_t2wchar_t.hxx>
+#include <osl/conditn.hxx>
+#include <osl/file.hxx>
+#include <osl/mutex.hxx>
+#include <rtl/bootstrap.hxx>
+#include <salhelper/thread.hxx>
#include <tools/stream.hxx>
#include <tools/helpers.hxx>
-#include <osl/mutex.hxx>
-#include <osl/module.hxx>
-#include <osl/diagnose.h>
#include <vcl/svapp.hxx>
-#include <vcl/wrkwin.hxx>
-#include <vcl/sysdata.hxx>
#include "scanner.hxx"
-#include <twain/twain.h>
-
using namespace ::com::sun::star;
-#define TWAIN_EVENT_NONE 0x00000000UL
-#define TWAIN_EVENT_QUIT 0x00000001UL
-#define TWAIN_EVENT_SCANNING 0x00000002UL
-#define TWAIN_EVENT_XFER 0x00000004UL
-
-#define PFUNC (*pDSM)
-#define PTWAINMSG MSG*
-#define FIXTODOUBLE( nFix ) (static_cast<double>(nFix.Whole)+static_cast<double>(nFix.Frac)/65536.)
-#define FIXTOLONG( nFix ) (static_cast<long>(floor(FIXTODOUBLE(nFix)+0.5)))
-#define TWAIN_FUNCNAME "DSM_Entry"
-
-#if defined(TWH_64BIT)
-# define TWAIN_LIBNAME "TWAINDSM.DLL"
-#else
-# define TWAIN_LIBNAME "TWAIN_32.DLL"
-#endif
-
enum TwainState
{
TWAIN_STATE_NONE = 0,
@@ -70,849 +43,534 @@ enum TwainState
TWAIN_STATE_CANCELED = 3
};
-static LRESULT CALLBACK TwainMsgProc( int nCode, WPARAM wParam, LPARAM lParam );
-
-class ImpTwain : public ::cppu::WeakImplHelper< util::XCloseListener >
+struct HANDLEDeleter
{
- friend LRESULT CALLBACK TwainMsgProc( int nCode, WPARAM wParam, LPARAM lParam );
-
- uno::Reference< uno::XInterface > mxSelfRef;
- uno::Reference< scanner::XScannerManager > mxMgr;
- ScannerManager& mrMgr;
- TW_IDENTITY aAppIdent;
- TW_IDENTITY aSrcIdent;
- Link<unsigned long,void> aNotifyLink;
- DSMENTRYPROC pDSM;
- osl::Module* pMod;
- ULONG_PTR nCurState;
- HWND hTwainWnd;
- HHOOK hTwainHook;
- bool mbCloseFrameOnExit;
-
- bool ImplHandleMsg( void* pMsg );
- void ImplOpenSourceManager();
- void ImplOpenSource();
- bool ImplEnableSource();
- void ImplXfer();
- void ImplFallback( ULONG_PTR nEvent );
- void ImplDeregisterCloseListener();
- void ImplRegisterCloseListener();
-
- DECL_LINK( ImplFallbackHdl, void*, void );
- DECL_LINK( ImplDestroyHdl, void*, void );
-
- // from util::XCloseListener
- virtual void SAL_CALL queryClosing( const lang::EventObject& Source, sal_Bool GetsOwnership ) override;
- virtual void SAL_CALL notifyClosing( const lang::EventObject& Source ) override;
-
- // from lang::XEventListener
- virtual void SAL_CALL disposing( const lang::EventObject& Source ) override;
-
-public:
-
- ImpTwain( ScannerManager& rMgr, const Link<unsigned long,void>& rNotifyLink );
- ~ImpTwain() override;
-
- void Destroy();
-
- bool SelectSource();
- bool InitXfer();
+ using pointer = HANDLE;
+ void operator()(HANDLE h) { CloseHandle(h); }
};
-static ImpTwain* pImpTwainInstance = nullptr;
-
-static LRESULT CALLBACK TwainWndProc( HWND hWnd,UINT nMsg, WPARAM nPar1, LPARAM nPar2 )
-{
- return DefWindowProcW( hWnd, nMsg, nPar1, nPar2 );
-}
-
-LRESULT CALLBACK TwainMsgProc( int nCode, WPARAM wParam, LPARAM lParam )
-{
- MSG* pMsg = reinterpret_cast<MSG*>(lParam);
-
- if( ( nCode < 0 ) || ( pImpTwainInstance->hTwainWnd != pMsg->hwnd ) || !pImpTwainInstance->ImplHandleMsg( reinterpret_cast<void*>(lParam) ) )
- {
- return CallNextHookEx( pImpTwainInstance->hTwainHook, nCode, wParam, lParam );
- }
- else
- {
- pMsg->message = WM_USER;
- pMsg->lParam = 0;
-
- return 0;
- }
-}
+using ScopedHANDLE = std::unique_ptr<HANDLE, HANDLEDeleter>;
-namespace {
-
-uno::Reference< frame::XFrame > ImplGetActiveFrame()
+class Twain
{
- try
+ friend class ShimListenerThread;
+ class ShimListenerThread : public salhelper::Thread
{
- // query desktop instance
- uno::Reference< frame::XDesktop2 > xDesktop = frame::Desktop::create( ::comphelper::getProcessComponentContext() );
-
- uno::Reference< frame::XFrame > xActiveFrame = xDesktop->getActiveFrame();
-
- if( xActiveFrame.is() )
+ public:
+ ShimListenerThread(Twain& rOwner)
+ : salhelper::Thread("TWAINShimListenerThread")
+ , mrOwner(rOwner)
{
- return xActiveFrame;
}
- }
- catch( const uno::Exception& )
- {
- }
+ void execute() override;
+ const OUString& getError() { return msErrorReported; }
+
+ // These methods are executed outside of own thread
+ bool WaitInitialization();
+ bool WaitRequestResult();
+ void DontNotify() { mbDontNotify = true; }
+ void RequestDestroy();
+ bool RequestSelectSource();
+ bool RequestInitXfer();
+
+ private:
+ Twain& mrOwner;
+ bool mbDontNotify = false;
+ HWND mhWndShim = nullptr; // shim main window handle
+ OUString msErrorReported;
+ osl::Condition mcInitCompleted; // initially not set
+ bool mbInitSucceeded = false;
+ osl::Condition mcGotRequestResult;
+ bool mbRequestResult = false;
+
+ void SendShimRequest(WPARAM nRequest);
+ bool SendShimRequestWithResult(WPARAM nRequest);
+ void NotificationHdl(WPARAM nEvent, LPARAM lParam);
+ void NotifyOwner(WPARAM nEvent);
+ void NotifyXFerOwner(LPARAM nHandle);
+ };
+ uno::Reference<lang::XEventListener> mxListener;
+ uno::Reference<scanner::XScannerManager> mxMgr;
+ ScannerManager* mpCurMgr = nullptr;
+ TwainState meState = TWAIN_STATE_NONE;
+ rtl::Reference<ShimListenerThread> mpThread;
+ osl::Mutex maMutex;
+
+ DECL_LINK(ImpNotifyHdl, void*, void);
+ DECL_LINK(ImpNotifyXferHdl, void*, void);
+ void Notify(WPARAM nEvent); // called by shim communication thread to notify me
+ void NotifyXFer(LPARAM nHandle); // called by shim communication thread to notify me
+
+ bool InitializeNewShim(ScannerManager& rMgr);
+
+ void Reset(); // cleanup thread and manager
- OSL_FAIL("ImpTwain::ImplGetActiveFrame: Could not determine active frame!");
- return uno::Reference< frame::XFrame >();
-}
+public:
+ Twain();
+ ~Twain();
-uno::Reference< util::XCloseBroadcaster > ImplGetActiveFrameCloseBroadcaster()
-{
- try
- {
- return uno::Reference< util::XCloseBroadcaster >( ImplGetActiveFrame(), uno::UNO_QUERY );
- }
- catch( const uno::Exception& )
- {
- }
+ bool SelectSource(ScannerManager& rMgr);
+ bool PerformTransfer(ScannerManager& rMgr,
+ const uno::Reference<lang::XEventListener>& rxListener);
- OSL_FAIL("ImpTwain::ImplGetActiveFrameCloseBroadcaster: Could determine close broadcaster on active frame!");
- return uno::Reference< util::XCloseBroadcaster >();
-}
+ TwainState GetState() const { return meState; }
+};
-void ImplSendCloseEvent()
+bool Twain::ShimListenerThread::WaitInitialization()
{
- try
- {
- uno::Reference< util::XCloseable > xCloseable( ImplGetActiveFrame(), uno::UNO_QUERY );
-
- if( xCloseable.is() )
- xCloseable->close( true );
- }
- catch( const uno::Exception& )
- {
- }
-
- OSL_FAIL("ImpTwain::ImplSendCloseEvent: Could not send required close broadcast!");
-}
-
+ mcInitCompleted.wait();
+ return mbInitSucceeded;
}
-// #107835# hold reference to ScannerManager, to prevent premature death
-ImpTwain::ImpTwain( ScannerManager& rMgr, const Link<unsigned long,void>& rNotifyLink ) :
- mxMgr( uno::Reference< scanner::XScannerManager >( static_cast< OWeakObject* >( &rMgr ), uno::UNO_QUERY) ),
- mrMgr( rMgr ),
- aNotifyLink( rNotifyLink ),
- pDSM( nullptr ),
- pMod( nullptr ),
- nCurState( 1 ),
- hTwainWnd( nullptr ),
- hTwainHook( nullptr ),
- mbCloseFrameOnExit( false )
+bool Twain::ShimListenerThread::WaitRequestResult()
{
- // setup TWAIN window
- pImpTwainInstance = this;
-
- aAppIdent.Id = 0;
- aAppIdent.Version.MajorNum = 1;
- aAppIdent.Version.MinorNum = 0;
- aAppIdent.Version.Language = TWLG_USA;
- aAppIdent.Version.Country = TWCY_USA;
- aAppIdent.ProtocolMajor = TWON_PROTOCOLMAJOR;
- aAppIdent.ProtocolMinor = TWON_PROTOCOLMINOR;
- aAppIdent.SupportedGroups = DG_IMAGE | DG_CONTROL;
- strncpy( aAppIdent.Version.Info, "8.0", 32 );
- aAppIdent.Version.Info[32] = aAppIdent.Version.Info[33] = 0;
- strncpy( aAppIdent.Manufacturer, "Sun Microsystems", 32 );
- aAppIdent.Manufacturer[32] = aAppIdent.Manufacturer[33] = 0;
- strncpy( aAppIdent.ProductFamily,"Office", 32 );
- aAppIdent.ProductFamily[32] = aAppIdent.ProductFamily[33] = 0;
- strncpy( aAppIdent.ProductName, "Office", 32 );
- aAppIdent.ProductName[32] = aAppIdent.ProductName[33] = 0;
-
- WNDCLASSW aWc = { 0, &TwainWndProc, 0, sizeof( WNDCLASSW ), GetModuleHandleW( nullptr ), nullptr, nullptr, nullptr, nullptr, L"TwainClass" };
- RegisterClassW( &aWc );
-
- hTwainWnd = CreateWindowExW( WS_EX_TOPMOST, aWc.lpszClassName, L"TWAIN", 0, 0, 0, 0, 0, HWND_DESKTOP, nullptr, aWc.hInstance, nullptr );
- hTwainHook = SetWindowsHookExW( WH_GETMESSAGE, &TwainMsgProc, nullptr, GetCurrentThreadId() );
-
- // block destruction until ImplDestroyHdl is called
- mxSelfRef = static_cast< ::cppu::OWeakObject* >( this );
+ mcGotRequestResult.wait();
+ return mbRequestResult;
}
-ImpTwain::~ImpTwain()
-{
- // are we responsible for application shutdown?
- if( mbCloseFrameOnExit )
- ImplSendCloseEvent();
-}
+void Twain::ShimListenerThread::RequestDestroy() { SendShimRequest(TWAIN_REQUEST_QUIT); }
-void ImpTwain::Destroy()
+bool Twain::ShimListenerThread::RequestSelectSource()
{
- ImplFallback( TWAIN_EVENT_NONE );
- Application::PostUserEvent( LINK( this, ImpTwain, ImplDestroyHdl ) );
+ assert(mbInitSucceeded);
+ return SendShimRequestWithResult(TWAIN_REQUEST_SELECTSOURCE);
}
-bool ImpTwain::SelectSource()
+bool Twain::ShimListenerThread::RequestInitXfer()
{
- TW_UINT16 nRet = TWRC_FAILURE;
-
- ImplOpenSourceManager();
-
- if( 3 == nCurState )
- {
- TW_IDENTITY aIdent;
-
- aIdent.Id = 0;
- aIdent.ProductName[ 0 ] = '\0';
- aNotifyLink.Call( TWAIN_EVENT_SCANNING );
- nRet = PFUNC( &aAppIdent, nullptr, DG_CONTROL, DAT_IDENTITY, MSG_USERSELECT, &aIdent );
- }
-
- ImplFallback( TWAIN_EVENT_QUIT );
-
- return( TWRC_SUCCESS == nRet );
+ assert(mbInitSucceeded);
+ return SendShimRequestWithResult(TWAIN_REQUEST_INITXFER);
}
-bool ImpTwain::InitXfer()
+void Twain::ShimListenerThread::SendShimRequest(WPARAM nRequest)
{
- bool bRet = false;
-
- ImplOpenSourceManager();
-
- if( 3 == nCurState )
- {
- ImplOpenSource();
-
- if( 4 == nCurState )
- bRet = ImplEnableSource();
- }
-
- if( !bRet )
- ImplFallback( TWAIN_EVENT_QUIT );
-
- return bRet;
+ if (mhWndShim)
+ PostMessageW(mhWndShim, WM_TWAIN_REQUEST, nRequest, 0);
}
-void ImpTwain::ImplOpenSourceManager()
+bool Twain::ShimListenerThread::SendShimRequestWithResult(WPARAM nRequest)
{
- if( 1 == nCurState )
- {
- pMod = new ::osl::Module( OUString() );
-
- if( pMod->load( TWAIN_LIBNAME ) )
- {
- nCurState = 2;
-
- pDSM = reinterpret_cast<DSMENTRYPROC>(pMod->getSymbol(TWAIN_FUNCNAME));
- if (pDSM &&
- ( PFUNC( &aAppIdent, nullptr, DG_CONTROL, DAT_PARENT, MSG_OPENDSM, &hTwainWnd ) == TWRC_SUCCESS ) )
- {
- nCurState = 3;
- }
- }
- else
- {
- delete pMod;
- pMod = nullptr;
- }
- }
+ mcGotRequestResult.reset();
+ mbRequestResult = false;
+ SendShimRequest(nRequest);
+ return WaitRequestResult();
}
-void ImpTwain::ImplOpenSource()
+void Twain::ShimListenerThread::NotifyOwner(WPARAM nEvent)
{
- if( 3 == nCurState )
- {
- if( ( PFUNC( &aAppIdent, nullptr, DG_CONTROL, DAT_IDENTITY, MSG_GETDEFAULT, &aSrcIdent ) == TWRC_SUCCESS ) &&
- ( PFUNC( &aAppIdent, nullptr, DG_CONTROL, DAT_IDENTITY, MSG_OPENDS, &aSrcIdent ) == TWRC_SUCCESS ) )
- {
- TW_CAPABILITY aCap = { CAP_XFERCOUNT, TWON_ONEVALUE, GlobalAlloc( GHND, sizeof( TW_ONEVALUE ) ) };
- TW_ONEVALUE* pVal = static_cast<TW_ONEVALUE*>(GlobalLock( aCap.hContainer ));
-
- pVal->ItemType = TWTY_INT16;
- pVal->Item = 1;
- GlobalUnlock( aCap.hContainer );
- PFUNC( &aAppIdent, &aSrcIdent, DG_CONTROL, DAT_CAPABILITY, MSG_SET, &aCap );
- GlobalFree( aCap.hContainer );
- nCurState = 4;
- }
- }
+ if (!mbDontNotify)
+ mrOwner.Notify(nEvent);
}
-bool ImpTwain::ImplEnableSource()
+void Twain::ShimListenerThread::NotifyXFerOwner(LPARAM nHandle)
{
- bool bRet = false;
-
- if( 4 == nCurState )
- {
- TW_USERINTERFACE aUI = { true, true, hTwainWnd };
-
- aNotifyLink.Call( TWAIN_EVENT_SCANNING );
- nCurState = 5;
-
- // register as vetoable close listener, to prevent application to die under us
- ImplRegisterCloseListener();
-
- if( PFUNC( &aAppIdent, &aSrcIdent, DG_CONTROL, DAT_USERINTERFACE, MSG_ENABLEDS, &aUI ) == TWRC_SUCCESS )
- {
- bRet = true;
- }
- else
- {
- nCurState = 4;
-
- // deregister as vetoable close listener, dialog failed
- ImplDeregisterCloseListener();
- }
- }
-
- return bRet;
+ if (!mbDontNotify)
+ mrOwner.NotifyXFer(nHandle);
}
-bool ImpTwain::ImplHandleMsg( void* pMsg )
+// May only be called from the own thread, so no threading issues modifying self
+void Twain::ShimListenerThread::NotificationHdl(WPARAM nEvent, LPARAM lParam)
{
- TW_UINT16 nRet;
- PTWAINMSG pMess = static_cast<PTWAINMSG>(pMsg);
- TW_EVENT aEvt = { pMess, MSG_NULL };
-
- if (pDSM)
- nRet = PFUNC( &aAppIdent, &aSrcIdent, DG_CONTROL, DAT_EVENT, MSG_PROCESSEVENT, &aEvt );
- else
- nRet = TWRC_NOTDSEVENT;
-
- if( aEvt.TWMessage != MSG_NULL )
+ switch (nEvent)
{
- switch( aEvt.TWMessage )
- {
- case MSG_XFERREADY:
+ case TWAIN_EVENT_NOTIFYHWND: // shim reported its main HWND for communications
+ if (!mcInitCompleted.check()) // only if not yet initialized!
{
- ULONG_PTR nEvent = TWAIN_EVENT_QUIT;
-
- if( 5 == nCurState )
- {
- nCurState = 6;
- ImplXfer();
+ // Owner is still waiting mcInitCompleted in its Twain::InitializeNewShim,
+ // holding its access mutex
+ mhWndShim = reinterpret_cast<HWND>(lParam);
- if( mrMgr.GetData() )
- nEvent = TWAIN_EVENT_XFER;
- }
- else if( 7 == nCurState && mrMgr.GetData() )
- {
- // Already sent TWAIN_EVENT_XFER; not processed yet;
- // duplicate event - avoid deleting mpImpTwain
- nEvent = TWAIN_EVENT_NONE;
- }
-
- ImplFallback( nEvent );
+ mbInitSucceeded = lParam != 0;
+ mcInitCompleted.set();
}
break;
-
- case MSG_CLOSEDSREQ:
- ImplFallback( TWAIN_EVENT_QUIT );
+ case TWAIN_EVENT_SCANNING:
+ NotifyOwner(nEvent);
break;
-
- default:
+ case TWAIN_EVENT_XFER:
+ NotifyXFerOwner(lParam);
break;
- }
+ case TWAIN_EVENT_REQUESTRESULT:
+ mbRequestResult = lParam;
+ mcGotRequestResult.set();
+ break;
+ // We don't handle TWAIN_EVENT_QUIT notification from shim, because we send it ourselves
+ // in the end of execute()
}
- else
- nRet = TWRC_NOTDSEVENT;
-
- return( TWRC_DSEVENT == nRet );
}
-void ImpTwain::ImplXfer()
+// Spawn a separate 32-bit process to use TWAIN on Windows, and listen for its notifications
+void Twain::ShimListenerThread::execute()
{
- if( nCurState == 6 )
- {
- TW_IMAGEINFO aInfo;
- HANDLE hDIB = nullptr;
- long nWidth, nHeight, nXRes, nYRes;
+ MSG msg;
+ // Initialize thread message queue before launching shim process
+ PeekMessageW(&msg, 0, 0, 0, PM_NOREMOVE);
- if( PFUNC( &aAppIdent, &aSrcIdent, DG_IMAGE, DAT_IMAGEINFO, MSG_GET, &aInfo ) == TWRC_SUCCESS )
+ try
+ {
+ ScopedHANDLE hShimProcess;
{
- nWidth = aInfo.ImageWidth;
- nHeight = aInfo.ImageLength;
- nXRes = FIXTOLONG( aInfo.XResolution );
- nYRes = FIXTOLONG( aInfo.YResolution );
+ // Determine twain32shim executable URL:
+ OUString shimURL("$BRAND_BASE_DIR/" LIBO_BIN_FOLDER "/twain32shim.exe");
+ rtl::Bootstrap::expandMacros(shimURL);
+
+ OUString sCmdLine;
+ if (osl::FileBase::getSystemPathFromFileURL(shimURL, sCmdLine) != osl_File_E_None)
+ throw std::exception("getSystemPathFromFileURL failed!");
+
+ HANDLE hDup;
+ if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(),
+ &hDup, SYNCHRONIZE | THREAD_QUERY_LIMITED_INFORMATION, TRUE, 0))
+ ThrowLastError("DuplicateHandle");
+ // we will not need our copy as soon as shim has its own inherited one
+ ScopedHANDLE hScopedDup(hDup);
+ DWORD nDup = reinterpret_cast<DWORD>(hDup);
+ if (reinterpret_cast<HANDLE>(nDup) != hDup)
+ throw std::exception("HANDLE does not fit to 32 bit - cannot pass to shim!");
+
+ // Send this thread handle as the first parameter
+ sCmdLine = "\"" + sCmdLine + "\" " + OUString::number(nDup);
+
+ // We need a WinAPI HANDLE of the process to be able to wait on it and detect the process
+ // termination; so use WinAPI to start the process, not osl_executeProcess.
+
+ STARTUPINFOW si{}; // null-initialize
+ si.cb = sizeof(si);
+ si.dwFlags = STARTF_USESHOWWINDOW;
+ si.wShowWindow = SW_HIDE;
+
+ PROCESS_INFORMATION pi;
+
+ if (!CreateProcessW(nullptr, const_cast<LPWSTR>(o3tl::toW(sCmdLine.getStr())), nullptr,
+ nullptr, TRUE, CREATE_NO_WINDOW, nullptr, nullptr, &si, &pi))
+ ThrowLastError("CreateProcessW");
+
+ CloseHandle(pi.hThread);
+ hShimProcess.reset(pi.hProcess);
}
- else
- nWidth = nHeight = nXRes = nYRes = -1;
-
- switch( PFUNC( &aAppIdent, &aSrcIdent, DG_IMAGE, DAT_IMAGENATIVEXFER, MSG_GET, &hDIB ) )
+ HANDLE h = hShimProcess.get();
+ while (true)
{
- case TWRC_CANCEL:
- nCurState = 7;
- break;
-
- case TWRC_XFERDONE:
+ DWORD nWaitResult = MsgWaitForMultipleObjects(1, &h, FALSE, INFINITE,
+ QS_ALLPOSTMESSAGE | QS_SENDMESSAGE);
+ // Process any messages in queue before checking if we need to break, to not loose
+ // possible pending notifications
+ while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE))
{
- if( hDIB )
+ // process it here
+ if (msg.message == WM_TWAIN_EVENT)
{
- if( ( nXRes != -1 ) && ( nYRes != - 1 ) && ( nWidth != - 1 ) && ( nHeight != - 1 ) )
- {
- // set resolution of bitmap
- BITMAPINFOHEADER* pBIH = static_cast<BITMAPINFOHEADER*>(GlobalLock( static_cast<HGLOBAL>(hDIB) ));
- static const double fFactor = 100.0 / 2.54;
-
- pBIH->biXPelsPerMeter = FRound( fFactor * nXRes );
- pBIH->biYPelsPerMeter = FRound( fFactor * nYRes );
-
- GlobalUnlock( static_cast<HGLOBAL>(hDIB) );
- }
-
- mrMgr.SetData( static_cast<void*>(hDIB) );
+ NotificationHdl(msg.wParam, msg.lParam);
}
- else
- GlobalFree( static_cast<HGLOBAL>(hDIB) );
-
- nCurState = 7;
}
- break;
-
- default:
- break;
- }
- }
-}
-
-void ImpTwain::ImplFallback( ULONG_PTR nEvent )
-{
- Application::PostUserEvent( LINK( this, ImpTwain, ImplFallbackHdl ), reinterpret_cast<void*>(nEvent) );
-}
-
-IMPL_LINK( ImpTwain, ImplFallbackHdl, void*, pData, void )
-{
- const sal_uIntPtr nEvent = reinterpret_cast<sal_uIntPtr>(pData);
- bool bFallback = true;
-
- switch( nCurState )
- {
- case 7:
- case 6:
- {
- TW_PENDINGXFERS aXfers;
-
- if( PFUNC( &aAppIdent, &aSrcIdent, DG_CONTROL, DAT_PENDINGXFERS, MSG_ENDXFER, &aXfers ) == TWRC_SUCCESS )
+ if (nWaitResult == WAIT_OBJECT_0)
{
- if( aXfers.Count != 0 )
- PFUNC( &aAppIdent, &aSrcIdent, DG_CONTROL, DAT_PENDINGXFERS, MSG_RESET, &aXfers );
+ // shim process exited - return
+ break;
+ }
+ if (nWaitResult == WAIT_FAILED)
+ {
+ // Some Win32 error - report and return
+ ThrowLastError("MsgWaitForMultipleObjects");
}
-
- nCurState = 5;
- }
- break;
-
- case 5:
- {
- TW_USERINTERFACE aUI = { true, true, hTwainWnd };
-
- PFUNC( &aAppIdent, &aSrcIdent, DG_CONTROL, DAT_USERINTERFACE, MSG_DISABLEDS, &aUI );
- nCurState = 4;
-
- // deregister as vetoable close listener
- ImplDeregisterCloseListener();
- }
- break;
-
- case 4:
- {
- PFUNC( &aAppIdent, nullptr, DG_CONTROL, DAT_IDENTITY, MSG_CLOSEDS, &aSrcIdent );
- nCurState = 3;
- }
- break;
-
- case 3:
- {
- PFUNC( &aAppIdent, nullptr, DG_CONTROL, DAT_PARENT, MSG_CLOSEDSM, &hTwainWnd );
- nCurState = 2;
- }
- break;
-
- case 2:
- {
- delete pMod;
- pMod = nullptr;
- nCurState = 1;
- }
- break;
-
- default:
- {
- if( nEvent != TWAIN_EVENT_NONE )
- aNotifyLink.Call( nEvent );
-
- bFallback = false;
- }
- break;
- }
-
- if( bFallback )
- ImplFallback( nEvent );
-}
-
-IMPL_LINK_NOARG( ImpTwain, ImplDestroyHdl, void*, void )
-{
- if( hTwainWnd )
- DestroyWindow( hTwainWnd );
-
- if( hTwainHook )
- UnhookWindowsHookEx( hTwainHook );
-
- // permit destruction of ourselves (normally, refcount
- // should drop to zero exactly here)
- mxSelfRef = nullptr;
- pImpTwainInstance = nullptr;
-}
-
-void ImpTwain::ImplRegisterCloseListener()
-{
- try
- {
- uno::Reference< util::XCloseBroadcaster > xCloseBroadcaster( ImplGetActiveFrameCloseBroadcaster() );
-
- if( xCloseBroadcaster.is() )
- {
- xCloseBroadcaster->addCloseListener(this);
- return; // successfully registered as a close listener
- }
- else
- {
- // interface unknown. don't register, then
- OSL_FAIL("ImpTwain::ImplRegisterCloseListener: XFrame has no XCloseBroadcaster!");
- return;
}
}
- catch( const uno::Exception& )
+ catch (const std::exception& e)
{
+ msErrorReported = OUString(e.what(), strlen(e.what()), RTL_TEXTENCODING_UTF8);
+ // allow owner to resume (in case the condition isn't set yet)
+ mcInitCompleted.set(); // let mbInitSucceeded keep its (maybe false) value!
}
-
- OSL_FAIL("ImpTwain::ImplRegisterCloseListener: Could not register as close listener!");
+ // allow owner to resume (in case the conditions isn't set yet)
+ mcGotRequestResult.set();
+ NotifyOwner(TWAIN_EVENT_QUIT);
}
-void ImpTwain::ImplDeregisterCloseListener()
-{
- try
- {
- uno::Reference< util::XCloseBroadcaster > xCloseBroadcaster(
- ImplGetActiveFrameCloseBroadcaster() );
+Twain::Twain() {}
- if( xCloseBroadcaster.is() )
- {
- xCloseBroadcaster->removeCloseListener(this);
- return; // successfully deregistered as a close listener
- }
- else
- {
- // interface unknown. don't deregister, then
- OSL_FAIL("ImpTwain::ImplDeregisterCloseListener: XFrame has no XCloseBroadcaster!");
- return;
- }
- }
- catch( const uno::Exception& )
+Twain::~Twain()
+{
+ osl::MutexGuard aGuard(maMutex);
+ if (mpThread)
{
+ mpThread->DontNotify();
+ mpThread->RequestDestroy();
+ mpThread->join();
+ mpThread.clear();
}
-
- OSL_FAIL("ImpTwain::ImplDeregisterCloseListener: Could not deregister as close listener!");
-}
-
-void SAL_CALL ImpTwain::queryClosing( const lang::EventObject& /*Source*/, sal_Bool GetsOwnership )
-{
- // shall we re-send the close query later on?
- mbCloseFrameOnExit = GetsOwnership;
-
- // the sole purpose of this listener is to forbid closing of the listened-at frame
- throw util::CloseVetoException();
-}
-
-void SAL_CALL ImpTwain::notifyClosing( const lang::EventObject& /*Source*/ )
-{
- // should not happen
- OSL_FAIL("ImpTwain::notifyClosing called, but we vetoed the closing before!");
}
-void SAL_CALL ImpTwain::disposing( const lang::EventObject& /*Source*/ )
+void Twain::Reset()
{
- // we're not holding any references to the frame, thus noop
+ mpThread->join();
+ if (!mpThread->getError().isEmpty())
+ SAL_WARN("extensions.scanner", mpThread->getError());
+ mpThread.clear();
+ mpCurMgr = nullptr;
+ mxMgr.clear();
}
-class Twain
+bool Twain::InitializeNewShim(ScannerManager& rMgr)
{
- uno::Reference< lang::XEventListener > mxListener;
- uno::Reference< scanner::XScannerManager > mxMgr;
- const ScannerManager* mpCurMgr;
- ImpTwain* mpImpTwain;
- TwainState meState;
-
- DECL_LINK( ImpNotifyHdl, unsigned long, void );
+ osl::MutexGuard aGuard(maMutex);
+ if (mpThread)
+ return false; // Have a shim for another task already!
-public:
-
- Twain();
- ~Twain();
+ // hold reference to ScannerManager, to prevent premature death
+ mxMgr.set(static_cast<OWeakObject*>(const_cast<ScannerManager*>(mpCurMgr = &rMgr)),
+ uno::UNO_QUERY);
- bool SelectSource( ScannerManager& rMgr );
- bool PerformTransfer( ScannerManager& rMgr, const uno::Reference< lang::XEventListener >& rxListener );
+ mpThread.set(new ShimListenerThread(*this));
+ mpThread->launch();
+ const bool bSuccess = mpThread->WaitInitialization();
+ if (!bSuccess)
+ Reset();
- TwainState GetState() const { return meState; }
-};
+ return bSuccess;
+}
-Twain::Twain() :
- mpCurMgr( nullptr ),
- mpImpTwain( nullptr ),
- meState( TWAIN_STATE_NONE )
+void Twain::Notify(WPARAM nEvent)
{
+ Application::PostUserEvent(LINK(this, Twain, ImpNotifyHdl), reinterpret_cast<void*>(nEvent));
}
-Twain::~Twain()
+void Twain::NotifyXFer(LPARAM nHandle)
{
- if( mpImpTwain )
- mpImpTwain->Destroy();
+ Application::PostUserEvent(LINK(this, Twain, ImpNotifyXferHdl),
+ reinterpret_cast<void*>(nHandle));
}
-bool Twain::SelectSource( ScannerManager& rMgr )
+bool Twain::SelectSource(ScannerManager& rMgr)
{
- bool bRet;
+ osl::MutexGuard aGuard(maMutex);
+ bool bRet = false;
- if( !mpImpTwain )
+ if (InitializeNewShim(rMgr))
{
- // hold reference to ScannerManager, to prevent premature death
- mxMgr.set( static_cast< OWeakObject* >( const_cast< ScannerManager* >( mpCurMgr = &rMgr ) ),
- uno::UNO_QUERY );
-
meState = TWAIN_STATE_NONE;
- mpImpTwain = new ImpTwain( rMgr, LINK( this, Twain, ImpNotifyHdl ) );
- bRet = mpImpTwain->SelectSource();
+ bRet = mpThread->RequestSelectSource();
}
- else
- bRet = false;
return bRet;
}
-bool Twain::PerformTransfer( ScannerManager& rMgr, const uno::Reference< lang::XEventListener >& rxListener )
+bool Twain::PerformTransfer(ScannerManager& rMgr,
+ const uno::Reference<lang::XEventListener>& rxListener)
{
- bool bRet;
+ osl::MutexGuard aGuard(maMutex);
+ bool bRet = false;
- if( !mpImpTwain )
+ if (InitializeNewShim(rMgr))
{
- // hold reference to ScannerManager, to prevent premature death
- mxMgr.set( static_cast< OWeakObject* >( const_cast< ScannerManager* >( mpCurMgr = &rMgr ) ),
- uno::UNO_QUERY );
-
mxListener = rxListener;
meState = TWAIN_STATE_NONE;
- mpImpTwain = new ImpTwain( rMgr, LINK( this, Twain, ImpNotifyHdl ) );
- bRet = mpImpTwain->InitXfer();
+ bRet = mpThread->RequestInitXfer();
}
- else
- bRet = false;
return bRet;
}
-IMPL_LINK( Twain, ImpNotifyHdl, unsigned long, nEvent, void )
+IMPL_LINK(Twain, ImpNotifyHdl, void*, pParam, void)
{
- switch( nEvent )
+ osl::MutexGuard aGuard(maMutex);
+ WPARAM nEvent = reinterpret_cast<WPARAM>(pParam);
+ switch (nEvent)
{
case TWAIN_EVENT_SCANNING:
meState = TWAIN_STATE_SCANNING;
- break;
+ break;
case TWAIN_EVENT_QUIT:
{
- if( meState != TWAIN_STATE_DONE )
+ if (meState != TWAIN_STATE_DONE)
meState = TWAIN_STATE_CANCELED;
- if( mpImpTwain )
- {
- mpImpTwain->Destroy();
- mpImpTwain = nullptr;
- mpCurMgr = nullptr;
- }
+ lang::EventObject event(mxMgr); // mxMgr will be cleared below
- if( mxListener.is() )
- mxListener->disposing( lang::EventObject( mxMgr ) );
+ if (mpThread)
+ Reset();
- mxListener = nullptr;
+ if (mxListener.is())
+ {
+ mxListener->disposing(event);
+ mxListener.clear();
+ }
}
break;
- case TWAIN_EVENT_XFER:
- {
- if( mpImpTwain )
- {
- meState = ( mpCurMgr->GetData() ? TWAIN_STATE_DONE : TWAIN_STATE_CANCELED );
+ default:
+ break;
+ }
+}
- mpImpTwain->Destroy();
- mpImpTwain = nullptr;
- mpCurMgr = nullptr;
+IMPL_LINK(Twain, ImpNotifyXferHdl, void*, pParam, void)
+{
+ osl::MutexGuard aGuard(maMutex);
+ if (mpThread)
+ {
+ mpCurMgr->SetData(pParam);
+ meState = pParam ? TWAIN_STATE_DONE : TWAIN_STATE_CANCELED;
- if( mxListener.is() )
- mxListener->disposing( lang::EventObject( mxMgr ) );
- }
+ lang::EventObject event(mxMgr); // mxMgr will be cleared below
- mxListener = nullptr;
- }
- break;
+ Reset();
- default:
- break;
+ if (mxListener.is())
+ mxListener->disposing(lang::EventObject(mxMgr));
}
+
+ mxListener.clear();
}
static Twain aTwain;
-void ScannerManager::AcquireData()
-{
-}
+void ScannerManager::AcquireData() {}
void ScannerManager::ReleaseData()
{
- if( mpData )
+ if (mpData)
{
- GlobalFree( static_cast<HGLOBAL>(mpData) );
+ CloseHandle(static_cast<HANDLE>(mpData));
mpData = nullptr;
}
}
awt::Size ScannerManager::getSize()
{
- awt::Size aRet;
- HGLOBAL hDIB = static_cast<HGLOBAL>(mpData);
+ awt::Size aRet;
- if( hDIB )
+ if (mpData)
{
- BITMAPINFOHEADER* pBIH = static_cast<BITMAPINFOHEADER*>(GlobalLock( hDIB ));
-
- if( pBIH )
+ HANDLE hMap = static_cast<HANDLE>(mpData);
+ // map full size
+ const sal_Int8* pMap = static_cast<sal_Int8*>(MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0));
+ if (pMap)
{
+ const BITMAPINFOHEADER* pBIH = reinterpret_cast<const BITMAPINFOHEADER*>(pMap + 4);
aRet.Width = pBIH->biWidth;
aRet.Height = pBIH->biHeight;
- }
- else
- aRet.Width = aRet.Height = 0;
- GlobalUnlock( hDIB );
+ UnmapViewOfFile(pMap);
+ }
}
- else
- aRet.Width = aRet.Height = 0;
return aRet;
}
-uno::Sequence< sal_Int8 > ScannerManager::getDIB()
+uno::Sequence<sal_Int8> ScannerManager::getDIB()
{
- uno::Sequence< sal_Int8 > aRet;
+ uno::Sequence<sal_Int8> aRet;
- if( mpData )
+ if (mpData)
{
- HGLOBAL hDIB = static_cast<HGLOBAL>(mpData);
- const sal_uInt32 nDIBSize = GlobalSize( hDIB );
- BITMAPINFOHEADER* pBIH = static_cast<BITMAPINFOHEADER*>(GlobalLock( hDIB ));
-
- if( pBIH )
+ HANDLE hMap = static_cast<HANDLE>(mpData);
+ // map full size
+ const sal_Int8* pMap = static_cast<sal_Int8*>(MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0));
+ if (pMap)
{
- sal_uInt32 nColEntries;
+ DWORD nDIBSize;
+ memcpy(&nDIBSize, pMap, 4); // size of the following DIB
- switch( pBIH->biBitCount )
+ const BITMAPINFOHEADER* pBIH = reinterpret_cast<const BITMAPINFOHEADER*>(pMap + 4);
+
+ sal_uInt32 nColEntries = 0;
+
+ switch (pBIH->biBitCount)
{
case 1:
case 4:
case 8:
- nColEntries = pBIH->biClrUsed ? pBIH->biClrUsed : ( 1 << pBIH->biBitCount );
- break;
+ nColEntries = pBIH->biClrUsed ? pBIH->biClrUsed : (1 << pBIH->biBitCount);
+ break;
case 24:
nColEntries = pBIH->biClrUsed ? pBIH->biClrUsed : 0;
- break;
+ break;
case 16:
case 32:
- {
nColEntries = pBIH->biClrUsed;
-
- if( pBIH->biCompression == BI_BITFIELDS )
+ if (pBIH->biCompression == BI_BITFIELDS)
nColEntries += 3;
- }
- break;
-
- default:
- nColEntries = 0;
- break;
+ break;
}
- aRet = uno::Sequence< sal_Int8 >( sizeof( BITMAPFILEHEADER ) + nDIBSize );
+ aRet = uno::Sequence<sal_Int8>(sizeof(BITMAPFILEHEADER) + nDIBSize);
- sal_Int8* pBuf = aRet.getArray();
- SvMemoryStream* pMemStm = new SvMemoryStream( pBuf, sizeof( BITMAPFILEHEADER ), StreamMode::WRITE );
+ sal_Int8* pBuf = aRet.getArray();
+ SvMemoryStream* pMemStm
+ = new SvMemoryStream(pBuf, sizeof(BITMAPFILEHEADER), StreamMode::WRITE);
- pMemStm->WriteChar( 'B' ).WriteChar( 'M' ).WriteUInt32( 0 ).WriteUInt32( 0 );
- pMemStm->WriteUInt32( sizeof( BITMAPFILEHEADER ) + pBIH->biSize + ( nColEntries * sizeof( RGBQUAD ) ) );
+ pMemStm->WriteChar('B').WriteChar('M').WriteUInt32(0).WriteUInt32(0);
+ pMemStm->WriteUInt32(sizeof(BITMAPFILEHEADER) + pBIH->biSize
+ + (nColEntries * sizeof(RGBQUAD)));
delete pMemStm;
- memcpy( pBuf + sizeof( BITMAPFILEHEADER ), pBIH, nDIBSize );
+ memcpy(pBuf + sizeof(BITMAPFILEHEADER), pBIH, nDIBSize);
+
+ UnmapViewOfFile(pMap);
}
- GlobalUnlock( hDIB );
ReleaseData();
}
return aRet;
}
-uno::Sequence< ScannerContext > SAL_CALL ScannerManager::getAvailableScanners()
+uno::Sequence<ScannerContext> SAL_CALL ScannerManager::getAvailableScanners()
{
- osl::MutexGuard aGuard( maProtector );
- uno::Sequence< ScannerContext > aRet( 1 );
+ osl::MutexGuard aGuard(maProtector);
+ uno::Sequence<ScannerContext> aRet(1);
- aRet.getArray()[0].ScannerName = "TWAIN" ;
+ aRet.getArray()[0].ScannerName = "TWAIN";
aRet.getArray()[0].InternalData = 0;
return aRet;
}
-sal_Bool SAL_CALL ScannerManager::configureScannerAndScan( ScannerContext& rContext, const uno::Reference< lang::XEventListener >& )
+sal_Bool SAL_CALL ScannerManager::configureScannerAndScan(
+ ScannerContext& rContext, const uno::Reference<lang::XEventListener>&)
{
- osl::MutexGuard aGuard( maProtector );
- uno::Reference< XScannerManager > xThis( this );
+ osl::MutexGuard aGuard(maProtector);
+ uno::Reference<XScannerManager> xThis(this);
- if( rContext.InternalData != 0 || rContext.ScannerName != "TWAIN" )
- throw ScannerException("Scanner does not exist", xThis, ScanError_InvalidContext );
+ if (rContext.InternalData != 0 || rContext.ScannerName != "TWAIN")
+ throw ScannerException("Scanner does not exist", xThis, ScanError_InvalidContext);
ReleaseData();
- return aTwain.SelectSource( *this );
+ return aTwain.SelectSource(*this);
}
-void SAL_CALL ScannerManager::startScan( const ScannerContext& rContext, const uno::Reference< lang::XEventListener >& rxListener )
+void SAL_CALL ScannerManager::startScan(const ScannerContext& rContext,
+ const uno::Reference<lang::XEventListener>& rxListener)
{
- osl::MutexGuard aGuard( maProtector );
- uno::Reference< XScannerManager > xThis( this );
+ osl::MutexGuard aGuard(maProtector);
+ uno::Reference<XScannerManager> xThis(this);
- if( rContext.InternalData != 0 || rContext.ScannerName != "TWAIN" )
- throw ScannerException("Scanner does not exist", xThis, ScanError_InvalidContext );
+ if (rContext.InternalData != 0 || rContext.ScannerName != "TWAIN")
+ throw ScannerException("Scanner does not exist", xThis, ScanError_InvalidContext);
ReleaseData();
- aTwain.PerformTransfer( *this, rxListener );
+ aTwain.PerformTransfer(*this, rxListener);
}
-ScanError SAL_CALL ScannerManager::getError( const ScannerContext& rContext )
+ScanError SAL_CALL ScannerManager::getError(const ScannerContext& rContext)
{
- osl::MutexGuard aGuard( maProtector );
- uno::Reference< XScannerManager > xThis( this );
+ osl::MutexGuard aGuard(maProtector);
+ uno::Reference<XScannerManager> xThis(this);
- if( rContext.InternalData != 0 || rContext.ScannerName != "TWAIN" )
- throw ScannerException("Scanner does not exist", xThis, ScanError_InvalidContext );
+ if (rContext.InternalData != 0 || rContext.ScannerName != "TWAIN")
+ throw ScannerException("Scanner does not exist", xThis, ScanError_InvalidContext);
- return( ( aTwain.GetState() == TWAIN_STATE_CANCELED ) ? ScanError_ScanCanceled : ScanError_ScanErrorNone );
+ return ((aTwain.GetState() == TWAIN_STATE_CANCELED) ? ScanError_ScanCanceled
+ : ScanError_ScanErrorNone);
}
-uno::Reference< awt::XBitmap > SAL_CALL ScannerManager::getBitmap( const ScannerContext& /*rContext*/ )
+uno::Reference<awt::XBitmap> SAL_CALL ScannerManager::getBitmap(const ScannerContext& /*rContext*/)
{
- osl::MutexGuard aGuard( maProtector );
- return uno::Reference< awt::XBitmap >( this );
+ osl::MutexGuard aGuard(maProtector);
+ return uno::Reference<awt::XBitmap>(this);
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/extensions/source/scanner/twain32shim.cxx b/extensions/source/scanner/twain32shim.cxx
new file mode 100644
index 000000000000..c5a4c0b3fa6f
--- /dev/null
+++ b/extensions/source/scanner/twain32shim.cxx
@@ -0,0 +1,598 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+/*
+ * twain32shim.exe is a separate 32-bit executable that serves as a shim
+ * between LibreOffice and Windows' 32-bit TWAIN component. Without it,
+ * it's impossible for 64-bit program to use TWAIN on Windows.
+ * Using 64-bit TWAIN DSM library from twain.org to avoid using the shim
+ * is not an option, because scanner manufacturers only provide 32-bit
+ * drivers, and 64-bit drivers are only offered as 3rd-party commercial
+ * products. The shim is also used in 32-bit LibreOffice for uniformity.
+*/
+
+#include "twain32shim.hxx"
+#include <tools/helpers.hxx>
+#include <twain/twain.h>
+
+#define WM_TWAIN_FALLBACK (WM_SHIM_INTERNAL + 0)
+
+namespace
+{
+long FixToLong(const TW_FIX32& rFix)
+{
+ return static_cast<long>(floor(rFix.Whole + rFix.Frac / 65536. + 0.5));
+}
+
+const wchar_t sTwainWndClass[] = L"TwainClass";
+
+class ImpTwain
+{
+public:
+ ImpTwain(HANDLE hParentThread);
+ ~ImpTwain();
+
+private:
+ TW_IDENTITY m_aAppId;
+ TW_IDENTITY m_aSrcId;
+ DWORD m_nParentThreadId;
+ HANDLE m_hProc;
+ DSMENTRYPROC m_pDSM = nullptr;
+ HMODULE m_hMod = nullptr;
+ ULONG_PTR m_nCurState = 1;
+ HWND m_hTwainWnd = nullptr;
+ HHOOK m_hTwainHook = nullptr;
+ HANDLE m_hMap = nullptr; // the *duplicated* handle
+
+ static bool IsTwainClassWnd(HWND hWnd);
+ static ImpTwain* GetImpFromWnd(HWND hWnd);
+ static void ImplCreateWnd(HWND hWnd, LPARAM lParam);
+ static LRESULT CALLBACK WndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam);
+ static LRESULT CALLBACK MsgHook(int nCode, WPARAM wParam, LPARAM lParam);
+
+ void Destroy();
+ bool SelectSource();
+ bool InitXfer();
+
+ void NotifyParent(WPARAM nEvent, LPARAM lParam);
+ bool ImplHandleMsg(MSG* pMsg);
+ void ImplOpenSourceManager();
+ void ImplOpenSource();
+ bool ImplEnableSource();
+ void ImplXfer();
+ void ImplFallback(WPARAM nEvent);
+
+ void ImplFallbackHdl(WPARAM nEvent);
+ void ImplRequestHdl(WPARAM nRequest);
+};
+
+//static
+bool ImpTwain::IsTwainClassWnd(HWND hWnd)
+{
+ const int nBufSize = SAL_N_ELEMENTS(sTwainWndClass);
+ wchar_t sClassName[nBufSize];
+ return (GetClassNameW(hWnd, sClassName, nBufSize) && wcscmp(sClassName, sTwainWndClass) == 0);
+}
+
+//static
+ImpTwain* ImpTwain::GetImpFromWnd(HWND hWnd)
+{
+ if (!IsTwainClassWnd(hWnd))
+ return nullptr;
+ return reinterpret_cast<ImpTwain*>(GetWindowLongPtrW(hWnd, GWLP_USERDATA));
+}
+
+//static
+void ImpTwain::ImplCreateWnd(HWND hWnd, LPARAM lParam)
+{
+ CREATESTRUCT* pCS = reinterpret_cast<CREATESTRUCT*>(lParam);
+ if (pCS && IsTwainClassWnd(hWnd))
+ SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pCS->lpCreateParams));
+}
+
+// static
+LRESULT CALLBACK ImpTwain::WndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
+{
+ ImpTwain* pImpTwain = GetImpFromWnd(hWnd);
+ switch (nMsg)
+ {
+ case WM_CREATE:
+ ImplCreateWnd(hWnd, lParam);
+ break;
+ case WM_TWAIN_FALLBACK:
+ if (pImpTwain)
+ pImpTwain->ImplFallbackHdl(wParam);
+ break;
+ case WM_TWAIN_REQUEST:
+ if (pImpTwain)
+ pImpTwain->ImplRequestHdl(wParam);
+ break;
+ }
+ return DefWindowProcW(hWnd, nMsg, wParam, lParam);
+}
+
+// static
+LRESULT CALLBACK ImpTwain::MsgHook(int nCode, WPARAM wParam, LPARAM lParam)
+{
+ MSG* pMsg = reinterpret_cast<MSG*>(lParam);
+ if (nCode >= 0 && pMsg)
+ {
+ ImpTwain* pImpTwain = GetImpFromWnd(pMsg->hwnd);
+ if (pImpTwain && pImpTwain->ImplHandleMsg(pMsg))
+ {
+ pMsg->message = WM_USER;
+ pMsg->lParam = 0;
+
+ return 0;
+ }
+ }
+
+ return CallNextHookEx(0, nCode, wParam, lParam);
+}
+
+HANDLE GetProcOfThread(HANDLE hThread)
+{
+ DWORD nProcId = GetProcessIdOfThread(hThread);
+ if (!nProcId)
+ ThrowLastError("GetProcessIdOfThread");
+ HANDLE hRet = OpenProcess(PROCESS_DUP_HANDLE, FALSE, nProcId);
+ if (!hRet)
+ ThrowLastError("OpenProcess");
+ return hRet;
+}
+
+ImpTwain::ImpTwain(HANDLE hParentThread)
+ : m_nParentThreadId(GetThreadId(hParentThread))
+ , m_hProc(GetProcOfThread(hParentThread))
+{
+ m_aAppId.Id = 0;
+ m_aAppId.Version.MajorNum = 1;
+ m_aAppId.Version.MinorNum = 0;
+ m_aAppId.Version.Language = TWLG_USA;
+ m_aAppId.Version.Country = TWCY_USA;
+ m_aAppId.ProtocolMajor = TWON_PROTOCOLMAJOR;
+ m_aAppId.ProtocolMinor = TWON_PROTOCOLMINOR;
+ m_aAppId.SupportedGroups = DG_IMAGE | DG_CONTROL;
+ strncpy(m_aAppId.Version.Info, "8.0", 32);
+ m_aAppId.Version.Info[32] = m_aAppId.Version.Info[33] = 0;
+ strncpy(m_aAppId.Manufacturer, "Sun Microsystems", 32);
+ m_aAppId.Manufacturer[32] = m_aAppId.Manufacturer[33] = 0;
+ strncpy(m_aAppId.ProductFamily, "Office", 32);
+ m_aAppId.ProductFamily[32] = m_aAppId.ProductFamily[33] = 0;
+ strncpy(m_aAppId.ProductName, "Office", 32);
+ m_aAppId.ProductName[32] = m_aAppId.ProductName[33] = 0;
+
+ WNDCLASSW aWc = { 0, &WndProc, 0, sizeof(WNDCLASSW), GetModuleHandleW(nullptr),
+ nullptr, nullptr, nullptr, nullptr, sTwainWndClass };
+ if (!RegisterClassW(&aWc))
+ ThrowLastError("RegisterClassW");
+ m_hTwainWnd = CreateWindowExW(WS_EX_TOPMOST, aWc.lpszClassName, L"TWAIN", 0, 0, 0, 0, 0,
+ HWND_DESKTOP, nullptr, aWc.hInstance, this);
+ if (!m_hTwainWnd)
+ ThrowLastError("CreateWindowExW");
+ m_hTwainHook = SetWindowsHookExW(WH_GETMESSAGE, &MsgHook, nullptr, GetCurrentThreadId());
+ if (!m_hTwainHook)
+ ThrowLastError("SetWindowsHookExW");
+
+ NotifyParent(TWAIN_EVENT_NOTIFYHWND, reinterpret_cast<LPARAM>(m_hTwainWnd));
+}
+
+ImpTwain::~ImpTwain()
+{
+ DestroyWindow(m_hTwainWnd);
+ UnhookWindowsHookEx(m_hTwainHook);
+}
+
+void ImpTwain::Destroy() { ImplFallback(TWAIN_EVENT_QUIT); }
+
+bool ImpTwain::SelectSource()
+{
+ TW_UINT16 nRet = TWRC_FAILURE;
+
+ ImplOpenSourceManager();
+
+ if (3 == m_nCurState)
+ {
+ TW_IDENTITY aIdent;
+
+ aIdent.Id = 0;
+ aIdent.ProductName[0] = '\0';
+ NotifyParent(TWAIN_EVENT_SCANNING, 0);
+ nRet = m_pDSM(&m_aAppId, nullptr, DG_CONTROL, DAT_IDENTITY, MSG_USERSELECT, &aIdent);
+ }
+
+ ImplFallback(TWAIN_EVENT_QUIT);
+
+ return (TWRC_SUCCESS == nRet);
+}
+
+bool ImpTwain::InitXfer()
+{
+ bool bRet = false;
+
+ ImplOpenSourceManager();
+
+ if (3 == m_nCurState)
+ {
+ ImplOpenSource();
+
+ if (4 == m_nCurState)
+ bRet = ImplEnableSource();
+ }
+
+ if (!bRet)
+ ImplFallback(TWAIN_EVENT_QUIT);
+
+ return bRet;
+}
+
+void ImpTwain::ImplOpenSourceManager()
+{
+ if (1 == m_nCurState)
+ {
+ if ((m_hMod = LoadLibraryW(L"TWAIN_32.DLL")))
+ {
+ m_nCurState = 2;
+
+ m_pDSM = reinterpret_cast<DSMENTRYPROC>(GetProcAddress(m_hMod, "DSM_Entry"));
+ if (m_pDSM
+ && (m_pDSM(&m_aAppId, nullptr, DG_CONTROL, DAT_PARENT, MSG_OPENDSM, &m_hTwainWnd)
+ == TWRC_SUCCESS))
+ {
+ m_nCurState = 3;
+ }
+ }
+ }
+}
+
+void ImpTwain::ImplOpenSource()
+{
+ if (3 == m_nCurState)
+ {
+ if ((m_pDSM(&m_aAppId, nullptr, DG_CONTROL, DAT_IDENTITY, MSG_GETDEFAULT, &m_aSrcId)
+ == TWRC_SUCCESS)
+ && (m_pDSM(&m_aAppId, nullptr, DG_CONTROL, DAT_IDENTITY, MSG_OPENDS, &m_aSrcId)
+ == TWRC_SUCCESS))
+ {
+ TW_CAPABILITY aCap
+ = { CAP_XFERCOUNT, TWON_ONEVALUE, GlobalAlloc(GHND, sizeof(TW_ONEVALUE)) };
+ TW_ONEVALUE* pVal = static_cast<TW_ONEVALUE*>(GlobalLock(aCap.hContainer));
+
+ pVal->ItemType = TWTY_INT16;
+ pVal->Item = 1;
+ GlobalUnlock(aCap.hContainer);
+ m_pDSM(&m_aAppId, &m_aSrcId, DG_CONTROL, DAT_CAPABILITY, MSG_SET, &aCap);
+ GlobalFree(aCap.hContainer);
+ m_nCurState = 4;
+ }
+ }
+}
+
+bool ImpTwain::ImplEnableSource()
+{
+ bool bRet = false;
+
+ if (4 == m_nCurState)
+ {
+ TW_USERINTERFACE aUI = { true, true, m_hTwainWnd };
+
+ NotifyParent(TWAIN_EVENT_SCANNING, 0);
+ m_nCurState = 5;
+
+ if (m_pDSM(&m_aAppId, &m_aSrcId, DG_CONTROL, DAT_USERINTERFACE, MSG_ENABLEDS, &aUI)
+ == TWRC_SUCCESS)
+ {
+ bRet = true;
+ }
+ else
+ {
+ // dialog failed
+ m_nCurState = 4;
+ }
+ }
+
+ return bRet;
+}
+
+void ImpTwain::NotifyParent(WPARAM nEvent, LPARAM lParam)
+{
+ PostThreadMessageW(m_nParentThreadId, WM_TWAIN_EVENT, nEvent, lParam);
+}
+
+bool ImpTwain::ImplHandleMsg(MSG* pMsg)
+{
+ TW_UINT16 nRet;
+ TW_EVENT aEvt = { pMsg, MSG_NULL };
+
+ if (m_pDSM)
+ nRet = m_pDSM(&m_aAppId, &m_aSrcId, DG_CONTROL, DAT_EVENT, MSG_PROCESSEVENT, &aEvt);
+ else
+ nRet = TWRC_NOTDSEVENT;
+
+ if (aEvt.TWMessage != MSG_NULL)
+ {
+ switch (aEvt.TWMessage)
+ {
+ case MSG_XFERREADY:
+ {
+ WPARAM nEvent = TWAIN_EVENT_QUIT;
+
+ if (5 == m_nCurState)
+ {
+ m_nCurState = 6;
+ ImplXfer();
+
+ if (m_hMap)
+ nEvent = TWAIN_EVENT_XFER;
+ }
+ else if (7 == m_nCurState && m_hMap)
+ {
+ // Already sent TWAIN_EVENT_XFER; not processed yet;
+ // duplicate event
+ nEvent = TWAIN_EVENT_NONE;
+ }
+
+ ImplFallback(nEvent);
+ }
+ break;
+
+ case MSG_CLOSEDSREQ:
+ ImplFallback(TWAIN_EVENT_QUIT);
+ break;
+
+ default:
+ break;
+ }
+ }
+ else
+ nRet = TWRC_NOTDSEVENT;
+
+ return (TWRC_DSEVENT == nRet);
+}
+
+void ImpTwain::ImplXfer()
+{
+ if (m_nCurState == 6)
+ {
+ TW_IMAGEINFO aInfo;
+ HANDLE hDIB = nullptr;
+ long nWidth, nHeight, nXRes, nYRes;
+
+ if (m_pDSM(&m_aAppId, &m_aSrcId, DG_IMAGE, DAT_IMAGEINFO, MSG_GET, &aInfo) == TWRC_SUCCESS)
+ {
+ nWidth = aInfo.ImageWidth;
+ nHeight = aInfo.ImageLength;
+ nXRes = FixToLong(aInfo.XResolution);
+ nYRes = FixToLong(aInfo.YResolution);
+ }
+ else
+ nWidth = nHeight = nXRes = nYRes = -1;
+
+ switch (m_pDSM(&m_aAppId, &m_aSrcId, DG_IMAGE, DAT_IMAGENATIVEXFER, MSG_GET, &hDIB))
+ {
+ case TWRC_CANCEL:
+ m_nCurState = 7;
+ break;
+
+ case TWRC_XFERDONE:
+ {
+ if (hDIB)
+ {
+ m_hMap = nullptr;
+ const HGLOBAL hGlob = static_cast<HGLOBAL>(hDIB);
+ const SIZE_T nDIBSize = GlobalSize(hGlob);
+ const DWORD nMapSize = nDIBSize + 4; // leading 4 bytes for size
+ if (nMapSize > nDIBSize) // check for wrap
+ {
+ if (LPVOID pBmpMem = GlobalLock(hGlob))
+ {
+ if ((nXRes != -1) && (nYRes != -1) && (nWidth != -1) && (nHeight != -1))
+ {
+ // set resolution of bitmap
+ BITMAPINFOHEADER* pBIH = static_cast<BITMAPINFOHEADER*>(pBmpMem);
+ static const double fFactor = 100.0 / 2.54;
+
+ pBIH->biXPelsPerMeter = FRound(fFactor * nXRes);
+ pBIH->biYPelsPerMeter = FRound(fFactor * nYRes);
+ }
+
+ HANDLE hMap = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr,
+ PAGE_READWRITE, 0, nMapSize, nullptr);
+ if (hMap)
+ {
+ LPVOID pMap = MapViewOfFile(hMap, FILE_MAP_WRITE, 0, 0, nMapSize);
+ if (pMap)
+ {
+ memcpy(pMap, &nMapSize, 4); // size of the following DIB
+ memcpy(static_cast<char*>(pMap) + 4, pBmpMem, nDIBSize);
+ FlushViewOfFile(pMap, nDIBSize);
+ UnmapViewOfFile(pMap);
+
+ DuplicateHandle(GetCurrentProcess(), hMap, m_hProc, &m_hMap, 0,
+ FALSE, DUPLICATE_SAME_ACCESS);
+ }
+
+ CloseHandle(hMap);
+ }
+
+ GlobalUnlock(hGlob);
+ }
+ }
+ }
+
+ GlobalFree(static_cast<HGLOBAL>(hDIB));
+
+ m_nCurState = 7;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+void ImpTwain::ImplFallback(WPARAM nEvent)
+{
+ PostMessageW(m_hTwainWnd, WM_TWAIN_FALLBACK, nEvent, 0);
+}
+
+void ImpTwain::ImplFallbackHdl(WPARAM nEvent)
+{
+ bool bFallback = true;
+
+ switch (m_nCurState)
+ {
+ case 7:
+ case 6:
+ {
+ TW_PENDINGXFERS aXfers;
+
+ if (m_pDSM(&m_aAppId, &m_aSrcId, DG_CONTROL, DAT_PENDINGXFERS, MSG_ENDXFER, &aXfers)
+ == TWRC_SUCCESS)
+ {
+ if (aXfers.Count != 0)
+ m_pDSM(&m_aAppId, &m_aSrcId, DG_CONTROL, DAT_PENDINGXFERS, MSG_RESET, &aXfers);
+ }
+
+ m_nCurState = 5;
+ }
+ break;
+
+ case 5:
+ {
+ TW_USERINTERFACE aUI = { true, true, m_hTwainWnd };
+
+ m_pDSM(&m_aAppId, &m_aSrcId, DG_CONTROL, DAT_USERINTERFACE, MSG_DISABLEDS, &aUI);
+ m_nCurState = 4;
+ }
+ break;
+
+ case 4:
+ {
+ m_pDSM(&m_aAppId, nullptr, DG_CONTROL, DAT_IDENTITY, MSG_CLOSEDS, &m_aSrcId);
+ m_nCurState = 3;
+ }
+ break;
+
+ case 3:
+ {
+ m_pDSM(&m_aAppId, nullptr, DG_CONTROL, DAT_PARENT, MSG_CLOSEDSM, &m_hTwainWnd);
+ m_nCurState = 2;
+ }
+ break;
+
+ case 2:
+ {
+ m_pDSM = nullptr;
+ FreeLibrary(m_hMod);
+ m_hMod = nullptr;
+ m_nCurState = 1;
+ }
+ break;
+
+ default:
+ {
+ if (nEvent > TWAIN_EVENT_NONE)
+ NotifyParent(nEvent, reinterpret_cast<LPARAM>(m_hMap));
+ if (nEvent == TWAIN_EVENT_QUIT)
+ PostQuitMessage(0);
+
+ bFallback = false;
+ }
+ break;
+ }
+
+ if (bFallback)
+ ImplFallback(nEvent);
+}
+
+void ImpTwain::ImplRequestHdl(WPARAM nRequest)
+{
+ switch (nRequest)
+ {
+ case TWAIN_REQUEST_QUIT:
+ Destroy();
+ break;
+ case TWAIN_REQUEST_SELECTSOURCE:
+ NotifyParent(TWAIN_EVENT_REQUESTRESULT, SelectSource());
+ break;
+ case TWAIN_REQUEST_INITXFER:
+ NotifyParent(TWAIN_EVENT_REQUESTRESULT, InitXfer());
+ break;
+ }
+}
+} // namespace
+
+int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int)
+{
+ int argc = 0;
+ LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc);
+ if (argc != 2)
+ return 1; // Wrong argument count
+ // 1st argument is parent thread handle; must be inherited.
+ // HANDLE is 32-bit in 32-bit applications, so wcstoul is OK.
+ HANDLE hParentThread = reinterpret_cast<HANDLE>(wcstoul(argv[1], nullptr, 10));
+ LocalFree(argv);
+ if (!hParentThread)
+ return 2; // Invalid parent thread handle argument value
+
+ int nRet = 0;
+ try
+ {
+ ImpTwain aImpTwain(hParentThread); // creates main window
+
+ MSG msg;
+ while (true)
+ {
+ DWORD nWaitResult = MsgWaitForMultipleObjects(1, &hParentThread, FALSE, INFINITE,
+ QS_ALLPOSTMESSAGE | QS_SENDMESSAGE);
+ if (nWaitResult == WAIT_OBJECT_0)
+ return 5; // Parent process' thread died before we exited
+ if (nWaitResult == WAIT_FAILED)
+ return 6; // Some Win32 error
+ // nWaitResult == WAIT_OBJECT_0 + nCount => an event is in queue
+ bool bQuit = false;
+ while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE))
+ {
+ // process it here
+ if (msg.message == WM_QUIT)
+ {
+ bQuit = true;
+ nRet = msg.wParam;
+ }
+ else
+ {
+ TranslateMessage(&msg);
+ DispatchMessageW(&msg);
+ }
+ }
+ if (bQuit)
+ break;
+ }
+ }
+ catch (const std::exception& e)
+ {
+ printf("Exception thrown: %s", e.what());
+ nRet = 7;
+ }
+ return nRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/extensions/source/scanner/twain32shim.hxx b/extensions/source/scanner/twain32shim.hxx
new file mode 100644
index 000000000000..b72df438d596
--- /dev/null
+++ b/extensions/source/scanner/twain32shim.hxx
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#ifndef INCLUDED_EXTENSIONS_SOURCE_SCANNER_TWAIN32SHIM_HXX
+#define INCLUDED_EXTENSIONS_SOURCE_SCANNER_TWAIN32SHIM_HXX
+
+#include <prewin.h>
+#include <postwin.h>
+#include <exception>
+#include <string>
+#include <sstream>
+#include <iomanip>
+
+// Don't use WM_USER
+
+// Notifications from shim to parent; wParam = event id
+#define WM_TWAIN_EVENT (WM_USER + 1)
+
+// lParam is HWND
+#define TWAIN_EVENT_NOTIFYHWND 0
+
+// lParam is result (bool indicating success)
+#define TWAIN_EVENT_REQUESTRESULT 1
+
+// lParam is ignored
+#define TWAIN_EVENT_NONE 10
+#define TWAIN_EVENT_QUIT 11
+#define TWAIN_EVENT_SCANNING 12
+
+// lParam is HANDLE to shared file mapping valid in context of parent process
+#define TWAIN_EVENT_XFER 13
+
+// Requests from parent to shim; wParam = request id
+#define WM_TWAIN_REQUEST (WM_USER + 2)
+
+#define TWAIN_REQUEST_QUIT 0 // Destroy()
+#define TWAIN_REQUEST_SELECTSOURCE 1
+#define TWAIN_REQUEST_INITXFER 2
+
+// messages starting from this are not to be used for interprocess communications
+#define WM_SHIM_INTERNAL (WM_USER + 200)
+
+template <typename IntType> std::string Num2Hex(IntType n)
+{
+ std::stringstream sMsg;
+ sMsg << "0x" << std::uppercase << std::setfill('0') << std::setw(sizeof(n) * 2) << std::hex
+ << n;
+ return sMsg.str();
+}
+
+void ThrowWin32Error(const char* sFunc, DWORD nWin32Error)
+{
+ std::stringstream sMsg;
+ sMsg << sFunc << " failed with Win32 error code " << Num2Hex(nWin32Error) << "!";
+
+ throw std::exception(sMsg.str().c_str());
+}
+
+void ThrowLastError(const char* sFunc) { ThrowWin32Error(sFunc, GetLastError()); }
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */