1
2 /*
3 * Unix micro-cmm to manage X11 display
4 * calibration and profile loading.
5 */
6
7 /*************************************************************************
8 Copyright 2008 Graeme W. Gill
9
10 Permission is hereby granted, free of charge, to any person obtaining a copy
11 of this software and associated documentation files (the "Software"), to deal
12 in the Software without restriction, including without limitation the rights
13 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 copies of the Software, and to permit persons to whom the Software is
15 furnished to do so, subject to the following conditions:
16
17 The above copyright notice and this permission notice shall be included in
18 all copies or substantial portions of the Software.
19
20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26 THE SOFTWARE.
27
28 *************************************************************************/
29
30 /* We use libjcnf to store the ICC profile association with particular displays */
31
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <stdarg.h>
35 #include <string.h>
36 #include <math.h>
37 #include <time.h>
38 #include <signal.h>
39 #ifndef NT
40 # include <unistd.h>
41 #endif
42 #include <sys/types.h>
43 #include <sys/stat.h>
44 #include "icc.h"
45 #include "xdg_bds.h"
46 #include "jcnf.h"
47 #include "ucmm.h"
48
49 #undef DEBUG
50
51 #define CONFIG_FILE "color.jcnf"
52 #define PROFILE_DIR "color/icc/devices/display"
53
54 #ifdef DEBUG
55 # define errout stderr
56 # define debug(xx) fprintf(errout, xx )
57 # define debug2(xx) fprintf xx
58 #else
59 # define debug(xx)
60 # define debug2(xx)
61 #endif
62
63 static unsigned int fnv_32_buf(void *buf, size_t len);
64
65 #ifdef NT
66
67 /* Given the path to a file, ensure that all the parent directories */
68 /* are created. return nz on error */
mkdirs(char * path)69 static int mkdirs(char *path) {
70 struct _stat sbuf;
71 char *pp = path;
72
73 if (*pp != '\000' /* Skip drive number */
74 && ((*pp >= 'a' && *pp <= 'z') || (*pp >= 'A' && *pp <= 'Z'))
75 && pp[1] == ':')
76 pp += 2;
77 if (*pp == '/')
78 pp++; /* Skip root directory */
79 for (;pp != NULL && *pp != '\000';) {
80 if ((pp = strchr(pp, '/')) != NULL) {
81 *pp = '\000';
82 if (_stat(path,&sbuf) != 0)
83 {
84 if (_mkdir(path) != 0)
85 return 1;
86 }
87 *pp = '/';
88 pp++;
89 }
90 }
91 return 0;
92 }
93
94 #else
95
96 /* Given the path to a file, ensure that all the parent directories */
97 /* are created. return nz on error */
mkpdirs(char * path)98 static int mkpdirs(char *path) {
99 struct stat sbuf;
100 char *pp = path;
101 mode_t mode = 0700; /* Default directory mode */
102
103 if (*pp == '/')
104 pp++; /* Skip root directory */
105 for (;pp != NULL && *pp != '\000';) {
106 if ((pp = strchr(pp, '/')) != NULL) {
107 *pp = '\000';
108 if (stat(path,&sbuf) != 0) {
109 if (mkdir(path, mode) != 0)
110 return 1;
111 } else
112 mode = sbuf.st_mode;
113 *pp = '/';
114 pp++;
115 }
116 }
117 return 0;
118 }
119 #endif /* !NT */
120
121 /* Given a block of binary, convert it to upper case hexadecimal, */
122 /* with a 0x prefix. Free the buffer returned. */
123 /* Return NULL on error */
buf2hex(unsigned char * buf,int len)124 static char *buf2hex(unsigned char *buf, int len) {
125 char *s;
126 int i;
127
128 char hex[17] = "0123456789ABCDEF";
129
130 if ((s = malloc(len * 2 + 3)) == NULL)
131 return NULL;
132
133 s[0] = '0';
134 s[1] = 'x';
135
136 for (i = 0; i < len; i++) {
137 s[2 + i * 2 + 0] = hex[(buf[i] >> 4) & 0xf];
138 s[2 + i * 2 + 1] = hex[buf[i] & 0xf];
139 }
140 s[2 + i * 2 + 0] = '\000';
141
142 return s;
143 }
144
145
146 /* Install a profile for a given monitor */
147 /* Either EDID or display_name may be NULL, but not both. */
148 /* Any existing association is overwritten. Installed profiles */
149 /* are not deleted. */
ucmm_install_monitor_profile(ucmm_scope scope,unsigned char * edid,int edid_len,char * display_name,char * profile)150 ucmm_error ucmm_install_monitor_profile(
151 ucmm_scope scope, /* Scope of instalation */
152 unsigned char *edid, /* Primary device identifier, NULL if none. */
153 int edid_len, /* Length of edid data */
154 char *display_name, /* Fall back device association, */
155 /* the X11 display name */
156 char *profile /* Path to profile to be installed. */
157 ) {
158 char *config_file = CONFIG_FILE;
159 char *profile_dir = PROFILE_DIR;
160 char *conf_name = NULL; /* Configuration path to use */
161 char *data_name = NULL; /* Data path to use */
162 char *dprof = NULL; /* Destination for profile */
163 unsigned int edid_hash = 0;
164
165 if (edid != NULL)
166 edid_hash = fnv_32_buf(edid, edid_len);
167
168 debug2((errout,"ucmm_install_monitor_profile called with profile '%s', edid 0x%x, disp '%s'\n",profile,edid_hash,display_name));
169
170 /* Verify that we've been given a suitable ICC profile */
171 /* And read it into a memory buffer */
172 {
173 icmFile *fp;
174 icc *icco;
175
176 if ((fp = new_icmFileStd_name(profile,"r")) == NULL) {
177 debug2((errout,"Unable to ope file '%s'\n",profile));
178 return ucmm_invalid_profile;
179 }
180
181 if ((icco = new_icc()) == NULL) {
182 debug2((errout,"new_icc() failed\n"));
183 fp->del(fp);
184 return ucmm_invalid_profile;
185 }
186
187 if (icco->read(icco,fp,0) != 0) {
188 debug2((errout,"icc read of '%s' failed\n",profile));
189 icco->del(icco);
190 fp->del(fp);
191 return ucmm_invalid_profile;
192 }
193
194 if (icco->header->deviceClass != icSigDisplayClass
195 || icco->header->colorSpace != icSigRgbData) {
196 debug2((errout,"profile '%s' isn't an RGB display profile\n",profile));
197 icco->del(icco);
198 fp->del(fp);
199 return ucmm_invalid_profile;
200 }
201 icco->del(icco);
202 fp->del(fp);
203 }
204
205 debug2((errout,"verified profile OK\n"));
206
207 /* Locate the directories where the config file is, */
208 /* and where we should copy the profile to. */
209 {
210 int npaths;
211 xdg_error er;
212 char *data_pathfile; /* Path & name of destination */
213 char **paths;
214 char *tt;
215
216 if ((npaths = xdg_bds(&er, &paths, xdg_conf, xdg_write,
217 scope == ucmm_local_system ? xdg_local : xdg_user,
218 config_file)) == 0) {
219 return ucmm_open_config;
220 }
221 if ((conf_name = strdup(paths[0])) == NULL) {
222 xdg_free(paths, npaths);
223 return ucmm_resource;
224 }
225 xdg_free(paths, npaths);
226
227 /* Combined sub-path and profile name */
228 if ((data_pathfile = malloc(strlen(profile_dir) + 1 + strlen(profile) + 1)) == NULL)
229 return ucmm_resource;
230 strcpy(data_pathfile, profile_dir);
231
232 if (strlen(data_pathfile) > 1 && data_pathfile[strlen(data_pathfile)-1] != '/')
233 strcat(data_pathfile, "/");
234
235 if ((tt = strrchr(profile, '/')) != NULL) /* Get base name of profile */
236 tt++;
237 else
238 tt = profile;
239 strcat(data_pathfile, tt);
240
241 if ((npaths = xdg_bds(&er, &paths, xdg_conf, xdg_write,
242 scope == ucmm_local_system ? xdg_local : xdg_user,
243 data_pathfile)) == 0) {
244 free(data_pathfile);
245 free(conf_name);
246 return ucmm_open_config;
247 }
248 free(data_pathfile);
249 if ((data_name = strdup(paths[0])) == NULL) {
250 free(conf_name);
251 xdg_free(paths, npaths);
252 return ucmm_resource;
253 }
254 xdg_free(paths, npaths);
255 }
256
257 debug2((errout,"config file = '%s'\n",conf_name));
258 debug2((errout,"data file = '%s'\n",data_name));
259
260 /* Copy the profile to the destination */
261 {
262 FILE *fp;
263 unsigned char *pdata;
264 unsigned long psize;
265
266 /* Read in the ICC profile, then set the X11 atom value */
267 #if defined(O_BINARY) || defined(_O_BINARY)
268 if ((fp = fopen(profile,"rb")) == NULL)
269 #else
270 if ((fp = fopen(profile,"r")) == NULL)
271 #endif
272 {
273 debug2((errout,"Can't open file '%s'\n",profile));
274 free(conf_name);
275 free(data_name);
276 return ucmm_profile_copy;
277 }
278
279 /* Figure out how big it is */
280 if (fseek(fp, 0, SEEK_END)) {
281 debug2((errout,"Seek '%s' to EOF failed\n",profile));
282 free(conf_name);
283 free(data_name);
284 return ucmm_profile_copy;
285 }
286 psize = (unsigned long)ftell(fp);
287
288 if (fseek(fp, 0, SEEK_SET)) {
289 debug2((errout,"Seek '%s' to SOF failed\n",profile));
290 free(conf_name);
291 free(data_name);
292 return ucmm_profile_copy;
293 }
294
295 if ((pdata = (unsigned char *)malloc(psize)) == NULL) {
296 debug2((errout,"Failed to allocate buffer for profile '%s'\n",profile));
297 free(conf_name);
298 free(data_name);
299 return ucmm_profile_copy;
300 }
301
302 if (fread(pdata, 1, psize, fp) != psize) {
303 debug2((errout,"Failed to read profile '%s' into buffer\n",profile));
304 free(conf_name);
305 free(data_name);
306 return ucmm_profile_copy;
307 }
308
309 fclose(fp);
310
311 /* Write the profile to its location */
312 if (mkpdirs(data_name)) {
313 debug2((errout,"Can't create directories for file '%s'\n",data_name));
314 free(conf_name);
315 free(data_name);
316 return ucmm_profile_copy;
317 }
318 #if defined(O_BINARY) || defined(_O_BINARY)
319 if ((fp = fopen(data_name,"wb")) == NULL)
320 #else
321 if ((fp = fopen(data_name,"w")) == NULL)
322 #endif
323 {
324 debug2((errout,"Can't create file '%s'\n",data_name));
325 free(conf_name);
326 free(data_name);
327 return ucmm_profile_copy;
328 }
329
330 if (fwrite(pdata, 1, psize, fp) != psize) {
331 debug2((errout,"Failed to write profile '%s' into buffer\n",data_name));
332 free(conf_name);
333 free(data_name);
334 return ucmm_profile_copy;
335 }
336
337 if (fclose(fp) != 0) {
338 debug2((errout,"Failed to close profile '%s' into buffer\n",data_name));
339 free(conf_name);
340 free(data_name);
341 return ucmm_profile_copy;
342 }
343 }
344
345 debug2((errout,"profile copied OK\n"));
346
347 /* Update the config file */
348 {
349 jc_error ev;
350 jcnf *jc;
351 char keyn1[100];
352 char keyn2[100];
353 char *mname; /* Name of key to match to */
354 char *mval; /* Value to match */
355 int ix = 0;
356 int recno = -1; /* Number of the last record read */
357
358 /* Open the configuration file for modification */
359 if (mkpdirs(conf_name)) {
360 debug2((errout,"Can't create directories for file '%s'\n",conf_name));
361 free(conf_name);
362 free(data_name);
363 return ucmm_open_config;
364 }
365
366 if ((jc = new_jcnf(&ev, conf_name, jc_modify, jc_create)) == NULL) {
367 debug2((errout,"new_jcnf '%s' failed with error %d\n",conf_name,ev));
368 free(conf_name);
369 free(data_name);
370 return ucmm_open_config;
371 }
372
373 /* if EDID supplied, Locate a matching EDID */
374 if (edid != NULL) {
375 mname = "EDID";
376 if ((mval = buf2hex(edid, edid_len)) == NULL) {
377 jc->del(jc);
378 free(conf_name);
379 free(data_name);
380 return ucmm_resource;
381 }
382
383 /* Else fall back to X11 display name and screen */
384 } else {
385 if (display_name == NULL) {
386 jc->del(jc);
387 free(conf_name);
388 free(data_name);
389 return ucmm_no_edid_or_display;
390 }
391 mname = "NAME";
392 if ((mval = strdup(display_name)) == NULL) {
393 jc->del(jc);
394 free(conf_name);
395 free(data_name);
396 return ucmm_resource;
397 }
398 }
399
400 debug2((errout,"Searching for %s = '%s'\n",mname,mval));
401 for (;;ix++) {
402 char *key, *pp;
403 jc_type type;
404 unsigned char *data;
405 size_t dataSize;
406 int ii;
407
408 if ((ev = jc->locate_key(jc, &ix, "devices/display/", 0, 0)) != jc_ok
409 || (ev = jc->get_key(jc, ix, &key, &type, &data, &dataSize, NULL)) != jc_ok) {
410 if (ev == jc_ix_oorange) {
411 break;
412 }
413 debug2((errout,"jcnf locate/get_key failed with error %d\n",ev));
414 free(mval);
415 jc->del(jc);
416 free(conf_name);
417 free(data_name);
418 return ucmm_open_config;
419 }
420
421 if ((pp = jc_get_nth_elem(key, 2)) == NULL) {
422 continue;
423 }
424 if ((ii = atoi(pp)) == 0) {
425 free(pp);
426 continue;
427 }
428 if (ii > recno) /* Track biggest, so we know what to create next */
429 recno = ii;
430 if ((pp = jc_get_nth_elem(key, 3)) != NULL && strcmp(pp, mname) == 0 && type == jc_string && strcmp(data, mval) == 0) {
431 /* Found matching record */
432 free(pp);
433 break;
434 }
435 if (pp != NULL)
436 free(pp);
437 }
438
439 /* Create a new record */
440 if (ev == jc_ix_oorange) {
441 recno++; /* Make it the next index */
442 if (recno <= 0)
443 recno = 1;
444 debug2((errout, "Adding a new record %d\n",recno));
445 } else {
446 debug2((errout, "Replacing record %d\n",recno));
447 }
448
449 /* Write the record */
450 sprintf(keyn1, "devices/display/%d/%s", recno, mname);
451 sprintf(keyn2, "devices/display/%d/ICC_PROFILE", recno);
452 if ((ev = jc->set_key(jc, -1, keyn1, jc_string, mval, strlen(mval)+1, NULL)) != jc_ok
453 || (ev = jc->set_key(jc, -1, keyn2, jc_string, data_name, strlen(data_name)+1, NULL)) != jc_ok) {
454 debug2((errout,"jcnf set_key failed with error %d\n",ev));
455 free(mval);
456 jc->del(jc);
457 free(conf_name);
458 free(data_name);
459 return ucmm_set_config;
460 }
461 free(mval);
462
463 /* write to record the EDID or display name and the profile path */
464 if ((ev = jc->update(jc)) != 0) {
465 debug2((errout,"jcnf write to '%s' failed with error %d\n",conf_name,ev));
466 jc->del(jc);
467 free(conf_name);
468 free(data_name);
469 return ucmm_save_config;
470 }
471 debug2((errout,"Updated config file '%s'\n",conf_name));
472
473 /* We're done with this */
474 jc->del(jc);
475 free(conf_name);
476 free(data_name);
477 }
478 debug2((errout,"ucmm done profile install\n"));
479 return ucmm_ok;
480 }
481
482 /* Un-install a profile for a given monitor. */
483 /* Either EDID or display_name may be NULL, but not both. */
484 /* The monitor is left with no profile association. If a profile */
485 /* name is provided and matches the one that was associated with */
486 /* the monitor, and has no other association, then it will be deleted */
487 /* from the data directory. */
488 /* Return an error code */
ucmm_uninstall_monitor_profile(ucmm_scope scope,unsigned char * edid,int edid_len,char * display_name,char * profile)489 ucmm_error ucmm_uninstall_monitor_profile(
490 ucmm_scope scope, /* Scope of instalation */
491 unsigned char *edid, /* Primary device identifier, NULL if none. */
492 int edid_len, /* Length of edid data */
493 char *display_name, /* Fall back device association, */
494 char *profile /* Base name of profile to be deleted. NULL if not to be deleted. */
495 ) {
496 char *config_file = CONFIG_FILE;
497 char *profile_dir = PROFILE_DIR;
498 char *conf_name = NULL; /* Configuration path to use */
499 char *data_name = NULL; /* Data path to use */
500 char *dprof = NULL; /* Destination for profile */
501 unsigned int edid_hash = 0;
502
503 if (edid != NULL)
504 edid_hash = fnv_32_buf(edid, edid_len);
505
506 debug2((errout,"ucmm_uninstall_monitor_profile called with profile '%s', edid 0x%x, disp '%s'\n",profile,edid_hash,display_name));
507
508 /* Locate the directories where the config file is, */
509 /* and where the profile should be too. */
510 {
511 int npaths;
512 xdg_error er;
513 char *data_pathfile; /* Path & name of destination */
514 char **paths;
515 char *tt;
516
517 if ((npaths = xdg_bds(&er, &paths, xdg_conf, xdg_read,
518 scope == ucmm_local_system ? xdg_local : xdg_user,
519 config_file)) == 0) {
520 return ucmm_open_config;
521 }
522 if ((conf_name = strdup(paths[0])) == NULL) {
523 xdg_free(paths, npaths);
524 return ucmm_resource;
525 }
526 xdg_free(paths, npaths);
527
528 if (profile != NULL) {
529 /* Combined sub-path and profile name */
530 if ((data_pathfile = malloc(strlen(profile_dir) + 1 + strlen(profile) + 1)) == NULL)
531 return ucmm_resource;
532 strcpy(data_pathfile, profile_dir);
533
534 if (strlen(data_pathfile) > 1 && data_pathfile[strlen(data_pathfile)-1] != '/')
535 strcat(data_pathfile, "/");
536
537 if ((tt = strrchr(profile, '/')) != NULL) /* Get base name of profile */
538 tt++;
539 else
540 tt = profile;
541 strcat(data_pathfile, tt);
542
543 if ((npaths = xdg_bds(&er, &paths, xdg_conf, xdg_read,
544 scope == ucmm_local_system ? xdg_local : xdg_user,
545 data_pathfile)) == 0) {
546 free(data_pathfile);
547 free(conf_name);
548 return ucmm_open_config;
549 }
550 free(data_pathfile);
551 if ((data_name = strdup(paths[0])) == NULL) {
552 free(conf_name);
553 xdg_free(paths, npaths);
554 return ucmm_resource;
555 }
556 xdg_free(paths, npaths);
557 }
558 }
559
560 debug2((errout,"config file = '%s'\n",conf_name));
561 if (data_name != NULL)
562 debug2((errout,"data file = '%s'\n",data_name));
563
564 /* Get the config file */
565 {
566 jc_error ev;
567 jcnf *jc;
568 char keyn1[100];
569 char *mname; /* Name of key to match to */
570 char *mval; /* Value to match */
571 int ix;
572 int recno = -1; /* Number of the last record read */
573
574 /* Open the configuration file for modification */
575 if (mkpdirs(conf_name)) {
576 debug2((errout,"Can't create directories for file '%s'\n",conf_name));
577 free(conf_name);
578 if (data_name != NULL)
579 free(data_name);
580 return ucmm_open_config;
581 }
582
583 if ((jc = new_jcnf(&ev, conf_name, jc_modify, jc_create)) == NULL) {
584 debug2((errout,"new_jcnf '%s' failed with error %d\n",conf_name,ev));
585 free(conf_name);
586 if (data_name != NULL)
587 free(data_name);
588 return ucmm_open_config;
589 }
590
591 /* if EDID supplied, Locate a matching EDID */
592 if (edid != NULL) {
593 mname = "EDID";
594 if ((mval = buf2hex(edid, edid_len)) == NULL) {
595 jc->del(jc);
596 free(conf_name);
597 if (data_name != NULL)
598 free(data_name);
599 return ucmm_resource;
600 }
601
602 /* Else fall back to X11 display name and screen */
603 } else {
604 if (display_name == NULL) {
605 jc->del(jc);
606 free(conf_name);
607 if (data_name != NULL)
608 free(data_name);
609 return ucmm_no_edid_or_display;
610 }
611 mname = "NAME";
612 if ((mval = strdup(display_name)) == NULL) {
613 jc->del(jc);
614 free(conf_name);
615 if (data_name != NULL)
616 free(data_name);
617 return ucmm_resource;
618 }
619 }
620
621 debug2((errout,"Searching for %s = '%s'\n",mname,mval));
622 for (ix = 0;;ix++) {
623 char *key, *pp;
624 jc_type type;
625 unsigned char *data;
626 size_t dataSize;
627 int ii;
628
629 if ((ev = jc->locate_key(jc, &ix, "devices/display/", 0, 0)) != jc_ok
630 || (ev = jc->get_key(jc, ix, &key, &type, &data, &dataSize, NULL)) != jc_ok) {
631 if (ev == jc_ix_oorange) {
632 break;
633 }
634 debug2((errout,"jcnf locate/get_key failed with error %d\n",ev));
635 free(mval);
636 jc->del(jc);
637 free(conf_name);
638 if (data_name != NULL)
639 free(data_name);
640 return ucmm_open_config;
641 }
642
643 if ((pp = jc_get_nth_elem(key, 2)) == NULL) {
644 continue;
645 }
646 if ((ii = atoi(pp)) == 0) {
647 free(pp);
648 continue;
649 }
650 if (ii > recno) /* Track biggest, so we know what to create next */
651 recno = ii;
652 if ((pp = jc_get_nth_elem(key, 3)) != NULL && strcmp(pp, mname) == 0 && type == jc_string && strcmp(data, mval) == 0) {
653 /* Found matching record */
654 free(pp);
655 break;
656 }
657 if (pp != NULL)
658 free(pp);
659 }
660
661 if (ev == jc_ix_oorange) {
662 debug2((errout,"No matching display was found\n"));
663 free(mval);
664 jc->del(jc);
665 free(conf_name);
666 if (data_name != NULL)
667 free(data_name);
668 return ucmm_monitor_not_found;
669 /* (Should we delete the file anyway ???) */
670 }
671 free(mval);
672
673 debug2((errout,"Deleting record %d key '%s'\n",recno,keyn1));
674
675 /* Delete the record */
676 sprintf(keyn1, "devices/display/%d/", recno);
677
678 for (ix = -1;;ix--) {
679 if ((ev = jc->locate_key(jc, &ix, keyn1, 0, 1)) == jc_ok) {
680 if ((ev = jc->delete_key(jc, ix, NULL)) != jc_ok) {
681 debug2((errout,"jcnf delete_key failed with error %d\n",ev));
682 jc->del(jc);
683 free(conf_name);
684 if (data_name != NULL)
685 free(data_name);
686 return ucmm_delete_key;
687 }
688 } else {
689 if (ev == jc_ix_oorange) {
690 break;
691 }
692 debug2((errout,"jcnf locate/get_key failed with error %d\n",ev));
693 jc->del(jc);
694 free(conf_name);
695 if (data_name != NULL)
696 free(data_name);
697 return ucmm_open_config;
698 }
699 }
700
701 if (data_name != NULL) {
702 /* See if the profile is used by any other device */
703
704 debug2((errout, "Searching for any reference to profile '%s'\n",data_name));
705 for (ix = 0;;ix++) {
706 char *key, *pp;
707 jc_type type;
708 unsigned char *data;
709 size_t dataSize;
710
711 if ((ev = jc->locate_key(jc, &ix, "devices/display/", 0, 0)) != jc_ok
712 || (ev = jc->get_key(jc, ix, &key, &type, &data, &dataSize, NULL)) != jc_ok) {
713 if (ev == jc_ix_oorange) {
714 break;
715 }
716 debug2((errout,"jcnf locate/get_key failed with error %d\n",ev));
717 jc->del(jc);
718 free(conf_name);
719 if (data_name != NULL)
720 free(data_name);
721 return ucmm_access_config;
722 }
723 if ((pp = jc_get_nth_elem(key, 3)) == NULL)
724 continue;
725 if (strcmp(pp,"ICC_PROFILE") != 0
726 || type != jc_string
727 || strcmp(data, data_name) != 0) {
728 free(pp);
729 continue;
730 }
731 free(pp);
732 break;
733 }
734 /* If not, delete the file */
735 if (ev == jc_ix_oorange) {
736 debug2((errout,"Deleting profile '%s'\n",data_name));
737 if (unlink(data_name) != 0) {
738 debug2((errout,"ucmm unlink '%s' failed\n",data_name));
739 jc->del(jc);
740 free(conf_name);
741 if (data_name != NULL)
742 free(data_name);
743 return ucmm_access_config;
744 }
745 }
746 }
747
748 /* Update the config */
749 if ((ev = jc->update(jc)) != 0) {
750 debug2((errout,"jcnf write to '%s' failed with error %d\n",conf_name,ev));
751 jc->del(jc);
752 free(conf_name);
753 if (data_name != NULL)
754 free(data_name);
755 return ucmm_save_config;
756 }
757 debug2((errout,"Updated config file '%s'\n",conf_name));
758
759 /* We're done with this */
760 jc->del(jc);
761 free(conf_name);
762 if (data_name != NULL)
763 free(data_name);
764 }
765 debug2((errout,"ucmm done profile un-install\n"));
766 return ucmm_ok;
767 }
768
769 /* Get an associated monitor profile. */
770 /* Return ucmm_no_profile if there is no installed profile for this */
771 /* monitor. */
772 /* Return an error code */
ucmm_get_monitor_profile(unsigned char * edid,int edid_len,char * display_name,char ** profile)773 ucmm_error ucmm_get_monitor_profile(
774 unsigned char *edid, /* Primary device identifier, NULL if none. */
775 int edid_len, /* Length of edid data */
776 char *display_name, /* Fall back device association, */
777 char **profile /* Return path to profile. free() afterwards. */
778 ) {
779 int scope;
780 char *config_file = "color.jcnf";
781 char *conf_name = NULL; /* Configuration path to use */
782 unsigned int edid_hash = 0;
783
784 if (edid != NULL)
785 edid_hash = fnv_32_buf(edid, edid_len);
786
787 debug2((errout,"ucmm_get_monitor_profile called edid 0x%x, disp '%s'\n",edid_hash,display_name));
788
789 /* Look at user then local system scope */
790 for (scope = 0; scope < 2; scope++) {
791
792 /* Locate the directories where the config file is, */
793 {
794 int npaths;
795 xdg_error er;
796 char **paths;
797 char *tt;
798
799 if ((npaths = xdg_bds(&er, &paths, xdg_conf, xdg_read,
800 scope == ucmm_local_system ? xdg_local : xdg_user,
801 config_file)) == 0) {
802 continue;
803 }
804 if ((conf_name = strdup(paths[0])) == NULL) {
805 xdg_free(paths, npaths);
806 return ucmm_resource;
807 }
808 xdg_free(paths, npaths);
809 }
810
811 /* Get the config file */
812 {
813 jc_error ev;
814 jcnf *jc;
815 char keyn1[100];
816 char *mname; /* Name of key to match to */
817 char *mval; /* Value to match */
818 int ix;
819 int recno = -1; /* Number of the last record read */
820 char *key, *pp;
821 jc_type type;
822 unsigned char *data;
823 size_t dataSize;
824
825 /* Open the configuration file for reading */
826 if ((jc = new_jcnf(&ev, conf_name, jc_read, jc_no_create)) == NULL) {
827 debug2((errout,"new_jcnf '%s' failed with error %d\n",conf_name,ev));
828 continue; /* Try the next scope */
829 }
830
831 /* if EDID supplied, Locate a matching EDID */
832 if (edid != NULL) {
833 mname = "EDID";
834 if ((mval = buf2hex(edid, edid_len)) == NULL) {
835 debug2((errout,"buf2jex failed\n"));
836 jc->del(jc);
837 free(conf_name);
838 return ucmm_resource;
839 }
840
841 /* Else fall back to X11 display name and screen */
842 } else {
843 if (display_name == NULL) {
844 debug2((errout,"No EDID and display name failed\n"));
845 jc->del(jc);
846 free(conf_name);
847 return ucmm_no_edid_or_display;
848 }
849 mname = "NAME";
850 if ((mval = strdup(display_name)) == NULL) {
851 debug2((errout,"strdup failed\n"));
852 jc->del(jc);
853 free(conf_name);
854 return ucmm_resource;
855 }
856 }
857
858 debug2((errout,"Searching for %s = '%s'\n",mname,mval));
859 for (ix = 0;;ix++) {
860 int ii;
861
862 if ((ev = jc->locate_key(jc, &ix, "devices/display/", 0, 0)) != jc_ok
863 || (ev = jc->get_key(jc, ix, &key, &type, &data, &dataSize, NULL)) != jc_ok) {
864 if (ev == jc_ix_oorange) {
865 break;
866 }
867 debug2((errout,"jcnf locate/get_key failed with error %d\n",ev));
868 free(mval);
869 jc->del(jc);
870 free(conf_name);
871 return ucmm_open_config;
872 }
873
874 if ((pp = jc_get_nth_elem(key, 2)) == NULL) {
875 continue;
876 }
877 if ((ii = atoi(pp)) == 0) {
878 free(pp);
879 continue;
880 }
881 if (ii > recno) /* Track biggest, so we know what to create next */
882 recno = ii;
883 if ((pp = jc_get_nth_elem(key, 3)) != NULL && strcmp(pp, mname) == 0 && type == jc_string
884 && strcmp(data, mval) == 0) {
885 /* Found matching record */
886 free(pp);
887 break;
888 }
889 if (pp != NULL)
890 free(pp);
891 }
892
893 if (ev == jc_ix_oorange) {
894 debug2((errout,"No matching display was found\n"));
895 continue; /* On to the next scope */
896 }
897 free(mval);
898
899 /* Get the profile path from the record */
900 sprintf(keyn1, "devices/display/%d/ICC_PROFILE", recno);
901 key = keyn1;
902 debug2((errout,"Looking up record %d key '%s'\n",recno,keyn1));
903
904 if ((ev = jc->get_key(jc, -1, &key, &type, &data, &dataSize, NULL)) != jc_ok
905 || type != jc_string) {
906 debug2((errout,"jcnf locate/get_key failed with error %d\n",ev));
907 jc->del(jc);
908 free(conf_name);
909 if (ev == jc_ix_oorange) {
910 continue; /* try the next config */
911 }
912 return ucmm_access_config;
913 }
914 if ((*profile = strdup(data)) == NULL) {
915 debug2((errout,"jcnf get_key malloc failed\n"));
916 jc->del(jc);
917 free(conf_name);
918 return ucmm_resource;
919 }
920
921 /* We're done with this */
922 jc->del(jc);
923 free(conf_name);
924 return ucmm_ok;
925 debug2((errout,"Returning current profile '%s'\n",data));
926 }
927 }
928 debug2((errout,"Failed to find a current profile\n"));
929
930 return ucmm_no_profile;
931 }
932
933
934 /* Return an ASCII error message string interpretation of an error number */
ucmm_error_string(ucmm_error erno)935 char *ucmm_error_string(ucmm_error erno) {
936
937 switch (erno) {
938 case ucmm_ok:
939 return "OK";
940 case ucmm_resource:
941 return "Resource failure (e.g. out of memory)";
942 case ucmm_invalid_profile:
943 return "Profile is not a valid display ICC profile";
944 case ucmm_no_profile:
945 return "There is no associated profile";
946 case ucmm_no_home:
947 return "There is no HOME environment variable defined";
948 case ucmm_no_edid_or_display:
949 return "There is no edid or display name";
950 case ucmm_profile_copy:
951 return "There was an error copying the profile";
952 case ucmm_open_config:
953 return "There was an error opening the config file";
954 case ucmm_access_config:
955 return "There was an error accessing the config information";
956 case ucmm_set_config:
957 return "There was an error setting the config file";
958 case ucmm_save_config:
959 return "There was an error saving the config file";
960 case ucmm_monitor_not_found:
961 return "The EDID or display wasn't matched";
962 case ucmm_delete_key:
963 return "Delete_key failed";
964 case ucmm_delete_profile:
965 return "Delete_key failed";
966 }
967 return "Unknown error number";
968 }
969
970
971 /* ============================================================= */
972
973 /*
974 * hash_32 - 32 bit Fowler/Noll/Vo hash code
975 *
976 * @(#) $Revision: 1.8 $
977 * @(#) $Id: hash_32.c,v 1.8 2003/10/03 20:38:13 chongo Exp $
978 * @(#) $Source: /usr/local/src/cmd/fnv/RCS/hash_32.c,v $
979 *
980 ***
981 *
982 * Fowler/Noll/Vo hash
983 *
984 * The basis of this hash algorithm was taken from an idea sent
985 * as reviewer comments to the IEEE POSIX P1003.2 committee by:
986 *
987 * Phong Vo (http://www.research.att.com/info/kpv/)
988 * Glenn Fowler (http://www.research.att.com/~gsf/)
989 *
990 * In a subsequent ballot round:
991 *
992 * Landon Curt Noll (http://www.isthe.com/chongo/)
993 *
994 * improved on their algorithm. Some people tried this hash
995 * and found that it worked rather well. In an EMail message
996 * to Landon, they named it the ``Fowler/Noll/Vo'' or FNV hash.
997 *
998 * FNV hashes are designed to be fast while maintaining a low
999 * collision rate. The FNV speed allows one to quickly hash lots
1000 * of data while maintaining a reasonable collision rate. See:
1001 *
1002 * http://www.isthe.com/chongo/tech/comp/fnv/index.html
1003 *
1004 * for more details as well as other forms of the FNV hash.
1005 ***
1006 *
1007 * NOTE: The FNV-0 historic hash is not recommended. One should use
1008 * the FNV-1 hash instead.
1009 *
1010 * To use the 32 bit FNV-0 historic hash, pass FNV0_32_INIT as the
1011 * unsigned int hashval argument to fnv_32_buf() or fnv_32_str().
1012 *
1013 * To use the recommended 32 bit FNV-1 hash, pass FNV1_32_INIT as the
1014 * unsigned int hashval argument to fnv_32_buf() or fnv_32_str().
1015 *
1016 ***
1017 *
1018 * Please do not copyright this code. This code is in the public domain.
1019 *
1020 * LANDON CURT NOLL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
1021 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO
1022 * EVENT SHALL LANDON CURT NOLL BE LIABLE FOR ANY SPECIAL, INDIRECT OR
1023 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
1024 * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
1025 * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
1026 * PERFORMANCE OF THIS SOFTWARE.
1027 *
1028 * By:
1029 * chongo <Landon Curt Noll> /\oo/\
1030 * http://www.isthe.com/chongo/
1031 *
1032 * Share and Enjoy! :-)
1033 */
1034
1035 /*
1036 * 32 bit magic FNV-0 and FNV-1 prime
1037 */
1038 #define FNV_32_PRIME ((unsigned int)0x01000193)
1039
1040 #define FNV1_32_INIT ((unsigned int)0x811c9dc5)
1041
1042 /*
1043 * fnv_32_buf - perform a 32 bit Fowler/Noll/Vo hash on a buffer
1044 *
1045 * input:
1046 * buf - start of buffer to hash
1047 * len - length of buffer in octets
1048 * hval - previous hash value or 0 if first call
1049 *
1050 * returns:
1051 * 32 bit hash as a static hash type
1052 *
1053 * NOTE: To use the recommended 32 bit FNV-1 hash, use FNV1_32_INIT as the hval
1054 * argument on the first call to either fnv_32_buf() or fnv_32_str().
1055 */
1056 static unsigned int
fnv_32_buf_cont(void * buf,size_t len,unsigned int hval)1057 fnv_32_buf_cont(void *buf, size_t len, unsigned int hval)
1058 {
1059 unsigned char *bp = (unsigned char *)buf; /* start of buffer */
1060 unsigned char *be = bp + len; /* beyond end of buffer */
1061
1062 /*
1063 * FNV-1 hash each octet in the buffer
1064 */
1065 while (bp < be) {
1066
1067 /* multiply by the 32 bit FNV magic prime mod 2^32 */
1068 #if defined(NO_FNV_GCC_OPTIMIZATION)
1069 hval *= FNV_32_PRIME;
1070 #else
1071 hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24);
1072 #endif
1073
1074 /* xor the bottom with the current octet */
1075 hval ^= (unsigned int)*bp++;
1076 }
1077
1078 /* return our new hash value */
1079 return hval;
1080 }
1081
1082 static unsigned int
fnv_32_buf(void * buf,size_t len)1083 fnv_32_buf(void *buf, size_t len) {
1084 return fnv_32_buf_cont(buf, len, FNV1_32_INIT);
1085 }
1086
1087