1
0
Fork 0
libreoffice/extensions/source/scanner/twain32shim.cxx
Daniel Baumann 8e63e14cf6
Adding upstream version 4:25.2.3.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 16:20:04 +02:00

605 lines
18 KiB
C++

/* -*- 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 <systools/win32/comtools.hxx>
#include <tools/helpers.hxx>
#include <twain/twain.h>
#include <o3tl/unit_conversion.hxx>
#define WM_TWAIN_FALLBACK (WM_SHIM_INTERNAL + 0)
namespace
{
double FixToDouble(const TW_FIX32& rFix) { return rFix.Whole + rFix.Frac / 65536.; }
const wchar_t sTwainWndClass[] = L"TwainClass";
class ImpTwain
{
public:
ImpTwain(HANDLE hParentThread);
~ImpTwain();
private:
enum class TWAINState
{
DSMunloaded = 1,
DSMloaded = 2,
DSMopened = 3,
DSopened = 4,
DSenabled = 5,
DSreadyToXfer = 6,
Xferring = 7,
};
TW_IDENTITY m_aAppId;
TW_IDENTITY m_aSrcId;
DWORD m_nParentThreadId;
HANDLE m_hProc;
DSMENTRYPROC m_pDSM = nullptr;
HMODULE m_hMod = nullptr;
TWAINState m_nCurState = TWAINState::DSMunloaded;
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() { ImplFallback(TWAIN_EVENT_QUIT); }
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(nullptr, 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_aAppId{ /* Id */ 0,
{ /* Version.MajorNum */ 1,
/* Version.MinorNum */ 0,
/* Version.Language */ TWLG_USA,
/* Version.Country */ TWCY_USA,
/* Version.Info */ "8.0" },
/* ProtocolMajor */ TWON_PROTOCOLMAJOR,
/* ProtocolMinor */ TWON_PROTOCOLMINOR,
/* SupportedGroups */ DG_IMAGE | DG_CONTROL,
/* Manufacturer */ "Sun Microsystems",
/* ProductFamily */ "Office",
/* ProductName */ "Office" }
, m_nParentThreadId(GetThreadId(hParentThread))
, m_hProc(GetProcOfThread(hParentThread))
{
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);
}
bool ImpTwain::SelectSource()
{
TW_UINT16 nRet = TWRC_FAILURE;
ImplOpenSourceManager();
if (TWAINState::DSMopened == 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);
}
Destroy();
return (TWRC_SUCCESS == nRet);
}
bool ImpTwain::InitXfer()
{
bool bRet = false;
ImplOpenSourceManager();
if (TWAINState::DSMopened == m_nCurState)
{
ImplOpenSource();
if (TWAINState::DSopened == m_nCurState)
bRet = ImplEnableSource();
}
if (!bRet)
Destroy();
return bRet;
}
void ImpTwain::ImplOpenSourceManager()
{
if (TWAINState::DSMunloaded == m_nCurState)
{
m_hMod = LoadLibraryW(L"TWAIN_32.DLL");
if (!m_hMod)
{
// Windows directory might not be in DLL search path sometimes, so try the full path
sal::systools::CoTaskMemAllocated<wchar_t> sPath;
if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_Windows, 0, nullptr, &sPath)))
{
std::wstring sPathAndFile(sPath);
sPathAndFile += L"\\TWAIN_32.DLL";
m_hMod = LoadLibraryW(sPathAndFile.c_str());
}
}
if (m_hMod)
{
m_nCurState = TWAINState::DSMloaded;
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 = TWAINState::DSMopened;
}
}
}
}
void ImpTwain::ImplOpenSource()
{
if (TWAINState::DSMopened == 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));
assert(pVal);
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 = TWAINState::DSopened;
}
}
}
bool ImpTwain::ImplEnableSource()
{
bool bRet = false;
if (TWAINState::DSopened == m_nCurState)
{
TW_USERINTERFACE aUI = { true, true, m_hTwainWnd };
NotifyParent(TWAIN_EVENT_SCANNING, 0);
m_nCurState = TWAINState::DSenabled;
if (m_pDSM(&m_aAppId, &m_aSrcId, DG_CONTROL, DAT_USERINTERFACE, MSG_ENABLEDS, &aUI)
== TWRC_SUCCESS)
{
bRet = true;
}
else
{
// dialog failed
m_nCurState = TWAINState::DSopened;
}
}
return bRet;
}
void ImpTwain::NotifyParent(WPARAM nEvent, LPARAM lParam)
{
PostThreadMessageW(m_nParentThreadId, WM_TWAIN_EVENT, nEvent, lParam);
}
bool ImpTwain::ImplHandleMsg(MSG* pMsg)
{
if (!m_pDSM)
return false;
TW_EVENT aEvt = { pMsg, MSG_NULL };
TW_UINT16 nRet = m_pDSM(&m_aAppId, &m_aSrcId, DG_CONTROL, DAT_EVENT, MSG_PROCESSEVENT, &aEvt);
switch (aEvt.TWMessage)
{
case MSG_XFERREADY:
{
WPARAM nEvent = TWAIN_EVENT_QUIT;
if (TWAINState::DSenabled == m_nCurState)
{
m_nCurState = TWAINState::DSreadyToXfer;
ImplXfer();
if (m_hMap)
nEvent = TWAIN_EVENT_XFER;
}
else if (TWAINState::Xferring == m_nCurState && m_hMap)
{
// Already sent TWAIN_EVENT_XFER; not processed yet;
// duplicate event
nEvent = TWAIN_EVENT_NONE;
}
ImplFallback(nEvent);
}
break;
case MSG_CLOSEDSREQ:
Destroy();
break;
case MSG_NULL:
nRet = TWRC_NOTDSEVENT;
break;
}
return (TWRC_DSEVENT == nRet);
}
void ImpTwain::ImplXfer()
{
if (m_nCurState == TWAINState::DSreadyToXfer)
{
TW_IMAGEINFO aInfo;
HANDLE hDIB = nullptr;
double nXRes, nYRes;
if (m_pDSM(&m_aAppId, &m_aSrcId, DG_IMAGE, DAT_IMAGEINFO, MSG_GET, &aInfo) == TWRC_SUCCESS)
{
nXRes = FixToDouble(aInfo.XResolution);
nYRes = FixToDouble(aInfo.YResolution);
}
else
nXRes = nYRes = -1;
switch (m_pDSM(&m_aAppId, &m_aSrcId, DG_IMAGE, DAT_IMAGENATIVEXFER, MSG_GET, &hDIB))
{
case TWRC_CANCEL:
m_nCurState = TWAINState::Xferring;
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 > 0) && (nYRes > 0))
{
// set resolution of bitmap
BITMAPINFOHEADER* pBIH = static_cast<BITMAPINFOHEADER*>(pBmpMem);
const auto[m, d]
= getConversionMulDiv(o3tl::Length::in, o3tl::Length::m);
pBIH->biXPelsPerMeter = std::round(o3tl::convert(nXRes, d, m));
pBIH->biYPelsPerMeter = std::round(o3tl::convert(nYRes, d, m));
}
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 = TWAINState::Xferring;
}
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 TWAINState::Xferring:
case TWAINState::DSreadyToXfer:
{
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 = TWAINState::DSenabled;
}
break;
case TWAINState::DSenabled:
{
TW_USERINTERFACE aUI = { true, true, m_hTwainWnd };
m_pDSM(&m_aAppId, &m_aSrcId, DG_CONTROL, DAT_USERINTERFACE, MSG_DISABLEDS, &aUI);
m_nCurState = TWAINState::DSopened;
}
break;
case TWAINState::DSopened:
{
m_pDSM(&m_aAppId, nullptr, DG_CONTROL, DAT_IDENTITY, MSG_CLOSEDS, &m_aSrcId);
m_nCurState = TWAINState::DSMopened;
}
break;
case TWAINState::DSMopened:
{
m_pDSM(&m_aAppId, nullptr, DG_CONTROL, DAT_PARENT, MSG_CLOSEDSM, &m_hTwainWnd);
m_nCurState = TWAINState::DSMloaded;
}
break;
case TWAINState::DSMloaded:
{
m_pDSM = nullptr;
FreeLibrary(m_hMod);
m_hMod = nullptr;
m_nCurState = TWAINState::DSMunloaded;
}
break;
case TWAINState::DSMunloaded:
{
if (nEvent > TWAIN_EVENT_NONE)
NotifyParent(nEvent, reinterpret_cast<LPARAM>(m_hMap));
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, LPARAM(SelectSource()));
break;
case TWAIN_REQUEST_INITXFER:
NotifyParent(TWAIN_EVENT_REQUESTRESULT, LPARAM(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_ALLINPUT);
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: */