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 * twain32shim.exe is a separate 32-bit executable that serves as a shim
22 * between LibreOffice and Windows' 32-bit TWAIN component. Without it,
23 * it's impossible for 64-bit program to use TWAIN on Windows.
24 * Using 64-bit TWAIN DSM library from twain.org to avoid using the shim
25 * is not an option, because scanner manufacturers only provide 32-bit
26 * drivers, and 64-bit drivers are only offered as 3rd-party commercial
27 * products. The shim is also used in 32-bit LibreOffice for uniformity.
30 #include "twain32shim.hxx"
31 #include <systools/win32/comtools.hxx>
32 #include <tools/helpers.hxx>
33 #include <twain/twain.h>
34 #include <o3tl/unit_conversion.hxx>
36 #define WM_TWAIN_FALLBACK (WM_SHIM_INTERNAL + 0)
40 double FixToDouble(const TW_FIX32
& rFix
) { return rFix
.Whole
+ rFix
.Frac
/ 65536.; }
42 const wchar_t sTwainWndClass
[] = L
"TwainClass";
47 ImpTwain(HANDLE hParentThread
);
64 DWORD m_nParentThreadId
;
66 DSMENTRYPROC m_pDSM
= nullptr;
67 HMODULE m_hMod
= nullptr;
68 TWAINState m_nCurState
= TWAINState::DSMunloaded
;
69 HWND m_hTwainWnd
= nullptr;
70 HHOOK m_hTwainHook
= nullptr;
71 HANDLE m_hMap
= nullptr; // the *duplicated* handle
73 static bool IsTwainClassWnd(HWND hWnd
);
74 static ImpTwain
* GetImpFromWnd(HWND hWnd
);
75 static void ImplCreateWnd(HWND hWnd
, LPARAM lParam
);
76 static LRESULT CALLBACK
WndProc(HWND hWnd
, UINT nMsg
, WPARAM wParam
, LPARAM lParam
);
77 static LRESULT CALLBACK
MsgHook(int nCode
, WPARAM wParam
, LPARAM lParam
);
79 void Destroy() { ImplFallback(TWAIN_EVENT_QUIT
); }
83 void NotifyParent(WPARAM nEvent
, LPARAM lParam
);
84 bool ImplHandleMsg(MSG
* pMsg
);
85 void ImplOpenSourceManager();
86 void ImplOpenSource();
87 bool ImplEnableSource();
89 void ImplFallback(WPARAM nEvent
);
91 void ImplFallbackHdl(WPARAM nEvent
);
92 void ImplRequestHdl(WPARAM nRequest
);
96 bool ImpTwain::IsTwainClassWnd(HWND hWnd
)
98 const int nBufSize
= SAL_N_ELEMENTS(sTwainWndClass
);
99 wchar_t sClassName
[nBufSize
];
100 return (GetClassNameW(hWnd
, sClassName
, nBufSize
) && wcscmp(sClassName
, sTwainWndClass
) == 0);
104 ImpTwain
* ImpTwain::GetImpFromWnd(HWND hWnd
)
106 if (!IsTwainClassWnd(hWnd
))
108 return reinterpret_cast<ImpTwain
*>(GetWindowLongPtrW(hWnd
, GWLP_USERDATA
));
112 void ImpTwain::ImplCreateWnd(HWND hWnd
, LPARAM lParam
)
114 CREATESTRUCT
* pCS
= reinterpret_cast<CREATESTRUCT
*>(lParam
);
115 if (pCS
&& IsTwainClassWnd(hWnd
))
116 SetWindowLongPtrW(hWnd
, GWLP_USERDATA
, reinterpret_cast<LONG_PTR
>(pCS
->lpCreateParams
));
120 LRESULT CALLBACK
ImpTwain::WndProc(HWND hWnd
, UINT nMsg
, WPARAM wParam
, LPARAM lParam
)
122 ImpTwain
* pImpTwain
= GetImpFromWnd(hWnd
);
126 ImplCreateWnd(hWnd
, lParam
);
128 case WM_TWAIN_FALLBACK
:
130 pImpTwain
->ImplFallbackHdl(wParam
);
132 case WM_TWAIN_REQUEST
:
134 pImpTwain
->ImplRequestHdl(wParam
);
137 return DefWindowProcW(hWnd
, nMsg
, wParam
, lParam
);
141 LRESULT CALLBACK
ImpTwain::MsgHook(int nCode
, WPARAM wParam
, LPARAM lParam
)
143 MSG
* pMsg
= reinterpret_cast<MSG
*>(lParam
);
144 if (nCode
>= 0 && pMsg
)
146 ImpTwain
* pImpTwain
= GetImpFromWnd(pMsg
->hwnd
);
147 if (pImpTwain
&& pImpTwain
->ImplHandleMsg(pMsg
))
149 pMsg
->message
= WM_USER
;
156 return CallNextHookEx(nullptr, nCode
, wParam
, lParam
);
159 HANDLE
GetProcOfThread(HANDLE hThread
)
161 DWORD nProcId
= GetProcessIdOfThread(hThread
);
163 ThrowLastError("GetProcessIdOfThread");
164 HANDLE hRet
= OpenProcess(PROCESS_DUP_HANDLE
, FALSE
, nProcId
);
166 ThrowLastError("OpenProcess");
170 ImpTwain::ImpTwain(HANDLE hParentThread
)
171 : m_aAppId
{ /* Id */ 0,
172 { /* Version.MajorNum */ 1,
173 /* Version.MinorNum */ 0,
174 /* Version.Language */ TWLG_USA
,
175 /* Version.Country */ TWCY_USA
,
176 /* Version.Info */ "8.0" },
177 /* ProtocolMajor */ TWON_PROTOCOLMAJOR
,
178 /* ProtocolMinor */ TWON_PROTOCOLMINOR
,
179 /* SupportedGroups */ DG_IMAGE
| DG_CONTROL
,
180 /* Manufacturer */ "Sun Microsystems",
181 /* ProductFamily */ "Office",
182 /* ProductName */ "Office" }
183 , m_nParentThreadId(GetThreadId(hParentThread
))
184 , m_hProc(GetProcOfThread(hParentThread
))
186 WNDCLASSW aWc
= { 0, &WndProc
, 0, sizeof(WNDCLASSW
), GetModuleHandleW(nullptr),
187 nullptr, nullptr, nullptr, nullptr, sTwainWndClass
};
188 if (!RegisterClassW(&aWc
))
189 ThrowLastError("RegisterClassW");
190 m_hTwainWnd
= CreateWindowExW(WS_EX_TOPMOST
, aWc
.lpszClassName
, L
"TWAIN", 0, 0, 0, 0, 0,
191 HWND_DESKTOP
, nullptr, aWc
.hInstance
, this);
193 ThrowLastError("CreateWindowExW");
194 m_hTwainHook
= SetWindowsHookExW(WH_GETMESSAGE
, &MsgHook
, nullptr, GetCurrentThreadId());
196 ThrowLastError("SetWindowsHookExW");
198 NotifyParent(TWAIN_EVENT_NOTIFYHWND
, reinterpret_cast<LPARAM
>(m_hTwainWnd
));
201 ImpTwain::~ImpTwain()
203 DestroyWindow(m_hTwainWnd
);
204 UnhookWindowsHookEx(m_hTwainHook
);
207 bool ImpTwain::SelectSource()
209 TW_UINT16 nRet
= TWRC_FAILURE
;
211 ImplOpenSourceManager();
213 if (TWAINState::DSMopened
== m_nCurState
)
218 aIdent
.ProductName
[0] = '\0';
219 NotifyParent(TWAIN_EVENT_SCANNING
, 0);
220 nRet
= m_pDSM(&m_aAppId
, nullptr, DG_CONTROL
, DAT_IDENTITY
, MSG_USERSELECT
, &aIdent
);
224 return (TWRC_SUCCESS
== nRet
);
227 bool ImpTwain::InitXfer()
231 ImplOpenSourceManager();
233 if (TWAINState::DSMopened
== m_nCurState
)
237 if (TWAINState::DSopened
== m_nCurState
)
238 bRet
= ImplEnableSource();
247 void ImpTwain::ImplOpenSourceManager()
249 if (TWAINState::DSMunloaded
== m_nCurState
)
251 m_hMod
= LoadLibraryW(L
"TWAIN_32.DLL");
254 // Windows directory might not be in DLL search path sometimes, so try the full path
255 sal::systools::CoTaskMemAllocated
<wchar_t> sPath
;
256 if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_Windows
, 0, nullptr, &sPath
)))
258 std::wstring
sPathAndFile(sPath
);
259 sPathAndFile
+= L
"\\TWAIN_32.DLL";
260 m_hMod
= LoadLibraryW(sPathAndFile
.c_str());
265 m_nCurState
= TWAINState::DSMloaded
;
267 m_pDSM
= reinterpret_cast<DSMENTRYPROC
>(GetProcAddress(m_hMod
, "DSM_Entry"));
269 && (m_pDSM(&m_aAppId
, nullptr, DG_CONTROL
, DAT_PARENT
, MSG_OPENDSM
, &m_hTwainWnd
)
272 m_nCurState
= TWAINState::DSMopened
;
278 void ImpTwain::ImplOpenSource()
280 if (TWAINState::DSMopened
== m_nCurState
)
282 if ((m_pDSM(&m_aAppId
, nullptr, DG_CONTROL
, DAT_IDENTITY
, MSG_GETDEFAULT
, &m_aSrcId
)
284 && (m_pDSM(&m_aAppId
, nullptr, DG_CONTROL
, DAT_IDENTITY
, MSG_OPENDS
, &m_aSrcId
)
288 = { CAP_XFERCOUNT
, TWON_ONEVALUE
, GlobalAlloc(GHND
, sizeof(TW_ONEVALUE
)) };
289 TW_ONEVALUE
* pVal
= static_cast<TW_ONEVALUE
*>(GlobalLock(aCap
.hContainer
));
291 pVal
->ItemType
= TWTY_INT16
;
293 GlobalUnlock(aCap
.hContainer
);
294 m_pDSM(&m_aAppId
, &m_aSrcId
, DG_CONTROL
, DAT_CAPABILITY
, MSG_SET
, &aCap
);
295 GlobalFree(aCap
.hContainer
);
296 m_nCurState
= TWAINState::DSopened
;
301 bool ImpTwain::ImplEnableSource()
305 if (TWAINState::DSopened
== m_nCurState
)
307 TW_USERINTERFACE aUI
= { true, true, m_hTwainWnd
};
309 NotifyParent(TWAIN_EVENT_SCANNING
, 0);
310 m_nCurState
= TWAINState::DSenabled
;
312 if (m_pDSM(&m_aAppId
, &m_aSrcId
, DG_CONTROL
, DAT_USERINTERFACE
, MSG_ENABLEDS
, &aUI
)
320 m_nCurState
= TWAINState::DSopened
;
327 void ImpTwain::NotifyParent(WPARAM nEvent
, LPARAM lParam
)
329 PostThreadMessageW(m_nParentThreadId
, WM_TWAIN_EVENT
, nEvent
, lParam
);
332 bool ImpTwain::ImplHandleMsg(MSG
* pMsg
)
337 TW_EVENT aEvt
= { pMsg
, MSG_NULL
};
338 TW_UINT16 nRet
= m_pDSM(&m_aAppId
, &m_aSrcId
, DG_CONTROL
, DAT_EVENT
, MSG_PROCESSEVENT
, &aEvt
);
340 switch (aEvt
.TWMessage
)
344 WPARAM nEvent
= TWAIN_EVENT_QUIT
;
346 if (TWAINState::DSenabled
== m_nCurState
)
348 m_nCurState
= TWAINState::DSreadyToXfer
;
352 nEvent
= TWAIN_EVENT_XFER
;
354 else if (TWAINState::Xferring
== m_nCurState
&& m_hMap
)
356 // Already sent TWAIN_EVENT_XFER; not processed yet;
358 nEvent
= TWAIN_EVENT_NONE
;
361 ImplFallback(nEvent
);
370 nRet
= TWRC_NOTDSEVENT
;
374 return (TWRC_DSEVENT
== nRet
);
377 void ImpTwain::ImplXfer()
379 if (m_nCurState
== TWAINState::DSreadyToXfer
)
382 HANDLE hDIB
= nullptr;
385 if (m_pDSM(&m_aAppId
, &m_aSrcId
, DG_IMAGE
, DAT_IMAGEINFO
, MSG_GET
, &aInfo
) == TWRC_SUCCESS
)
387 nXRes
= FixToDouble(aInfo
.XResolution
);
388 nYRes
= FixToDouble(aInfo
.YResolution
);
393 switch (m_pDSM(&m_aAppId
, &m_aSrcId
, DG_IMAGE
, DAT_IMAGENATIVEXFER
, MSG_GET
, &hDIB
))
396 m_nCurState
= TWAINState::Xferring
;
404 const HGLOBAL hGlob
= static_cast<HGLOBAL
>(hDIB
);
405 const SIZE_T nDIBSize
= GlobalSize(hGlob
);
406 const DWORD nMapSize
= nDIBSize
+ 4; // leading 4 bytes for size
407 if (nMapSize
> nDIBSize
) // check for wrap
409 if (LPVOID pBmpMem
= GlobalLock(hGlob
))
411 if ((nXRes
> 0) && (nYRes
> 0))
413 // set resolution of bitmap
414 BITMAPINFOHEADER
* pBIH
= static_cast<BITMAPINFOHEADER
*>(pBmpMem
);
417 = getConversionMulDiv(o3tl::Length::in
, o3tl::Length::m
);
418 pBIH
->biXPelsPerMeter
= std::round(o3tl::convert(nXRes
, d
, m
));
419 pBIH
->biYPelsPerMeter
= std::round(o3tl::convert(nYRes
, d
, m
));
422 HANDLE hMap
= CreateFileMappingW(INVALID_HANDLE_VALUE
, nullptr,
423 PAGE_READWRITE
, 0, nMapSize
, nullptr);
426 LPVOID pMap
= MapViewOfFile(hMap
, FILE_MAP_WRITE
, 0, 0, nMapSize
);
429 memcpy(pMap
, &nMapSize
, 4); // size of the following DIB
430 memcpy(static_cast<char*>(pMap
) + 4, pBmpMem
, nDIBSize
);
431 FlushViewOfFile(pMap
, nDIBSize
);
432 UnmapViewOfFile(pMap
);
434 DuplicateHandle(GetCurrentProcess(), hMap
, m_hProc
, &m_hMap
, 0,
435 FALSE
, DUPLICATE_SAME_ACCESS
);
446 GlobalFree(static_cast<HGLOBAL
>(hDIB
));
448 m_nCurState
= TWAINState::Xferring
;
458 void ImpTwain::ImplFallback(WPARAM nEvent
)
460 PostMessageW(m_hTwainWnd
, WM_TWAIN_FALLBACK
, nEvent
, 0);
463 void ImpTwain::ImplFallbackHdl(WPARAM nEvent
)
465 bool bFallback
= true;
469 case TWAINState::Xferring
:
470 case TWAINState::DSreadyToXfer
:
472 TW_PENDINGXFERS aXfers
;
474 if (m_pDSM(&m_aAppId
, &m_aSrcId
, DG_CONTROL
, DAT_PENDINGXFERS
, MSG_ENDXFER
, &aXfers
)
477 if (aXfers
.Count
!= 0)
478 m_pDSM(&m_aAppId
, &m_aSrcId
, DG_CONTROL
, DAT_PENDINGXFERS
, MSG_RESET
, &aXfers
);
481 m_nCurState
= TWAINState::DSenabled
;
485 case TWAINState::DSenabled
:
487 TW_USERINTERFACE aUI
= { true, true, m_hTwainWnd
};
489 m_pDSM(&m_aAppId
, &m_aSrcId
, DG_CONTROL
, DAT_USERINTERFACE
, MSG_DISABLEDS
, &aUI
);
490 m_nCurState
= TWAINState::DSopened
;
494 case TWAINState::DSopened
:
496 m_pDSM(&m_aAppId
, nullptr, DG_CONTROL
, DAT_IDENTITY
, MSG_CLOSEDS
, &m_aSrcId
);
497 m_nCurState
= TWAINState::DSMopened
;
501 case TWAINState::DSMopened
:
503 m_pDSM(&m_aAppId
, nullptr, DG_CONTROL
, DAT_PARENT
, MSG_CLOSEDSM
, &m_hTwainWnd
);
504 m_nCurState
= TWAINState::DSMloaded
;
508 case TWAINState::DSMloaded
:
513 m_nCurState
= TWAINState::DSMunloaded
;
517 case TWAINState::DSMunloaded
:
519 if (nEvent
> TWAIN_EVENT_NONE
)
520 NotifyParent(nEvent
, reinterpret_cast<LPARAM
>(m_hMap
));
529 ImplFallback(nEvent
);
532 void ImpTwain::ImplRequestHdl(WPARAM nRequest
)
536 case TWAIN_REQUEST_QUIT
:
539 case TWAIN_REQUEST_SELECTSOURCE
:
540 NotifyParent(TWAIN_EVENT_REQUESTRESULT
, LPARAM(SelectSource()));
542 case TWAIN_REQUEST_INITXFER
:
543 NotifyParent(TWAIN_EVENT_REQUESTRESULT
, LPARAM(InitXfer()));
549 int WINAPI
wWinMain(HINSTANCE
, HINSTANCE
, LPWSTR
, int)
552 LPWSTR
* argv
= CommandLineToArgvW(GetCommandLineW(), &argc
);
554 return 1; // Wrong argument count
555 // 1st argument is parent thread handle; must be inherited.
556 // HANDLE is 32-bit in 32-bit applications, so wcstoul is OK.
557 HANDLE hParentThread
= reinterpret_cast<HANDLE
>(wcstoul(argv
[1], nullptr, 10));
560 return 2; // Invalid parent thread handle argument value
565 ImpTwain
aImpTwain(hParentThread
); // creates main window
571 = MsgWaitForMultipleObjects(1, &hParentThread
, FALSE
, INFINITE
, QS_ALLINPUT
);
572 if (nWaitResult
== WAIT_OBJECT_0
)
573 return 5; // Parent process' thread died before we exited
574 if (nWaitResult
== WAIT_FAILED
)
575 return 6; // Some Win32 error
576 // nWaitResult == WAIT_OBJECT_0 + nCount => an event is in queue
578 while (PeekMessageW(&msg
, nullptr, 0, 0, PM_REMOVE
))
581 if (msg
.message
== WM_QUIT
)
588 TranslateMessage(&msg
);
589 DispatchMessageW(&msg
);
596 catch (const std::exception
& e
)
598 printf("Exception thrown: %s", e
.what());
604 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */