[PATCH 0/1] MR9184: sane.ds: Adding saving scanner settings to the registry
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 -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9184
From: Ivan Lyugaev <valy(a)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 { -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9184
Hi, Esme! Can you please explain why my previous MR was not added? Is it related to the missing Assignees? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9184#note_118680
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.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/9184#note_118759
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? :)
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/9184#note_118761
This merge request was closed by Alexandre Julliard. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9184
Please don't file duplicate MRs. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9184#note_120049
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: -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9184#note_120373
On Mon Nov 3 16:51:21 2025 +0000, Bernd Herd wrote: > 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: I guess a general solution that won't loop infinitely in case of impossible combinations could be: * Change only the parameters that are configured differently from their current value. * After changing a parameter, recheck all previous parameters, changing and rechecking the ones before that, if necessary. * Load the values into the dialog after making all parameter changes. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9184#note_120550
On Mon Nov 3 16:51:33 2025 +0000, Esme Povirk wrote: > I guess a general solution that won't loop infinitely in case of > impossible combinations could be: > * Change only the parameters that are configured differently from their > current value. > * After changing a parameter, recheck all previous parameters, changing > and rechecking the ones before that, if necessary. > * Load the values into the dialog after making all parameter changes. Hm, that would be O(n**2) in normal cases though. We could do better: 1. Check all parameter values. Change those that are configured differently from their current state. Note the last index changed. 2. As long as at least one change was needed, repeat step 1, but only for parameters with index before the last one changed. 3. Load the values into the dialog. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9184#note_120551
On Mon Nov 3 16:53:52 2025 +0000, Esme Povirk wrote:
Hm, that would be O(n**2) in normal cases though. We could do better: 1. Check all parameter values. Change those that are configured differently from their current state. Note the last index changed. 2. As long as at least one change was needed, repeat step 1, but only for parameters with index before the last one changed. 3. Load the values into the dialog. That could still fail for complicated dependency graphs. I guess there's no general solution with a reasonable runtime that we can be sure will terminate.
Maybe if option names are relatively consistent between devices, we could just hard-code some ordering rules for the ones that have been known to do this? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9184#note_120558
On Mon Nov 3 17:08:52 2025 +0000, Esme Povirk wrote:
That could still fail for complicated dependency graphs. I guess there's no general solution with a reasonable runtime that we can be sure will terminate. Maybe if option names are relatively consistent between devices, we could just hard-code some ordering rules for the ones that have been known to do this? It may be possible to make an additional configuration file in which information about the order of loading parameters can be placed, for example:
[Vendor_Product path] order Might you also consider creating an oriented dependency graph at the time of sane.ds initialization? Keep in mind that in O(n**2), the value of n is small in the context of the number of parameter settings. You can try another option. 1) Enter a global variable with the name of the last modified parameter 2) At the time of the first initialization of the settings window, this global variable will be empty, therefore there have been no changes. 3) When making changes to a global variable, save the changed parameter. 4) At the next initialization of the window, check the parameters from the registry and the real ones in sane. If the values differ, there may be two options: 4.1) The parameter name matches the name from the global variable - start the cycle again and return to point 4, check the parameter for a second match with the variable (there may be a complex relationship) 4.2) The parameter name does not match the name from the global variable - you need to replace the current value in sane with the value from the config This is an approximate algorithm for possible correction, what do you think? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9184#note_121010
participants (5)
-
Alexandre Julliard (@julliard) -
Bernd Herd (@herdsoft) -
Esme Povirk (@madewokherd) -
Ivan Lyugaev -
Ivan Lyugaev (@valy)