LibreOffice Module extensions (master) 1
twain32shim.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
20/*
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.
28*/
29
30#include "twain32shim.hxx"
31#include <systools/win32/comtools.hxx>
32#include <tools/helpers.hxx>
33#include <twain/twain.h>
35
36#define WM_TWAIN_FALLBACK (WM_SHIM_INTERNAL + 0)
37
38namespace
39{
40double FixToDouble(const TW_FIX32& rFix) { return rFix.Whole + rFix.Frac / 65536.; }
41
42const wchar_t sTwainWndClass[] = L"TwainClass";
43
44class ImpTwain
45{
46public:
47 ImpTwain(HANDLE hParentThread);
48 ~ImpTwain();
49
50private:
51 enum class TWAINState
52 {
53 DSMunloaded = 1,
54 DSMloaded = 2,
55 DSMopened = 3,
56 DSopened = 4,
57 DSenabled = 5,
58 DSreadyToXfer = 6,
59 Xferring = 7,
60 };
61
62 TW_IDENTITY m_aAppId;
63 TW_IDENTITY m_aSrcId;
64 DWORD m_nParentThreadId;
65 HANDLE m_hProc;
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
72
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);
78
79 void Destroy() { ImplFallback(TWAIN_EVENT_QUIT); }
80 bool SelectSource();
81 bool InitXfer();
82
83 void NotifyParent(WPARAM nEvent, LPARAM lParam);
84 bool ImplHandleMsg(MSG* pMsg);
85 void ImplOpenSourceManager();
86 void ImplOpenSource();
87 bool ImplEnableSource();
88 void ImplXfer();
89 void ImplFallback(WPARAM nEvent);
90
91 void ImplFallbackHdl(WPARAM nEvent);
92 void ImplRequestHdl(WPARAM nRequest);
93};
94
95//static
96bool ImpTwain::IsTwainClassWnd(HWND hWnd)
97{
98 const int nBufSize = SAL_N_ELEMENTS(sTwainWndClass);
99 wchar_t sClassName[nBufSize];
100 return (GetClassNameW(hWnd, sClassName, nBufSize) && wcscmp(sClassName, sTwainWndClass) == 0);
101}
102
103//static
104ImpTwain* ImpTwain::GetImpFromWnd(HWND hWnd)
105{
106 if (!IsTwainClassWnd(hWnd))
107 return nullptr;
108 return reinterpret_cast<ImpTwain*>(GetWindowLongPtrW(hWnd, GWLP_USERDATA));
109}
110
111//static
112void ImpTwain::ImplCreateWnd(HWND hWnd, LPARAM lParam)
113{
114 CREATESTRUCT* pCS = reinterpret_cast<CREATESTRUCT*>(lParam);
115 if (pCS && IsTwainClassWnd(hWnd))
116 SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pCS->lpCreateParams));
117}
118
119// static
120LRESULT CALLBACK ImpTwain::WndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
121{
122 ImpTwain* pImpTwain = GetImpFromWnd(hWnd);
123 switch (nMsg)
124 {
125 case WM_CREATE:
126 ImplCreateWnd(hWnd, lParam);
127 break;
129 if (pImpTwain)
130 pImpTwain->ImplFallbackHdl(wParam);
131 break;
132 case WM_TWAIN_REQUEST:
133 if (pImpTwain)
134 pImpTwain->ImplRequestHdl(wParam);
135 break;
136 }
137 return DefWindowProcW(hWnd, nMsg, wParam, lParam);
138}
139
140// static
141LRESULT CALLBACK ImpTwain::MsgHook(int nCode, WPARAM wParam, LPARAM lParam)
142{
143 MSG* pMsg = reinterpret_cast<MSG*>(lParam);
144 if (nCode >= 0 && pMsg)
145 {
146 ImpTwain* pImpTwain = GetImpFromWnd(pMsg->hwnd);
147 if (pImpTwain && pImpTwain->ImplHandleMsg(pMsg))
148 {
149 pMsg->message = WM_USER;
150 pMsg->lParam = 0;
151
152 return 0;
153 }
154 }
155
156 return CallNextHookEx(nullptr, nCode, wParam, lParam);
157}
158
159HANDLE GetProcOfThread(HANDLE hThread)
160{
161 DWORD nProcId = GetProcessIdOfThread(hThread);
162 if (!nProcId)
163 ThrowLastError("GetProcessIdOfThread");
164 HANDLE hRet = OpenProcess(PROCESS_DUP_HANDLE, FALSE, nProcId);
165 if (!hRet)
166 ThrowLastError("OpenProcess");
167 return hRet;
168}
169
170ImpTwain::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))
185{
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);
192 if (!m_hTwainWnd)
193 ThrowLastError("CreateWindowExW");
194 m_hTwainHook = SetWindowsHookExW(WH_GETMESSAGE, &MsgHook, nullptr, GetCurrentThreadId());
195 if (!m_hTwainHook)
196 ThrowLastError("SetWindowsHookExW");
197
198 NotifyParent(TWAIN_EVENT_NOTIFYHWND, reinterpret_cast<LPARAM>(m_hTwainWnd));
199}
200
201ImpTwain::~ImpTwain()
202{
203 DestroyWindow(m_hTwainWnd);
204 UnhookWindowsHookEx(m_hTwainHook);
205}
206
207bool ImpTwain::SelectSource()
208{
209 TW_UINT16 nRet = TWRC_FAILURE;
210
211 ImplOpenSourceManager();
212
213 if (TWAINState::DSMopened == m_nCurState)
214 {
215 TW_IDENTITY aIdent;
216
217 aIdent.Id = 0;
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);
221 }
222
223 Destroy();
224 return (TWRC_SUCCESS == nRet);
225}
226
227bool ImpTwain::InitXfer()
228{
229 bool bRet = false;
230
231 ImplOpenSourceManager();
232
233 if (TWAINState::DSMopened == m_nCurState)
234 {
235 ImplOpenSource();
236
237 if (TWAINState::DSopened == m_nCurState)
238 bRet = ImplEnableSource();
239 }
240
241 if (!bRet)
242 Destroy();
243
244 return bRet;
245}
246
247void ImpTwain::ImplOpenSourceManager()
248{
249 if (TWAINState::DSMunloaded == m_nCurState)
250 {
251 m_hMod = LoadLibraryW(L"TWAIN_32.DLL");
252 if (!m_hMod)
253 {
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)))
257 {
258 std::wstring sPathAndFile(sPath);
259 sPathAndFile += L"\\TWAIN_32.DLL";
260 m_hMod = LoadLibraryW(sPathAndFile.c_str());
261 }
262 }
263 if (m_hMod)
264 {
265 m_nCurState = TWAINState::DSMloaded;
266
267 m_pDSM = reinterpret_cast<DSMENTRYPROC>(GetProcAddress(m_hMod, "DSM_Entry"));
268 if (m_pDSM
269 && (m_pDSM(&m_aAppId, nullptr, DG_CONTROL, DAT_PARENT, MSG_OPENDSM, &m_hTwainWnd)
270 == TWRC_SUCCESS))
271 {
272 m_nCurState = TWAINState::DSMopened;
273 }
274 }
275 }
276}
277
278void ImpTwain::ImplOpenSource()
279{
280 if (TWAINState::DSMopened == m_nCurState)
281 {
282 if ((m_pDSM(&m_aAppId, nullptr, DG_CONTROL, DAT_IDENTITY, MSG_GETDEFAULT, &m_aSrcId)
283 == TWRC_SUCCESS)
284 && (m_pDSM(&m_aAppId, nullptr, DG_CONTROL, DAT_IDENTITY, MSG_OPENDS, &m_aSrcId)
285 == TWRC_SUCCESS))
286 {
287 TW_CAPABILITY aCap
288 = { CAP_XFERCOUNT, TWON_ONEVALUE, GlobalAlloc(GHND, sizeof(TW_ONEVALUE)) };
289 TW_ONEVALUE* pVal = static_cast<TW_ONEVALUE*>(GlobalLock(aCap.hContainer));
290
291 pVal->ItemType = TWTY_INT16;
292 pVal->Item = 1;
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;
297 }
298 }
299}
300
301bool ImpTwain::ImplEnableSource()
302{
303 bool bRet = false;
304
305 if (TWAINState::DSopened == m_nCurState)
306 {
307 TW_USERINTERFACE aUI = { true, true, m_hTwainWnd };
308
309 NotifyParent(TWAIN_EVENT_SCANNING, 0);
310 m_nCurState = TWAINState::DSenabled;
311
312 if (m_pDSM(&m_aAppId, &m_aSrcId, DG_CONTROL, DAT_USERINTERFACE, MSG_ENABLEDS, &aUI)
313 == TWRC_SUCCESS)
314 {
315 bRet = true;
316 }
317 else
318 {
319 // dialog failed
320 m_nCurState = TWAINState::DSopened;
321 }
322 }
323
324 return bRet;
325}
326
327void ImpTwain::NotifyParent(WPARAM nEvent, LPARAM lParam)
328{
329 PostThreadMessageW(m_nParentThreadId, WM_TWAIN_EVENT, nEvent, lParam);
330}
331
332bool ImpTwain::ImplHandleMsg(MSG* pMsg)
333{
334 if (!m_pDSM)
335 return false;
336
337 TW_EVENT aEvt = { pMsg, MSG_NULL };
338 TW_UINT16 nRet = m_pDSM(&m_aAppId, &m_aSrcId, DG_CONTROL, DAT_EVENT, MSG_PROCESSEVENT, &aEvt);
339
340 switch (aEvt.TWMessage)
341 {
342 case MSG_XFERREADY:
343 {
344 WPARAM nEvent = TWAIN_EVENT_QUIT;
345
346 if (TWAINState::DSenabled == m_nCurState)
347 {
348 m_nCurState = TWAINState::DSreadyToXfer;
349 ImplXfer();
350
351 if (m_hMap)
352 nEvent = TWAIN_EVENT_XFER;
353 }
354 else if (TWAINState::Xferring == m_nCurState && m_hMap)
355 {
356 // Already sent TWAIN_EVENT_XFER; not processed yet;
357 // duplicate event
358 nEvent = TWAIN_EVENT_NONE;
359 }
360
361 ImplFallback(nEvent);
362 }
363 break;
364
365 case MSG_CLOSEDSREQ:
366 Destroy();
367 break;
368
369 case MSG_NULL:
370 nRet = TWRC_NOTDSEVENT;
371 break;
372 }
373
374 return (TWRC_DSEVENT == nRet);
375}
376
377void ImpTwain::ImplXfer()
378{
379 if (m_nCurState == TWAINState::DSreadyToXfer)
380 {
381 TW_IMAGEINFO aInfo;
382 HANDLE hDIB = nullptr;
383 double nXRes, nYRes;
384
385 if (m_pDSM(&m_aAppId, &m_aSrcId, DG_IMAGE, DAT_IMAGEINFO, MSG_GET, &aInfo) == TWRC_SUCCESS)
386 {
387 nXRes = FixToDouble(aInfo.XResolution);
388 nYRes = FixToDouble(aInfo.YResolution);
389 }
390 else
391 nXRes = nYRes = -1;
392
393 switch (m_pDSM(&m_aAppId, &m_aSrcId, DG_IMAGE, DAT_IMAGENATIVEXFER, MSG_GET, &hDIB))
394 {
395 case TWRC_CANCEL:
396 m_nCurState = TWAINState::Xferring;
397 break;
398
399 case TWRC_XFERDONE:
400 {
401 if (hDIB)
402 {
403 m_hMap = nullptr;
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
408 {
409 if (LPVOID pBmpMem = GlobalLock(hGlob))
410 {
411 if ((nXRes > 0) && (nYRes > 0))
412 {
413 // set resolution of bitmap
414 BITMAPINFOHEADER* pBIH = static_cast<BITMAPINFOHEADER*>(pBmpMem);
415
416 const auto[m, d]
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));
420 }
421
422 HANDLE hMap = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr,
423 PAGE_READWRITE, 0, nMapSize, nullptr);
424 if (hMap)
425 {
426 LPVOID pMap = MapViewOfFile(hMap, FILE_MAP_WRITE, 0, 0, nMapSize);
427 if (pMap)
428 {
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);
433
434 DuplicateHandle(GetCurrentProcess(), hMap, m_hProc, &m_hMap, 0,
435 FALSE, DUPLICATE_SAME_ACCESS);
436 }
437
438 CloseHandle(hMap);
439 }
440
441 GlobalUnlock(hGlob);
442 }
443 }
444 }
445
446 GlobalFree(static_cast<HGLOBAL>(hDIB));
447
448 m_nCurState = TWAINState::Xferring;
449 }
450 break;
451
452 default:
453 break;
454 }
455 }
456}
457
458void ImpTwain::ImplFallback(WPARAM nEvent)
459{
460 PostMessageW(m_hTwainWnd, WM_TWAIN_FALLBACK, nEvent, 0);
461}
462
463void ImpTwain::ImplFallbackHdl(WPARAM nEvent)
464{
465 bool bFallback = true;
466
467 switch (m_nCurState)
468 {
469 case TWAINState::Xferring:
470 case TWAINState::DSreadyToXfer:
471 {
472 TW_PENDINGXFERS aXfers;
473
474 if (m_pDSM(&m_aAppId, &m_aSrcId, DG_CONTROL, DAT_PENDINGXFERS, MSG_ENDXFER, &aXfers)
475 == TWRC_SUCCESS)
476 {
477 if (aXfers.Count != 0)
478 m_pDSM(&m_aAppId, &m_aSrcId, DG_CONTROL, DAT_PENDINGXFERS, MSG_RESET, &aXfers);
479 }
480
481 m_nCurState = TWAINState::DSenabled;
482 }
483 break;
484
485 case TWAINState::DSenabled:
486 {
487 TW_USERINTERFACE aUI = { true, true, m_hTwainWnd };
488
489 m_pDSM(&m_aAppId, &m_aSrcId, DG_CONTROL, DAT_USERINTERFACE, MSG_DISABLEDS, &aUI);
490 m_nCurState = TWAINState::DSopened;
491 }
492 break;
493
494 case TWAINState::DSopened:
495 {
496 m_pDSM(&m_aAppId, nullptr, DG_CONTROL, DAT_IDENTITY, MSG_CLOSEDS, &m_aSrcId);
497 m_nCurState = TWAINState::DSMopened;
498 }
499 break;
500
501 case TWAINState::DSMopened:
502 {
503 m_pDSM(&m_aAppId, nullptr, DG_CONTROL, DAT_PARENT, MSG_CLOSEDSM, &m_hTwainWnd);
504 m_nCurState = TWAINState::DSMloaded;
505 }
506 break;
507
508 case TWAINState::DSMloaded:
509 {
510 m_pDSM = nullptr;
511 FreeLibrary(m_hMod);
512 m_hMod = nullptr;
513 m_nCurState = TWAINState::DSMunloaded;
514 }
515 break;
516
517 case TWAINState::DSMunloaded:
518 {
519 if (nEvent > TWAIN_EVENT_NONE)
520 NotifyParent(nEvent, reinterpret_cast<LPARAM>(m_hMap));
521 PostQuitMessage(0);
522
523 bFallback = false;
524 }
525 break;
526 }
527
528 if (bFallback)
529 ImplFallback(nEvent);
530}
531
532void ImpTwain::ImplRequestHdl(WPARAM nRequest)
533{
534 switch (nRequest)
535 {
537 Destroy();
538 break;
540 NotifyParent(TWAIN_EVENT_REQUESTRESULT, LPARAM(SelectSource()));
541 break;
543 NotifyParent(TWAIN_EVENT_REQUESTRESULT, LPARAM(InitXfer()));
544 break;
545 }
546}
547} // namespace
548
549int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int)
550{
551 int argc = 0;
552 LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc);
553 if (argc != 2)
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));
558 LocalFree(argv);
559 if (!hParentThread)
560 return 2; // Invalid parent thread handle argument value
561
562 int nRet = 0;
563 try
564 {
565 ImpTwain aImpTwain(hParentThread); // creates main window
566
567 MSG msg;
568 while (true)
569 {
570 DWORD nWaitResult
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
577 bool bQuit = false;
578 while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE))
579 {
580 // process it here
581 if (msg.message == WM_QUIT)
582 {
583 bQuit = true;
584 nRet = msg.wParam;
585 }
586 else
587 {
588 TranslateMessage(&msg);
589 DispatchMessageW(&msg);
590 }
591 }
592 if (bQuit)
593 break;
594 }
595 }
596 catch (const std::exception& e)
597 {
598 printf("Exception thrown: %s", e.what());
599 nRet = 7;
600 }
601 return nRet;
602}
603
604/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
double d
#define FALSE
#define SAL_N_ELEMENTS(arr)
m
constexpr Point convert(const Point &rPoint, o3tl::Length eFrom, o3tl::Length eTo)
#define CALLBACK
int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int)
#define WM_TWAIN_FALLBACK
Definition: twain32shim.cxx:36
#define WM_TWAIN_REQUEST
Definition: twain32shim.hxx:41
#define WM_TWAIN_EVENT
Definition: twain32shim.hxx:24
#define TWAIN_REQUEST_SELECTSOURCE
Definition: twain32shim.hxx:44
void ThrowLastError(const char *sFunc)
Definition: twain32shim.hxx:66
#define TWAIN_EVENT_QUIT
Definition: twain32shim.hxx:34
#define TWAIN_EVENT_REQUESTRESULT
Definition: twain32shim.hxx:30
#define TWAIN_REQUEST_INITXFER
Definition: twain32shim.hxx:45
#define TWAIN_EVENT_NOTIFYHWND
Definition: twain32shim.hxx:27
#define TWAIN_REQUEST_QUIT
Definition: twain32shim.hxx:43
#define TWAIN_EVENT_SCANNING
Definition: twain32shim.hxx:35
#define TWAIN_EVENT_XFER
Definition: twain32shim.hxx:38
#define TWAIN_EVENT_NONE
Definition: twain32shim.hxx:33