From: Ivan Lyugaev valy@etersoft.ru
The latest scanner settings are now automatically saved in the HKEY_CURRENT_USER\Software\ScannersSettings<Vendor><Product> path --- dlls/sane.ds/Makefile.in | 5 +- dlls/sane.ds/cfg.c | 148 +++++++++++++++++++++++ dlls/sane.ds/cfg.h | 48 ++++++++ dlls/sane.ds/sane_main.c | 3 + dlls/sane.ds/ui.c | 254 ++++++++++++++++++++++++++++++++++++--- 5 files changed, 440 insertions(+), 18 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..cbbdf834298 --- /dev/null +++ b/dlls/sane.ds/cfg.c @@ -0,0 +1,148 @@ +/* +* 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 is_exist_reg( WCHAR* path ) +{ + HKEY h_key; + DWORD dispos; + LSTATUS res; + + res = get_info_key(path, &h_key, &dispos); + + if ( res != ERROR_SUCCESS ) + { + ERR( "RegCreateKeyExW error: %ld\n", res ); + return FALSE; + } + + RegCloseKey(h_key); + return ( dispos == REG_OPENED_EXISTING_KEY ); +} + +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 ); + + if ( res != ERROR_SUCCESS ) { + ERR( "RegGetValueExA error: %ld\n", res ); + return FALSE; + } + + return TRUE; +} diff --git a/dlls/sane.ds/cfg.h b/dlls/sane.ds/cfg.h new file mode 100644 index 00000000000..9bea843deef --- /dev/null +++ b/dlls/sane.ds/cfg.h @@ -0,0 +1,48 @@ +/* +* 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 ); + +BOOL is_exist_reg( WCHAR* path ); 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..61e2c782a19 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,26 @@ 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)); + + for (int i = 0; activeDS.identity.ProductFamily[i]; i++) + { + if (activeDS.identity.ProductFamily[i] == L'/') + { + activeDS.identity.ProductFamily[i] = L'_'; + } + } + 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 +568,117 @@ BOOL DoScannerUI(void) return FALSE; }
+static void get_option(struct option_descriptor* opt, ScannerOption* option) +{ + CHAR title[OPTION_NAME_MAX]; + WideCharToMultiByte(CP_UTF8, 0, opt->title, -1, title, sizeof(title), NULL, NULL); + lstrcpynA(option->name, title, 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); + } + } +} + +static BOOL save_option(int optno) +{ + ScannerOption option; + struct option_descriptor opt; + + opt.optno = optno; + SANE_CALL( option_get_descriptor, &opt); + + get_option(&opt, &option); + return save_to_reg(path, option.reg_type, option.name, option.value, option.size); +} + +static BOOL save_options(void) +{ + TW_UINT16 rc; + int optcount; + + rc = sane_option_get_value( 0, &optcount ); + if (rc != TWCC_SUCCESS) + { + ERR("Unable to read number of options\n"); + optcount = gOptCount; + } + else + gOptCount = optcount; + + for (int i = 1; i < optcount; i++) + { + ScannerOption option; + struct option_descriptor opt; + opt.optno = i; + SANE_CALL( option_get_descriptor, &opt); + if (!opt.is_active) + continue; + get_option(&opt, &option); + if (!save_to_reg(path, option.reg_type, option.name, option.value, option.size)) + { + ERR("Option %s could not be saved to the registry!", option.name); + } + } + return TRUE; +} + static void UpdateRelevantEdit(HWND hwnd, const struct option_descriptor *opt, int position) { WCHAR buffer[244]; @@ -607,6 +738,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 +748,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 +768,27 @@ 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; + + if ( !is_exist_reg(path) ) + { + save_options(); }
for ( i = 1; i < optcount; i++) { + CHAR title[64]; 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 +796,77 @@ static INT_PTR InitializeDialog(HWND hwnd)
SendMessageA(control,CB_RESETCONTENT,0,0); /* initialize values */ + + WideCharToMultiByte(CP_UTF8, 0, opt.title, -1, title, sizeof(title), NULL, NULL); + 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_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_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 +877,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 +888,16 @@ static INT_PTR InitializeDialog(HWND hwnd)
SendMessageA(control,SBM_SETRANGE,min,max);
+ if (si >= min && si <= max) + is_correct = TRUE; + else + 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 +906,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 +921,23 @@ static INT_PTR InitializeDialog(HWND hwnd)
SendMessageA(control,SBM_SETRANGE,min,max);
+ is_exist = load_from_reg(path, opt.type, title, &val); + + if (val >= min && val <= max) + is_correct = TRUE; + else + 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 +1029,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 +1068,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) @@ -907,7 +1128,8 @@ static int CALLBACK PropSheetProc(HWND hwnd, UINT msg, LPARAM lParam) HWND scan = GetDlgItem(hwnd,IDOK); SetWindowTextA(scan,"Scan"); } - return TRUE; + + return TRUE; }