The latest scanner settings are now automatically saved in the HKEY_CURRENT_USER\Software\ScannersSettings\Vendor\Product path. This modification was tested and worked correctly
This is the second MR on this topic, and the first MR has been approved for a month, but it has not been added without merge :disappointed:
Link: https://gitlab.winehq.org/wine/wine/-/merge_requests/8858
From: Ivan Lyugaev valy@etersoft.ru
The latest scanner settings are now automatically saved in the HKEY_CURRENT_USER\Software\ScannersSettings\Vendor\Product path. This modification was tested and worked correctly --- dlls/sane.ds/Makefile.in | 5 +- dlls/sane.ds/cfg.c | 125 ++++++++++++++++++++++ dlls/sane.ds/cfg.h | 46 ++++++++ dlls/sane.ds/sane_main.c | 3 + dlls/sane.ds/ui.c | 219 ++++++++++++++++++++++++++++++++++++--- dlls/sane.ds/unixlib.c | 3 + dlls/sane.ds/unixlib.h | 1 + 7 files changed, 385 insertions(+), 17 deletions(-) create mode 100644 dlls/sane.ds/cfg.c create mode 100644 dlls/sane.ds/cfg.h
diff --git a/dlls/sane.ds/Makefile.in b/dlls/sane.ds/Makefile.in index eed1acdf918..28549e75740 100644 --- a/dlls/sane.ds/Makefile.in +++ b/dlls/sane.ds/Makefile.in @@ -1,6 +1,6 @@ MODULE = sane.ds UNIXLIB = sane.so -IMPORTS = comctl32 user32 gdi32 +IMPORTS = comctl32 user32 gdi32 kernelbase UNIX_LIBS = $(SANE_LIBS) UNIX_CFLAGS = $(SANE_CFLAGS)
@@ -12,4 +12,5 @@ SOURCES = \ sane.rc \ sane_main.c \ ui.c \ - unixlib.c + unixlib.c \ + cfg.c diff --git a/dlls/sane.ds/cfg.c b/dlls/sane.ds/cfg.c new file mode 100644 index 00000000000..e036e56ab7a --- /dev/null +++ b/dlls/sane.ds/cfg.c @@ -0,0 +1,125 @@ +/* +* TWAIN32 Configuration Manager +* +* Copyright 2025 Ivan Lyugaev +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; either +* version 2.1 of the License, or (at your option) any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +*/ + +#include <stdio.h> +#include <stdlib.h> +#include "wine/debug.h" + +#include "cfg.h" + +WINE_DEFAULT_DEBUG_CHANNEL( twain ); + +LSTATUS get_info_key( WCHAR* path, HKEY* h_key, DWORD* dispos ) +{ + WCHAR reg_path[MAX_PATH]; + swprintf( reg_path, MAX_PATH, L"Software\ScannersSettings\%s", path ); + + return RegCreateKeyExW( + HKEY_CURRENT_USER, + reg_path, + 0, + NULL, + REG_OPTION_NON_VOLATILE, + KEY_ALL_ACCESS, + NULL, + h_key, + dispos + ); +} + +BOOL save_to_reg( WCHAR* path, DWORD reg_type, CHAR* name, const BYTE* value, DWORD size ) +{ + HKEY h_key; + DWORD dispos; + LSTATUS res; + + res = get_info_key(path, &h_key, &dispos); + + if( res != ERROR_SUCCESS ) + { + ERR( "RegCreateKeyExW: %ld\n", res ); + return FALSE; + } + + res = RegSetValueExA( + h_key, + name, + 0, + reg_type, + value, + size + ); + + RegCloseKey( h_key ); + + if ( res != ERROR_SUCCESS ) { + ERR( "RegSetValueExA error: %ld\n", res ); + return FALSE; + } + + return TRUE; +} + +BOOL load_from_reg( WCHAR* path, int opt_type, CHAR* name, void* value ) +{ + HKEY h_key; + DWORD dispos, flag, size; + LSTATUS res; + + res = get_info_key(path, &h_key, &dispos); + + if( res != ERROR_SUCCESS ) + { + ERR( "RegCreateKeyExW: %ld\n", res ); + return FALSE; + } + + switch( opt_type ) + { + case TYPE_INT: + case TYPE_FIXED: + case TYPE_BOOL: + flag = RRF_RT_REG_DWORD; + size = sizeof(DWORD); + break; + case TYPE_STRING: + flag = RRF_RT_REG_SZ; + size = OPTION_VALUE_MAX; + break; + default: + RegCloseKey( h_key ); + ERR( "Unknown type: %d\n", opt_type ); + return FALSE; + } + + res = RegGetValueA( + h_key, + NULL, + name, + flag, + NULL, + value, + &size + ); + + RegCloseKey( h_key ); + + return res == ERROR_SUCCESS; +} diff --git a/dlls/sane.ds/cfg.h b/dlls/sane.ds/cfg.h new file mode 100644 index 00000000000..64814f31069 --- /dev/null +++ b/dlls/sane.ds/cfg.h @@ -0,0 +1,46 @@ +/* +* TWAIN32 Configuration Manager +* +* Copyright 2025 Ivan Lyugaev +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; either +* version 2.1 of the License, or (at your option) any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +*/ + +#include <stdlib.h> +#include <stdarg.h> +#include <stdio.h> + +#include "windef.h" +#include "winbase.h" +#include "winreg.h" + +#include "sane_i.h" + +#define OPTION_VALUE_MAX 255 +#define OPTION_NAME_MAX 64 + +typedef struct +{ + INT opt_type; + DWORD reg_type; + CHAR name[OPTION_NAME_MAX]; + INT optno; + BYTE value[OPTION_VALUE_MAX]; + DWORD size; + BOOL is_enabled; +} ScannerOption; + +BOOL save_to_reg( WCHAR* path, DWORD reg_type, CHAR* name, const BYTE* value, DWORD size ); +BOOL load_from_reg( WCHAR* path, int opt_type, CHAR* name, void* value ); diff --git a/dlls/sane.ds/sane_main.c b/dlls/sane.ds/sane_main.c index 8f07ce6d52a..360509e2bf6 100644 --- a/dlls/sane.ds/sane_main.c +++ b/dlls/sane.ds/sane_main.c @@ -74,6 +74,9 @@ static TW_UINT16 SANE_OpenDS( pTW_IDENTITY pOrigin, pTW_IDENTITY self) activeDS.twCC = SANE_SaneSetDefaults(); if (activeDS.twCC == TWCC_SUCCESS) { + strcpy(activeDS.identity.Manufacturer, self->Manufacturer); + strcpy(activeDS.identity.ProductFamily, self->ProductFamily); + strcpy(activeDS.identity.ProductName, self->ProductName); activeDS.currentState = 4; activeDS.identity.Id = self->Id; activeDS.appIdentity = *pOrigin; diff --git a/dlls/sane.ds/ui.c b/dlls/sane.ds/ui.c index 25a1cf33970..2386b23a311 100644 --- a/dlls/sane.ds/ui.c +++ b/dlls/sane.ds/ui.c @@ -30,11 +30,16 @@ #include "wine/debug.h" #include "resource.h"
+#include "cfg.h" + WINE_DEFAULT_DEBUG_CHANNEL(twain);
-#define ID_BASE 0x100 -#define ID_EDIT_BASE 0x1000 -#define ID_STATIC_BASE 0x2000 +#define ID_BASE 0x100 +#define ID_EDIT_BASE 0x1000 +#define ID_STATIC_BASE 0x2000 + +WCHAR path[MAX_PATH]; +int gOptCount;
static INT_PTR CALLBACK DialogProc (HWND , UINT , WPARAM , LPARAM ); static INT CALLBACK PropSheetProc(HWND, UINT,LPARAM); @@ -485,6 +490,8 @@ BOOL DoScannerUI(void)
hdc = CreateCompatibleDC(0);
+ gOptCount = optcount; + while (index < optcount) { struct option_descriptor opt; @@ -508,13 +515,19 @@ BOOL DoScannerUI(void) psp[page_count].lParam = (LPARAM)&activeDS; page_count ++; } - + index ++; } - + len = lstrlenA(activeDS.identity.Manufacturer) + lstrlenA(activeDS.identity.ProductName) + 2; szCaption = malloc(len *sizeof(WCHAR)); + + swprintf(path, MAX_PATH, L"%S\%S_%S.sc", + activeDS.identity.Manufacturer, + activeDS.identity.ProductFamily, + activeDS.identity.ProductName); + MultiByteToWideChar(CP_ACP,0,activeDS.identity.Manufacturer,-1, szCaption,len); szCaption[lstrlenA(activeDS.identity.Manufacturer)] = ' '; @@ -548,6 +561,97 @@ BOOL DoScannerUI(void) return FALSE; }
+static BOOL get_option(struct option_descriptor* opt, ScannerOption* option) +{ + lstrcpynA(option->name, opt->name, OPTION_NAME_MAX); + option->is_enabled = opt->is_active; + option->optno = opt->optno; + + if (opt->type ==TYPE_STRING && opt->constraint_type != CONSTRAINT_NONE) + { + CHAR buffer[255]; + option->reg_type = REG_SZ; + option->opt_type = opt->type; + sane_option_get_value(opt->optno, buffer); + lstrcpynA((CHAR*)option->value, buffer, OPTION_VALUE_MAX); + option->size = (DWORD)(strlen(buffer) + 1); + } + else if (opt->type == TYPE_BOOL) + { + BOOL b; + option->opt_type = opt->type; + option->reg_type = REG_DWORD; + sane_option_get_value(opt->optno, &b); + memcpy(option->value, &b, sizeof(BOOL)); + option->size = sizeof(b); + } + else if (opt->type == TYPE_INT && opt->constraint_type == CONSTRAINT_WORD_LIST) + { + int val; + option->opt_type = opt->type; + option->reg_type = REG_DWORD; + sane_option_get_value(opt->optno, &val); + memcpy(option->value, &val, sizeof(INT)); + option->size = sizeof(val); + } + else if (opt->constraint_type == CONSTRAINT_RANGE) + { + if (opt->type == TYPE_INT) + { + int si; + option->opt_type = opt->type; + option->reg_type = REG_DWORD; + sane_option_get_value(opt->optno, &si); + if (opt->constraint.range.quant) + { + si = si / opt->constraint.range.quant; + } + memcpy(option->value, &si, sizeof(INT)); + option->size = sizeof(si); + } + else if (opt->type == TYPE_FIXED) + { + int pos, *sf; + option->opt_type = opt->type; + option->reg_type = REG_DWORD; + sf = calloc( opt->size, sizeof(int) ); + sane_option_get_value(opt->optno, sf ); + if (opt->constraint.range.quant) + pos = *sf / opt->constraint.range.quant; + else + pos = MulDiv( *sf, 100, 65536 ); + memcpy(option->value, &pos, sizeof(INT)); + option->size = sizeof(pos); + free(sf); + } + else + { + FIXME("Unhandled option type %d with constraint\n", opt->type); + return FALSE; + } + } + else + { + FIXME("Unhandled option type %d\n", opt->type); + return FALSE; + } + + return TRUE; +} + +static BOOL save_option(int optno) +{ + ScannerOption option; + struct option_descriptor opt; + + opt.optno = optno; + SANE_CALL(option_get_descriptor, &opt); + + if (!get_option(&opt, &option)) return FALSE; + + return save_to_reg(path, option.reg_type, option.name, option.value, option.size); +} + static void UpdateRelevantEdit(HWND hwnd, const struct option_descriptor *opt, int position) { WCHAR buffer[244]; @@ -607,6 +711,7 @@ static BOOL UpdateSaneScrollOption(const struct option_descriptor *opt, DWORD po si = position;
sane_option_set_value( opt->optno, &si, &result ); + save_option(opt->optno); break; } case TYPE_FIXED: @@ -616,6 +721,7 @@ static BOOL UpdateSaneScrollOption(const struct option_descriptor *opt, DWORD po si = MulDiv( position, 65536, 100 );
sane_option_set_value( opt->optno, &si, &result ); + save_option(opt->optno); break; default: break; @@ -635,19 +741,22 @@ static INT_PTR InitializeDialog(HWND hwnd) if (rc != TWCC_SUCCESS) { ERR("Unable to read number of options\n"); - return FALSE; + optcount = gOptCount; } + else + gOptCount = optcount;
for ( i = 1; i < optcount; i++) { + CHAR title[256]; struct option_descriptor opt; - control = GetDlgItem(hwnd,i+ID_BASE);
if (!control) continue;
opt.optno = i; + SANE_CALL( option_get_descriptor, &opt );
TRACE("%i %s %i %i\n",i,debugstr_w(opt.title),opt.type,opt.constraint_type); @@ -655,34 +764,77 @@ static INT_PTR InitializeDialog(HWND hwnd)
SendMessageA(control,CB_RESETCONTENT,0,0); /* initialize values */ + + lstrcpynA(title, opt.name, ARRAY_SIZE(title)); + if (opt.type == TYPE_STRING && opt.constraint_type != CONSTRAINT_NONE) { CHAR buffer[255]; WCHAR *p;
+ BOOL is_exist = load_from_reg(path, opt.type, title, buffer); + BOOL is_correct = FALSE; + for (p = opt.constraint.strings; *p; p += lstrlenW(p) + 1) + { + CHAR param[256]; SendMessageW( control,CB_ADDSTRING,0, (LPARAM)p ); + WideCharToMultiByte(CP_UTF8, 0, p, -1, param, sizeof(param), NULL, NULL); + if (is_exist && !strcmp(param, buffer)) + { + is_correct = TRUE; + } + } + + if (is_exist && is_correct) + { + sane_option_set_value(opt.optno, buffer, NULL); + } + + if (is_exist && !is_correct) + { + ERR("%s=%s is incorrect. The default value is set!", title, buffer); + } + sane_option_get_value( i, buffer ); SendMessageA(control,CB_SELECTSTRING,0,(LPARAM)buffer); } else if (opt.type == TYPE_BOOL) { BOOL b; - sane_option_get_value( i, &b ); - if (b) - SendMessageA(control,BM_SETCHECK,BST_CHECKED,0); + BOOL is_exist = load_from_reg(path, opt.type, title, &b);
+ if (is_exist) + { + sane_option_set_value( i, &b, NULL ); + } + + sane_option_get_value( i, &b ); + SendMessageA(control,BM_SETCHECK, b ? BST_CHECKED : BST_UNCHECKED,0); } else if (opt.type == TYPE_INT && opt.constraint_type == CONSTRAINT_WORD_LIST) { int j, count = opt.constraint.word_list[0]; CHAR buffer[16]; int val; + BOOL is_exist = load_from_reg(path, opt.type, title, &val); + BOOL is_correct = FALSE; + for (j=1; j<=count; j++) { + if (opt.constraint.word_list[j] == val) + { + is_correct = TRUE; + } sprintf(buffer, "%d", opt.constraint.word_list[j]); SendMessageA(control, CB_ADDSTRING, 0, (LPARAM)buffer); } + if (is_exist && is_correct) + sane_option_set_value( i, &val, NULL ); + + if (is_exist && !is_correct) + ERR("%s=%d is incorrect. The default value is set!\n", title, val); + sane_option_get_value( i, &val ); sprintf(buffer, "%d", val); SendMessageA(control,CB_SELECTSTRING,0,(LPARAM)buffer); @@ -693,6 +845,8 @@ static INT_PTR InitializeDialog(HWND hwnd) { int si; int min,max; + BOOL is_exist = load_from_reg(path, opt.type, title, &si); + BOOL is_correct = FALSE;
min = opt.constraint.range.min / (opt.constraint.range.quant ? opt.constraint.range.quant : 1); @@ -702,7 +856,16 @@ static INT_PTR InitializeDialog(HWND hwnd)
SendMessageA(control,SBM_SETRANGE,min,max);
+ if (is_exist && si >= min && si <= max) + is_correct = TRUE; + else if (is_exist) + ERR("%s=%d is out of range [%d..%d]. The default value is used!\n", title, si, min, max); + + if (is_correct && is_exist) + sane_option_set_value( i, &si, NULL); + sane_option_get_value( i, &si ); + if (opt.constraint.range.quant) si = si / opt.constraint.range.quant;
@@ -711,8 +874,8 @@ static INT_PTR InitializeDialog(HWND hwnd) } else if (opt.type == TYPE_FIXED) { - int pos, min, max, *sf; - + int pos, min, max, *sf, val; + BOOL is_exist, is_correct = FALSE; if (opt.constraint.range.quant) { min = opt.constraint.range.min / opt.constraint.range.quant; @@ -726,6 +889,23 @@ static INT_PTR InitializeDialog(HWND hwnd)
SendMessageA(control,SBM_SETRANGE,min,max);
+ is_exist = load_from_reg(path, opt.type, title, &val); + + if (is_exist && val >= min && val <= max) + is_correct = TRUE; + else if (is_exist) + ERR("%s = %d is out of range [%d..%d]. The default value is used!\n", title, val, min, max); + + if (is_exist && is_correct) + { + int valSet; + if (opt.constraint.range.quant) + valSet = val * opt.constraint.range.quant; + else + valSet = MulDiv(val, 65536, 100); + sane_option_set_value(i, &valSet, NULL); + } +
sf = calloc( opt.size, sizeof(int) ); sane_option_get_value( i, sf ); @@ -817,7 +997,12 @@ static void ButtonClicked(HWND hwnd, INT id, HWND control) { BOOL r = SendMessageW(control,BM_GETCHECK,0,0)==BST_CHECKED; sane_option_set_value( opt.optno, &r, &changed ); - if (changed) InitializeDialog(hwnd); + + if (changed) + { + save_option(opt.optno); + InitializeDialog(hwnd); + } } }
@@ -851,11 +1036,15 @@ static void ComboChanged(HWND hwnd, INT id, HWND control) int val = atoi( value ); sane_option_set_value( opt.optno, &val, &changed ); } - if (changed) InitializeDialog(hwnd); + + if (changed) + { + save_option(opt.optno); + InitializeDialog(hwnd); + } free( value ); }
- static INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) diff --git a/dlls/sane.ds/unixlib.c b/dlls/sane.ds/unixlib.c index 69f085450a8..0ec443f786b 100644 --- a/dlls/sane.ds/unixlib.c +++ b/dlls/sane.ds/unixlib.c @@ -160,9 +160,12 @@ static void map_descr( struct option_descriptor *descr, const SANE_Option_Descri descr->size = opt->size; descr->is_active = SANE_OPTION_IS_ACTIVE( opt->cap ); descr->is_settable = SANE_OPTION_IS_SETTABLE( opt->cap ); + if (opt->title) len = ntdll_umbstowcs( opt->title, strlen(opt->title), descr->title, ARRAY_SIZE(descr->title) ); descr->title[len] = 0; + if (opt->name) lstrcpynA(descr->name, opt->name, ARRAY_SIZE(descr->name)); + switch (descr->constraint_type) { case CONSTRAINT_RANGE: diff --git a/dlls/sane.ds/unixlib.h b/dlls/sane.ds/unixlib.h index 34e7a9a9796..7d817c89785 100644 --- a/dlls/sane.ds/unixlib.h +++ b/dlls/sane.ds/unixlib.h @@ -45,6 +45,7 @@ struct option_descriptor enum { CONSTRAINT_NONE, CONSTRAINT_RANGE, CONSTRAINT_WORD_LIST, CONSTRAINT_STRING_LIST } constraint_type;
WCHAR title[256]; + CHAR name[256];
union {
Hi, Esme!
Can you please explain why my previous MR was not added? Is it related to the missing Assignees?
On Thu Oct 16 18:27:53 2025 +0000, Ivan Lyugaev wrote:
Hi, Esme! Can you please explain why my previous MR was not added? Is it related to the missing Assignees?
I don't know. It was ready as far as I'm concerned, but it's @julliard's decision whether to merge.
On Thu Oct 16 18:27:53 2025 +0000, Esme Povirk wrote:
I don't know. It was ready as far as I'm concerned, but it's @julliard's decision whether to merge.
Thank you for the reply! @julliard, maybe you can help me? :)
This merge request was closed by Alexandre Julliard.
Please don't file duplicate MRs.
I'd like to thank Ivan Lyugaev very much for this patch.
However I found a problem with this patch occuring with my test scanner. That problem results in being unable to change the scan resolution to anything but 75 DPI.
The scanner has two scan sources ADF and Flatbed. The scan resolutions for both are different, essentially ADF is limited to 300 DPI.
Setting the scan source changes the resolution to the default 75 DPI. Even if the value for the scan source has not changed. So even if setting to Flatbet when Flatbet was already set.
My test scanner is an HP Officejet Pro 8600 N911a . 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]; ```
Since the code from this patch always transfers all parameters to the sane source, and the source has a higher parameter index number than the resolution, it always overwrites the resolution successfully just set by setting the scan source immediatly afterwards. So the dialog control still shows 300 DPI, but the scanner sees resultionList[1]=75 DPI.
Since a solution should not only solve the problem for just that one device, and the parameter indices are different depending on the device type, a solution to this problem is tricky.
A suggested solution could be to transfer parameters twice:
1. Set all parameters 2. Ask for all parameter values and only set those parameters again, that are different. So resolution would be different (75 DPI) but scan source would be unchanged from the first run, so not set. So it wouldn't overwrite resolution again.
Yours
Bernd Herd
:grinning: