SANE不可见属性,在TWAIN中可见

This commit is contained in:
gb 2023-09-23 16:07:23 +08:00
parent f3326d973a
commit 7d0998924a
10 changed files with 134 additions and 43 deletions

File diff suppressed because one or more lines are too long

View File

@ -15,12 +15,6 @@
#include "simple_logic.h"
#include <json/gb_json.h>
enum opt_visible_level // "visible" field
{
OPT_VISIBLE_ALL = 0, // visible on ui and accessible
OPT_VISIBLE_ACCESS, // accessible only
OPT_VISIBLE_HIDE, // invisible and inaccessible unless user has DEVELOPE privilege
};
class device_option
{

View File

@ -12,7 +12,7 @@
#include "user.h"
static std::string device_opt_json[] = {
"{\"company\":{\"cat\":\"base\",\"group\":\"\\u53ea\\u8bfb\\u5c5e\\u6027\",\"title\":\"\\u5236\\u9020\\u5546\",\"desc\":\"\\u8bbe\\u5907\\u5236\\u9020\\u5546\",\"ver\":0,\"pos\":0,\"fix-id\":34891,\"type\":\"string\",\"unit\":\"none\",\"affect\":0,\"readonly\":true,\"size\":128,\"auto\":false,\"cur\":\"0\",\"default\":\"0\"},\"copyright\":{\"cat\":\"base\",\"group\":\"\\u53ea\\u8bfb\\u5c5e\\u6027\",\"title\":\"\\u7248\\u672c\\u4fe1\\u606f\",\"desc\":\"\\u7248\\u6743\\u58f0\\u660e\\u7b49\\u4fe1\\u606f\",\"ver\":0,\"pos\":0,\"fix-id\":34892,\"type\":\"string\",\"unit\":\"none\",\"affect\":0,\"readonly\":true,\"size\":128,\"auto\":false,\"cur\":\"0\",\"default\":\"0\"},\"co-url\":{\"cat\":\"base\",\"group\":\"\\u53ea\\u8bfb\\u5c5e\\u6027\",\"title\":\"\\u516c\\u53f8\\u7f51\\u5740\",\"desc\":\"\\u516c\\u53f8\\u5b98\\u7f51\\u6216\\u552e\\u540e\\u670d\\u52a1\\u7f51\\u7ad9\\u5730\\u5740\",\"ver\":0,\"pos\":0,\"fix-id\":34893,\"type\":\"string\",\"unit\":\"none\",\"affect\":0,\"readonly\":true,\"size\":256,\"auto\":false,\"cur\":\"0\",\"default\":\"0\"},\"co-tel\":{\"cat\":\"base\",\"group\":\"\\u53ea\\u8bfb\\u5c5e\\u6027\",\"title\":\"\\u516c\\u53f8\\u7535\\u8bdd\",\"desc\":\"\\u516c\\u53f8\\u8054\\u7cfb\\u7535\\u8bdd\",\"ver\":0,\"pos\":0,\"fix-id\":34894,\"type\":\"string\",\"unit\":\"none\",\"affect\":0,\"readonly\":true,\"size\":129,\"auto\":false,\"cur\":\"0\",\"default\":\"0\"},\"co-addr\":{\"cat\":\"base\",\"group\":\"\\u53ea\\u8bfb\\u5c5e\\u6027\",\"title\":\"\\u516c\\u53f8\\u5730\\u5740\",\"desc\":\"\\u516c\\u53f8\\u5730\\u5740\",\"ver\":0,\"pos\":0,\"fix-id\":34895,\"type\":\"string\",\"unit\":\"none\",\"affect\":0,\"readonly\":true,\"size\":128,\"auto\":false,\"cur\":\"0\",\"default\":\"0\"},\"co-gps\":{\"cat\":\"base\",\"group\":\"\\u53ea\\u8bfb\\u5c5e\\u6027\",\"title\":\"\\u516c\\u53f8GPS\",\"desc\":\"\\u516c\\u53f8\\u5730\\u56fe\\u5730\\u5740\",\"ver\":0,\"pos\":0,\"fix-id\":34896,\"type\":\"string\",\"unit\":\"none\",\"affect\":0,\"readonly\":true,\"size\":256,\"auto\":false,\"cur\":\"0\",\"default\":\"0\"},\"drv-ver\":{\"cat\":\"base\",\"group\":\"\\u53ea\\u8bfb\\u5c5e\\u6027\",\"title\":\"\\u9a71\\u52a8\\u7248\\u672c\\u53f7\",\"desc\":\"PC\\u7aef\\u9a71\\u52a8\\u7a0b\\u5e8f\\u7248\\u672c\",\"ver\":0,\"pos\":0,\"fix-id\":34890,\"type\":\"string\",\"unit\":\"none\",\"affect\":0,\"readonly\":true,\"size\":48,\"auto\":false,\"cur\":\"0\",\"default\":\"0\"},\"login\":{\"cat\":\"advanced\",\"group\":\"\\u53ea\\u8bfb\\u5c5e\\u6027\",\"title\":\"\\u767b\\u5f55\",\"desc\":\"\\u7528\\u6237\\u767b\\u5f55\\u64cd\\u4f5c\",\"ver\":0,\"pos\":0,\"fix-id\":39168,\"type\":\"string\",\"unit\":\"none\",\"affect\":0,\"readonly\":true,\"size\":64,\"auto\":false,\"cur\":\"false\",\"default\":\"false\"},\"logout\":{\"cat\":\"advanced\",\"group\":\"\\u53ea\\u8bfb\\u5c5e\\u6027\",\"title\":\"\\u767b\\u51fa\",\"desc\":\"\\u7528\\u6237\\u767b\\u51fa\\u64cd\\u4f5c\",\"ver\":0,\"pos\":0,\"fix-id\":39169,\"type\":\"string\",\"unit\":\"none\",\"affect\":0,\"readonly\":true,\"size\":64,\"auto\":false,\"cur\":\"false\",\"default\":\"false\"},\"drv-log\":{\"cat\":\"advanced\",\"group\":\"\\u9ad8\\u7ea7\\u8bbe\\u7f6e\",\"title\":\"\\u9a71\\u52a8\\u65e5\\u5fd7\",\"desc\":\"PC\\u7aef\\u9a71\\u52a8\\u5de5\\u4f5c\\u65e5\\u5fd7\",\"ver\":0,\"pos\":0,\"fix-id\":39171,\"type\":\"string\",\"unit\":\"none\",\"affect\":0,\"size\":256,\"auto\":false,\"cur\":\"0\",\"default\":\"0\"}}"
"{\"company\":{\"cat\":\"base\",\"group\":\"\\u53ea\\u8bfb\\u5c5e\\u6027\",\"title\":\"\\u5236\\u9020\\u5546\",\"desc\":\"\\u8bbe\\u5907\\u5236\\u9020\\u5546\",\"ver\":0,\"pos\":0,\"fix-id\":34891,\"type\":\"string\",\"unit\":\"none\",\"affect\":0,\"readonly\":true,\"size\":128,\"auto\":false,\"cur\":\"0\",\"default\":\"0\"},\"copyright\":{\"cat\":\"base\",\"group\":\"\\u53ea\\u8bfb\\u5c5e\\u6027\",\"title\":\"\\u7248\\u672c\\u4fe1\\u606f\",\"desc\":\"\\u7248\\u6743\\u58f0\\u660e\\u7b49\\u4fe1\\u606f\",\"ver\":0,\"pos\":0,\"fix-id\":34892,\"type\":\"string\",\"unit\":\"none\",\"affect\":0,\"readonly\":true,\"size\":128,\"auto\":false,\"cur\":\"0\",\"default\":\"0\"},\"co-url\":{\"cat\":\"base\",\"group\":\"\\u53ea\\u8bfb\\u5c5e\\u6027\",\"title\":\"\\u516c\\u53f8\\u7f51\\u5740\",\"desc\":\"\\u516c\\u53f8\\u5b98\\u7f51\\u6216\\u552e\\u540e\\u670d\\u52a1\\u7f51\\u7ad9\\u5730\\u5740\",\"ver\":0,\"pos\":0,\"fix-id\":34893,\"type\":\"string\",\"unit\":\"none\",\"affect\":0,\"readonly\":true,\"size\":256,\"auto\":false,\"cur\":\"0\",\"default\":\"0\"},\"co-tel\":{\"cat\":\"base\",\"group\":\"\\u53ea\\u8bfb\\u5c5e\\u6027\",\"title\":\"\\u516c\\u53f8\\u7535\\u8bdd\",\"desc\":\"\\u516c\\u53f8\\u8054\\u7cfb\\u7535\\u8bdd\",\"ver\":0,\"pos\":0,\"fix-id\":34894,\"type\":\"string\",\"unit\":\"none\",\"affect\":0,\"readonly\":true,\"size\":129,\"auto\":false,\"cur\":\"0\",\"default\":\"0\"},\"co-addr\":{\"cat\":\"base\",\"group\":\"\\u53ea\\u8bfb\\u5c5e\\u6027\",\"title\":\"\\u516c\\u53f8\\u5730\\u5740\",\"desc\":\"\\u516c\\u53f8\\u5730\\u5740\",\"ver\":0,\"pos\":0,\"fix-id\":34895,\"type\":\"string\",\"unit\":\"none\",\"affect\":0,\"readonly\":true,\"size\":128,\"auto\":false,\"cur\":\"0\",\"default\":\"0\"},\"co-gps\":{\"cat\":\"base\",\"group\":\"\\u53ea\\u8bfb\\u5c5e\\u6027\",\"title\":\"\\u516c\\u53f8GPS\",\"desc\":\"\\u516c\\u53f8\\u5730\\u56fe\\u5730\\u5740\",\"ver\":0,\"pos\":0,\"fix-id\":34896,\"type\":\"string\",\"unit\":\"none\",\"affect\":0,\"readonly\":true,\"size\":256,\"auto\":false,\"cur\":\"0\",\"default\":\"0\"},\"drv-ver\":{\"cat\":\"base\",\"group\":\"\\u53ea\\u8bfb\\u5c5e\\u6027\",\"title\":\"\\u9a71\\u52a8\\u7248\\u672c\\u53f7\",\"desc\":\"PC\\u7aef\\u9a71\\u52a8\\u7a0b\\u5e8f\\u7248\\u672c\",\"ver\":0,\"pos\":0,\"fix-id\":34890,\"type\":\"string\",\"unit\":\"none\",\"affect\":0,\"readonly\":true,\"size\":48,\"auto\":false,\"cur\":\"0\",\"default\":\"0\"},\"login\":{\"cat\":\"advanced\",\"group\":\"\\u53ea\\u8bfb\\u5c5e\\u6027\",\"title\":\"\\u767b\\u5f55\",\"desc\":\"\\u7528\\u6237\\u767b\\u5f55\\u64cd\\u4f5c\",\"ver\":0,\"pos\":0,\"fix-id\":39168,\"type\":\"string\",\"unit\":\"none\",\"affect\":0,\"readonly\":true,\"visible\":1,\"size\":64,\"auto\":false,\"cur\":\"false\",\"default\":\"false\"},\"logout\":{\"cat\":\"advanced\",\"group\":\"\\u53ea\\u8bfb\\u5c5e\\u6027\",\"title\":\"\\u767b\\u51fa\",\"desc\":\"\\u7528\\u6237\\u767b\\u51fa\\u64cd\\u4f5c\",\"ver\":0,\"pos\":0,\"fix-id\":39169,\"type\":\"string\",\"unit\":\"none\",\"affect\":0,\"readonly\":true,\"visible\":1,\"size\":64,\"auto\":false,\"cur\":\"false\",\"default\":\"false\"},\"drv-log\":{\"cat\":\"advanced\",\"group\":\"\\u9ad8\\u7ea7\\u8bbe\\u7f6e\",\"title\":\"\\u9a71\\u52a8\\u65e5\\u5fd7\",\"desc\":\"PC\\u7aef\\u9a71\\u52a8\\u5de5\\u4f5c\\u65e5\\u5fd7\",\"ver\":0,\"pos\":0,\"fix-id\":39171,\"type\":\"string\",\"unit\":\"none\",\"affect\":0,\"visible\":1,\"size\":256,\"auto\":false,\"cur\":\"0\",\"default\":\"0\"}}"
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

View File

@ -798,6 +798,14 @@ SANE_Status hg_sane_middleware::control_option(SANE_Handle h, const void* option
{
err = get_option_fixed_id(inst, option, value);
}
else if (action == SANE_ACTION_ENUM_INVISIBLE_FIX_ID)
{
struct _fix_id_cb* fcb = (struct _fix_id_cb*)value;
if (!inst->opts->enum_invisible_fix_ids(fcb) && inst != offline_)
offline_->opts->enum_invisible_fix_ids(fcb);
err = SANE_STATUS_GOOD;
}
return err;
}

View File

@ -7,7 +7,7 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// sane_opt
sane_opt::sane_opt() : fix_id_(-1), sn_(0), enabled_(true)
sane_opt::sane_opt() : fix_id_(-1), enabled_(true), visible_(true)
{
memset(&opt_desc_, 0, sizeof(opt_desc_));
}
@ -35,7 +35,6 @@ void sane_opt::clear()
}
memset(&opt_desc_, 0, sizeof(opt_desc_));
fix_id_ = -1;
sn_ = 0;
enabled_ = true;
}
void sane_opt::set_opt_desc_string_value(char** buf, const char* val)
@ -73,6 +72,12 @@ void sane_opt::init_cap(gb_json* jsn)
if (cat == "advanced")
opt_desc_.cap |= SANE_CAP_ADVANCED;
}
int visb = 0;
if (jsn->get_value("visible", visb))
{
visible_ = visb == OPT_VISIBLE_ALL;
}
}
void sane_opt::init_range(gb_json* jsn)
{
@ -193,10 +198,6 @@ int sane_opt::get_fix_id(void)
{
return fix_id_;
}
int sane_opt::get_sn(void)
{
return sn_;
}
SANE_Option_Descriptor* sane_opt::get_descriptor(void)
{
return &opt_desc_;
@ -209,8 +210,13 @@ bool sane_opt::is_enabled(void)
{
return enabled_;
}
bool sane_opt::is_visible(void)
{
return visible_;
}
bool sane_opt::from_json_text(int sn, const char* key, const char* json, void(*err_msg)(const char*))
bool sane_opt::from_json_text(const char* key, const char* json, void(*err_msg)(const char*))
{
bool ret = false;
std::string text(json);
@ -218,7 +224,6 @@ bool sane_opt::from_json_text(int sn, const char* key, const char* json, void(*e
char *buf = nullptr;
clear();
sn_ = sn;
while (jsn->attach_text(&text[0]))
{
jsn->get_value("fix-id", fix_id_);
@ -321,12 +326,17 @@ device_opts::~device_opts()
void device_opts::clear(void)
{
std::vector<sane_opt*> tofree;
for (auto& v : opts_)
{
if(v.first == v.second->get_sn()) // fix-id has the same object, we delete object at it's option number
delete v.second;
if (std::find(tofree.begin(), tofree.end(), v.second) == tofree.end())
tofree.push_back(v.second);
}
for (auto& v : tofree)
delete v;
tofree.clear();
opts_.clear();
opt_cnt_ = 0;
}
@ -336,7 +346,7 @@ bool device_opts::init_from(const char* jsn_text/*all options*/, void(*err_msg)(
bool ret = false;
gb_json* jsn = new gb_json();
std::string str(jsn_text);
int sn = 1;
int sn = 0;
clear();
if (jsn->attach_text(&str[0]))
@ -347,7 +357,8 @@ bool device_opts::init_from(const char* jsn_text/*all options*/, void(*err_msg)(
while (child)
{
str = std::move(child->to_string());
ret = add_or_replace_opt(sn++, child->key().c_str(), str.c_str(), err_msg);
sn++;
ret = add_or_replace_opt(sn, child->key().c_str(), str.c_str(), err_msg);
if (!ret)
{
child->release();
@ -362,26 +373,33 @@ bool device_opts::init_from(const char* jsn_text/*all options*/, void(*err_msg)(
return ret;
}
bool device_opts::add_or_replace_opt(int sn, const char* name, const char* jsn_text, void(*err_msg)(const char*))
bool device_opts::add_or_replace_opt(int& sn, const char* name, const char* jsn_text, void(*err_msg)(const char*))
{
if (opts_.count(sn))
{
bool en = opts_[sn]->is_enabled(),
ret = opts_[sn]->from_json_text(sn, name, jsn_text, err_msg);
ret = opts_[sn]->from_json_text(name, jsn_text, err_msg);
return ret;
}
sane_opt* opt = new sane_opt();
if (opt->from_json_text(sn, name, jsn_text, err_msg))
if (opt->from_json_text(name, jsn_text, err_msg))
{
opts_[sn] = opt;
if (opt->get_fix_id() != -1)
if (opt->is_visible())
{
opts_[sn] = opt;
opt_cnt_++;
}
else
sn--;
if (opt->get_fix_id() > SANE_OPT_ID_BASE)
{
opts_[opt->get_fix_id()] = opt;
}
opt_cnt_++;
else if (!opt->is_visible()) // no fix-id and SANE_Int number will not be existence
delete opt;
return true;
}
@ -436,3 +454,21 @@ int device_opts::get_option_count(void)
{
return opt_cnt_;
}
bool device_opts::enum_invisible_fix_ids(struct _fix_id_cb* fcb) // return whether the callback stopped the enumeration
{
bool stopped = false;
for (auto& v : opts_)
{
if (!v.second->is_visible())
{
if (!fcb->cb(v.second->get_fix_id(), fcb->param))
{
stopped = true;
break;
}
}
}
return stopped;
}

View File

@ -20,8 +20,8 @@ class gb_json;
class sane_opt
{
int fix_id_;
int sn_;
bool enabled_;
bool visible_;
SANE_Option_Descriptor opt_desc_;
@ -36,24 +36,24 @@ public:
public:
int get_fix_id(void);
int get_sn(void);
SANE_Option_Descriptor* get_descriptor(void);
const char* name(void);
bool is_enabled(void);
bool is_visible(void);
bool from_json_text(int sn, const char* key, const char* json, void(*err_msg)(const char*) = nullptr);
bool from_json_text(const char* key, const char* json, void(*err_msg)(const char*) = nullptr);
};
class device_opts
{
std::map<int, sane_opt*> opts_;
int opt_cnt_;
std::map<int, sane_opt*> opts_; // SANE_Int && fix-id point the same object. SANE_Int is visible and accessible, only accessible by fix-id if without SANE_Int
int opt_cnt_; // count for visible options
SANE_Option_Descriptor opt_0_;
void clear(void);
bool add_or_replace_opt(int sn, const char* name, const char* jsn_text, void(*err_msg)(const char*));
bool add_or_replace_opt(int& sn, const char* name, const char* jsn_text, void(*err_msg)(const char*));
public:
device_opts();
@ -63,4 +63,5 @@ public:
bool init_from(const char* jsn_text/*all options*/, void(*err_msg)(const char*));
SANE_Option_Descriptor* get_opt_descriptor(const void* opt, int* fix_id = nullptr, int ind_base = 0);
int get_option_count(void);
bool enum_invisible_fix_ids(struct _fix_id_cb* fcb); // return whether the callback stopped the enumeration
};

View File

@ -765,11 +765,11 @@ static void log_attr_access(int attr, int method)
// get fixed-ids from SANE option ...
static bool got_fixed_id(uint32_t id, void* param)
static SANE_Bool got_fixed_id(int id, void* param)
{
((std::vector<uint32_t>*)param)->push_back(id);
return true;
return SANE_TRUE;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

View File

@ -196,7 +196,7 @@ public:
//
// bytes - *type 为 VAL_TYPE_STR时需要的最小空间字节数
COM_API_DECLARE(bool, get_option_info(int sn, value_type* type, value_limit* limit, int *bytes, bool* readonly));
COM_API_DECLARE(int, get_fixed_ids(bool(* cb)(uint32_t id, void* param), void* param));
COM_API_DECLARE(int, get_fixed_ids(SANE_Bool(* cb)(int id, void* param), void* param));
COM_API_DECLARE(bool, get_value(int sn, set_opt_value func, void* param));
COM_API_DECLARE(bool, get_value(int sn, void* data, int* len)); // get operation with in-parameter

View File

@ -318,6 +318,12 @@ namespace callback
hui = nullptr;
}
}
static SANE_Bool get_invisible_fix_id(int id, void* param)
{
((std::vector<int>*)param)->push_back(id);
return SANE_TRUE;
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -657,6 +663,33 @@ int scanner::init_options_id(void)
}
}
// invisible option in SANE
{
std::vector<int> inv;
struct _fix_id_cb fcb;
fcb.cb = callback::get_invisible_fix_id;
fcb.param = &inv;
sane_helper_->invoke_sane_control_option(handle_, SANE_OPT_ID_BASE, (SANE_Action)SANE_ACTION_ENUM_INVISIBLE_FIX_ID, &fcb, nullptr);
for (auto& v : inv)
{
desc = sane_helper_->invoke_sane_get_option_descriptor(handle_, v);
if (desc)
{
SANEOPT opt;
opt.opt_sn = v;
if (desc->type == SANE_TYPE_BOOL) // bool to enumeration constraint
opt.limit = VAL_LIMIT_ENUM;
else
opt.limit = scanner::from_sane_constraint(desc->constraint_type);
opt.type = scanner::from_sane_type(desc->type);
opt.size = desc->size;
opt.cap = desc->cap;
sane_opts_[v] = opt;
}
}
}
return ret;
}
char* scanner::get_opt_value(int id, int size, bool def_val)
@ -1089,10 +1122,19 @@ COM_API_IMPLEMENT(scanner, bool, get_option_info(int sn, value_type* type, value
}
COM_API_IMPLEMENT(scanner, bool, get_value(int sn, set_opt_value setval, void* param))
{
const SANE_Option_Descriptor* desc = sane_helper_->invoke_sane_get_option_descriptor(handle_, sn);
if (desc)
utils::to_log(LOG_LEVEL_DEBUG, "get_value(0x%04X - %s) ...\r\n", sn, desc->name);
else
{
utils::to_log(LOG_LEVEL_WARNING, "get_value(0x%04X, ...), but the option is not found !!!\r\n", sn);
return SCANNER_ERR_DEVICE_NOT_SUPPORT;
}
if (get_option_value_with_parent(sn, setval, param))
return true;
const SANE_Option_Descriptor* desc = sane_helper_->invoke_sane_get_option_descriptor(handle_, sn);
char* init = get_opt_value(sn, desc->size, true),
* now = get_opt_value(sn, desc->size, false);
int ret = SANE_STATUS_GOOD;
@ -1213,10 +1255,15 @@ COM_API_IMPLEMENT(scanner, int, set_value(int sn, void* val))
SANE_Int after = 0;
const SANE_Option_Descriptor* desc = sane_helper_->invoke_sane_get_option_descriptor(handle_, sn);
{
if (desc)
utils::to_log(LOG_LEVEL_DEBUG, "set_value(0x%04X - %s, '%s') ...\r\n", sn, desc->name, callback::option_value_2_string(desc->type, val).c_str());
}
else
{
utils::to_log(LOG_LEVEL_WARNING, "set_value(0x%04X, ...), but the option is not found !!!\r\n", sn);
return SCANNER_ERR_DEVICE_NOT_SUPPORT;
}
twain_set_ = true;
if (!set_option_value_with_parent(sn, val, &ret))
ret = set_option_value(sn, desc->type, desc->size, val);
@ -1227,12 +1274,17 @@ COM_API_IMPLEMENT(scanner, int, convert_image(SANE_ImageFormatConvert* conv))
{
return sane_helper_->invoke_sane_control_option(handle_, SANE_OPT_ID_TRANSFORM_IMAGE_FORMAT, SANE_ACTION_SET_VALUE, conv, nullptr);
}
COM_API_IMPLEMENT(scanner, int, get_fixed_ids(bool(* cb)(uint32_t id, void* param), void* param))
COM_API_IMPLEMENT(scanner, int, get_fixed_ids(SANE_Bool(* cb)(int id, void* param), void* param))
{
bool stopped = false;
for (auto& v : sane_opts_)
{
if (!cb(v.first, param))
{
stopped = true;
break;
}
}
return 0;

View File

@ -165,7 +165,7 @@ public:
COM_API_OVERRIDE(int, twain_set_config(char* buf, size_t len));
COM_API_OVERRIDE(bool, get_option_info(int sn, value_type* type, value_limit* limit, int* bytes, bool* readonly));
COM_API_OVERRIDE(int, get_fixed_ids(bool(*cb)(uint32_t id, void* param), void* param));
COM_API_OVERRIDE(int, get_fixed_ids(SANE_Bool(*cb)(int id, void* param), void* param));
COM_API_OVERRIDE(bool, get_value(int sn, set_opt_value func, void* param)); // get all values
COM_API_OVERRIDE(bool, get_value(int sn, void* data, int* len)); // get current value