/*
*
* cmap_alloc.c 1.x
*
* Copyright (c) 1991, 2015, Oracle and/or its affiliates. All rights reserved.
*
* 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 (including the next
* paragraph) 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.
*/


#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xos.h>
#include <X11/Xatom.h>
#include <stdio.h>
#include <stdlib.h>

#define All	-1
#define DYNAMIC_VISUAL(class)	(((class) % 2) ? True : False)

char	       *prog_name;
char	       *display_name = NULL;
int		force = False;
int		all_screens = False;
int		depth = All;
int		visual_class = All;
int		verbose = False;

int			 created_colormap = False;
XStandardColormap	*allocated_cmaps;
int			 num_cmaps;
XVisualInfo		*available_visuals;
int			 num_visuals;

static void	alloc_cmaps_for_screen(Display *display, int screen);
static void	alloc_cmap_for_visual(Display *display, int screen,
				      XVisualInfo *vinfo);
static void	create_colormap(Display *display, XVisualInfo *vinfo,
				XStandardColormap *std_colormap);
static char    *visual_class_name(int class);
static void	parse_cmdline(int argc, char **argv);
static int	string_to_depth(const char *str);
static int	string_to_visual(const char *str);
static void	usage(void) _X_NORETURN;


int
main(
    int         argc,
    char      **argv)
{
    Display *display;
    int screen;

    /* Take care of command line options */
    parse_cmdline(argc, argv);
    
    /* Try to open the display */
    if ((display = XOpenDisplay(display_name)) == NULL) {
	(void)fprintf(stderr, "Error %s: can't open display \"%s\".\n",
		      argv[0], XDisplayName(display_name));
	exit(0);
    }

    /* Handle all necessary screens */
    if (all_screens)
      for (screen = 0; screen < ScreenCount(display); screen++)
	alloc_cmaps_for_screen(display, screen);
    else
      alloc_cmaps_for_screen(display, DefaultScreen(display));

    /* If we created any colormaps, we need to ensure 
     * that they live after the program exits.
     */
    if (created_colormap)
      XSetCloseDownMode(display, RetainPermanent);
    
    XCloseDisplay(display);
    return (1);
}


static void
alloc_cmaps_for_screen(
    Display *display,
    int	     screen)
{
    XVisualInfo vinfo_template;
    XVisualInfo *vinfo;
    VisualID	 default_visualid;
    int v;
    
    if (verbose)
      (void)printf("Creating colormaps for screen #%d:\n", screen);
    
    /* Find out if any colormaps already exist in the property */
    if (!XGetRGBColormaps(display, RootWindow(display, screen), 
			  &allocated_cmaps, &num_cmaps, XA_RGB_DEFAULT_MAP))
      num_cmaps = 0;
    
    /* Find the available visuals on the screen */
    vinfo_template.screen = screen;
    available_visuals = XGetVisualInfo(display, VisualScreenMask, &vinfo_template, &num_visuals);

    default_visualid = XVisualIDFromVisual(DefaultVisual(display, screen));

    /* Only try the specified visual */
    if ((visual_class != All) && (depth != All)) {
	v = 0;
	vinfo = NULL;
	while (!vinfo && (v < num_visuals))
	  if ((available_visuals[v].class == visual_class) &&
	      (available_visuals[v].depth == depth))
	    vinfo = &available_visuals[v];
	  else
	    v++;
	if (vinfo)
	  if (vinfo->visualid == default_visualid) {
	      fprintf(stderr, "%s: no need to create a colormap for the default visual\n",
		      prog_name);
	      exit(0);
	  } else 
	    alloc_cmap_for_visual(display, screen, vinfo);
    }   
    
    /* Try all visuals of visual_class with any depth */
    else if (visual_class != All) {
	for (v = 0; v < num_visuals; v++)
	  if ((visual_class == available_visuals[v].class) &&
	      (available_visuals[v].visualid != default_visualid))
	    alloc_cmap_for_visual(display, screen, &available_visuals[v]);
    }
    
    /* Try all visuals of depth and any dynamic class */
    else if (depth != All) {
	for (v = 0; v < num_visuals; v++)
	  if ((depth == available_visuals[v].depth) &&
	      DYNAMIC_VISUAL(available_visuals[v].class) &&
	      (available_visuals[v].visualid != default_visualid))
	    alloc_cmap_for_visual(display, screen, &available_visuals[v]);
    }

    /* Try all visuals */
    else 
      for (v = 0; v < num_visuals; v++)
	if (DYNAMIC_VISUAL(available_visuals[v].class) &&
	    (available_visuals[v].visualid != default_visualid))
	  alloc_cmap_for_visual(display, screen, &available_visuals[v]);
}


static void
alloc_cmap_for_visual(
    Display *display,
    int screen,
    XVisualInfo *vinfo)
{
    int c = 0;
    XStandardColormap *std_cmap = NULL;
    XStandardColormap new_cmap;
    
    if (verbose)
      (void)printf("  Creating a colormap for the %s %d bit visual...",
		   visual_class_name(vinfo->class), vinfo->depth);

    /* Check to see if one already exists */
    while (!std_cmap && (c < num_cmaps))
      if ((allocated_cmaps[c].visualid == vinfo->visualid) &&
	  (allocated_cmaps[c].red_max == 0) &&
	  (allocated_cmaps[c].red_mult == 0) &&
	  (allocated_cmaps[c].green_max == 0) &&
	  (allocated_cmaps[c].green_mult == 0) &&
	  (allocated_cmaps[c].blue_max == 0) &&
	  (allocated_cmaps[c].blue_mult == 0))
	std_cmap = &allocated_cmaps[c];
      else
	c++;

    if (std_cmap && !force && verbose)
      (void)printf("one already exists\n");
    else if (!std_cmap || force) {
	/* Create the colormap */
	create_colormap(display, vinfo, &new_cmap);

	/* append it to the property on the root window */
	XChangeProperty(display, RootWindow(display, screen),
			XA_RGB_DEFAULT_MAP, XA_RGB_COLOR_MAP,
			32, PropModeAppend, (unsigned char *)(&new_cmap),
			10);
	if (verbose) 
	  (void)printf("done\n    new colormap id = 0x%lx\n", new_cmap.colormap);
    }
}


static void
create_colormap(
    Display		*display,
    XVisualInfo		*vinfo,
    XStandardColormap	*std_colormap	/* RETURN */
    )
{
    Colormap colormap;
    XColor color;

    colormap = XCreateColormap(display, RootWindow(display, vinfo->screen),
			       vinfo->visual, AllocNone);

    /* Allocate black from the colormap */
    color.red = color.green = color.blue = 0;
    XAllocColor(display, colormap, &color);

    /* Fill out the standard colormap information */
    std_colormap->colormap = colormap;
    std_colormap->red_max = 0;
    std_colormap->red_mult = 0;
    std_colormap->green_max = 0;
    std_colormap->green_mult = 0;
    std_colormap->blue_max = 0;
    std_colormap->blue_mult = 0;
    std_colormap->base_pixel = color.pixel;
    std_colormap->visualid = vinfo->visualid;

    /* We don't want anybody pulling the colormap out from
     * under running clients, so set the killid to 0.
     */
    std_colormap->killid = 0;
    
    created_colormap = True;
}


static char *
visual_class_name(
    int         class)
{
    char *name;
    
    switch (class) {
      case StaticGray:
	name = "StaticGray";
	break;
      case GrayScale:
	name = "GrayScale";
	break;
      case StaticColor:
	name = "StaticColor";
	break;
      case PseudoColor:
	name = "PseudoColor";
	break;
      case TrueColor:
	name = "TrueColor";
	break;
      case DirectColor:	
	name = "DirectColor";
	break;
      default:
	name = "";
	break;
    }
    return name;
}


static void
parse_cmdline(
    int         argc,
    char      **argv)
{
    int option = 1;
    
    if (argc)
      prog_name = argv[0];
    
    while (option < argc) {
	if (!strcmp(argv[option], "-display"))
	  if (++option < argc)
	    display_name = argv[option];
	  else
	    usage();

	else if (!strcmp(argv[option], "-force"))
	  force = True;

	else if (!strcmp(argv[option], "-allscreens"))
	  all_screens = True;

	else if (!strcmp(argv[option], "-depth"))
	  if (++option < argc) {
	      depth = string_to_depth(argv[option]);
	      if (depth == All) {
		  fprintf(stderr, "%s: unknown depth %s\n", prog_name, argv[option]);
		  usage();
	      }
	  } else
	    usage();

	else if (!strcmp(argv[option], "-visual"))
	  if (++option < argc) {
	      visual_class = string_to_visual(argv[option]);
	      if (visual_class == All) {
		  fprintf(stderr, "%s: unknown visual class %s\n", prog_name, argv[option]);
		  usage();
	      } else if (!DYNAMIC_VISUAL(visual_class)) {
		  fprintf(stderr, "%s: no need to create a colormap for a static visual\n", prog_name);
		  exit(0);
	      }
	  } else
	    usage();

	else if (!strcmp(argv[option], "-verbose"))
	  verbose = True;

	else if (!strcmp(argv[option], "-help"))
	  usage();

	else {
	    (void)fprintf(stderr, "%s: unknown command line option \"%s\"\n", 
			  prog_name, argv[option]);
	    usage();
	}
	option++;
    }
}


static int
string_to_depth(const char *str)
{
    int depth;
    
    if ((sscanf(str, "%d", &depth) != 1) || (depth < 1))
      depth = All;

    return depth;
}


static int
string_to_visual(const char *str)
{
    int visual_class;
    
    if (!strcmp(str, "StaticGray"))
      visual_class = StaticGray;
    else if (!strcmp(str, "GrayScale"))
      visual_class = GrayScale;
    else if (!strcmp(str, "StaticColor"))
      visual_class = StaticColor;
    else if (!strcmp(str, "PseudoColor"))
      visual_class = PseudoColor;
    else if (!strcmp(str, "TrueColor"))
      visual_class = TrueColor;
    else if (!strcmp(str, "DirectColor"))
      visual_class = DirectColor;
    else 
      visual_class = All;

    return visual_class;
}


static void
usage(void)
{
    (void)fprintf(stderr, "Usage: %s [-display <display:n.screen>]\n", prog_name);
    (void)fprintf(stderr, "\t\t[-force] [-allscreens]\n");
    (void)fprintf(stderr, "\t\t[-depth <n>] [-visual <class>]\n");
    (void)fprintf(stderr, "\t\t[-verbose] [-help]\n");
    exit(0);
}