diff options
Diffstat (limited to 'ucmm')
-rw-r--r-- | ucmm/Jamfile | 26 | ||||
-rw-r--r-- | ucmm/Makefile.am | 9 | ||||
-rw-r--r-- | ucmm/afiles | 4 | ||||
-rw-r--r-- | ucmm/ucmm.c | 1086 | ||||
-rw-r--r-- | ucmm/ucmm.h | 98 |
5 files changed, 1223 insertions, 0 deletions
diff --git a/ucmm/Jamfile b/ucmm/Jamfile new file mode 100644 index 0000000..5805272 --- /dev/null +++ b/ucmm/Jamfile @@ -0,0 +1,26 @@ + +# JAM style makefile for yajl + +#PREF_CCFLAGS = $(CCOPTFLAG) ; # Turn optimisation on +PREF_CCFLAGS = $(CCDEBUGFLAG) ; # Debugging flags +#PREF_CCFLAGS = $(CCHEAPDEBUG) ; # Heap Debugging flags +PREF_LINKFLAGS = $(LINKDEBUGFLAG) ; # Link debugging flags + +#Products +Libraries = libucmm ; +Executables = ; +Headers = ucmm.h ; + +#Install +#InstallBin $(DESTDIR)$(PREFIX)/bin : $(Executables) ; +#InstallFile $(DESTDIR)$(PREFIX)/h : $(Headers) ; +#InstallLib $(DESTDIR)$(PREFIX)/lib : $(Libraries) ; + +HDRS = ../jcnf ../spectro ../icc ; + +# config parser based on yajl +Library libucmm : ucmm.c ; + + + + diff --git a/ucmm/Makefile.am b/ucmm/Makefile.am new file mode 100644 index 0000000..0140619 --- /dev/null +++ b/ucmm/Makefile.am @@ -0,0 +1,9 @@ +include $(top_srcdir)/Makefile.shared + +privatelib_LTLIBRARIES = libucmm.la +privatelibdir = $(pkglibdir) + +libucmm_la_SOURCES = ucmm.h ucmm.c +libucmm_la_LIBADD = $(ICC_LIBS) ../jcnf/libjcnf.la ../spectro/libconv.la + +LDADD = libucmm.la diff --git a/ucmm/afiles b/ucmm/afiles new file mode 100644 index 0000000..3204a05 --- /dev/null +++ b/ucmm/afiles @@ -0,0 +1,4 @@ +Jamfile +afiles +ucmm.h +ucmm.c diff --git a/ucmm/ucmm.c b/ucmm/ucmm.c new file mode 100644 index 0000000..dc5598f --- /dev/null +++ b/ucmm/ucmm.c @@ -0,0 +1,1086 @@ + +/* + * Unix micro-cmm to manage X11 display + * calibration and profile loading. + */ + +/************************************************************************* + Copyright 2008 Graeme W. Gill + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + *************************************************************************/ + +/* We use libjcnf to store the ICC profile association with particular displays */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <math.h> +#include <time.h> +#include <signal.h> +#ifndef NT +# include <unistd.h> +#endif +#include <sys/types.h> +#include <sys/stat.h> +#include "icc.h" +#include "xdg_bds.h" +#include "jcnf.h" +#include "ucmm.h" + +#undef DEBUG + +#define CONFIG_FILE "color.jcnf" +#define PROFILE_DIR "color/icc/devices/display" + +#ifdef DEBUG +# define errout stderr +# define debug(xx) fprintf(errout, xx ) +# define debug2(xx) fprintf xx +#else +# define debug(xx) +# define debug2(xx) +#endif + +static unsigned int fnv_32_buf(void *buf, size_t len); + +#ifdef NT + +/* Given the path to a file, ensure that all the parent directories */ +/* are created. return nz on error */ +static int mkdirs(char *path) { + struct _stat sbuf; + char *pp = path; + + if (*pp != '\000' /* Skip drive number */ + && ((*pp >= 'a' && *pp <= 'z') || (*pp >= 'A' && *pp <= 'Z')) + && pp[1] == ':') + pp += 2; + if (*pp == '/') + pp++; /* Skip root directory */ + for (;pp != NULL && *pp != '\000';) { + if ((pp = strchr(pp, '/')) != NULL) { + *pp = '\000'; + if (_stat(path,&sbuf) != 0) + { + if (_mkdir(path) != 0) + return 1; + } + *pp = '/'; + pp++; + } + } + return 0; +} + +#else + +/* Given the path to a file, ensure that all the parent directories */ +/* are created. return nz on error */ +static int mkpdirs(char *path) { + struct stat sbuf; + char *pp = path; + mode_t mode = 0700; /* Default directory mode */ + + if (*pp == '/') + pp++; /* Skip root directory */ + for (;pp != NULL && *pp != '\000';) { + if ((pp = strchr(pp, '/')) != NULL) { + *pp = '\000'; + if (stat(path,&sbuf) != 0) { + if (mkdir(path, mode) != 0) + return 1; + } else + mode = sbuf.st_mode; + *pp = '/'; + pp++; + } + } + return 0; +} +#endif /* !NT */ + +/* Given a block of binary, convert it to upper case hexadecimal, */ +/* with a 0x prefix. Free the buffer returned. */ +/* Return NULL on error */ +static char *buf2hex(unsigned char *buf, int len) { + char *s; + int i; + + char hex[17] = "0123456789ABCDEF"; + + if ((s = malloc(len * 2 + 3)) == NULL) + return NULL; + + s[0] = '0'; + s[1] = 'x'; + + for (i = 0; i < len; i++) { + s[2 + i * 2 + 0] = hex[(buf[i] >> 4) & 0xf]; + s[2 + i * 2 + 1] = hex[buf[i] & 0xf]; + } + s[2 + i * 2 + 0] = '\000'; + + return s; +} + + +/* Install a profile for a given monitor */ +/* Either EDID or display_name may be NULL, but not both. */ +/* Any existing association is overwritten. Installed profiles */ +/* are not deleted. */ +ucmm_error ucmm_install_monitor_profile( + ucmm_scope scope, /* Scope of instalation */ + unsigned char *edid, /* Primary device identifier, NULL if none. */ + int edid_len, /* Length of edid data */ + char *display_name, /* Fall back device association, */ + /* the X11 display name */ + char *profile /* Path to profile to be installed. */ +) { + char *config_file = CONFIG_FILE; + char *profile_dir = PROFILE_DIR; + char *conf_name = NULL; /* Configuration path to use */ + char *data_name = NULL; /* Data path to use */ + char *dprof = NULL; /* Destination for profile */ + unsigned int edid_hash = 0; + + if (edid != NULL) + edid_hash = fnv_32_buf(edid, edid_len); + + debug2((errout,"ucmm_install_monitor_profile called with profile '%s', edid 0x%x, disp '%s'\n",profile,edid_hash,display_name)); + + /* Verify that we've been given a suitable ICC profile */ + /* And read it into a memory buffer */ + { + icmFile *fp; + icc *icco; + + if ((fp = new_icmFileStd_name(profile,"r")) == NULL) { + debug2((errout,"Unable to ope file '%s'\n",profile)); + return ucmm_invalid_profile; + } + + if ((icco = new_icc()) == NULL) { + debug2((errout,"new_icc() failed\n")); + fp->del(fp); + return ucmm_invalid_profile; + } + + if (icco->read(icco,fp,0) != 0) { + debug2((errout,"icc read of '%s' failed\n",profile)); + icco->del(icco); + fp->del(fp); + return ucmm_invalid_profile; + } + + if (icco->header->deviceClass != icSigDisplayClass + || icco->header->colorSpace != icSigRgbData) { + debug2((errout,"profile '%s' isn't an RGB display profile\n",profile)); + icco->del(icco); + fp->del(fp); + return ucmm_invalid_profile; + } + icco->del(icco); + fp->del(fp); + } + + debug2((errout,"verified profile OK\n")); + + /* Locate the directories where the config file is, */ + /* and where we should copy the profile to. */ + { + int npaths; + xdg_error er; + char *data_pathfile; /* Path & name of destination */ + char **paths; + char *tt; + + if (npaths = xdg_bds(&er, &paths, xdg_conf, xdg_write, + scope == ucmm_local_system ? xdg_local : xdg_user, + config_file) == 0) { + return ucmm_open_config; + } + if ((conf_name = strdup(paths[0])) == NULL) { + xdg_free(paths, npaths); + return ucmm_resource; + } + xdg_free(paths, npaths); + + /* Combined sub-path and profile name */ + if ((data_pathfile = malloc(strlen(profile_dir) + 1 + strlen(profile))) == NULL) + return ucmm_resource; + strcpy(data_pathfile, profile_dir); + + if (strlen(data_pathfile) > 1 && data_pathfile[strlen(data_pathfile)-1] != '/') + strcat(data_pathfile, "/"); + + if ((tt = strrchr(profile, '/')) != NULL) /* Get base name of profile */ + tt++; + else + tt = profile; + strcat(data_pathfile, tt); + + if (npaths = xdg_bds(&er, &paths, xdg_conf, xdg_write, + scope == ucmm_local_system ? xdg_local : xdg_user, + data_pathfile) == 0) { + free(data_pathfile); + free(conf_name); + return ucmm_open_config; + } + free(data_pathfile); + if ((data_name = strdup(paths[0])) == NULL) { + free(conf_name); + xdg_free(paths, npaths); + return ucmm_resource; + } + xdg_free(paths, npaths); + } + + debug2((errout,"config file = '%s'\n",conf_name)); + debug2((errout,"data file = '%s'\n",data_name)); + + /* Copy the profile to the destination */ + { + FILE *fp; + unsigned char *pdata; + unsigned long psize; + + /* Read in the ICC profile, then set the X11 atom value */ +#if defined(O_BINARY) || defined(_O_BINARY) + if ((fp = fopen(profile,"rb")) == NULL) +#else + if ((fp = fopen(profile,"r")) == NULL) +#endif + { + debug2((errout,"Can't open file '%s'\n",profile)); + free(conf_name); + free(data_name); + return ucmm_profile_copy; + } + + /* Figure out how big it is */ + if (fseek(fp, 0, SEEK_END)) { + debug2((errout,"Seek '%s' to EOF failed\n",profile)); + free(conf_name); + free(data_name); + return ucmm_profile_copy; + } + psize = (unsigned long)ftell(fp); + + if (fseek(fp, 0, SEEK_SET)) { + debug2((errout,"Seek '%s' to SOF failed\n",profile)); + free(conf_name); + free(data_name); + return ucmm_profile_copy; + } + + if ((pdata = (unsigned char *)malloc(psize)) == NULL) { + debug2((errout,"Failed to allocate buffer for profile '%s'\n",profile)); + free(conf_name); + free(data_name); + return ucmm_profile_copy; + } + + if (fread(pdata, 1, psize, fp) != psize) { + debug2((errout,"Failed to read profile '%s' into buffer\n",profile)); + free(conf_name); + free(data_name); + return ucmm_profile_copy; + } + + fclose(fp); + + /* Write the profile to its location */ + if (mkpdirs(data_name)) { + debug2((errout,"Can't create directories for file '%s'\n",data_name)); + free(conf_name); + free(data_name); + return ucmm_profile_copy; + } +#if defined(O_BINARY) || defined(_O_BINARY) + if ((fp = fopen(data_name,"wb")) == NULL) +#else + if ((fp = fopen(data_name,"w")) == NULL) +#endif + { + debug2((errout,"Can't create file '%s'\n",data_name)); + free(conf_name); + free(data_name); + return ucmm_profile_copy; + } + + if (fwrite(pdata, 1, psize, fp) != psize) { + debug2((errout,"Failed to write profile '%s' into buffer\n",data_name)); + free(conf_name); + free(data_name); + return ucmm_profile_copy; + } + + if (fclose(fp) != 0) { + debug2((errout,"Failed to close profile '%s' into buffer\n",data_name)); + free(conf_name); + free(data_name); + return ucmm_profile_copy; + } + } + + debug2((errout,"profile copied OK\n")); + + /* Update the config file */ + { + jc_error ev; + jcnf *jc; + char keyn1[100]; + char keyn2[100]; + char *mname; /* Name of key to match to */ + char *mval; /* Value to match */ + int ix = 0; + int recno = -1; /* Number of the last record read */ + + /* Open the configuration file for modification */ + if (mkpdirs(conf_name)) { + debug2((errout,"Can't create directories for file '%s'\n",conf_name)); + free(conf_name); + free(data_name); + return ucmm_open_config; + } + + if ((jc = new_jcnf(&ev, conf_name, jc_modify, jc_create)) == NULL) { + debug2((errout,"new_jcnf '%s' failed with error %d\n",conf_name,ev)); + free(conf_name); + free(data_name); + return ucmm_open_config; + } + + /* if EDID supplied, Locate a matching EDID */ + if (edid != NULL) { + mname = "EDID"; + if ((mval = buf2hex(edid, edid_len)) == NULL) { + jc->del(jc); + free(conf_name); + free(data_name); + return ucmm_resource; + } + + /* Else fall back to X11 display name and screen */ + } else { + if (display_name == NULL) { + jc->del(jc); + free(conf_name); + free(data_name); + return ucmm_no_edid_or_display; + } + mname = "NAME"; + if ((mval = strdup(display_name)) == NULL) { + jc->del(jc); + free(conf_name); + free(data_name); + return ucmm_resource; + } + } + + debug2((errout,"Searching for %s = '%s'\n",mname,mval)); + for (;;ix++) { + char *key, *pp; + jc_type type; + unsigned char *data; + size_t dataSize; + int ii; + + if ((ev = jc->locate_key(jc, &ix, "devices/display/", 0, 0)) != jc_ok + || (ev = jc->get_key(jc, ix, &key, &type, &data, &dataSize, NULL)) != jc_ok) { + if (ev == jc_ix_oorange) { + break; + } + debug2((errout,"jcnf locate/get_key failed with error %d\n",ev)); + free(mval); + jc->del(jc); + free(conf_name); + free(data_name); + return ucmm_open_config; + } + + if ((pp = jc_get_nth_elem(key, 2)) == NULL) { + continue; + } + if ((ii = atoi(pp)) == 0) { + free(pp); + continue; + } + if (ii > recno) /* Track biggest, so we know what to create next */ + recno = ii; + if ((pp = jc_get_nth_elem(key, 3)) != NULL && strcmp(pp, mname) == 0 && type == jc_string && strcmp(data, mval) == 0) { + /* Found matching record */ + free(pp); + break; + } + if (pp != NULL) + free(pp); + } + + /* Create a new record */ + if (ev == jc_ix_oorange) { + recno++; /* Make it the next index */ + if (recno <= 0) + recno = 1; + debug2((errout, "Adding a new record %d\n",recno)); + } else { + debug2((errout, "Replacing record %d\n",recno)); + } + + /* Write the record */ + sprintf(keyn1, "devices/display/%d/%s", recno, mname); + sprintf(keyn2, "devices/display/%d/ICC_PROFILE", recno); + if ((ev = jc->set_key(jc, -1, keyn1, jc_string, mval, strlen(mval)+1, NULL)) != jc_ok + || (ev = jc->set_key(jc, -1, keyn2, jc_string, data_name, strlen(data_name)+1, NULL)) != jc_ok) { + debug2((errout,"jcnf set_key failed with error %d\n",ev)); + free(mval); + jc->del(jc); + free(conf_name); + free(data_name); + return ucmm_set_config; + } + free(mval); + + /* write to record the EDID or display name and the profile path */ + if ((ev = jc->update(jc)) != 0) { + debug2((errout,"jcnf write to '%s' failed with error %d\n",conf_name,ev)); + jc->del(jc); + free(conf_name); + free(data_name); + return ucmm_save_config; + } + debug2((errout,"Updated config file '%s'\n",conf_name)); + + /* We're done with this */ + jc->del(jc); + free(conf_name); + free(data_name); + } + debug2((errout,"ucmm done profile install\n")); + return ucmm_ok; +} + +/* Un-install a profile for a given monitor. */ +/* Either EDID or display_name may be NULL, but not both. */ +/* The monitor is left with no profile association. If a profile */ +/* name is provided and matches the one that was associated with */ +/* the monitor, and has no other association, then it will be deleted */ +/* from the data directory. */ +/* Return an error code */ +ucmm_error ucmm_uninstall_monitor_profile( + ucmm_scope scope, /* Scope of instalation */ + unsigned char *edid, /* Primary device identifier, NULL if none. */ + int edid_len, /* Length of edid data */ + char *display_name, /* Fall back device association, */ + char *profile /* Base name of profile to be deleted. NULL if not to be deleted. */ +) { + char *config_file = CONFIG_FILE; + char *profile_dir = PROFILE_DIR; + char *conf_name = NULL; /* Configuration path to use */ + char *data_name = NULL; /* Data path to use */ + char *dprof = NULL; /* Destination for profile */ + unsigned int edid_hash = 0; + + if (edid != NULL) + edid_hash = fnv_32_buf(edid, edid_len); + + debug2((errout,"ucmm_uninstall_monitor_profile called with profile '%s', edid 0x%x, disp '%s'\n",profile,edid_hash,display_name)); + + /* Locate the directories where the config file is, */ + /* and where the profile should be too. */ + { + int npaths; + xdg_error er; + char *data_pathfile; /* Path & name of destination */ + char **paths; + char *tt; + + if (npaths = xdg_bds(&er, &paths, xdg_conf, xdg_read, + scope == ucmm_local_system ? xdg_local : xdg_user, + config_file) == 0) { + return ucmm_open_config; + } + if ((conf_name = strdup(paths[0])) == NULL) { + xdg_free(paths, npaths); + return ucmm_resource; + } + xdg_free(paths, npaths); + + if (profile != NULL) { + /* Combined sub-path and profile name */ + if ((data_pathfile = malloc(strlen(profile_dir) + 1 + strlen(profile))) == NULL) + return ucmm_resource; + strcpy(data_pathfile, profile_dir); + + if (strlen(data_pathfile) > 1 && data_pathfile[strlen(data_pathfile)-1] != '/') + strcat(data_pathfile, "/"); + + if ((tt = strrchr(profile, '/')) != NULL) /* Get base name of profile */ + tt++; + else + tt = profile; + strcat(data_pathfile, tt); + + if (npaths = xdg_bds(&er, &paths, xdg_conf, xdg_read, + scope == ucmm_local_system ? xdg_local : xdg_user, + data_pathfile) == 0) { + free(data_pathfile); + free(conf_name); + return ucmm_open_config; + } + free(data_pathfile); + if ((data_name = strdup(paths[0])) == NULL) { + free(conf_name); + xdg_free(paths, npaths); + return ucmm_resource; + } + xdg_free(paths, npaths); + } + } + + debug2((errout,"config file = '%s'\n",conf_name)); + if (data_name != NULL) + debug2((errout,"data file = '%s'\n",data_name)); + + /* Get the config file */ + { + jc_error ev; + jcnf *jc; + char keyn1[100]; + char *mname; /* Name of key to match to */ + char *mval; /* Value to match */ + int ix; + int recno = -1; /* Number of the last record read */ + + /* Open the configuration file for modification */ + if (mkpdirs(conf_name)) { + debug2((errout,"Can't create directories for file '%s'\n",conf_name)); + free(conf_name); + if (data_name != NULL) + free(data_name); + return ucmm_open_config; + } + + if ((jc = new_jcnf(&ev, conf_name, jc_modify, jc_create)) == NULL) { + debug2((errout,"new_jcnf '%s' failed with error %d\n",conf_name,ev)); + free(conf_name); + if (data_name != NULL) + free(data_name); + return ucmm_open_config; + } + + /* if EDID supplied, Locate a matching EDID */ + if (edid != NULL) { + mname = "EDID"; + if ((mval = buf2hex(edid, edid_len)) == NULL) { + jc->del(jc); + free(conf_name); + if (data_name != NULL) + free(data_name); + return ucmm_resource; + } + + /* Else fall back to X11 display name and screen */ + } else { + if (display_name == NULL) { + jc->del(jc); + free(conf_name); + if (data_name != NULL) + free(data_name); + return ucmm_no_edid_or_display; + } + mname = "NAME"; + if ((mval = strdup(display_name)) == NULL) { + jc->del(jc); + free(conf_name); + if (data_name != NULL) + free(data_name); + return ucmm_resource; + } + } + + debug2((errout,"Searching for %s = '%s'\n",mname,mval)); + for (ix = 0;;ix++) { + char *key, *pp; + jc_type type; + unsigned char *data; + size_t dataSize; + int ii; + + if ((ev = jc->locate_key(jc, &ix, "devices/display/", 0, 0)) != jc_ok + || (ev = jc->get_key(jc, ix, &key, &type, &data, &dataSize, NULL)) != jc_ok) { + if (ev == jc_ix_oorange) { + break; + } + debug2((errout,"jcnf locate/get_key failed with error %d\n",ev)); + free(mval); + jc->del(jc); + free(conf_name); + if (data_name != NULL) + free(data_name); + return ucmm_open_config; + } + + if ((pp = jc_get_nth_elem(key, 2)) == NULL) { + continue; + } + if ((ii = atoi(pp)) == 0) { + free(pp); + continue; + } + if (ii > recno) /* Track biggest, so we know what to create next */ + recno = ii; + if ((pp = jc_get_nth_elem(key, 3)) != NULL && strcmp(pp, mname) == 0 && type == jc_string && strcmp(data, mval) == 0) { + /* Found matching record */ + free(pp); + break; + } + if (pp != NULL) + free(pp); + } + + if (ev == jc_ix_oorange) { + debug2((errout,"No matching display was found\n")); + free(mval); + jc->del(jc); + free(conf_name); + if (data_name != NULL) + free(data_name); + return ucmm_monitor_not_found; + /* (Should we delete the file anyway ???) */ + } + free(mval); + + debug2((errout,"Deleting record %d key '%s'\n",recno,keyn1)); + + /* Delete the record */ + sprintf(keyn1, "devices/display/%d/", recno); + + for (ix = -1;;ix--) { + if ((ev = jc->locate_key(jc, &ix, keyn1, 0, 1)) == jc_ok) { + if ((ev = jc->delete_key(jc, ix, NULL)) != jc_ok) { + debug2((errout,"jcnf delete_key failed with error %d\n",ev)); + jc->del(jc); + free(conf_name); + if (data_name != NULL) + free(data_name); + return ucmm_delete_key; + } + } else { + if (ev == jc_ix_oorange) { + break; + } + debug2((errout,"jcnf locate/get_key failed with error %d\n",ev)); + jc->del(jc); + free(conf_name); + if (data_name != NULL) + free(data_name); + return ucmm_open_config; + } + } + + if (data_name != NULL) { + /* See if the profile is used by any other device */ + + debug2((errout, "Searching for any reference to profile '%s'\n",data_name)); + for (ix = 0;;ix++) { + char *key, *pp; + jc_type type; + unsigned char *data; + size_t dataSize; + + if ((ev = jc->locate_key(jc, &ix, "devices/display/", 0, 0)) != jc_ok + || (ev = jc->get_key(jc, ix, &key, &type, &data, &dataSize, NULL)) != jc_ok) { + if (ev == jc_ix_oorange) { + break; + } + debug2((errout,"jcnf locate/get_key failed with error %d\n",ev)); + jc->del(jc); + free(conf_name); + if (data_name != NULL) + free(data_name); + return ucmm_access_config; + } + if ((pp = jc_get_nth_elem(key, 3)) == NULL) + continue; + if (strcmp(pp,"ICC_PROFILE") != 0 + || type != jc_string + || strcmp(data, data_name) != 0) { + free(pp); + continue; + } + free(pp); + break; + } + /* If not, delete the file */ + if (ev == jc_ix_oorange) { + debug2((errout,"Deleting profile '%s'\n",data_name)); + if (unlink(data_name) != 0) { + debug2((errout,"ucmm unlink '%s' failed\n",data_name)); + jc->del(jc); + free(conf_name); + if (data_name != NULL) + free(data_name); + return ucmm_access_config; + } + } + } + + /* Update the config */ + if ((ev = jc->update(jc)) != 0) { + debug2((errout,"jcnf write to '%s' failed with error %d\n",conf_name,ev)); + jc->del(jc); + free(conf_name); + if (data_name != NULL) + free(data_name); + return ucmm_save_config; + } + debug2((errout,"Updated config file '%s'\n",conf_name)); + + /* We're done with this */ + jc->del(jc); + free(conf_name); + if (data_name != NULL) + free(data_name); + } + debug2((errout,"ucmm done profile un-install\n")); + return ucmm_ok; +} + +/* Get an associated monitor profile. */ +/* Return ucmm_no_profile if there is no installed profile for this */ +/* monitor. */ +/* Return an error code */ +ucmm_error ucmm_get_monitor_profile( + unsigned char *edid, /* Primary device identifier, NULL if none. */ + int edid_len, /* Length of edid data */ + char *display_name, /* Fall back device association, */ + char **profile /* Return path to profile. free() afterwards. */ +) { + int scope; + char *config_file = "color.jcnf"; + char *conf_name = NULL; /* Configuration path to use */ + unsigned int edid_hash = 0; + + if (edid != NULL) + edid_hash = fnv_32_buf(edid, edid_len); + + debug2((errout,"ucmm_get_monitor_profile called edid 0x%x, disp '%s'\n",edid_hash,display_name)); + + /* Look at user then local system scope */ + for (scope = 0; scope < 2; scope++) { + + /* Locate the directories where the config file is, */ + { + int npaths; + xdg_error er; + char **paths; + char *tt; + + if (npaths = xdg_bds(&er, &paths, xdg_conf, xdg_read, + scope == ucmm_local_system ? xdg_local : xdg_user, + config_file) == 0) { + continue; + } + if ((conf_name = strdup(paths[0])) == NULL) { + xdg_free(paths, npaths); + return ucmm_resource; + } + xdg_free(paths, npaths); + } + + /* Get the config file */ + { + jc_error ev; + jcnf *jc; + char keyn1[100]; + char *mname; /* Name of key to match to */ + char *mval; /* Value to match */ + int ix; + int recno = -1; /* Number of the last record read */ + char *key, *pp; + jc_type type; + unsigned char *data; + size_t dataSize; + + /* Open the configuration file for reading */ + if ((jc = new_jcnf(&ev, conf_name, jc_read, jc_no_create)) == NULL) { + debug2((errout,"new_jcnf '%s' failed with error %d\n",conf_name,ev)); + continue; /* Try the next scope */ + } + + /* if EDID supplied, Locate a matching EDID */ + if (edid != NULL) { + mname = "EDID"; + if ((mval = buf2hex(edid, edid_len)) == NULL) { + debug2((errout,"buf2jex failed\n")); + jc->del(jc); + free(conf_name); + return ucmm_resource; + } + + /* Else fall back to X11 display name and screen */ + } else { + if (display_name == NULL) { + debug2((errout,"No EDID and display name failed\n")); + jc->del(jc); + free(conf_name); + return ucmm_no_edid_or_display; + } + mname = "NAME"; + if ((mval = strdup(display_name)) == NULL) { + debug2((errout,"strdup failed\n")); + jc->del(jc); + free(conf_name); + return ucmm_resource; + } + } + + debug2((errout,"Searching for %s = '%s'\n",mname,mval)); + for (ix = 0;;ix++) { + int ii; + + if ((ev = jc->locate_key(jc, &ix, "devices/display/", 0, 0)) != jc_ok + || (ev = jc->get_key(jc, ix, &key, &type, &data, &dataSize, NULL)) != jc_ok) { + if (ev == jc_ix_oorange) { + break; + } + debug2((errout,"jcnf locate/get_key failed with error %d\n",ev)); + free(mval); + jc->del(jc); + free(conf_name); + return ucmm_open_config; + } + + if ((pp = jc_get_nth_elem(key, 2)) == NULL) { + continue; + } + if ((ii = atoi(pp)) == 0) { + free(pp); + continue; + } + if (ii > recno) /* Track biggest, so we know what to create next */ + recno = ii; + if ((pp = jc_get_nth_elem(key, 3)) != NULL && strcmp(pp, mname) == 0 && type == jc_string + && strcmp(data, mval) == 0) { + /* Found matching record */ + free(pp); + break; + } + if (pp != NULL) + free(pp); + } + + if (ev == jc_ix_oorange) { + debug2((errout,"No matching display was found\n")); + continue; /* On to the next scope */ + } + free(mval); + + /* Get the profile path from the record */ + sprintf(keyn1, "devices/display/%d/ICC_PROFILE", recno); + key = keyn1; + debug2((errout,"Looking up record %d key '%s'\n",recno,keyn1)); + + if ((ev = jc->get_key(jc, -1, &key, &type, &data, &dataSize, NULL)) != jc_ok + || type != jc_string) { + debug2((errout,"jcnf locate/get_key failed with error %d\n",ev)); + jc->del(jc); + free(conf_name); + if (ev == jc_ix_oorange) { + continue; /* try the next config */ + } + return ucmm_access_config; + } + if ((*profile = strdup(data)) == NULL) { + debug2((errout,"jcnf get_key malloc failed\n")); + jc->del(jc); + free(conf_name); + return ucmm_resource; + } + + /* We're done with this */ + jc->del(jc); + free(conf_name); + return ucmm_ok; + debug2((errout,"Returning current profile '%s'\n",data)); + } + } + debug2((errout,"Failed to find a current profile\n")); + + return ucmm_no_profile; +} + + +/* Return an ASCII error message string interpretation of an error number */ +char *ucmm_error_string(ucmm_error erno) { + + switch (erno) { + case ucmm_ok: + return "OK"; + case ucmm_resource: + return "Resource failure (e.g. out of memory)"; + case ucmm_invalid_profile: + return "Profile is not a valid display ICC profile"; + case ucmm_no_profile: + return "There is no associated profile"; + case ucmm_no_home: + return "There is no HOME environment variable defined"; + case ucmm_no_edid_or_display: + return "There is no edid or display name"; + case ucmm_profile_copy: + return "There was an error copying the profile"; + case ucmm_open_config: + return "There was an error opening the config file"; + case ucmm_access_config: + return "There was an error accessing the config information"; + case ucmm_set_config: + return "There was an error setting the config file"; + case ucmm_save_config: + return "There was an error saving the config file"; + case ucmm_monitor_not_found: + return "The EDID or display wasn't matched"; + case ucmm_delete_key: + return "Delete_key failed"; + case ucmm_delete_profile: + return "Delete_key failed"; + } + return "Unknown error number"; +} + + +/* ============================================================= */ + +/* + * hash_32 - 32 bit Fowler/Noll/Vo hash code + * + * @(#) $Revision: 1.8 $ + * @(#) $Id: hash_32.c,v 1.8 2003/10/03 20:38:13 chongo Exp $ + * @(#) $Source: /usr/local/src/cmd/fnv/RCS/hash_32.c,v $ + * + *** + * + * Fowler/Noll/Vo hash + * + * The basis of this hash algorithm was taken from an idea sent + * as reviewer comments to the IEEE POSIX P1003.2 committee by: + * + * Phong Vo (http://www.research.att.com/info/kpv/) + * Glenn Fowler (http://www.research.att.com/~gsf/) + * + * In a subsequent ballot round: + * + * Landon Curt Noll (http://www.isthe.com/chongo/) + * + * improved on their algorithm. Some people tried this hash + * and found that it worked rather well. In an EMail message + * to Landon, they named it the ``Fowler/Noll/Vo'' or FNV hash. + * + * FNV hashes are designed to be fast while maintaining a low + * collision rate. The FNV speed allows one to quickly hash lots + * of data while maintaining a reasonable collision rate. See: + * + * http://www.isthe.com/chongo/tech/comp/fnv/index.html + * + * for more details as well as other forms of the FNV hash. + *** + * + * NOTE: The FNV-0 historic hash is not recommended. One should use + * the FNV-1 hash instead. + * + * To use the 32 bit FNV-0 historic hash, pass FNV0_32_INIT as the + * unsigned int hashval argument to fnv_32_buf() or fnv_32_str(). + * + * To use the recommended 32 bit FNV-1 hash, pass FNV1_32_INIT as the + * unsigned int hashval argument to fnv_32_buf() or fnv_32_str(). + * + *** + * + * Please do not copyright this code. This code is in the public domain. + * + * LANDON CURT NOLL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO + * EVENT SHALL LANDON CURT NOLL BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + * By: + * chongo <Landon Curt Noll> /\oo/\ + * http://www.isthe.com/chongo/ + * + * Share and Enjoy! :-) + */ + +/* + * 32 bit magic FNV-0 and FNV-1 prime + */ +#define FNV_32_PRIME ((unsigned int)0x01000193) + +#define FNV1_32_INIT ((unsigned int)0x811c9dc5) + +/* + * fnv_32_buf - perform a 32 bit Fowler/Noll/Vo hash on a buffer + * + * input: + * buf - start of buffer to hash + * len - length of buffer in octets + * hval - previous hash value or 0 if first call + * + * returns: + * 32 bit hash as a static hash type + * + * NOTE: To use the recommended 32 bit FNV-1 hash, use FNV1_32_INIT as the hval + * argument on the first call to either fnv_32_buf() or fnv_32_str(). + */ +static unsigned int +fnv_32_buf_cont(void *buf, size_t len, unsigned int hval) +{ + unsigned char *bp = (unsigned char *)buf; /* start of buffer */ + unsigned char *be = bp + len; /* beyond end of buffer */ + + /* + * FNV-1 hash each octet in the buffer + */ + while (bp < be) { + + /* multiply by the 32 bit FNV magic prime mod 2^32 */ +#if defined(NO_FNV_GCC_OPTIMIZATION) + hval *= FNV_32_PRIME; +#else + hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24); +#endif + + /* xor the bottom with the current octet */ + hval ^= (unsigned int)*bp++; + } + + /* return our new hash value */ + return hval; +} + +static unsigned int +fnv_32_buf(void *buf, size_t len) { + return fnv_32_buf_cont(buf, len, FNV1_32_INIT); +} + diff --git a/ucmm/ucmm.h b/ucmm/ucmm.h new file mode 100644 index 0000000..fc08ca1 --- /dev/null +++ b/ucmm/ucmm.h @@ -0,0 +1,98 @@ + +#ifndef UCMM_H +#define UCMM_H + +/* + * Unix micro-cmm to manage X11 display + * calibration and profile loading. + */ + +/************************************************************************* + Copyright 2008 Graeme W. Gill + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + *************************************************************************/ + +typedef enum { + ucmm_ok = 0, + ucmm_resource, /* Resource failure (e.g. out of memory) */ + ucmm_invalid_profile, /* Profile is not a valid display ICC profile */ + ucmm_no_profile, /* There is no associated profile */ + ucmm_no_home, /* There is no HOME environment variable defined */ + ucmm_no_edid_or_display, /* There is no edid or display name */ + ucmm_profile_copy, /* There was an error copying the profile */ + ucmm_open_config, /* There was an error opening the config file */ + ucmm_access_config, /* There was an error accessing the config information */ + ucmm_set_config, /* There was an error setting the config file */ + ucmm_save_config, /* There was an error saving the config file */ + ucmm_monitor_not_found, /* The EDID or display wasn't matched */ + ucmm_delete_key, /* Delete_key failed */ + ucmm_delete_profile, /* Delete_key failed */ +} ucmm_error; + +/* Install scope */ +typedef enum { + ucmm_user, + ucmm_local_system +} ucmm_scope; + +/* Install a profile for a given monitor */ +/* Either EDID or display_name may be NULL, but not both. */ +/* Any existing association is overwritten. Installed profiles */ +/* are not deleted. */ +ucmm_error ucmm_install_monitor_profile( + ucmm_scope scope, /* Scope of instalation */ + unsigned char *edid, /* Primary device identifier, NULL if none. */ + int edid_len, /* Length of edid data */ + char *display_name, /* Fall back device association, */ + /* the X11 display name */ + char *profile /* Path to profile to be installed. */ +); + +/* Un-install a profile for a given monitor. */ +/* Either EDID or display_name may be NULL, but not both. */ +/* The monitor is left with no profile association. If a profile */ +/* name is provided and matches the one that was associated with */ +/* the monitor, and has no other association, then it will be deleted */ +/* from the data directory. */ +/* Return an error code */ +ucmm_error ucmm_uninstall_monitor_profile( + ucmm_scope scope, /* Scope of instalation */ + unsigned char *edid, /* Primary device identifier, NULL if none. */ + int edid_len, /* Length of edid data */ + char *display_name, /* Fall back device association, */ + char *profile /* Base name of profile to be deleted, NULL if not to be deleted. */ +); + +/* Get an associated monitor profile. */ +/* Return ucmm_no_profile if there is no installed profile for this */ +/* monitor. */ +/* Return an error code */ +ucmm_error ucmm_get_monitor_profile( + unsigned char *edid, /* Primary device identifier, NULL if none. */ + int edid_len, /* Length of edid data */ + char *display_name, /* Fall back device association, */ + char **profile /* Return path to profile. free() afterwards. */ +); + +/* Return an ASCII error message string interpretation of an error number */ +char *ucmm_error_string(ucmm_error erno); + +#endif /* UCMM_H */ |