Avoid potential negative array index access to cached text.
[LibreOffice.git] / extensions / source / scanner / twain32shim.cxx
blob6e0be8149471c442e1fade7a9fb2ade0704209d0
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 .
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)
38 namespace
40 double FixToDouble(const TW_FIX32& rFix) { return rFix.Whole + rFix.Frac / 65536.; }
42 const wchar_t sTwainWndClass[] = L"TwainClass";
44 class ImpTwain
46 public:
47 ImpTwain(HANDLE hParentThread);
48 ~ImpTwain();
50 private:
51 enum class TWAINState
53 DSMunloaded = 1,
54 DSMloaded = 2,
55 DSMopened = 3,
56 DSopened = 4,
57 DSenabled = 5,
58 DSreadyToXfer = 6,
59 Xferring = 7,
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
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); }
80 bool SelectSource();
81 bool InitXfer();
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);
91 void ImplFallbackHdl(WPARAM nEvent);
92 void ImplRequestHdl(WPARAM nRequest);
95 //static
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);
103 //static
104 ImpTwain* ImpTwain::GetImpFromWnd(HWND hWnd)
106 if (!IsTwainClassWnd(hWnd))
107 return nullptr;
108 return reinterpret_cast<ImpTwain*>(GetWindowLongPtrW(hWnd, GWLP_USERDATA));
111 //static
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));
119 // static
120 LRESULT CALLBACK ImpTwain::WndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
122 ImpTwain* pImpTwain = GetImpFromWnd(hWnd);
123 switch (nMsg)
125 case WM_CREATE:
126 ImplCreateWnd(hWnd, lParam);
127 break;
128 case WM_TWAIN_FALLBACK:
129 if (pImpTwain)
130 pImpTwain->ImplFallbackHdl(wParam);
131 break;
132 case WM_TWAIN_REQUEST:
133 if (pImpTwain)
134 pImpTwain->ImplRequestHdl(wParam);
135 break;
137 return DefWindowProcW(hWnd, nMsg, wParam, lParam);
140 // static
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;
150 pMsg->lParam = 0;
152 return 0;
156 return CallNextHookEx(nullptr, nCode, wParam, lParam);
159 HANDLE GetProcOfThread(HANDLE hThread)
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;
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);
192 if (!m_hTwainWnd)
193 ThrowLastError("CreateWindowExW");
194 m_hTwainHook = SetWindowsHookExW(WH_GETMESSAGE, &MsgHook, nullptr, GetCurrentThreadId());
195 if (!m_hTwainHook)
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)
215 TW_IDENTITY aIdent;
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);
223 Destroy();
224 return (TWRC_SUCCESS == nRet);
227 bool ImpTwain::InitXfer()
229 bool bRet = false;
231 ImplOpenSourceManager();
233 if (TWAINState::DSMopened == m_nCurState)
235 ImplOpenSource();
237 if (TWAINState::DSopened == m_nCurState)
238 bRet = ImplEnableSource();
241 if (!bRet)
242 Destroy();
244 return bRet;
247 void ImpTwain::ImplOpenSourceManager()
249 if (TWAINState::DSMunloaded == m_nCurState)
251 m_hMod = LoadLibraryW(L"TWAIN_32.DLL");
252 if (!m_hMod)
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());
263 if (m_hMod)
265 m_nCurState = TWAINState::DSMloaded;
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))
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)
283 == TWRC_SUCCESS)
284 && (m_pDSM(&m_aAppId, nullptr, DG_CONTROL, DAT_IDENTITY, MSG_OPENDS, &m_aSrcId)
285 == TWRC_SUCCESS))
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));
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;
301 bool ImpTwain::ImplEnableSource()
303 bool bRet = false;
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)
313 == TWRC_SUCCESS)
315 bRet = true;
317 else
319 // dialog failed
320 m_nCurState = TWAINState::DSopened;
324 return bRet;
327 void ImpTwain::NotifyParent(WPARAM nEvent, LPARAM lParam)
329 PostThreadMessageW(m_nParentThreadId, WM_TWAIN_EVENT, nEvent, lParam);
332 bool ImpTwain::ImplHandleMsg(MSG* pMsg)
334 if (!m_pDSM)
335 return false;
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)
342 case MSG_XFERREADY:
344 WPARAM nEvent = TWAIN_EVENT_QUIT;
346 if (TWAINState::DSenabled == m_nCurState)
348 m_nCurState = TWAINState::DSreadyToXfer;
349 ImplXfer();
351 if (m_hMap)
352 nEvent = TWAIN_EVENT_XFER;
354 else if (TWAINState::Xferring == m_nCurState && m_hMap)
356 // Already sent TWAIN_EVENT_XFER; not processed yet;
357 // duplicate event
358 nEvent = TWAIN_EVENT_NONE;
361 ImplFallback(nEvent);
363 break;
365 case MSG_CLOSEDSREQ:
366 Destroy();
367 break;
369 case MSG_NULL:
370 nRet = TWRC_NOTDSEVENT;
371 break;
374 return (TWRC_DSEVENT == nRet);
377 void ImpTwain::ImplXfer()
379 if (m_nCurState == TWAINState::DSreadyToXfer)
381 TW_IMAGEINFO aInfo;
382 HANDLE hDIB = nullptr;
383 double nXRes, nYRes;
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);
390 else
391 nXRes = nYRes = -1;
393 switch (m_pDSM(&m_aAppId, &m_aSrcId, DG_IMAGE, DAT_IMAGENATIVEXFER, MSG_GET, &hDIB))
395 case TWRC_CANCEL:
396 m_nCurState = TWAINState::Xferring;
397 break;
399 case TWRC_XFERDONE:
401 if (hDIB)
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
409 if (LPVOID pBmpMem = GlobalLock(hGlob))
411 if ((nXRes > 0) && (nYRes > 0))
413 // set resolution of bitmap
414 BITMAPINFOHEADER* pBIH = static_cast<BITMAPINFOHEADER*>(pBmpMem);
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));
422 HANDLE hMap = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr,
423 PAGE_READWRITE, 0, nMapSize, nullptr);
424 if (hMap)
426 LPVOID pMap = MapViewOfFile(hMap, FILE_MAP_WRITE, 0, 0, nMapSize);
427 if (pMap)
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);
438 CloseHandle(hMap);
441 GlobalUnlock(hGlob);
446 GlobalFree(static_cast<HGLOBAL>(hDIB));
448 m_nCurState = TWAINState::Xferring;
450 break;
452 default:
453 break;
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;
467 switch (m_nCurState)
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)
475 == TWRC_SUCCESS)
477 if (aXfers.Count != 0)
478 m_pDSM(&m_aAppId, &m_aSrcId, DG_CONTROL, DAT_PENDINGXFERS, MSG_RESET, &aXfers);
481 m_nCurState = TWAINState::DSenabled;
483 break;
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;
492 break;
494 case TWAINState::DSopened:
496 m_pDSM(&m_aAppId, nullptr, DG_CONTROL, DAT_IDENTITY, MSG_CLOSEDS, &m_aSrcId);
497 m_nCurState = TWAINState::DSMopened;
499 break;
501 case TWAINState::DSMopened:
503 m_pDSM(&m_aAppId, nullptr, DG_CONTROL, DAT_PARENT, MSG_CLOSEDSM, &m_hTwainWnd);
504 m_nCurState = TWAINState::DSMloaded;
506 break;
508 case TWAINState::DSMloaded:
510 m_pDSM = nullptr;
511 FreeLibrary(m_hMod);
512 m_hMod = nullptr;
513 m_nCurState = TWAINState::DSMunloaded;
515 break;
517 case TWAINState::DSMunloaded:
519 if (nEvent > TWAIN_EVENT_NONE)
520 NotifyParent(nEvent, reinterpret_cast<LPARAM>(m_hMap));
521 PostQuitMessage(0);
523 bFallback = false;
525 break;
528 if (bFallback)
529 ImplFallback(nEvent);
532 void ImpTwain::ImplRequestHdl(WPARAM nRequest)
534 switch (nRequest)
536 case TWAIN_REQUEST_QUIT:
537 Destroy();
538 break;
539 case TWAIN_REQUEST_SELECTSOURCE:
540 NotifyParent(TWAIN_EVENT_REQUESTRESULT, LPARAM(SelectSource()));
541 break;
542 case TWAIN_REQUEST_INITXFER:
543 NotifyParent(TWAIN_EVENT_REQUESTRESULT, LPARAM(InitXfer()));
544 break;
547 } // namespace
549 int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int)
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
562 int nRet = 0;
565 ImpTwain aImpTwain(hParentThread); // creates main window
567 MSG msg;
568 while (true)
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))
580 // process it here
581 if (msg.message == WM_QUIT)
583 bQuit = true;
584 nRet = msg.wParam;
586 else
588 TranslateMessage(&msg);
589 DispatchMessageW(&msg);
592 if (bQuit)
593 break;
596 catch (const std::exception& e)
598 printf("Exception thrown: %s", e.what());
599 nRet = 7;
601 return nRet;
604 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */