This is a merge request to solve a problem that became visible by this merge request from Ivan Lyugaev:
https://gitlab.winehq.org/wine/wine/-/merge_requests/9184#note_121010
When used with a HP Officejet Pro 8600 N911a, it is no longer possible to choose a scan resolution other than 75 DPI.
The reason is that the code always set the "source" option after the "resolution" option, and setting the "source" option resets the resolution on that scanner to 75 DPI, even if it is not changed. Sane services are implemented in hplip. On Debian:
```
apt source hplip
sed -n '560,563p' hplip-3.22.10+dfsg0/scan/sane/ledm.c
i = session->adf_resolutionList[0] + 1;
while(i--) session->resolutionList[i] = session->adf_resolutionList[i];
}
ps->currentResolution = session->resolutionList[1];
```
This new commit changes two things:
1. The order in which parameters are set in sane is changed. A parameter called "source" is set earlier than all other parameters.
2. If a parameter derived from a combobox of named options is not changed, it is not set in sane to the same value again.
This seems to solve the problem on the HP Officejet Pro 8600 N911a, but other sane backends might have more dependencies that might still need to be addressed.
--
v4: dlls/sane.ds: Fix setting resolution in user interface
https://gitlab.winehq.org/wine/wine/-/merge_requests/9399
This merge request adds support to scan a batch of images from a scanner with Automatic Document Feeder (ADF) to the sane.ds. It also fixes problems with the buffered memory transfer mode TWSX_MEMORY, that are caused by related reasons.
Before, activating the ADF only scanned the first image in the batch and then dischared the remaining images without scanning.
# Why ADF scans failed
Prior to this merge request, the sane.ds was based on the principle that for every successful call to sane_start there is exactly one call to sane_cancel. This was already established on the lowest level in unixlib.c:
```
static NTSTATUS start_device( void *args )
{
SANE_Status status;
- if (device_started) return STATUS_SUCCESS;
status = sane_start( device_handle );
[...]
static NTSTATUS cancel_device( void *args )
{
if (device_started) sane_cancel( device_handle );
device_started = FALSE;
return STATUS_SUCCESS;
```
However the sane API is designed so that for every image in a batch, sane_start must be called without a prior call to sane_cancel, as sane_cancel ends the whole batch. So for an ADF-Scan with 5 pages, sane_start is called 5 times and sane_cancel is to be called once.
https://sane-project.gitlab.io/standard/api.html#code-flow
So the call to
```
SANE_CALL( cancel_device, NULL );
```
in SANE_ImageNativeXferGet prevented the scan of the 2nd frame. But as long as the "decice_started" flag in unixlib.c was not cleared, it was not possible to scan the next frame, so just removing that call is not sufficient.
# New Approach
An application can scan a whole image with DG_IMAGE/DAT_IMAGEMEMXFER/MSG_GET but then the application will not know the scan resolution. So it is usual to call DG_IMAGE/DAT_IMAGEINFO/MSG_GET to retrieve the scan resolution before the DAT_IMAGEMEMXFER. However the sane specification says that the obtained scan parameters "are guaranteed to be accurate between the time a scan has been started (`sane_start()` has been called) and the completion of that request. Outside of that window, the returned values are best-effort estimates of what the parameters will be when `sane_start()` gets invoked." So it is neccessary to call sane_start to make sure that the scan parameters are valid.
https://sane-project.gitlab.io/standard/api.html#sane-get-parameters
The TWAIN Spec says about DG_IMAGE / DAT_IMAGEINFO / MSG_GET:
"Data Source writers are strongly encouraged to report back finished image values in State 6"
The values that sane reports before calling sane_start can be completely wrong. One driver gave a 0-width estimate.
So this code calls sane_start when the application does call one of the functions that require addition informations and then set the currentState from 6 to 7. With a state of 7, sane_start is not called again.
# Terminating the Batch
Batch scans are done when either:
* The application negotiated a value \>0 for CAP_XFERCOUNT or
* The sane-parameter "source" is set to ADF.
The applcation calls DG_CONTROL/DAT_PENDINGXFERS/MSG_ENDXFER and the field pPendingXfers-\>Count determines if the application expects more frames.
Without a batch scan, SANE_Cancel is called to make sure the process ends (else some backends could restart scanning the flatbed on and on).
In batch scan the application counts the number of pages and stops when it is reached.
Else it calls SANE_Start() without SANE_Cancel() to initiate scanning the next page. If this fails, DAT_PENDINGXFERS/MSG_ENDXFER returns pendingcount 0 to inform the application that there are no further pages.
# Why buffered memory transfers failed oftenly
1. The scan parameters from SANE retrieved by the application in DG_IMAGE / DAT_IMAGEINFO / MSG_GET were wrong. For example the image width could be 0.
2. The YOffset parameter in cally to DG_IMAGE/DAT_IMAGEMEMXFER/MSG_GET was always set to 0. It is unclear if this violates the TWAIN specification, as this value is only required in tiled transfer mode. But it more robust to fill in that value, as does the gphoto2.ds and many other TWAIN drivers.
# Tests performed
As this change heavily changes the way the sane API is applied, the exact consequences are not easily predictable. So I tested both with different application programs and with different sane backends.
## Testapplication
There are rather few application programs out there with the capability to do an ADF batch scan and buffered memory transfers. I'm attaching a minimalistic program with 600 lines of C-Code that can do batch scans and buffered memory transfers and save the results into .bmp files. I tested the 32-Bit testprogram to work correctly in Windows 11 with the official driver for the HP Officejet Pro 8600 N911a provided by HP.
## Tested applications
* IrfanView 4.72 both 32-Bit and 64-Bit. 64-Bit needs twaindsm.dll from TWAIN .org to use 64-Bit sane.ds. Irfanview is one of the few easily available applications that can do ADF batch scans.
* LibreOffice_6.2.8.2 64-Bit with twaindsm.dll from TWAIN .org to use 64-Bit sane.ds. Newer versions of Libreoffice 64-Bit are using a 32-Bit EXE to address 32-Bit TWAIN. 6.2.8 is the last version with native 64-Bit TWAIN based on twaindsm.dll.
* LibreOffice_25.8.2_Win_x86.msi 32-Bit.
* OpenOffice.org 3.3.0 32-Bit (May 2011).
* Microsoft Word 97 32-Bit (Starts Microsoft Photo Editor for scanning).
## Tested Backends
* HP Officejet Pro 8600 N911a using the hpaio backend.
* Brother MFC-L9570CDW using the escl backend.
* Brother MFC-L9570CDW using the brscan4 backend (Prprietary driver from brother).
## State of Tests
Some application tests were not repeated with the very last changes in the code. I'd like to see if more changes are neccessary before a wine release before I repeat tests.
[twain_memxfer.zip](/uploads/b0ce7dd6738e0921f1714c09207e3c73/twain_memxfer.zip)
```
Length Date Time Name
--------- ---------- ----- ----
17942 2025-11-05 11:10 memxfer.c
77104 2025-10-16 14:02 twain.h
302 2025-11-05 08:00 Makefile
--------- -------
95348 3 files
```
# Note on twaindsm.dll
The twain data source manager is called twain_32.dll for 32-Bit Windows. However on windows there is not such module for 64-Bit environments. Instead the twain consortium offers twaindsm.dll (Both 32-Bit and 64-Bit).
https://github.com/twain/twain-dsm
It contains an extension in the interface allowing a DS to work with a DSM of any name by transfering the callback entry point. Some documentation on the reasons can be found here: https://github.com/twain/twain-dsm/blob/master/TWAIN_DSM/src/readme.doc even if the filenames documented in later documentations are different.
When renaming the 64-Bit twain_32.dll from wine to twaindsm.dll, a problem occurs due to sane_main.c explicitly searching for twain_32:
```
static TW_UINT16 SANE_OpenDS( pTW_IDENTITY pOrigin, pTW_IDENTITY self)
{
if (SANE_dsmentry == NULL)
{
HMODULE moddsm = GetModuleHandleW(L"twain_32");
```
And the current code (from me) that sends the entry points is called too late (After SANE_OpenDS) so runs into an error. I've got a patch for this. But essentially for 64-Bit TWAIN, the twain_32.dll should be renamed to twaindsm.dll as expected by Windows applications for 64-Bit TWAIN to work out of the box. I have no idea how to patch wine to create twain_32.dll for 32-Bit windows and twaindsm.dll for 64-Bit Windows.
#
--
https://gitlab.winehq.org/wine/wine/-/merge_requests/9397
As prescribed some time ago, this attempts to implement correct reparse
behaviour simply without attempting at all to make reparse points resolvable
Unix symlinks.
A reparse point consists two files. For a file whose NT name is "foobar", the
contents of the file or directory (and note that a reparse point *can* have
arbitrary contents) is stored in a file or directory "foobar?". The contents of
the reparse point are stored in a file "foobar*". Both * and ? are valid in
POSIX filenames but not NT, so no collision should be possible.
The actual file "foobar" does not exist in this case. This is to allow the case
where there are no reparse points (which is most common) to work as it currently
does, without any extra overhead to check for a reparse point. [Simply guarding
this behind FILE_OPEN_REPARSE_POINT does not seem good enough to me; that flag
is used for example by many builtin functions like MoveFile().]
When failing to locate a path segment "foobar", we check for "foobar*" and
attempt to parse and resolve that reparse point.
Issues:
(1) Since we are manipulating two files, are there any race conditions?
I believe there are no meaningful races.
Read/write races:
- When setting a reparse point, we move the contents file out of the way last,
so attempts to open the file before that will see a normal file. Attempts to
read the reparse point might see a partial file, but we treat that as
failure.
- When removing a reparse point from a file, we move the contents file back
first, so attempts to open or read will see a normal file.
- When deleting a reparse point entirely, we delete the contents first, so
attempts to open the file will see neither a normal file nor the contents of
the symlink, and fail. Attempts to query attributes don't need to open the
contents, only the data, which will either be present or not.
Write/write races:
- Races involving file deletion don't matter, because deletion doesn't take
effect until all handles to an inode are closed.
- If we try to simultaneously set the reparse point from two different
threads, one will fail with no effect since we create the reparse data file
with O_EXCL.
- If we try to remove the reparse point while setting it, the contents won't
be in the right place and we'll fail.
- If we try to remove the reparse point from two different threads, one will
fail to move the contents back or delete them.
(2) How does this interact with hard links?
Creating a hard link to an existing reparse point is not too hard to do; we
can just hard link both the data file and the contents file.
However, turning an existing hardlinked file into a reparse point cannot work.
This is also a limitation of other approaches that have been proposed.
For this reason I've elected to forbid hard link interaction for now.
One alternative approach that would allow turning hardlinked files into
reparse points would be to store the reparse data in xattr. This would be less
portable though.
--
v6: ntdll: Resolve IO_REPARSE_TAG_MOUNT_POINT during path lookup.
ntdll: Pass attr and nt_name down to lookup_unix_name().
server: Implement FSCTL_GET_REPARSE_POINT.
https://gitlab.winehq.org/wine/wine/-/merge_requests/9293
The `IPrintable` interface seems to just be an alias around `IStringable` with a different IID.
--
v3: vccorlib140: Implement IPrintable for Platform::Type.
vccorlib140: Implement IPrintable for Platform::Exception.
vccorlib140: Implement __abi_ObjectToString.
vccorlib140: Implement ToString() for well-known types.
vccorlib140: Add ToString() stubs for well-known types.
https://gitlab.winehq.org/wine/wine/-/merge_requests/9403
The `IPrintable` interface seems to just be an alias around `IStringable` with a different IID.
--
v2: vccorlib140: Implement IPrintable for Platform::Type.
vccorlib140: Implement IPrintable for Platform::Exception.
vccorlib140: Implement __abi_ObjectToString.
vccorlib140: Implement ToString() for well-known types.
vccorlib140: Add ToString() stubs for well-known types.
vccorlib140: Add stub for __abi_ObjectToString.
https://gitlab.winehq.org/wine/wine/-/merge_requests/9403