Go to most recent revision | Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
4358 | Serge | 1 | /* |
2 | * XML DRI client-side driver configuration |
||
3 | * Copyright (C) 2003 Felix Kuehling |
||
4 | * |
||
5 | * Permission is hereby granted, free of charge, to any person obtaining a |
||
6 | * copy of this software and associated documentation files (the "Software"), |
||
7 | * to deal in the Software without restriction, including without limitation |
||
8 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
||
9 | * and/or sell copies of the Software, and to permit persons to whom the |
||
10 | * Software is furnished to do so, subject to the following conditions: |
||
11 | * |
||
12 | * The above copyright notice and this permission notice shall be included |
||
13 | * in all copies or substantial portions of the Software. |
||
14 | * |
||
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
||
16 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||
17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
||
18 | * FELIX KUEHLING, OR ANY OTHER CONTRIBUTORS BE LIABLE FOR ANY CLAIM, |
||
19 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
||
20 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE |
||
21 | * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||
22 | * |
||
23 | */ |
||
24 | /** |
||
25 | * \file xmlconfig.c |
||
26 | * \brief Driver-independent client-side part of the XML configuration |
||
27 | * \author Felix Kuehling |
||
28 | */ |
||
29 | |||
30 | #include "main/glheader.h" |
||
31 | |||
32 | #include |
||
33 | #include |
||
34 | #include |
||
35 | #include |
||
36 | #include |
||
37 | #include |
||
38 | #include "main/imports.h" |
||
39 | #include "utils.h" |
||
40 | #include "xmlconfig.h" |
||
41 | |||
42 | #undef GET_PROGRAM_NAME |
||
43 | |||
44 | #if (defined(__GNU_LIBRARY__) || defined(__GLIBC__)) && !defined(__UCLIBC__) |
||
45 | # if !defined(__GLIBC__) || (__GLIBC__ < 2) |
||
46 | /* These aren't declared in any libc5 header */ |
||
47 | extern char *program_invocation_name, *program_invocation_short_name; |
||
48 | # endif |
||
49 | # define GET_PROGRAM_NAME() program_invocation_short_name |
||
50 | #elif defined(__FreeBSD__) && (__FreeBSD__ >= 2) |
||
51 | # include |
||
52 | # if (__FreeBSD_version >= 440000) |
||
53 | # include |
||
54 | # define GET_PROGRAM_NAME() getprogname() |
||
55 | # endif |
||
56 | #elif defined(__NetBSD__) && defined(__NetBSD_Version) && (__NetBSD_Version >= 106000100) |
||
57 | # include |
||
58 | # define GET_PROGRAM_NAME() getprogname() |
||
59 | #elif defined(__APPLE__) |
||
60 | # include |
||
61 | # define GET_PROGRAM_NAME() getprogname() |
||
62 | #elif defined(__sun) |
||
63 | /* Solaris has getexecname() which returns the full path - return just |
||
64 | the basename to match BSD getprogname() */ |
||
65 | # include |
||
66 | # include |
||
67 | |||
68 | static const char *__getProgramName () { |
||
69 | static const char *progname; |
||
70 | |||
71 | if (progname == NULL) { |
||
72 | const char *e = getexecname(); |
||
73 | if (e != NULL) { |
||
74 | /* Have to make a copy since getexecname can return a readonly |
||
75 | string, but basename expects to be able to modify its arg. */ |
||
76 | char *n = strdup(e); |
||
77 | if (n != NULL) { |
||
78 | progname = basename(n); |
||
79 | } |
||
80 | } |
||
81 | } |
||
82 | return progname; |
||
83 | } |
||
84 | |||
85 | # define GET_PROGRAM_NAME() __getProgramName() |
||
86 | #endif |
||
87 | |||
88 | #if !defined(GET_PROGRAM_NAME) |
||
89 | # if defined(__OpenBSD__) || defined(NetBSD) || defined(__UCLIBC__) || defined(ANDROID) |
||
90 | /* This is a hack. It's said to work on OpenBSD, NetBSD and GNU. |
||
91 | * Rogelio M.Serrano Jr. reported it's also working with UCLIBC. It's |
||
92 | * used as a last resort, if there is no documented facility available. */ |
||
93 | static const char *__getProgramName () { |
||
94 | extern const char *__progname; |
||
95 | char * arg = strrchr(__progname, '/'); |
||
96 | if (arg) |
||
97 | return arg+1; |
||
98 | else |
||
99 | return __progname; |
||
100 | } |
||
101 | # define GET_PROGRAM_NAME() __getProgramName() |
||
102 | # else |
||
103 | # define GET_PROGRAM_NAME() "" |
||
104 | # warning "Per application configuration won't work with your OS version." |
||
105 | # endif |
||
106 | #endif |
||
107 | |||
108 | /** \brief Find an option in an option cache with the name as key */ |
||
109 | static GLuint findOption (const driOptionCache *cache, const char *name) { |
||
110 | GLuint len = strlen (name); |
||
111 | GLuint size = 1 << cache->tableSize, mask = size - 1; |
||
112 | GLuint hash = 0; |
||
113 | GLuint i, shift; |
||
114 | |||
115 | /* compute a hash from the variable length name */ |
||
116 | for (i = 0, shift = 0; i < len; ++i, shift = (shift+8) & 31) |
||
117 | hash += (GLuint)name[i] << shift; |
||
118 | hash *= hash; |
||
119 | hash = (hash >> (16-cache->tableSize/2)) & mask; |
||
120 | |||
121 | /* this is just the starting point of the linear search for the option */ |
||
122 | for (i = 0; i < size; ++i, hash = (hash+1) & mask) { |
||
123 | /* if we hit an empty entry then the option is not defined (yet) */ |
||
124 | if (cache->info[hash].name == 0) |
||
125 | break; |
||
126 | else if (!strcmp (name, cache->info[hash].name)) |
||
127 | break; |
||
128 | } |
||
129 | /* this assertion fails if the hash table is full */ |
||
130 | assert (i < size); |
||
131 | |||
132 | return hash; |
||
133 | } |
||
134 | |||
135 | /** \brief Count the real number of options in an option cache */ |
||
136 | static GLuint countOptions (const driOptionCache *cache) { |
||
137 | GLuint size = 1 << cache->tableSize; |
||
138 | GLuint i, count = 0; |
||
139 | for (i = 0; i < size; ++i) |
||
140 | if (cache->info[i].name) |
||
141 | count++; |
||
142 | return count; |
||
143 | } |
||
144 | |||
145 | /** \brief Like strdup but using malloc and with error checking. */ |
||
146 | #define XSTRDUP(dest,source) do { \ |
||
147 | GLuint len = strlen (source); \ |
||
148 | if (!(dest = malloc(len+1))) { \ |
||
149 | fprintf (stderr, "%s: %d: out of memory.\n", __FILE__, __LINE__); \ |
||
150 | abort(); \ |
||
151 | } \ |
||
152 | memcpy (dest, source, len+1); \ |
||
153 | } while (0) |
||
154 | |||
155 | static int compare (const void *a, const void *b) { |
||
156 | return strcmp (*(char *const*)a, *(char *const*)b); |
||
157 | } |
||
158 | /** \brief Binary search in a string array. */ |
||
159 | static GLuint bsearchStr (const XML_Char *name, |
||
160 | const XML_Char *elems[], GLuint count) { |
||
161 | const XML_Char **found; |
||
162 | found = bsearch (&name, elems, count, sizeof (XML_Char *), compare); |
||
163 | if (found) |
||
164 | return found - elems; |
||
165 | else |
||
166 | return count; |
||
167 | } |
||
168 | |||
169 | /** \brief Locale-independent integer parser. |
||
170 | * |
||
171 | * Works similar to strtol. Leading space is NOT skipped. The input |
||
172 | * number may have an optional sign. Radix is specified by base. If |
||
173 | * base is 0 then decimal is assumed unless the input number is |
||
174 | * prefixed by 0x or 0X for hexadecimal or 0 for octal. After |
||
175 | * returning tail points to the first character that is not part of |
||
176 | * the integer number. If no number was found then tail points to the |
||
177 | * start of the input string. */ |
||
178 | static GLint strToI (const XML_Char *string, const XML_Char **tail, int base) { |
||
179 | GLint radix = base == 0 ? 10 : base; |
||
180 | GLint result = 0; |
||
181 | GLint sign = 1; |
||
182 | GLboolean numberFound = GL_FALSE; |
||
183 | const XML_Char *start = string; |
||
184 | |||
185 | assert (radix >= 2 && radix <= 36); |
||
186 | |||
187 | if (*string == '-') { |
||
188 | sign = -1; |
||
189 | string++; |
||
190 | } else if (*string == '+') |
||
191 | string++; |
||
192 | if (base == 0 && *string == '0') { |
||
193 | numberFound = GL_TRUE; |
||
194 | if (*(string+1) == 'x' || *(string+1) == 'X') { |
||
195 | radix = 16; |
||
196 | string += 2; |
||
197 | } else { |
||
198 | radix = 8; |
||
199 | string++; |
||
200 | } |
||
201 | } |
||
202 | do { |
||
203 | GLint digit = -1; |
||
204 | if (radix <= 10) { |
||
205 | if (*string >= '0' && *string < '0' + radix) |
||
206 | digit = *string - '0'; |
||
207 | } else { |
||
208 | if (*string >= '0' && *string <= '9') |
||
209 | digit = *string - '0'; |
||
210 | else if (*string >= 'a' && *string < 'a' + radix - 10) |
||
211 | digit = *string - 'a' + 10; |
||
212 | else if (*string >= 'A' && *string < 'A' + radix - 10) |
||
213 | digit = *string - 'A' + 10; |
||
214 | } |
||
215 | if (digit != -1) { |
||
216 | numberFound = GL_TRUE; |
||
217 | result = radix*result + digit; |
||
218 | string++; |
||
219 | } else |
||
220 | break; |
||
221 | } while (GL_TRUE); |
||
222 | *tail = numberFound ? string : start; |
||
223 | return sign * result; |
||
224 | } |
||
225 | |||
226 | /** \brief Locale-independent floating-point parser. |
||
227 | * |
||
228 | * Works similar to strtod. Leading space is NOT skipped. The input |
||
229 | * number may have an optional sign. '.' is interpreted as decimal |
||
230 | * point and may occur at most once. Optionally the number may end in |
||
231 | * [eE] |
||
232 | * strToI. In that case the result is number * 10^exponent. After |
||
233 | * returning tail points to the first character that is not part of |
||
234 | * the floating point number. If no number was found then tail points |
||
235 | * to the start of the input string. |
||
236 | * |
||
237 | * Uses two passes for maximum accuracy. */ |
||
238 | static GLfloat strToF (const XML_Char *string, const XML_Char **tail) { |
||
239 | GLint nDigits = 0, pointPos, exponent; |
||
240 | GLfloat sign = 1.0f, result = 0.0f, scale; |
||
241 | const XML_Char *start = string, *numStart; |
||
242 | |||
243 | /* sign */ |
||
244 | if (*string == '-') { |
||
245 | sign = -1.0f; |
||
246 | string++; |
||
247 | } else if (*string == '+') |
||
248 | string++; |
||
249 | |||
250 | /* first pass: determine position of decimal point, number of |
||
251 | * digits, exponent and the end of the number. */ |
||
252 | numStart = string; |
||
253 | while (*string >= '0' && *string <= '9') { |
||
254 | string++; |
||
255 | nDigits++; |
||
256 | } |
||
257 | pointPos = nDigits; |
||
258 | if (*string == '.') { |
||
259 | string++; |
||
260 | while (*string >= '0' && *string <= '9') { |
||
261 | string++; |
||
262 | nDigits++; |
||
263 | } |
||
264 | } |
||
265 | if (nDigits == 0) { |
||
266 | /* no digits, no number */ |
||
267 | *tail = start; |
||
268 | return 0.0f; |
||
269 | } |
||
270 | *tail = string; |
||
271 | if (*string == 'e' || *string == 'E') { |
||
272 | const XML_Char *expTail; |
||
273 | exponent = strToI (string+1, &expTail, 10); |
||
274 | if (expTail == string+1) |
||
275 | exponent = 0; |
||
276 | else |
||
277 | *tail = expTail; |
||
278 | } else |
||
279 | exponent = 0; |
||
280 | string = numStart; |
||
281 | |||
282 | /* scale of the first digit */ |
||
283 | scale = sign * (GLfloat)pow (10.0, (GLdouble)(pointPos-1 + exponent)); |
||
284 | |||
285 | /* second pass: parse digits */ |
||
286 | do { |
||
287 | if (*string != '.') { |
||
288 | assert (*string >= '0' && *string <= '9'); |
||
289 | result += scale * (GLfloat)(*string - '0'); |
||
290 | scale *= 0.1f; |
||
291 | nDigits--; |
||
292 | } |
||
293 | string++; |
||
294 | } while (nDigits > 0); |
||
295 | |||
296 | return result; |
||
297 | } |
||
298 | |||
299 | /** \brief Parse a value of a given type. */ |
||
300 | static GLboolean parseValue (driOptionValue *v, driOptionType type, |
||
301 | const XML_Char *string) { |
||
302 | const XML_Char *tail = NULL; |
||
303 | /* skip leading white-space */ |
||
304 | string += strspn (string, " \f\n\r\t\v"); |
||
305 | switch (type) { |
||
306 | case DRI_BOOL: |
||
307 | if (!strcmp (string, "false")) { |
||
308 | v->_bool = GL_FALSE; |
||
309 | tail = string + 5; |
||
310 | } else if (!strcmp (string, "true")) { |
||
311 | v->_bool = GL_TRUE; |
||
312 | tail = string + 4; |
||
313 | } |
||
314 | else |
||
315 | return GL_FALSE; |
||
316 | break; |
||
317 | case DRI_ENUM: /* enum is just a special integer */ |
||
318 | case DRI_INT: |
||
319 | v->_int = strToI (string, &tail, 0); |
||
320 | break; |
||
321 | case DRI_FLOAT: |
||
322 | v->_float = strToF (string, &tail); |
||
323 | break; |
||
324 | } |
||
325 | |||
326 | if (tail == string) |
||
327 | return GL_FALSE; /* empty string (or containing only white-space) */ |
||
328 | /* skip trailing white space */ |
||
329 | if (*tail) |
||
330 | tail += strspn (tail, " \f\n\r\t\v"); |
||
331 | if (*tail) |
||
332 | return GL_FALSE; /* something left over that is not part of value */ |
||
333 | |||
334 | return GL_TRUE; |
||
335 | } |
||
336 | |||
337 | /** \brief Parse a list of ranges of type info->type. */ |
||
338 | static GLboolean parseRanges (driOptionInfo *info, const XML_Char *string) { |
||
339 | XML_Char *cp, *range; |
||
340 | GLuint nRanges, i; |
||
341 | driOptionRange *ranges; |
||
342 | |||
343 | XSTRDUP (cp, string); |
||
344 | /* pass 1: determine the number of ranges (number of commas + 1) */ |
||
345 | range = cp; |
||
346 | for (nRanges = 1; *range; ++range) |
||
347 | if (*range == ',') |
||
348 | ++nRanges; |
||
349 | |||
350 | if ((ranges = malloc(nRanges*sizeof(driOptionRange))) == NULL) { |
||
351 | fprintf (stderr, "%s: %d: out of memory.\n", __FILE__, __LINE__); |
||
352 | abort(); |
||
353 | } |
||
354 | |||
355 | /* pass 2: parse all ranges into preallocated array */ |
||
356 | range = cp; |
||
357 | for (i = 0; i < nRanges; ++i) { |
||
358 | XML_Char *end, *sep; |
||
359 | assert (range); |
||
360 | end = strchr (range, ','); |
||
361 | if (end) |
||
362 | *end = '\0'; |
||
363 | sep = strchr (range, ':'); |
||
364 | if (sep) { /* non-empty interval */ |
||
365 | *sep = '\0'; |
||
366 | if (!parseValue (&ranges[i].start, info->type, range) || |
||
367 | !parseValue (&ranges[i].end, info->type, sep+1)) |
||
368 | break; |
||
369 | if (info->type == DRI_INT && |
||
370 | ranges[i].start._int > ranges[i].end._int) |
||
371 | break; |
||
372 | if (info->type == DRI_FLOAT && |
||
373 | ranges[i].start._float > ranges[i].end._float) |
||
374 | break; |
||
375 | } else { /* empty interval */ |
||
376 | if (!parseValue (&ranges[i].start, info->type, range)) |
||
377 | break; |
||
378 | ranges[i].end = ranges[i].start; |
||
379 | } |
||
380 | if (end) |
||
381 | range = end+1; |
||
382 | else |
||
383 | range = NULL; |
||
384 | } |
||
385 | free(cp); |
||
386 | if (i < nRanges) { |
||
387 | free(ranges); |
||
388 | return GL_FALSE; |
||
389 | } else |
||
390 | assert (range == NULL); |
||
391 | |||
392 | info->nRanges = nRanges; |
||
393 | info->ranges = ranges; |
||
394 | return GL_TRUE; |
||
395 | } |
||
396 | |||
397 | /** \brief Check if a value is in one of info->ranges. */ |
||
398 | static GLboolean checkValue (const driOptionValue *v, const driOptionInfo *info) { |
||
399 | GLuint i; |
||
400 | assert (info->type != DRI_BOOL); /* should be caught by the parser */ |
||
401 | if (info->nRanges == 0) |
||
402 | return GL_TRUE; |
||
403 | switch (info->type) { |
||
404 | case DRI_ENUM: /* enum is just a special integer */ |
||
405 | case DRI_INT: |
||
406 | for (i = 0; i < info->nRanges; ++i) |
||
407 | if (v->_int >= info->ranges[i].start._int && |
||
408 | v->_int <= info->ranges[i].end._int) |
||
409 | return GL_TRUE; |
||
410 | break; |
||
411 | case DRI_FLOAT: |
||
412 | for (i = 0; i < info->nRanges; ++i) |
||
413 | if (v->_float >= info->ranges[i].start._float && |
||
414 | v->_float <= info->ranges[i].end._float) |
||
415 | return GL_TRUE; |
||
416 | break; |
||
417 | default: |
||
418 | assert (0); /* should never happen */ |
||
419 | } |
||
420 | return GL_FALSE; |
||
421 | } |
||
422 | |||
423 | /** |
||
424 | * Print message to \c stderr if the \c LIBGL_DEBUG environment variable |
||
425 | * is set. |
||
426 | * |
||
427 | * Is called from the drivers. |
||
428 | * |
||
429 | * \param f \c printf like format string. |
||
430 | */ |
||
431 | static void |
||
432 | __driUtilMessage(const char *f, ...) |
||
433 | { |
||
434 | va_list args; |
||
435 | |||
436 | if (getenv("LIBGL_DEBUG")) { |
||
437 | fprintf(stderr, "libGL: "); |
||
438 | va_start(args, f); |
||
439 | vfprintf(stderr, f, args); |
||
440 | va_end(args); |
||
441 | fprintf(stderr, "\n"); |
||
442 | } |
||
443 | } |
||
444 | |||
445 | /** \brief Output a warning message. */ |
||
446 | #define XML_WARNING1(msg) do {\ |
||
447 | __driUtilMessage ("Warning in %s line %d, column %d: "msg, data->name, \ |
||
448 | (int) XML_GetCurrentLineNumber(data->parser), \ |
||
449 | (int) XML_GetCurrentColumnNumber(data->parser)); \ |
||
450 | } while (0) |
||
451 | #define XML_WARNING(msg,args...) do { \ |
||
452 | __driUtilMessage ("Warning in %s line %d, column %d: "msg, data->name, \ |
||
453 | (int) XML_GetCurrentLineNumber(data->parser), \ |
||
454 | (int) XML_GetCurrentColumnNumber(data->parser), \ |
||
455 | args); \ |
||
456 | } while (0) |
||
457 | /** \brief Output an error message. */ |
||
458 | #define XML_ERROR1(msg) do { \ |
||
459 | __driUtilMessage ("Error in %s line %d, column %d: "msg, data->name, \ |
||
460 | (int) XML_GetCurrentLineNumber(data->parser), \ |
||
461 | (int) XML_GetCurrentColumnNumber(data->parser)); \ |
||
462 | } while (0) |
||
463 | #define XML_ERROR(msg,args...) do { \ |
||
464 | __driUtilMessage ("Error in %s line %d, column %d: "msg, data->name, \ |
||
465 | (int) XML_GetCurrentLineNumber(data->parser), \ |
||
466 | (int) XML_GetCurrentColumnNumber(data->parser), \ |
||
467 | args); \ |
||
468 | } while (0) |
||
469 | /** \brief Output a fatal error message and abort. */ |
||
470 | #define XML_FATAL1(msg) do { \ |
||
471 | fprintf (stderr, "Fatal error in %s line %d, column %d: "msg"\n", \ |
||
472 | data->name, \ |
||
473 | (int) XML_GetCurrentLineNumber(data->parser), \ |
||
474 | (int) XML_GetCurrentColumnNumber(data->parser)); \ |
||
475 | abort();\ |
||
476 | } while (0) |
||
477 | #define XML_FATAL(msg,args...) do { \ |
||
478 | fprintf (stderr, "Fatal error in %s line %d, column %d: "msg"\n", \ |
||
479 | data->name, \ |
||
480 | (int) XML_GetCurrentLineNumber(data->parser), \ |
||
481 | (int) XML_GetCurrentColumnNumber(data->parser), \ |
||
482 | args); \ |
||
483 | abort();\ |
||
484 | } while (0) |
||
485 | |||
486 | /** \brief Parser context for __driConfigOptions. */ |
||
487 | struct OptInfoData { |
||
488 | const char *name; |
||
489 | XML_Parser parser; |
||
490 | driOptionCache *cache; |
||
491 | GLboolean inDriInfo; |
||
492 | GLboolean inSection; |
||
493 | GLboolean inDesc; |
||
494 | GLboolean inOption; |
||
495 | GLboolean inEnum; |
||
496 | int curOption; |
||
497 | }; |
||
498 | |||
499 | /** \brief Elements in __driConfigOptions. */ |
||
500 | enum OptInfoElem { |
||
501 | OI_DESCRIPTION = 0, OI_DRIINFO, OI_ENUM, OI_OPTION, OI_SECTION, OI_COUNT |
||
502 | }; |
||
503 | static const XML_Char *OptInfoElems[] = { |
||
504 | "description", "driinfo", "enum", "option", "section" |
||
505 | }; |
||
506 | |||
507 | /** \brief Parse attributes of an enum element. |
||
508 | * |
||
509 | * We're not actually interested in the data. Just make sure this is ok |
||
510 | * for external configuration tools. |
||
511 | */ |
||
512 | static void parseEnumAttr (struct OptInfoData *data, const XML_Char **attr) { |
||
513 | GLuint i; |
||
514 | const XML_Char *value = NULL, *text = NULL; |
||
515 | driOptionValue v; |
||
516 | GLuint opt = data->curOption; |
||
517 | for (i = 0; attr[i]; i += 2) { |
||
518 | if (!strcmp (attr[i], "value")) value = attr[i+1]; |
||
519 | else if (!strcmp (attr[i], "text")) text = attr[i+1]; |
||
520 | else XML_FATAL("illegal enum attribute: %s.", attr[i]); |
||
521 | } |
||
522 | if (!value) XML_FATAL1 ("value attribute missing in enum."); |
||
523 | if (!text) XML_FATAL1 ("text attribute missing in enum."); |
||
524 | if (!parseValue (&v, data->cache->info[opt].type, value)) |
||
525 | XML_FATAL ("illegal enum value: %s.", value); |
||
526 | if (!checkValue (&v, &data->cache->info[opt])) |
||
527 | XML_FATAL ("enum value out of valid range: %s.", value); |
||
528 | } |
||
529 | |||
530 | /** \brief Parse attributes of a description element. |
||
531 | * |
||
532 | * We're not actually interested in the data. Just make sure this is ok |
||
533 | * for external configuration tools. |
||
534 | */ |
||
535 | static void parseDescAttr (struct OptInfoData *data, const XML_Char **attr) { |
||
536 | GLuint i; |
||
537 | const XML_Char *lang = NULL, *text = NULL; |
||
538 | for (i = 0; attr[i]; i += 2) { |
||
539 | if (!strcmp (attr[i], "lang")) lang = attr[i+1]; |
||
540 | else if (!strcmp (attr[i], "text")) text = attr[i+1]; |
||
541 | else XML_FATAL("illegal description attribute: %s.", attr[i]); |
||
542 | } |
||
543 | if (!lang) XML_FATAL1 ("lang attribute missing in description."); |
||
544 | if (!text) XML_FATAL1 ("text attribute missing in description."); |
||
545 | } |
||
546 | |||
547 | /** \brief Parse attributes of an option element. */ |
||
548 | static void parseOptInfoAttr (struct OptInfoData *data, const XML_Char **attr) { |
||
549 | enum OptAttr {OA_DEFAULT = 0, OA_NAME, OA_TYPE, OA_VALID, OA_COUNT}; |
||
550 | static const XML_Char *optAttr[] = {"default", "name", "type", "valid"}; |
||
551 | const XML_Char *attrVal[OA_COUNT] = {NULL, NULL, NULL, NULL}; |
||
552 | const char *defaultVal; |
||
553 | driOptionCache *cache = data->cache; |
||
554 | GLuint opt, i; |
||
555 | for (i = 0; attr[i]; i += 2) { |
||
556 | GLuint attrName = bsearchStr (attr[i], optAttr, OA_COUNT); |
||
557 | if (attrName >= OA_COUNT) |
||
558 | XML_FATAL ("illegal option attribute: %s", attr[i]); |
||
559 | attrVal[attrName] = attr[i+1]; |
||
560 | } |
||
561 | if (!attrVal[OA_NAME]) XML_FATAL1 ("name attribute missing in option."); |
||
562 | if (!attrVal[OA_TYPE]) XML_FATAL1 ("type attribute missing in option."); |
||
563 | if (!attrVal[OA_DEFAULT]) XML_FATAL1 ("default attribute missing in option."); |
||
564 | |||
565 | opt = findOption (cache, attrVal[OA_NAME]); |
||
566 | if (cache->info[opt].name) |
||
567 | XML_FATAL ("option %s redefined.", attrVal[OA_NAME]); |
||
568 | data->curOption = opt; |
||
569 | |||
570 | XSTRDUP (cache->info[opt].name, attrVal[OA_NAME]); |
||
571 | |||
572 | if (!strcmp (attrVal[OA_TYPE], "bool")) |
||
573 | cache->info[opt].type = DRI_BOOL; |
||
574 | else if (!strcmp (attrVal[OA_TYPE], "enum")) |
||
575 | cache->info[opt].type = DRI_ENUM; |
||
576 | else if (!strcmp (attrVal[OA_TYPE], "int")) |
||
577 | cache->info[opt].type = DRI_INT; |
||
578 | else if (!strcmp (attrVal[OA_TYPE], "float")) |
||
579 | cache->info[opt].type = DRI_FLOAT; |
||
580 | else |
||
581 | XML_FATAL ("illegal type in option: %s.", attrVal[OA_TYPE]); |
||
582 | |||
583 | defaultVal = getenv (cache->info[opt].name); |
||
584 | if (defaultVal != NULL) { |
||
585 | /* don't use XML_WARNING, we want the user to see this! */ |
||
586 | fprintf (stderr, |
||
587 | "ATTENTION: default value of option %s overridden by environment.\n", |
||
588 | cache->info[opt].name); |
||
589 | } else |
||
590 | defaultVal = attrVal[OA_DEFAULT]; |
||
591 | if (!parseValue (&cache->values[opt], cache->info[opt].type, defaultVal)) |
||
592 | XML_FATAL ("illegal default value for %s: %s.", cache->info[opt].name, defaultVal); |
||
593 | |||
594 | if (attrVal[OA_VALID]) { |
||
595 | if (cache->info[opt].type == DRI_BOOL) |
||
596 | XML_FATAL1 ("boolean option with valid attribute."); |
||
597 | if (!parseRanges (&cache->info[opt], attrVal[OA_VALID])) |
||
598 | XML_FATAL ("illegal valid attribute: %s.", attrVal[OA_VALID]); |
||
599 | if (!checkValue (&cache->values[opt], &cache->info[opt])) |
||
600 | XML_FATAL ("default value out of valid range '%s': %s.", |
||
601 | attrVal[OA_VALID], defaultVal); |
||
602 | } else if (cache->info[opt].type == DRI_ENUM) { |
||
603 | XML_FATAL1 ("valid attribute missing in option (mandatory for enums)."); |
||
604 | } else { |
||
605 | cache->info[opt].nRanges = 0; |
||
606 | cache->info[opt].ranges = NULL; |
||
607 | } |
||
608 | } |
||
609 | |||
610 | /** \brief Handler for start element events. */ |
||
611 | static void optInfoStartElem (void *userData, const XML_Char *name, |
||
612 | const XML_Char **attr) { |
||
613 | struct OptInfoData *data = (struct OptInfoData *)userData; |
||
614 | enum OptInfoElem elem = bsearchStr (name, OptInfoElems, OI_COUNT); |
||
615 | switch (elem) { |
||
616 | case OI_DRIINFO: |
||
617 | if (data->inDriInfo) |
||
618 | XML_FATAL1 ("nested |
||
619 | if (attr[0]) |
||
620 | XML_FATAL1 ("attributes specified on |
||
621 | data->inDriInfo = GL_TRUE; |
||
622 | break; |
||
623 | case OI_SECTION: |
||
624 | if (!data->inDriInfo) |
||
625 | XML_FATAL1 (" |
||
626 | if (data->inSection) |
||
627 | XML_FATAL1 ("nested |
||
628 | if (attr[0]) |
||
629 | XML_FATAL1 ("attributes specified on |
||
630 | data->inSection = GL_TRUE; |
||
631 | break; |
||
632 | case OI_DESCRIPTION: |
||
633 | if (!data->inSection && !data->inOption) |
||
634 | XML_FATAL1 (" |
||
635 | if (data->inDesc) |
||
636 | XML_FATAL1 ("nested |
||
637 | data->inDesc = GL_TRUE; |
||
638 | parseDescAttr (data, attr); |
||
639 | break; |
||
640 | case OI_OPTION: |
||
641 | if (!data->inSection) |
||
642 | XML_FATAL1 (" |
||
643 | if (data->inDesc) |
||
644 | XML_FATAL1 (" |
||
645 | if (data->inOption) |
||
646 | XML_FATAL1 ("nested |
||
647 | data->inOption = GL_TRUE; |
||
648 | parseOptInfoAttr (data, attr); |
||
649 | break; |
||
650 | case OI_ENUM: |
||
651 | if (!(data->inOption && data->inDesc)) |
||
652 | XML_FATAL1 (" |
||
653 | if (data->inEnum) |
||
654 | XML_FATAL1 ("nested |
||
655 | data->inEnum = GL_TRUE; |
||
656 | parseEnumAttr (data, attr); |
||
657 | break; |
||
658 | default: |
||
659 | XML_FATAL ("unknown element: %s.", name); |
||
660 | } |
||
661 | } |
||
662 | |||
663 | /** \brief Handler for end element events. */ |
||
664 | static void optInfoEndElem (void *userData, const XML_Char *name) { |
||
665 | struct OptInfoData *data = (struct OptInfoData *)userData; |
||
666 | enum OptInfoElem elem = bsearchStr (name, OptInfoElems, OI_COUNT); |
||
667 | switch (elem) { |
||
668 | case OI_DRIINFO: |
||
669 | data->inDriInfo = GL_FALSE; |
||
670 | break; |
||
671 | case OI_SECTION: |
||
672 | data->inSection = GL_FALSE; |
||
673 | break; |
||
674 | case OI_DESCRIPTION: |
||
675 | data->inDesc = GL_FALSE; |
||
676 | break; |
||
677 | case OI_OPTION: |
||
678 | data->inOption = GL_FALSE; |
||
679 | break; |
||
680 | case OI_ENUM: |
||
681 | data->inEnum = GL_FALSE; |
||
682 | break; |
||
683 | default: |
||
684 | assert (0); /* should have been caught by StartElem */ |
||
685 | } |
||
686 | } |
||
687 | |||
688 | void driParseOptionInfo (driOptionCache *info, |
||
689 | const char *configOptions, GLuint nConfigOptions) { |
||
690 | XML_Parser p; |
||
691 | int status; |
||
692 | struct OptInfoData userData; |
||
693 | struct OptInfoData *data = &userData; |
||
694 | GLuint realNoptions; |
||
695 | |||
696 | /* determine hash table size and allocate memory: |
||
697 | * 3/2 of the number of options, rounded up, so there remains always |
||
698 | * at least one free entry. This is needed for detecting undefined |
||
699 | * options in configuration files without getting a hash table overflow. |
||
700 | * Round this up to a power of two. */ |
||
701 | GLuint minSize = (nConfigOptions*3 + 1) / 2; |
||
702 | GLuint size, log2size; |
||
703 | for (size = 1, log2size = 0; size < minSize; size <<= 1, ++log2size); |
||
704 | info->tableSize = log2size; |
||
705 | info->info = calloc(size, sizeof (driOptionInfo)); |
||
706 | info->values = calloc(size, sizeof (driOptionValue)); |
||
707 | if (info->info == NULL || info->values == NULL) { |
||
708 | fprintf (stderr, "%s: %d: out of memory.\n", __FILE__, __LINE__); |
||
709 | abort(); |
||
710 | } |
||
711 | |||
712 | p = XML_ParserCreate ("UTF-8"); /* always UTF-8 */ |
||
713 | XML_SetElementHandler (p, optInfoStartElem, optInfoEndElem); |
||
714 | XML_SetUserData (p, data); |
||
715 | |||
716 | userData.name = "__driConfigOptions"; |
||
717 | userData.parser = p; |
||
718 | userData.cache = info; |
||
719 | userData.inDriInfo = GL_FALSE; |
||
720 | userData.inSection = GL_FALSE; |
||
721 | userData.inDesc = GL_FALSE; |
||
722 | userData.inOption = GL_FALSE; |
||
723 | userData.inEnum = GL_FALSE; |
||
724 | userData.curOption = -1; |
||
725 | |||
726 | status = XML_Parse (p, configOptions, strlen (configOptions), 1); |
||
727 | if (!status) |
||
728 | XML_FATAL ("%s.", XML_ErrorString(XML_GetErrorCode(p))); |
||
729 | |||
730 | XML_ParserFree (p); |
||
731 | |||
732 | /* Check if the actual number of options matches nConfigOptions. |
||
733 | * A mismatch is not fatal (a hash table overflow would be) but we |
||
734 | * want the driver developer's attention anyway. */ |
||
735 | realNoptions = countOptions (info); |
||
736 | if (realNoptions != nConfigOptions) { |
||
737 | fprintf (stderr, |
||
738 | "Error: nConfigOptions (%u) does not match the actual number of options in\n" |
||
739 | " __driConfigOptions (%u).\n", |
||
740 | nConfigOptions, realNoptions); |
||
741 | } |
||
742 | } |
||
743 | |||
744 | /** \brief Parser context for configuration files. */ |
||
745 | struct OptConfData { |
||
746 | const char *name; |
||
747 | XML_Parser parser; |
||
748 | driOptionCache *cache; |
||
749 | GLint screenNum; |
||
750 | const char *driverName, *execName; |
||
751 | GLuint ignoringDevice; |
||
752 | GLuint ignoringApp; |
||
753 | GLuint inDriConf; |
||
754 | GLuint inDevice; |
||
755 | GLuint inApp; |
||
756 | GLuint inOption; |
||
757 | }; |
||
758 | |||
759 | /** \brief Elements in configuration files. */ |
||
760 | enum OptConfElem { |
||
761 | OC_APPLICATION = 0, OC_DEVICE, OC_DRICONF, OC_OPTION, OC_COUNT |
||
762 | }; |
||
763 | static const XML_Char *OptConfElems[] = { |
||
764 | "application", "device", "driconf", "option" |
||
765 | }; |
||
766 | |||
767 | /** \brief Parse attributes of a device element. */ |
||
768 | static void parseDeviceAttr (struct OptConfData *data, const XML_Char **attr) { |
||
769 | GLuint i; |
||
770 | const XML_Char *driver = NULL, *screen = NULL; |
||
771 | for (i = 0; attr[i]; i += 2) { |
||
772 | if (!strcmp (attr[i], "driver")) driver = attr[i+1]; |
||
773 | else if (!strcmp (attr[i], "screen")) screen = attr[i+1]; |
||
774 | else XML_WARNING("unknown device attribute: %s.", attr[i]); |
||
775 | } |
||
776 | if (driver && strcmp (driver, data->driverName)) |
||
777 | data->ignoringDevice = data->inDevice; |
||
778 | else if (screen) { |
||
779 | driOptionValue screenNum; |
||
780 | if (!parseValue (&screenNum, DRI_INT, screen)) |
||
781 | XML_WARNING("illegal screen number: %s.", screen); |
||
782 | else if (screenNum._int != data->screenNum) |
||
783 | data->ignoringDevice = data->inDevice; |
||
784 | } |
||
785 | } |
||
786 | |||
787 | /** \brief Parse attributes of an application element. */ |
||
788 | static void parseAppAttr (struct OptConfData *data, const XML_Char **attr) { |
||
789 | GLuint i; |
||
790 | const XML_Char *exec = NULL; |
||
791 | for (i = 0; attr[i]; i += 2) { |
||
792 | if (!strcmp (attr[i], "name")) /* not needed here */; |
||
793 | else if (!strcmp (attr[i], "executable")) exec = attr[i+1]; |
||
794 | else XML_WARNING("unknown application attribute: %s.", attr[i]); |
||
795 | } |
||
796 | if (exec && strcmp (exec, data->execName)) |
||
797 | data->ignoringApp = data->inApp; |
||
798 | } |
||
799 | |||
800 | /** \brief Parse attributes of an option element. */ |
||
801 | static void parseOptConfAttr (struct OptConfData *data, const XML_Char **attr) { |
||
802 | GLuint i; |
||
803 | const XML_Char *name = NULL, *value = NULL; |
||
804 | for (i = 0; attr[i]; i += 2) { |
||
805 | if (!strcmp (attr[i], "name")) name = attr[i+1]; |
||
806 | else if (!strcmp (attr[i], "value")) value = attr[i+1]; |
||
807 | else XML_WARNING("unknown option attribute: %s.", attr[i]); |
||
808 | } |
||
809 | if (!name) XML_WARNING1 ("name attribute missing in option."); |
||
810 | if (!value) XML_WARNING1 ("value attribute missing in option."); |
||
811 | if (name && value) { |
||
812 | driOptionCache *cache = data->cache; |
||
813 | GLuint opt = findOption (cache, name); |
||
814 | if (cache->info[opt].name == NULL) |
||
815 | /* don't use XML_WARNING, drirc defines options for all drivers, |
||
816 | * but not all drivers support them */ |
||
817 | return; |
||
818 | else if (getenv (cache->info[opt].name)) |
||
819 | /* don't use XML_WARNING, we want the user to see this! */ |
||
820 | fprintf (stderr, "ATTENTION: option value of option %s ignored.\n", |
||
821 | cache->info[opt].name); |
||
822 | else if (!parseValue (&cache->values[opt], cache->info[opt].type, value)) |
||
823 | XML_WARNING ("illegal option value: %s.", value); |
||
824 | } |
||
825 | } |
||
826 | |||
827 | /** \brief Handler for start element events. */ |
||
828 | static void optConfStartElem (void *userData, const XML_Char *name, |
||
829 | const XML_Char **attr) { |
||
830 | struct OptConfData *data = (struct OptConfData *)userData; |
||
831 | enum OptConfElem elem = bsearchStr (name, OptConfElems, OC_COUNT); |
||
832 | switch (elem) { |
||
833 | case OC_DRICONF: |
||
834 | if (data->inDriConf) |
||
835 | XML_WARNING1 ("nested |
||
836 | if (attr[0]) |
||
837 | XML_WARNING1 ("attributes specified on |
||
838 | data->inDriConf++; |
||
839 | break; |
||
840 | case OC_DEVICE: |
||
841 | if (!data->inDriConf) |
||
842 | XML_WARNING1 (" |
||
843 | if (data->inDevice) |
||
844 | XML_WARNING1 ("nested |
||
845 | data->inDevice++; |
||
846 | if (!data->ignoringDevice && !data->ignoringApp) |
||
847 | parseDeviceAttr (data, attr); |
||
848 | break; |
||
849 | case OC_APPLICATION: |
||
850 | if (!data->inDevice) |
||
851 | XML_WARNING1 (" |
||
852 | if (data->inApp) |
||
853 | XML_WARNING1 ("nested |
||
854 | data->inApp++; |
||
855 | if (!data->ignoringDevice && !data->ignoringApp) |
||
856 | parseAppAttr (data, attr); |
||
857 | break; |
||
858 | case OC_OPTION: |
||
859 | if (!data->inApp) |
||
860 | XML_WARNING1 (" |
||
861 | if (data->inOption) |
||
862 | XML_WARNING1 ("nested |
||
863 | data->inOption++; |
||
864 | if (!data->ignoringDevice && !data->ignoringApp) |
||
865 | parseOptConfAttr (data, attr); |
||
866 | break; |
||
867 | default: |
||
868 | XML_WARNING ("unknown element: %s.", name); |
||
869 | } |
||
870 | } |
||
871 | |||
872 | /** \brief Handler for end element events. */ |
||
873 | static void optConfEndElem (void *userData, const XML_Char *name) { |
||
874 | struct OptConfData *data = (struct OptConfData *)userData; |
||
875 | enum OptConfElem elem = bsearchStr (name, OptConfElems, OC_COUNT); |
||
876 | switch (elem) { |
||
877 | case OC_DRICONF: |
||
878 | data->inDriConf--; |
||
879 | break; |
||
880 | case OC_DEVICE: |
||
881 | if (data->inDevice-- == data->ignoringDevice) |
||
882 | data->ignoringDevice = 0; |
||
883 | break; |
||
884 | case OC_APPLICATION: |
||
885 | if (data->inApp-- == data->ignoringApp) |
||
886 | data->ignoringApp = 0; |
||
887 | break; |
||
888 | case OC_OPTION: |
||
889 | data->inOption--; |
||
890 | break; |
||
891 | default: |
||
892 | /* unknown element, warning was produced on start tag */; |
||
893 | } |
||
894 | } |
||
895 | |||
896 | /** \brief Initialize an option cache based on info */ |
||
897 | static void initOptionCache (driOptionCache *cache, const driOptionCache *info) { |
||
898 | cache->info = info->info; |
||
899 | cache->tableSize = info->tableSize; |
||
900 | cache->values = malloc((1< |
||
901 | if (cache->values == NULL) { |
||
902 | fprintf (stderr, "%s: %d: out of memory.\n", __FILE__, __LINE__); |
||
903 | abort(); |
||
904 | } |
||
905 | memcpy (cache->values, info->values, |
||
906 | (1< |
||
907 | } |
||
908 | |||
909 | /** \brief Parse the named configuration file */ |
||
910 | static void parseOneConfigFile (XML_Parser p) { |
||
911 | #define BUF_SIZE 0x1000 |
||
912 | struct OptConfData *data = (struct OptConfData *)XML_GetUserData (p); |
||
913 | int status; |
||
914 | int fd; |
||
915 | |||
916 | if ((fd = open (data->name, O_RDONLY)) == -1) { |
||
917 | __driUtilMessage ("Can't open configuration file %s: %s.", |
||
918 | data->name, strerror (errno)); |
||
919 | return; |
||
920 | } |
||
921 | |||
922 | while (1) { |
||
923 | int bytesRead; |
||
924 | void *buffer = XML_GetBuffer (p, BUF_SIZE); |
||
925 | if (!buffer) { |
||
926 | __driUtilMessage ("Can't allocate parser buffer."); |
||
927 | break; |
||
928 | } |
||
929 | bytesRead = read (fd, buffer, BUF_SIZE); |
||
930 | if (bytesRead == -1) { |
||
931 | __driUtilMessage ("Error reading from configuration file %s: %s.", |
||
932 | data->name, strerror (errno)); |
||
933 | break; |
||
934 | } |
||
935 | status = XML_ParseBuffer (p, bytesRead, bytesRead == 0); |
||
936 | if (!status) { |
||
937 | XML_ERROR ("%s.", XML_ErrorString(XML_GetErrorCode(p))); |
||
938 | break; |
||
939 | } |
||
940 | if (bytesRead == 0) |
||
941 | break; |
||
942 | } |
||
943 | |||
944 | close (fd); |
||
945 | #undef BUF_SIZE |
||
946 | } |
||
947 | |||
948 | void driParseConfigFiles (driOptionCache *cache, const driOptionCache *info, |
||
949 | GLint screenNum, const char *driverName) { |
||
950 | char *filenames[2] = {"/etc/drirc", NULL}; |
||
951 | char *home; |
||
952 | GLuint i; |
||
953 | struct OptConfData userData; |
||
954 | |||
955 | initOptionCache (cache, info); |
||
956 | |||
957 | userData.cache = cache; |
||
958 | userData.screenNum = screenNum; |
||
959 | userData.driverName = driverName; |
||
960 | userData.execName = GET_PROGRAM_NAME(); |
||
961 | |||
962 | if ((home = getenv ("HOME"))) { |
||
963 | GLuint len = strlen (home); |
||
964 | filenames[1] = malloc(len + 7+1); |
||
965 | if (filenames[1] == NULL) |
||
966 | __driUtilMessage ("Can't allocate memory for %s/.drirc.", home); |
||
967 | else { |
||
968 | memcpy (filenames[1], home, len); |
||
969 | memcpy (filenames[1] + len, "/.drirc", 7+1); |
||
970 | } |
||
971 | } |
||
972 | |||
973 | for (i = 0; i < 2; ++i) { |
||
974 | XML_Parser p; |
||
975 | if (filenames[i] == NULL) |
||
976 | continue; |
||
977 | |||
978 | p = XML_ParserCreate (NULL); /* use encoding specified by file */ |
||
979 | XML_SetElementHandler (p, optConfStartElem, optConfEndElem); |
||
980 | XML_SetUserData (p, &userData); |
||
981 | userData.parser = p; |
||
982 | userData.name = filenames[i]; |
||
983 | userData.ignoringDevice = 0; |
||
984 | userData.ignoringApp = 0; |
||
985 | userData.inDriConf = 0; |
||
986 | userData.inDevice = 0; |
||
987 | userData.inApp = 0; |
||
988 | userData.inOption = 0; |
||
989 | |||
990 | parseOneConfigFile (p); |
||
991 | XML_ParserFree (p); |
||
992 | } |
||
993 | |||
994 | free(filenames[1]); |
||
995 | } |
||
996 | |||
997 | void driDestroyOptionInfo (driOptionCache *info) { |
||
998 | driDestroyOptionCache (info); |
||
999 | if (info->info) { |
||
1000 | GLuint i, size = 1 << info->tableSize; |
||
1001 | for (i = 0; i < size; ++i) { |
||
1002 | if (info->info[i].name) { |
||
1003 | free(info->info[i].name); |
||
1004 | free(info->info[i].ranges); |
||
1005 | } |
||
1006 | } |
||
1007 | free(info->info); |
||
1008 | } |
||
1009 | } |
||
1010 | |||
1011 | void driDestroyOptionCache (driOptionCache *cache) { |
||
1012 | free(cache->values); |
||
1013 | } |
||
1014 | |||
1015 | GLboolean driCheckOption (const driOptionCache *cache, const char *name, |
||
1016 | driOptionType type) { |
||
1017 | GLuint i = findOption (cache, name); |
||
1018 | return cache->info[i].name != NULL && cache->info[i].type == type; |
||
1019 | } |
||
1020 | |||
1021 | GLboolean driQueryOptionb (const driOptionCache *cache, const char *name) { |
||
1022 | GLuint i = findOption (cache, name); |
||
1023 | /* make sure the option is defined and has the correct type */ |
||
1024 | assert (cache->info[i].name != NULL); |
||
1025 | assert (cache->info[i].type == DRI_BOOL); |
||
1026 | return cache->values[i]._bool; |
||
1027 | } |
||
1028 | |||
1029 | GLint driQueryOptioni (const driOptionCache *cache, const char *name) { |
||
1030 | GLuint i = findOption (cache, name); |
||
1031 | /* make sure the option is defined and has the correct type */ |
||
1032 | assert (cache->info[i].name != NULL); |
||
1033 | assert (cache->info[i].type == DRI_INT || cache->info[i].type == DRI_ENUM); |
||
1034 | return cache->values[i]._int; |
||
1035 | } |
||
1036 | |||
1037 | GLfloat driQueryOptionf (const driOptionCache *cache, const char *name) { |
||
1038 | GLuint i = findOption (cache, name); |
||
1039 | /* make sure the option is defined and has the correct type */ |
||
1040 | assert (cache->info[i].name != NULL); |
||
1041 | assert (cache->info[i].type == DRI_FLOAT); |
||
1042 | return cache->values[i]._float; |
||
1043 | }>><>> |