You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1189 lines
33 KiB
1189 lines
33 KiB
/*
|
|
* Option conflict management routines for CUPS.
|
|
*
|
|
* Copyright 2007-2018 by Apple Inc.
|
|
* Copyright 1997-2007 by Easy Software Products, all rights reserved.
|
|
*
|
|
* Licensed under Apache License v2.0. See the file "LICENSE" for more
|
|
* information.
|
|
*
|
|
* PostScript is a trademark of Adobe Systems, Inc.
|
|
*/
|
|
|
|
/*
|
|
* Include necessary headers...
|
|
*/
|
|
|
|
#include "cups-private.h"
|
|
#include "ppd-private.h"
|
|
#include "debug-internal.h"
|
|
|
|
|
|
/*
|
|
* Local constants...
|
|
*/
|
|
|
|
enum
|
|
{
|
|
_PPD_OPTION_CONSTRAINTS,
|
|
_PPD_INSTALLABLE_CONSTRAINTS,
|
|
_PPD_ALL_CONSTRAINTS
|
|
};
|
|
|
|
|
|
/*
|
|
* Local functions...
|
|
*/
|
|
|
|
static int ppd_is_installable(ppd_group_t *installable,
|
|
const char *option);
|
|
static void ppd_load_constraints(ppd_file_t *ppd);
|
|
static cups_array_t *ppd_test_constraints(ppd_file_t *ppd,
|
|
const char *option,
|
|
const char *choice,
|
|
int num_options,
|
|
cups_option_t *options,
|
|
int which);
|
|
|
|
|
|
/*
|
|
* 'cupsGetConflicts()' - Get a list of conflicting options in a marked PPD.
|
|
*
|
|
* This function gets a list of options that would conflict if "option" and
|
|
* "choice" were marked in the PPD. You would typically call this function
|
|
* after marking the currently selected options in the PPD in order to
|
|
* determine whether a new option selection would cause a conflict.
|
|
*
|
|
* The number of conflicting options are returned with "options" pointing to
|
|
* the conflicting options. The returned option array must be freed using
|
|
* @link cupsFreeOptions@.
|
|
*
|
|
* @since CUPS 1.4/macOS 10.6@
|
|
*/
|
|
|
|
int /* O - Number of conflicting options */
|
|
cupsGetConflicts(
|
|
ppd_file_t *ppd, /* I - PPD file */
|
|
const char *option, /* I - Option to test */
|
|
const char *choice, /* I - Choice to test */
|
|
cups_option_t **options) /* O - Conflicting options */
|
|
{
|
|
int i, /* Looping var */
|
|
num_options; /* Number of conflicting options */
|
|
cups_array_t *active; /* Active conflicts */
|
|
_ppd_cups_uiconsts_t *c; /* Current constraints */
|
|
_ppd_cups_uiconst_t *cptr; /* Current constraint */
|
|
ppd_choice_t *marked; /* Marked choice */
|
|
|
|
|
|
/*
|
|
* Range check input...
|
|
*/
|
|
|
|
if (options)
|
|
*options = NULL;
|
|
|
|
if (!ppd || !option || !choice || !options)
|
|
return (0);
|
|
|
|
/*
|
|
* Test for conflicts...
|
|
*/
|
|
|
|
active = ppd_test_constraints(ppd, option, choice, 0, NULL,
|
|
_PPD_ALL_CONSTRAINTS);
|
|
|
|
/*
|
|
* Loop through all of the UI constraints and add any options that conflict...
|
|
*/
|
|
|
|
for (num_options = 0, c = (_ppd_cups_uiconsts_t *)cupsArrayFirst(active);
|
|
c;
|
|
c = (_ppd_cups_uiconsts_t *)cupsArrayNext(active))
|
|
{
|
|
for (i = c->num_constraints, cptr = c->constraints;
|
|
i > 0;
|
|
i --, cptr ++)
|
|
if (_cups_strcasecmp(cptr->option->keyword, option))
|
|
{
|
|
if (cptr->choice)
|
|
num_options = cupsAddOption(cptr->option->keyword,
|
|
cptr->choice->choice, num_options,
|
|
options);
|
|
else if ((marked = ppdFindMarkedChoice(ppd,
|
|
cptr->option->keyword)) != NULL)
|
|
num_options = cupsAddOption(cptr->option->keyword, marked->choice,
|
|
num_options, options);
|
|
}
|
|
}
|
|
|
|
cupsArrayDelete(active);
|
|
|
|
return (num_options);
|
|
}
|
|
|
|
|
|
/*
|
|
* 'cupsResolveConflicts()' - Resolve conflicts in a marked PPD.
|
|
*
|
|
* This function attempts to resolve any conflicts in a marked PPD, returning
|
|
* a list of option changes that are required to resolve them. On input,
|
|
* "num_options" and "options" contain any pending option changes that have
|
|
* not yet been marked, while "option" and "choice" contain the most recent
|
|
* selection which may or may not be in "num_options" or "options".
|
|
*
|
|
* On successful return, "num_options" and "options" are updated to contain
|
|
* "option" and "choice" along with any changes required to resolve conflicts
|
|
* specified in the PPD file and 1 is returned.
|
|
*
|
|
* If option conflicts cannot be resolved, "num_options" and "options" are not
|
|
* changed and 0 is returned.
|
|
*
|
|
* When resolving conflicts, @code cupsResolveConflicts@ does not consider
|
|
* changes to the current page size (@code media@, @code PageSize@, and
|
|
* @code PageRegion@) or to the most recent option specified in "option".
|
|
* Thus, if the only way to resolve a conflict is to change the page size
|
|
* or the option the user most recently changed, @code cupsResolveConflicts@
|
|
* will return 0 to indicate it was unable to resolve the conflicts.
|
|
*
|
|
* The @code cupsResolveConflicts@ function uses one of two sources of option
|
|
* constraint information. The preferred constraint information is defined by
|
|
* @code cupsUIConstraints@ and @code cupsUIResolver@ attributes - in this
|
|
* case, the PPD file provides constraint resolution actions.
|
|
*
|
|
* The backup constraint information is defined by the
|
|
* @code UIConstraints@ and @code NonUIConstraints@ attributes. These
|
|
* constraints are resolved algorithmically by first selecting the default
|
|
* choice for the conflicting option, then iterating over all possible choices
|
|
* until a non-conflicting option choice is found.
|
|
*
|
|
* @since CUPS 1.4/macOS 10.6@
|
|
*/
|
|
|
|
int /* O - 1 on success, 0 on failure */
|
|
cupsResolveConflicts(
|
|
ppd_file_t *ppd, /* I - PPD file */
|
|
const char *option, /* I - Newly selected option or @code NULL@ for none */
|
|
const char *choice, /* I - Newly selected choice or @code NULL@ for none */
|
|
int *num_options, /* IO - Number of additional selected options */
|
|
cups_option_t **options) /* IO - Additional selected options */
|
|
{
|
|
int i, /* Looping var */
|
|
tries, /* Number of tries */
|
|
num_newopts; /* Number of new options */
|
|
cups_option_t *newopts; /* New options */
|
|
cups_array_t *active = NULL, /* Active constraints */
|
|
*pass, /* Resolvers for this pass */
|
|
*resolvers, /* Resolvers we have used */
|
|
*test; /* Test array for conflicts */
|
|
_ppd_cups_uiconsts_t *consts; /* Current constraints */
|
|
_ppd_cups_uiconst_t *constptr; /* Current constraint */
|
|
ppd_attr_t *resolver; /* Current resolver */
|
|
const char *resval; /* Pointer into resolver value */
|
|
char resoption[PPD_MAX_NAME],
|
|
/* Current resolver option */
|
|
reschoice[PPD_MAX_NAME],
|
|
/* Current resolver choice */
|
|
*resptr, /* Pointer into option/choice */
|
|
firstpage[255]; /* AP_FIRSTPAGE_Keyword string */
|
|
const char *value; /* Selected option value */
|
|
int changed; /* Did we change anything? */
|
|
ppd_choice_t *marked; /* Marked choice */
|
|
|
|
|
|
/*
|
|
* Range check input...
|
|
*/
|
|
|
|
if (!ppd || !num_options || !options || (option == NULL) != (choice == NULL))
|
|
return (0);
|
|
|
|
/*
|
|
* Build a shadow option array...
|
|
*/
|
|
|
|
num_newopts = 0;
|
|
newopts = NULL;
|
|
|
|
for (i = 0; i < *num_options; i ++)
|
|
num_newopts = cupsAddOption((*options)[i].name, (*options)[i].value,
|
|
num_newopts, &newopts);
|
|
if (option && _cups_strcasecmp(option, "Collate"))
|
|
num_newopts = cupsAddOption(option, choice, num_newopts, &newopts);
|
|
|
|
/*
|
|
* Loop until we have no conflicts...
|
|
*/
|
|
|
|
cupsArraySave(ppd->sorted_attrs);
|
|
|
|
resolvers = NULL;
|
|
pass = cupsArrayNew((cups_array_func_t)_cups_strcasecmp, NULL);
|
|
tries = 0;
|
|
|
|
while (tries < 100 &&
|
|
(active = ppd_test_constraints(ppd, NULL, NULL, num_newopts, newopts,
|
|
_PPD_ALL_CONSTRAINTS)) != NULL)
|
|
{
|
|
tries ++;
|
|
|
|
if (!resolvers)
|
|
resolvers = cupsArrayNew((cups_array_func_t)_cups_strcasecmp, NULL);
|
|
|
|
for (consts = (_ppd_cups_uiconsts_t *)cupsArrayFirst(active), changed = 0;
|
|
consts;
|
|
consts = (_ppd_cups_uiconsts_t *)cupsArrayNext(active))
|
|
{
|
|
if (consts->resolver[0])
|
|
{
|
|
/*
|
|
* Look up the resolver...
|
|
*/
|
|
|
|
if (cupsArrayFind(pass, consts->resolver))
|
|
continue; /* Already applied this resolver... */
|
|
|
|
if (cupsArrayFind(resolvers, consts->resolver))
|
|
{
|
|
/*
|
|
* Resolver loop!
|
|
*/
|
|
|
|
DEBUG_printf(("1cupsResolveConflicts: Resolver loop with %s!",
|
|
consts->resolver));
|
|
goto error;
|
|
}
|
|
|
|
if ((resolver = ppdFindAttr(ppd, "cupsUIResolver",
|
|
consts->resolver)) == NULL)
|
|
{
|
|
DEBUG_printf(("1cupsResolveConflicts: Resolver %s not found!",
|
|
consts->resolver));
|
|
goto error;
|
|
}
|
|
|
|
if (!resolver->value)
|
|
{
|
|
DEBUG_printf(("1cupsResolveConflicts: Resolver %s has no value!",
|
|
consts->resolver));
|
|
goto error;
|
|
}
|
|
|
|
/*
|
|
* Add the options from the resolver...
|
|
*/
|
|
|
|
cupsArrayAdd(pass, consts->resolver);
|
|
cupsArrayAdd(resolvers, consts->resolver);
|
|
|
|
for (resval = resolver->value; *resval && !changed;)
|
|
{
|
|
while (_cups_isspace(*resval))
|
|
resval ++;
|
|
|
|
if (*resval != '*')
|
|
break;
|
|
|
|
for (resval ++, resptr = resoption;
|
|
*resval && !_cups_isspace(*resval);
|
|
resval ++)
|
|
if (resptr < (resoption + sizeof(resoption) - 1))
|
|
*resptr++ = *resval;
|
|
|
|
*resptr = '\0';
|
|
|
|
while (_cups_isspace(*resval))
|
|
resval ++;
|
|
|
|
for (resptr = reschoice;
|
|
*resval && !_cups_isspace(*resval);
|
|
resval ++)
|
|
if (resptr < (reschoice + sizeof(reschoice) - 1))
|
|
*resptr++ = *resval;
|
|
|
|
*resptr = '\0';
|
|
|
|
if (!resoption[0] || !reschoice[0])
|
|
break;
|
|
|
|
/*
|
|
* Is this the option we are changing?
|
|
*/
|
|
|
|
snprintf(firstpage, sizeof(firstpage), "AP_FIRSTPAGE_%s", resoption);
|
|
|
|
if (option &&
|
|
(!_cups_strcasecmp(resoption, option) ||
|
|
!_cups_strcasecmp(firstpage, option) ||
|
|
(!_cups_strcasecmp(option, "PageSize") &&
|
|
!_cups_strcasecmp(resoption, "PageRegion")) ||
|
|
(!_cups_strcasecmp(option, "AP_FIRSTPAGE_PageSize") &&
|
|
!_cups_strcasecmp(resoption, "PageSize")) ||
|
|
(!_cups_strcasecmp(option, "AP_FIRSTPAGE_PageSize") &&
|
|
!_cups_strcasecmp(resoption, "PageRegion")) ||
|
|
(!_cups_strcasecmp(option, "PageRegion") &&
|
|
!_cups_strcasecmp(resoption, "PageSize")) ||
|
|
(!_cups_strcasecmp(option, "AP_FIRSTPAGE_PageRegion") &&
|
|
!_cups_strcasecmp(resoption, "PageSize")) ||
|
|
(!_cups_strcasecmp(option, "AP_FIRSTPAGE_PageRegion") &&
|
|
!_cups_strcasecmp(resoption, "PageRegion"))))
|
|
continue;
|
|
|
|
/*
|
|
* Try this choice...
|
|
*/
|
|
|
|
if ((test = ppd_test_constraints(ppd, resoption, reschoice,
|
|
num_newopts, newopts,
|
|
_PPD_ALL_CONSTRAINTS)) == NULL)
|
|
{
|
|
/*
|
|
* That worked...
|
|
*/
|
|
|
|
changed = 1;
|
|
}
|
|
else
|
|
cupsArrayDelete(test);
|
|
|
|
/*
|
|
* Add the option/choice from the resolver regardless of whether it
|
|
* worked; this makes sure that we can cascade several changes to
|
|
* make things resolve...
|
|
*/
|
|
|
|
num_newopts = cupsAddOption(resoption, reschoice, num_newopts,
|
|
&newopts);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Try resolving by choosing the default values for non-installable
|
|
* options, then by iterating through the possible choices...
|
|
*/
|
|
|
|
int j; /* Looping var */
|
|
ppd_choice_t *cptr; /* Current choice */
|
|
ppd_size_t *size; /* Current page size */
|
|
|
|
|
|
for (i = consts->num_constraints, constptr = consts->constraints;
|
|
i > 0 && !changed;
|
|
i --, constptr ++)
|
|
{
|
|
/*
|
|
* Can't resolve by changing an installable option...
|
|
*/
|
|
|
|
if (constptr->installable)
|
|
continue;
|
|
|
|
/*
|
|
* Is this the option we are changing?
|
|
*/
|
|
|
|
if (option &&
|
|
(!_cups_strcasecmp(constptr->option->keyword, option) ||
|
|
(!_cups_strcasecmp(option, "PageSize") &&
|
|
!_cups_strcasecmp(constptr->option->keyword, "PageRegion")) ||
|
|
(!_cups_strcasecmp(option, "PageRegion") &&
|
|
!_cups_strcasecmp(constptr->option->keyword, "PageSize"))))
|
|
continue;
|
|
|
|
/*
|
|
* Get the current option choice...
|
|
*/
|
|
|
|
if ((value = cupsGetOption(constptr->option->keyword, num_newopts,
|
|
newopts)) == NULL)
|
|
{
|
|
if (!_cups_strcasecmp(constptr->option->keyword, "PageSize") ||
|
|
!_cups_strcasecmp(constptr->option->keyword, "PageRegion"))
|
|
{
|
|
if ((value = cupsGetOption("PageSize", num_newopts,
|
|
newopts)) == NULL)
|
|
value = cupsGetOption("PageRegion", num_newopts, newopts);
|
|
|
|
if (!value)
|
|
{
|
|
if ((size = ppdPageSize(ppd, NULL)) != NULL)
|
|
value = size->name;
|
|
else
|
|
value = "";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
marked = ppdFindMarkedChoice(ppd, constptr->option->keyword);
|
|
value = marked ? marked->choice : "";
|
|
}
|
|
}
|
|
|
|
if (!_cups_strncasecmp(value, "Custom.", 7))
|
|
value = "Custom";
|
|
|
|
/*
|
|
* Try the default choice...
|
|
*/
|
|
|
|
test = NULL;
|
|
|
|
if (_cups_strcasecmp(value, constptr->option->defchoice) &&
|
|
(test = ppd_test_constraints(ppd, constptr->option->keyword,
|
|
constptr->option->defchoice,
|
|
num_newopts, newopts,
|
|
_PPD_OPTION_CONSTRAINTS)) == NULL)
|
|
{
|
|
/*
|
|
* That worked...
|
|
*/
|
|
|
|
num_newopts = cupsAddOption(constptr->option->keyword,
|
|
constptr->option->defchoice,
|
|
num_newopts, &newopts);
|
|
changed = 1;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Try each choice instead...
|
|
*/
|
|
|
|
for (j = constptr->option->num_choices,
|
|
cptr = constptr->option->choices;
|
|
j > 0;
|
|
j --, cptr ++)
|
|
{
|
|
cupsArrayDelete(test);
|
|
test = NULL;
|
|
|
|
if (_cups_strcasecmp(value, cptr->choice) &&
|
|
_cups_strcasecmp(constptr->option->defchoice, cptr->choice) &&
|
|
_cups_strcasecmp("Custom", cptr->choice) &&
|
|
(test = ppd_test_constraints(ppd, constptr->option->keyword,
|
|
cptr->choice, num_newopts,
|
|
newopts,
|
|
_PPD_OPTION_CONSTRAINTS)) == NULL)
|
|
{
|
|
/*
|
|
* This choice works...
|
|
*/
|
|
|
|
num_newopts = cupsAddOption(constptr->option->keyword,
|
|
cptr->choice, num_newopts,
|
|
&newopts);
|
|
changed = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
cupsArrayDelete(test);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!changed)
|
|
{
|
|
DEBUG_puts("1cupsResolveConflicts: Unable to automatically resolve "
|
|
"constraint!");
|
|
goto error;
|
|
}
|
|
|
|
cupsArrayClear(pass);
|
|
cupsArrayDelete(active);
|
|
active = NULL;
|
|
}
|
|
|
|
if (tries >= 100)
|
|
goto error;
|
|
|
|
/*
|
|
* Free the caller's option array...
|
|
*/
|
|
|
|
cupsFreeOptions(*num_options, *options);
|
|
|
|
/*
|
|
* If Collate is the option we are testing, add it here. Otherwise, remove
|
|
* any Collate option from the resolve list since the filters automatically
|
|
* handle manual collation...
|
|
*/
|
|
|
|
if (option && !_cups_strcasecmp(option, "Collate"))
|
|
num_newopts = cupsAddOption(option, choice, num_newopts, &newopts);
|
|
else
|
|
num_newopts = cupsRemoveOption("Collate", num_newopts, &newopts);
|
|
|
|
/*
|
|
* Return the new list of options to the caller...
|
|
*/
|
|
|
|
*num_options = num_newopts;
|
|
*options = newopts;
|
|
|
|
cupsArrayDelete(pass);
|
|
cupsArrayDelete(resolvers);
|
|
|
|
cupsArrayRestore(ppd->sorted_attrs);
|
|
|
|
DEBUG_printf(("1cupsResolveConflicts: Returning %d options:", num_newopts));
|
|
#ifdef DEBUG
|
|
for (i = 0; i < num_newopts; i ++)
|
|
DEBUG_printf(("1cupsResolveConflicts: options[%d]: %s=%s", i,
|
|
newopts[i].name, newopts[i].value));
|
|
#endif /* DEBUG */
|
|
|
|
return (1);
|
|
|
|
/*
|
|
* If we get here, we failed to resolve...
|
|
*/
|
|
|
|
error:
|
|
|
|
cupsFreeOptions(num_newopts, newopts);
|
|
|
|
cupsArrayDelete(active);
|
|
cupsArrayDelete(pass);
|
|
cupsArrayDelete(resolvers);
|
|
|
|
cupsArrayRestore(ppd->sorted_attrs);
|
|
|
|
DEBUG_puts("1cupsResolveConflicts: Unable to resolve conflicts!");
|
|
|
|
return (0);
|
|
}
|
|
|
|
|
|
/*
|
|
* 'ppdConflicts()' - Check to see if there are any conflicts among the
|
|
* marked option choices.
|
|
*
|
|
* The returned value is the same as returned by @link ppdMarkOption@.
|
|
*/
|
|
|
|
int /* O - Number of conflicts found */
|
|
ppdConflicts(ppd_file_t *ppd) /* I - PPD to check */
|
|
{
|
|
int i, /* Looping variable */
|
|
conflicts; /* Number of conflicts */
|
|
cups_array_t *active; /* Active conflicts */
|
|
_ppd_cups_uiconsts_t *c; /* Current constraints */
|
|
_ppd_cups_uiconst_t *cptr; /* Current constraint */
|
|
ppd_option_t *o; /* Current option */
|
|
|
|
|
|
if (!ppd)
|
|
return (0);
|
|
|
|
/*
|
|
* Clear all conflicts...
|
|
*/
|
|
|
|
cupsArraySave(ppd->options);
|
|
|
|
for (o = ppdFirstOption(ppd); o; o = ppdNextOption(ppd))
|
|
o->conflicted = 0;
|
|
|
|
cupsArrayRestore(ppd->options);
|
|
|
|
/*
|
|
* Test for conflicts...
|
|
*/
|
|
|
|
active = ppd_test_constraints(ppd, NULL, NULL, 0, NULL,
|
|
_PPD_ALL_CONSTRAINTS);
|
|
conflicts = cupsArrayCount(active);
|
|
|
|
/*
|
|
* Loop through all of the UI constraints and flag any options
|
|
* that conflict...
|
|
*/
|
|
|
|
for (c = (_ppd_cups_uiconsts_t *)cupsArrayFirst(active);
|
|
c;
|
|
c = (_ppd_cups_uiconsts_t *)cupsArrayNext(active))
|
|
{
|
|
for (i = c->num_constraints, cptr = c->constraints;
|
|
i > 0;
|
|
i --, cptr ++)
|
|
cptr->option->conflicted = 1;
|
|
}
|
|
|
|
cupsArrayDelete(active);
|
|
|
|
/*
|
|
* Return the number of conflicts found...
|
|
*/
|
|
|
|
return (conflicts);
|
|
}
|
|
|
|
|
|
/*
|
|
* 'ppdInstallableConflict()' - Test whether an option choice conflicts with
|
|
* an installable option.
|
|
*
|
|
* This function tests whether a particular option choice is available based
|
|
* on constraints against options in the "InstallableOptions" group.
|
|
*
|
|
* @since CUPS 1.4/macOS 10.6@
|
|
*/
|
|
|
|
int /* O - 1 if conflicting, 0 if not conflicting */
|
|
ppdInstallableConflict(
|
|
ppd_file_t *ppd, /* I - PPD file */
|
|
const char *option, /* I - Option */
|
|
const char *choice) /* I - Choice */
|
|
{
|
|
cups_array_t *active; /* Active conflicts */
|
|
|
|
|
|
DEBUG_printf(("2ppdInstallableConflict(ppd=%p, option=\"%s\", choice=\"%s\")",
|
|
ppd, option, choice));
|
|
|
|
/*
|
|
* Range check input...
|
|
*/
|
|
|
|
if (!ppd || !option || !choice)
|
|
return (0);
|
|
|
|
/*
|
|
* Test constraints using the new option...
|
|
*/
|
|
|
|
active = ppd_test_constraints(ppd, option, choice, 0, NULL,
|
|
_PPD_INSTALLABLE_CONSTRAINTS);
|
|
|
|
cupsArrayDelete(active);
|
|
|
|
return (active != NULL);
|
|
}
|
|
|
|
|
|
/*
|
|
* 'ppd_is_installable()' - Determine whether an option is in the
|
|
* InstallableOptions group.
|
|
*/
|
|
|
|
static int /* O - 1 if installable, 0 if normal */
|
|
ppd_is_installable(
|
|
ppd_group_t *installable, /* I - InstallableOptions group */
|
|
const char *name) /* I - Option name */
|
|
{
|
|
if (installable)
|
|
{
|
|
int i; /* Looping var */
|
|
ppd_option_t *option; /* Current option */
|
|
|
|
|
|
for (i = installable->num_options, option = installable->options;
|
|
i > 0;
|
|
i --, option ++)
|
|
if (!_cups_strcasecmp(option->keyword, name))
|
|
return (1);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
|
|
/*
|
|
* 'ppd_load_constraints()' - Load constraints from a PPD file.
|
|
*/
|
|
|
|
static void
|
|
ppd_load_constraints(ppd_file_t *ppd) /* I - PPD file */
|
|
{
|
|
int i; /* Looping var */
|
|
ppd_const_t *oldconst; /* Current UIConstraints data */
|
|
ppd_attr_t *constattr; /* Current cupsUIConstraints attribute */
|
|
_ppd_cups_uiconsts_t *consts; /* Current cupsUIConstraints data */
|
|
_ppd_cups_uiconst_t *constptr; /* Current constraint */
|
|
ppd_group_t *installable; /* Installable options group */
|
|
const char *vptr; /* Pointer into constraint value */
|
|
char option[PPD_MAX_NAME], /* Option name/MainKeyword */
|
|
choice[PPD_MAX_NAME], /* Choice/OptionKeyword */
|
|
*ptr; /* Pointer into option or choice */
|
|
|
|
|
|
DEBUG_printf(("7ppd_load_constraints(ppd=%p)", ppd));
|
|
|
|
/*
|
|
* Create an array to hold the constraint data...
|
|
*/
|
|
|
|
ppd->cups_uiconstraints = cupsArrayNew(NULL, NULL);
|
|
|
|
/*
|
|
* Find the installable options group if it exists...
|
|
*/
|
|
|
|
for (i = ppd->num_groups, installable = ppd->groups;
|
|
i > 0;
|
|
i --, installable ++)
|
|
if (!_cups_strcasecmp(installable->name, "InstallableOptions"))
|
|
break;
|
|
|
|
if (i <= 0)
|
|
installable = NULL;
|
|
|
|
/*
|
|
* Load old-style [Non]UIConstraints data...
|
|
*/
|
|
|
|
for (i = ppd->num_consts, oldconst = ppd->consts; i > 0; i --, oldconst ++)
|
|
{
|
|
/*
|
|
* Weed out nearby duplicates, since the PPD spec requires that you
|
|
* define both "*Foo foo *Bar bar" and "*Bar bar *Foo foo"...
|
|
*/
|
|
|
|
if (i > 1 &&
|
|
!_cups_strcasecmp(oldconst[0].option1, oldconst[1].option2) &&
|
|
!_cups_strcasecmp(oldconst[0].choice1, oldconst[1].choice2) &&
|
|
!_cups_strcasecmp(oldconst[0].option2, oldconst[1].option1) &&
|
|
!_cups_strcasecmp(oldconst[0].choice2, oldconst[1].choice1))
|
|
continue;
|
|
|
|
/*
|
|
* Allocate memory...
|
|
*/
|
|
|
|
if ((consts = calloc(1, sizeof(_ppd_cups_uiconsts_t))) == NULL)
|
|
{
|
|
DEBUG_puts("8ppd_load_constraints: Unable to allocate memory for "
|
|
"UIConstraints!");
|
|
return;
|
|
}
|
|
|
|
if ((constptr = calloc(2, sizeof(_ppd_cups_uiconst_t))) == NULL)
|
|
{
|
|
free(consts);
|
|
DEBUG_puts("8ppd_load_constraints: Unable to allocate memory for "
|
|
"UIConstraints!");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Fill in the information...
|
|
*/
|
|
|
|
consts->num_constraints = 2;
|
|
consts->constraints = constptr;
|
|
|
|
if (!_cups_strncasecmp(oldconst->option1, "Custom", 6) &&
|
|
!_cups_strcasecmp(oldconst->choice1, "True"))
|
|
{
|
|
constptr[0].option = ppdFindOption(ppd, oldconst->option1 + 6);
|
|
constptr[0].choice = ppdFindChoice(constptr[0].option, "Custom");
|
|
constptr[0].installable = 0;
|
|
}
|
|
else
|
|
{
|
|
constptr[0].option = ppdFindOption(ppd, oldconst->option1);
|
|
constptr[0].choice = ppdFindChoice(constptr[0].option,
|
|
oldconst->choice1);
|
|
constptr[0].installable = ppd_is_installable(installable,
|
|
oldconst->option1);
|
|
}
|
|
|
|
if (!constptr[0].option || (!constptr[0].choice && oldconst->choice1[0]))
|
|
{
|
|
DEBUG_printf(("8ppd_load_constraints: Unknown option *%s %s!",
|
|
oldconst->option1, oldconst->choice1));
|
|
free(consts->constraints);
|
|
free(consts);
|
|
continue;
|
|
}
|
|
|
|
if (!_cups_strncasecmp(oldconst->option2, "Custom", 6) &&
|
|
!_cups_strcasecmp(oldconst->choice2, "True"))
|
|
{
|
|
constptr[1].option = ppdFindOption(ppd, oldconst->option2 + 6);
|
|
constptr[1].choice = ppdFindChoice(constptr[1].option, "Custom");
|
|
constptr[1].installable = 0;
|
|
}
|
|
else
|
|
{
|
|
constptr[1].option = ppdFindOption(ppd, oldconst->option2);
|
|
constptr[1].choice = ppdFindChoice(constptr[1].option,
|
|
oldconst->choice2);
|
|
constptr[1].installable = ppd_is_installable(installable,
|
|
oldconst->option2);
|
|
}
|
|
|
|
if (!constptr[1].option || (!constptr[1].choice && oldconst->choice2[0]))
|
|
{
|
|
DEBUG_printf(("8ppd_load_constraints: Unknown option *%s %s!",
|
|
oldconst->option2, oldconst->choice2));
|
|
free(consts->constraints);
|
|
free(consts);
|
|
continue;
|
|
}
|
|
|
|
consts->installable = constptr[0].installable || constptr[1].installable;
|
|
|
|
/*
|
|
* Add it to the constraints array...
|
|
*/
|
|
|
|
cupsArrayAdd(ppd->cups_uiconstraints, consts);
|
|
}
|
|
|
|
/*
|
|
* Then load new-style constraints...
|
|
*/
|
|
|
|
for (constattr = ppdFindAttr(ppd, "cupsUIConstraints", NULL);
|
|
constattr;
|
|
constattr = ppdFindNextAttr(ppd, "cupsUIConstraints", NULL))
|
|
{
|
|
if (!constattr->value)
|
|
{
|
|
DEBUG_puts("8ppd_load_constraints: Bad cupsUIConstraints value!");
|
|
continue;
|
|
}
|
|
|
|
for (i = 0, vptr = strchr(constattr->value, '*');
|
|
vptr;
|
|
i ++, vptr = strchr(vptr + 1, '*'));
|
|
|
|
if (i == 0)
|
|
{
|
|
DEBUG_puts("8ppd_load_constraints: Bad cupsUIConstraints value!");
|
|
continue;
|
|
}
|
|
|
|
if ((consts = calloc(1, sizeof(_ppd_cups_uiconsts_t))) == NULL)
|
|
{
|
|
DEBUG_puts("8ppd_load_constraints: Unable to allocate memory for "
|
|
"cupsUIConstraints!");
|
|
return;
|
|
}
|
|
|
|
if ((constptr = calloc((size_t)i, sizeof(_ppd_cups_uiconst_t))) == NULL)
|
|
{
|
|
free(consts);
|
|
DEBUG_puts("8ppd_load_constraints: Unable to allocate memory for "
|
|
"cupsUIConstraints!");
|
|
return;
|
|
}
|
|
|
|
consts->num_constraints = i;
|
|
consts->constraints = constptr;
|
|
|
|
strlcpy(consts->resolver, constattr->spec, sizeof(consts->resolver));
|
|
|
|
for (i = 0, vptr = strchr(constattr->value, '*');
|
|
vptr;
|
|
i ++, vptr = strchr(vptr, '*'), constptr ++)
|
|
{
|
|
/*
|
|
* Extract "*Option Choice" or just "*Option"...
|
|
*/
|
|
|
|
for (vptr ++, ptr = option; *vptr && !_cups_isspace(*vptr); vptr ++)
|
|
if (ptr < (option + sizeof(option) - 1))
|
|
*ptr++ = *vptr;
|
|
|
|
*ptr = '\0';
|
|
|
|
while (_cups_isspace(*vptr))
|
|
vptr ++;
|
|
|
|
if (*vptr == '*')
|
|
choice[0] = '\0';
|
|
else
|
|
{
|
|
for (ptr = choice; *vptr && !_cups_isspace(*vptr); vptr ++)
|
|
if (ptr < (choice + sizeof(choice) - 1))
|
|
*ptr++ = *vptr;
|
|
|
|
*ptr = '\0';
|
|
}
|
|
|
|
if (!_cups_strncasecmp(option, "Custom", 6) && !_cups_strcasecmp(choice, "True"))
|
|
{
|
|
_cups_strcpy(option, option + 6);
|
|
strlcpy(choice, "Custom", sizeof(choice));
|
|
}
|
|
|
|
constptr->option = ppdFindOption(ppd, option);
|
|
constptr->choice = ppdFindChoice(constptr->option, choice);
|
|
constptr->installable = ppd_is_installable(installable, option);
|
|
consts->installable |= constptr->installable;
|
|
|
|
if (!constptr->option || (!constptr->choice && choice[0]))
|
|
{
|
|
DEBUG_printf(("8ppd_load_constraints: Unknown option *%s %s!",
|
|
option, choice));
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!vptr)
|
|
cupsArrayAdd(ppd->cups_uiconstraints, consts);
|
|
else
|
|
{
|
|
free(consts->constraints);
|
|
free(consts);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* 'ppd_test_constraints()' - See if any constraints are active.
|
|
*/
|
|
|
|
static cups_array_t * /* O - Array of active constraints */
|
|
ppd_test_constraints(
|
|
ppd_file_t *ppd, /* I - PPD file */
|
|
const char *option, /* I - Current option */
|
|
const char *choice, /* I - Current choice */
|
|
int num_options, /* I - Number of additional options */
|
|
cups_option_t *options, /* I - Additional options */
|
|
int which) /* I - Which constraints to test */
|
|
{
|
|
int i; /* Looping var */
|
|
_ppd_cups_uiconsts_t *consts; /* Current constraints */
|
|
_ppd_cups_uiconst_t *constptr; /* Current constraint */
|
|
ppd_choice_t key, /* Search key */
|
|
*marked; /* Marked choice */
|
|
cups_array_t *active = NULL; /* Active constraints */
|
|
const char *value, /* Current value */
|
|
*firstvalue; /* AP_FIRSTPAGE_Keyword value */
|
|
char firstpage[255]; /* AP_FIRSTPAGE_Keyword string */
|
|
|
|
|
|
DEBUG_printf(("7ppd_test_constraints(ppd=%p, option=\"%s\", choice=\"%s\", "
|
|
"num_options=%d, options=%p, which=%d)", ppd, option, choice,
|
|
num_options, options, which));
|
|
|
|
if (!ppd->cups_uiconstraints)
|
|
ppd_load_constraints(ppd);
|
|
|
|
DEBUG_printf(("9ppd_test_constraints: %d constraints!",
|
|
cupsArrayCount(ppd->cups_uiconstraints)));
|
|
|
|
cupsArraySave(ppd->marked);
|
|
|
|
for (consts = (_ppd_cups_uiconsts_t *)cupsArrayFirst(ppd->cups_uiconstraints);
|
|
consts;
|
|
consts = (_ppd_cups_uiconsts_t *)cupsArrayNext(ppd->cups_uiconstraints))
|
|
{
|
|
DEBUG_printf(("9ppd_test_constraints: installable=%d, resolver=\"%s\", "
|
|
"num_constraints=%d option1=\"%s\", choice1=\"%s\", "
|
|
"option2=\"%s\", choice2=\"%s\", ...",
|
|
consts->installable, consts->resolver, consts->num_constraints,
|
|
consts->constraints[0].option->keyword,
|
|
consts->constraints[0].choice ?
|
|
consts->constraints[0].choice->choice : "",
|
|
consts->constraints[1].option->keyword,
|
|
consts->constraints[1].choice ?
|
|
consts->constraints[1].choice->choice : ""));
|
|
|
|
if (consts->installable && which < _PPD_INSTALLABLE_CONSTRAINTS)
|
|
continue; /* Skip installable option constraint */
|
|
|
|
if (!consts->installable && which == _PPD_INSTALLABLE_CONSTRAINTS)
|
|
continue; /* Skip non-installable option constraint */
|
|
|
|
if ((which == _PPD_OPTION_CONSTRAINTS || which == _PPD_INSTALLABLE_CONSTRAINTS) && option)
|
|
{
|
|
/*
|
|
* Skip constraints that do not involve the current option...
|
|
*/
|
|
|
|
for (i = consts->num_constraints, constptr = consts->constraints;
|
|
i > 0;
|
|
i --, constptr ++)
|
|
{
|
|
if (!_cups_strcasecmp(constptr->option->keyword, option))
|
|
break;
|
|
|
|
if (!_cups_strncasecmp(option, "AP_FIRSTPAGE_", 13) &&
|
|
!_cups_strcasecmp(constptr->option->keyword, option + 13))
|
|
break;
|
|
}
|
|
|
|
if (!i)
|
|
continue;
|
|
}
|
|
|
|
DEBUG_puts("9ppd_test_constraints: Testing...");
|
|
|
|
for (i = consts->num_constraints, constptr = consts->constraints;
|
|
i > 0;
|
|
i --, constptr ++)
|
|
{
|
|
DEBUG_printf(("9ppd_test_constraints: %s=%s?", constptr->option->keyword,
|
|
constptr->choice ? constptr->choice->choice : ""));
|
|
|
|
if (constptr->choice &&
|
|
(!_cups_strcasecmp(constptr->option->keyword, "PageSize") ||
|
|
!_cups_strcasecmp(constptr->option->keyword, "PageRegion")))
|
|
{
|
|
/*
|
|
* PageSize and PageRegion are used depending on the selected input slot
|
|
* and manual feed mode. Validate against the selected page size instead
|
|
* of an individual option...
|
|
*/
|
|
|
|
if (option && choice &&
|
|
(!_cups_strcasecmp(option, "PageSize") ||
|
|
!_cups_strcasecmp(option, "PageRegion")))
|
|
{
|
|
value = choice;
|
|
}
|
|
else if ((value = cupsGetOption("PageSize", num_options,
|
|
options)) == NULL)
|
|
if ((value = cupsGetOption("PageRegion", num_options,
|
|
options)) == NULL)
|
|
if ((value = cupsGetOption("media", num_options, options)) == NULL)
|
|
{
|
|
ppd_size_t *size = ppdPageSize(ppd, NULL);
|
|
|
|
if (size)
|
|
value = size->name;
|
|
}
|
|
|
|
if (value && !_cups_strncasecmp(value, "Custom.", 7))
|
|
value = "Custom";
|
|
|
|
if (option && choice &&
|
|
(!_cups_strcasecmp(option, "AP_FIRSTPAGE_PageSize") ||
|
|
!_cups_strcasecmp(option, "AP_FIRSTPAGE_PageRegion")))
|
|
{
|
|
firstvalue = choice;
|
|
}
|
|
else if ((firstvalue = cupsGetOption("AP_FIRSTPAGE_PageSize",
|
|
num_options, options)) == NULL)
|
|
firstvalue = cupsGetOption("AP_FIRSTPAGE_PageRegion", num_options,
|
|
options);
|
|
|
|
if (firstvalue && !_cups_strncasecmp(firstvalue, "Custom.", 7))
|
|
firstvalue = "Custom";
|
|
|
|
if ((!value || _cups_strcasecmp(value, constptr->choice->choice)) &&
|
|
(!firstvalue || _cups_strcasecmp(firstvalue, constptr->choice->choice)))
|
|
{
|
|
DEBUG_puts("9ppd_test_constraints: NO");
|
|
break;
|
|
}
|
|
}
|
|
else if (constptr->choice)
|
|
{
|
|
/*
|
|
* Compare against the constrained choice...
|
|
*/
|
|
|
|
if (option && choice && !_cups_strcasecmp(option, constptr->option->keyword))
|
|
{
|
|
if (!_cups_strncasecmp(choice, "Custom.", 7))
|
|
value = "Custom";
|
|
else
|
|
value = choice;
|
|
}
|
|
else if ((value = cupsGetOption(constptr->option->keyword, num_options,
|
|
options)) != NULL)
|
|
{
|
|
if (!_cups_strncasecmp(value, "Custom.", 7))
|
|
value = "Custom";
|
|
}
|
|
else if (constptr->choice->marked)
|
|
value = constptr->choice->choice;
|
|
else
|
|
value = NULL;
|
|
|
|
/*
|
|
* Now check AP_FIRSTPAGE_option...
|
|
*/
|
|
|
|
snprintf(firstpage, sizeof(firstpage), "AP_FIRSTPAGE_%s",
|
|
constptr->option->keyword);
|
|
|
|
if (option && choice && !_cups_strcasecmp(option, firstpage))
|
|
{
|
|
if (!_cups_strncasecmp(choice, "Custom.", 7))
|
|
firstvalue = "Custom";
|
|
else
|
|
firstvalue = choice;
|
|
}
|
|
else if ((firstvalue = cupsGetOption(firstpage, num_options,
|
|
options)) != NULL)
|
|
{
|
|
if (!_cups_strncasecmp(firstvalue, "Custom.", 7))
|
|
firstvalue = "Custom";
|
|
}
|
|
else
|
|
firstvalue = NULL;
|
|
|
|
DEBUG_printf(("9ppd_test_constraints: value=%s, firstvalue=%s", value,
|
|
firstvalue));
|
|
|
|
if ((!value || _cups_strcasecmp(value, constptr->choice->choice)) &&
|
|
(!firstvalue || _cups_strcasecmp(firstvalue, constptr->choice->choice)))
|
|
{
|
|
DEBUG_puts("9ppd_test_constraints: NO");
|
|
break;
|
|
}
|
|
}
|
|
else if (option && choice &&
|
|
!_cups_strcasecmp(option, constptr->option->keyword))
|
|
{
|
|
if (!_cups_strcasecmp(choice, "None") || !_cups_strcasecmp(choice, "Off") ||
|
|
!_cups_strcasecmp(choice, "False"))
|
|
{
|
|
DEBUG_puts("9ppd_test_constraints: NO");
|
|
break;
|
|
}
|
|
}
|
|
else if ((value = cupsGetOption(constptr->option->keyword, num_options,
|
|
options)) != NULL)
|
|
{
|
|
if (!_cups_strcasecmp(value, "None") || !_cups_strcasecmp(value, "Off") ||
|
|
!_cups_strcasecmp(value, "False"))
|
|
{
|
|
DEBUG_puts("9ppd_test_constraints: NO");
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
key.option = constptr->option;
|
|
|
|
if ((marked = (ppd_choice_t *)cupsArrayFind(ppd->marked, &key))
|
|
== NULL ||
|
|
(!_cups_strcasecmp(marked->choice, "None") ||
|
|
!_cups_strcasecmp(marked->choice, "Off") ||
|
|
!_cups_strcasecmp(marked->choice, "False")))
|
|
{
|
|
DEBUG_puts("9ppd_test_constraints: NO");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (i <= 0)
|
|
{
|
|
if (!active)
|
|
active = cupsArrayNew(NULL, NULL);
|
|
|
|
cupsArrayAdd(active, consts);
|
|
DEBUG_puts("9ppd_test_constraints: Added...");
|
|
}
|
|
}
|
|
|
|
cupsArrayRestore(ppd->marked);
|
|
|
|
DEBUG_printf(("8ppd_test_constraints: Found %d active constraints!",
|
|
cupsArrayCount(active)));
|
|
|
|
return (active);
|
|
}
|