aboutsummaryrefslogblamecommitdiffstats
path: root/src/servers/coopgamma.c
blob: 6e544be8ac38608cb04c2705d5898d0cdba68274 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
















                                                                        

                      


                             
                            



                    
                  





                   



















































































                                                                                                   
                                                                                              







































                                                                                                 

                           




























                                                                                                       



                                  














                                                                                       




                    















                                                                




                                                                  









                                                                                         




















































































                                                                                                  
                           












                                                                       

                                                                                





















































































































                                                                                                              


                                       

                     

















































                                                                                                






































                                                                                                              
/**
 * coopgammad -- Cooperative gamma server
 * Copyright (C) 2016  Mattias Andrée (maandree@kth.se)
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "coopgamma.h"
#include "gamma.h"
#include "../state.h"
#include "../communication.h"
#include "../util.h"
#include "../types/output.h"

#include <libclut.h>

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>



/**
 * Apply a filter on top of another filter
 * 
 * @param  dest         The output for the resulting ramp-trio, must be initialised
 * @param  application  The red, green and blue ramps, as one single raw array,
 *                      of the filter that should be applied
 * @param  depth        -1: `float` stops
 *                      -2: `double` stops
 *                      Other: the number of bits of each (integral) stop
 * @param  base         The CLUT on top of which the new filter should be applied,
 *                      this can be the same pointer as `dest`
 */
static void apply_filter(union gamma_ramps* restrict dest, void* restrict application,
			 int depth, union gamma_ramps* restrict base)
{
  union gamma_ramps app;
  size_t bytedepth;
  size_t red_width, green_width, blue_width;
  
  if (depth == -1)
    bytedepth = sizeof(float);
  else if (depth == -2)
    bytedepth = sizeof(double);
  else
    bytedepth = (size_t)depth / 8;
  
  red_width   = (app.u8.red_size   = base->u8.red_size)   * bytedepth;
  green_width = (app.u8.green_size = base->u8.green_size) * bytedepth;
  blue_width  = (app.u8.blue_size  = base->u8.blue_size)  * bytedepth;
  
  app.u8.red   = application;
  app.u8.green = app.u8.red   + red_width;
  app.u8.blue  = app.u8.green + green_width;
  
  if (dest != base)
    {
      memcpy(dest->u8.red,   base->u8.red,   red_width);
      memcpy(dest->u8.green, base->u8.green, green_width);
      memcpy(dest->u8.blue,  base->u8.blue,  blue_width);
    }
  
  switch (depth)
    {
    case 8:
      libclut_apply(&(dest->u8), UINT8_MAX, uint8_t, &(app.u8), UINT8_MAX, uint8_t, 1, 1, 1);
      break;
    case 16:
      libclut_apply(&(dest->u16), UINT16_MAX, uint16_t, &(app.u16), UINT16_MAX, uint16_t, 1, 1, 1);
      break;
    case 32:
      libclut_apply(&(dest->u32), UINT32_MAX, uint32_t, &(app.u32), UINT32_MAX, uint32_t, 1, 1, 1);
      break;
    case 64:
      libclut_apply(&(dest->u64), UINT64_MAX, uint64_t, &(app.u64), UINT64_MAX, uint64_t, 1, 1, 1);
      break;
    case -1:
      libclut_apply(&(dest->f), 1.0f, float, &(app.d), 1.0f, float, 1, 1, 1);
      break;
    case -2:
      libclut_apply(&(dest->d), (double)1, double, &(app.f), (double)1, double, 1, 1, 1);
      break;
    default:
      abort();
    }
}



/**
 * Remove a filter from an output
 * 
 * @param   out     The output
 * @param   filter  The filter
 * @return          The index of the filter, `out->table_size` if not found
 */
static ssize_t remove_filter(struct output* restrict out, struct filter* restrict filter)
{
  size_t i, n = out->table_size;
  
  for (i = 0; i < n; i++)
    if (!strcmp(filter->class, out->table_filters[i].class))
      break;
  
  if (i == out->table_size)
    {
      fprintf(stderr, "%s: ignoring attempt to removing non-existing filter on CRTC %s: %s\n",
	      argv0, out->name, filter->class);
      return (ssize_t)(out->table_size);
    }
  
  filter_destroy(out->table_filters + i);
  libgamma_gamma_ramps8_destroy(&(out->table_sums[i].u8));
  
  n = n - i - 1;
  memmove(out->table_filters + i, out->table_filters + i + 1, n * sizeof(*(out->table_filters)));
  memmove(out->table_sums    + i, out->table_sums    + i + 1, n * sizeof(*(out->table_sums)));
  out->table_size--;
  
  return (ssize_t)i;
}


/**
 * Add a filter to an output
 * 
 * @param   out     The output
 * @param   filter  The filter
 * @return          The index given to the filter, -1 on error
 */
static ssize_t add_filter(struct output* restrict out, struct filter* restrict filter)
{
  size_t i, n = out->table_size;
  int r = -1;
  
  /* Remove? */
  if (filter->lifespan == LIFESPAN_REMOVE)
    return remove_filter(out, filter);
  
  /* Update? */
  for (i = 0; i < n; i++)
    if (!strcmp(filter->class, out->table_filters[i].class))
      break;
  if (i != n)
    {
      filter_destroy(out->table_filters + i);
      out->table_filters[i] = *filter;
      filter->class = NULL;
      filter->ramps = NULL;
      return (ssize_t)i;
    }
  
  /* Add! */
  for (i = 0; i < n; i++)
    if (filter->priority > out->table_filters[i].priority)
      break;
  
  if (n == out->table_alloc)
    {
      void* new;
      
      new = realloc(out->table_filters, (n + 10) * sizeof(*(out->table_filters)));
      if (new == NULL)
	return -1;
      out->table_filters = new;
      
      new = realloc(out->table_sums, (n + 10) * sizeof(*(out->table_sums)));
      if (new == NULL)
	return -1;
      out->table_sums = new;
      
      out->table_alloc += 10;
    }
  
  memmove(out->table_filters + i + 1, out->table_filters + i, (n - i) * sizeof(*(out->table_filters)));
  memmove(out->table_sums    + i + 1, out->table_sums    + i, (n - i) * sizeof(*(out->table_sums)));
  out->table_size++;
  
  out->table_filters[i] = *filter;
  filter->class = NULL;
  filter->ramps = NULL;
  
  COPY_RAMP_SIZES(&(out->table_sums[i].u8), out);
  switch (out->depth)
    {
    case  8:  r = libgamma_gamma_ramps8_initialise(&(out->table_sums[i].u8));    break;
    case 16:  r = libgamma_gamma_ramps16_initialise(&(out->table_sums[i].u16));  break;
    case 32:  r = libgamma_gamma_ramps32_initialise(&(out->table_sums[i].u32));  break;
    case 64:  r = libgamma_gamma_ramps64_initialise(&(out->table_sums[i].u64));  break;
    case -1:  r = libgamma_gamma_rampsf_initialise(&(out->table_sums[i].f));     break;
    case -2:  r = libgamma_gamma_rampsd_initialise(&(out->table_sums[i].d));     break;
    default:
      abort();
    }
  if (r < 0)
    return -1;
  
  return (ssize_t)i;
}


/**
 * Handle a closed connection
 * 
 * @param   client  The file descriptor for the client
 * @return          Zero on success, -1 on error
 */
int connection_closed(int client)
{
  size_t i, j, k;
  int remove;
  
  for (i = 0; i < outputs_n; i++)
    {
      struct output* output = outputs + i;
      ssize_t updated = -1;
      for (j = k = 0; j < output->table_size; j += !remove, k++)
	{
	  if (j != k)
	    {
	      output->table_filters[j] = output->table_filters[k];
	      output->table_sums[j]    = output->table_sums[k];
	    }
	  remove = output->table_filters[j].client == client;
	  remove = remove && (output->table_filters[j].lifespan == LIFESPAN_UNTIL_DEATH);
	  if (remove)
	    {
	      filter_destroy(output->table_filters + j);
	      libgamma_gamma_ramps8_destroy(&(output->table_sums[j].u8));
	      output->table_size -= 1;
	      if (updated == -1)
		updated = (ssize_t)j;
	    }
	}
      if (updated >= 0)
	if (flush_filters(output, (size_t)updated) < 0)
	  return -1;
    }
  
  return 0;
}


/**
 * Handle a ‘Command: get-gamma’ message
 * 
 * @param   conn           The index of the connection
 * @param   message_id     The value of the ‘Message ID’ header
 * @param   crtc           The value of the ‘CRTC’ header
 * @param   coalesce       The value of the ‘Coalesce’ header
 * @param   high_priority  The value of the ‘High priority’ header
 * @param   low_priority   The value of the ‘Low priority’ header
 * @return                 Zero on success (even if ignored), -1 on error,
 *                         1 if connection closed
 */
int handle_get_gamma(size_t conn, const char* restrict message_id, const char* restrict crtc,
		     const char* restrict coalesce, const char* restrict high_priority,
		     const char* restrict low_priority)
{
  struct output* restrict output;
  int64_t high, low;
  int coal;
  char* restrict buf;
  size_t start, end, len, n, i;
  char depth[3];
  char tables[sizeof("Tables: \n") + 3 * sizeof(size_t)];
  
  if (crtc          == NULL)  return send_error("protocol error: 'CRTC' header omitted");
  if (coalesce      == NULL)  return send_error("protocol error: 'Coalesce' header omitted");
  if (high_priority == NULL)  return send_error("protocol error: 'High priority' header omitted");
  if (low_priority  == NULL)  return send_error("protocol error: 'Low priority' header omitted");
  
  high = (int64_t)atoll(high_priority);
  low  = (int64_t)atoll(low_priority);
  
  if (!strcmp(coalesce, "yes"))
    coal = 1;
  else if (!strcmp(coalesce, "no"))
    coal = 0;
  else
    return send_error("protocol error: recognised value for 'Coalesce' header");
  
  output = output_find_by_name(crtc, outputs, outputs_n);
  if (output == NULL)
    return send_error("selected CRTC does not exist");
  else if (output->supported == LIBGAMMA_NO)
    return send_error("selected CRTC does not support gamma adjustments");
  
  for (start = 0; start < output->table_size; start++)
    if (output->table_filters[start].priority <= high)
      break;
  
  for (end = output->table_size; end > 0; end--)
    if (output->table_filters[end - 1].priority >= low)
      break;
  
  switch (output->depth)
    {
    case -2:  strcpy(depth, "d");  break;
    case -1:  strcpy(depth, "f");  break;
    default:
      sprintf(depth, "%i", output->depth);
      break;
    }
  
  if (coal)
    {
      *tables = '\0';
      n = output->ramps_size;
    }
  else
    {
      sprintf(tables, "Tables: %zu\n", end - start);
      n = (sizeof(int64_t) + output->ramps_size) * (end - start);
      for (i = start; i < end; i++)
	n += strlen(output->table_filters[i].class) + 1;
    }
  
  MAKE_MESSAGE(&buf, &n, n,
	       "In response to: %s\n"
	       "Depth: %s\n"
	       "Red size: %zu\n"
	       "Green size: %zu\n"
	       "Blue size: %zu\n"
	       "%s"
	       "Length: %zu\n"
	       "\n",
	       message_id, depth, output->red_size, output->green_size,
	       output->blue_size, tables, n);
  
  if (coal)
    {
      if ((start == 0) && (start < end))
	memcpy(buf + n, output->table_sums[end - 1].u8.red, output->ramps_size);
      else
	{
	  union gamma_ramps ramps;
	  if (make_plain_ramps(&ramps, output))
	    {
	      int saved_errno = errno;
	      free(buf);
	      errno = saved_errno;
	      return -1;
	    }
	  for (i = start; i < end; i++)
	    apply_filter(&ramps, output->table_filters[i].ramps, output->depth, &ramps);
	  memcpy(buf + n, ramps.u8.red, output->ramps_size);
	  libgamma_gamma_ramps8_destroy(&(ramps.u8));
	}
      n += output->ramps_size;
    }
  else
    for (i = start; i < end; i++)
      {
#if defined(__clang__)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wcast-align"
#endif
	*(int64_t*)(buf + n) = output->table_filters[i].priority;
#if defined(__clang__)
# pragma GCC diagnostic pop
#endif
	n += sizeof(int64_t);
	len = strlen(output->table_filters[i].class) + 1;
	memcpy(buf + n, output->table_filters[i].class, len);
	n += len;
	memcpy(buf + n, output->table_filters[i].ramps, output->ramps_size);
	n += output->ramps_size;
      }
  
  return send_message(conn, buf, n);
}


/**
 * Handle a ‘Command: set-gamma’ message
 * 
 * @param   conn        The index of the connection
 * @param   message_id  The value of the ‘Message ID’ header
 * @param   crtc        The value of the ‘CRTC’ header
 * @param   priority    The value of the ‘Priority’ header
 * @param   class       The value of the ‘Class’ header
 * @param   lifespan    The value of the ‘Lifespan’ header
 * @return              Zero on success (even if ignored), -1 on error,
 *                      1 if connection closed
 */
int handle_set_gamma(size_t conn, const char* restrict message_id, const char* restrict crtc,
		     const char* restrict priority, const char* restrict class, const char* restrict lifespan)
{
  struct message* restrict msg = inbound + conn;
  struct output* restrict output = NULL;
  struct filter filter;
  char* restrict p;
  char* restrict q;
  int saved_errno;
  ssize_t r;
  
  if (crtc     == NULL)  return send_error("protocol error: 'CRTC' header omitted");
  if (class    == NULL)  return send_error("protocol error: 'Class' header omitted");
  if (lifespan == NULL)  return send_error("protocol error: 'Lifespan' header omitted");
  
  filter.client   = connections[conn];
  filter.priority = priority == NULL ? 0 : (int64_t)atoll(priority);
  filter.ramps    = NULL;
  
  output = output_find_by_name(crtc, outputs, outputs_n);
  if (output == NULL)
    return send_error("CRTC does not exists");
  
  p = strstr(class, "::");
  if ((p == NULL) || (p == class))
    return send_error("protocol error: malformatted value for 'Class' header");
  q = strstr(p + 2, "::");
  if ((q == NULL) || (q == p))
    return send_error("protocol error: malformatted value for 'Class' header");
  
  if (!strcmp(lifespan, "until-removal"))
    filter.lifespan = LIFESPAN_UNTIL_REMOVAL;
  else if (!strcmp(lifespan, "until-death"))
    filter.lifespan = LIFESPAN_UNTIL_DEATH;
  else if (!strcmp(lifespan, "remove"))
    filter.lifespan = LIFESPAN_REMOVE;
  else
    return send_error("protocol error: recognised value for 'Lifespan' header");
  
  if (filter.lifespan == LIFESPAN_REMOVE)
    {
      if (msg->payload_size)
	fprintf(stderr, "%s: ignoring superfluous payload on Command: set-gamma message with "
			"Lifespan: remove\n", argv0);
      if (priority != NULL)
	fprintf(stderr, "%s: ignoring superfluous Priority header on Command: set-gamma message with "
			"Lifespan: remove\n", argv0);
    }
  else if (msg->payload_size != output->ramps_size)
    return send_error("invalid payload: size of message payload does matched the expectancy");
  else if (priority == NULL)
    return send_error("protocol error: 'Priority' header omitted");
  
  filter.class = memdup(class, strlen(class) + 1);
  if (filter.class == NULL)
    goto fail;
  
  if (filter.lifespan != LIFESPAN_REMOVE)
    {
      filter.ramps = memdup(msg->payload, msg->payload_size);
      if (filter.ramps == NULL)
	goto fail;
    }
  
  if ((r = add_filter(output, &filter)) < 0)
    goto fail;
  if (flush_filters(output, (size_t)r))
    goto fail;
  
  free(filter.class);
  free(filter.ramps);
  return send_errno(0);
  
 fail:
  saved_errno = errno;
  send_errno(saved_errno);
  free(filter.class);
  free(filter.ramps);
  errno = saved_errno;
  return -1;
}



/**
 * Recalculate the resulting gamma and
 * update push the new gamma ramps to the CRTC
 * 
 * @param   output         The output
 * @param   first_updated  The index of the first added or removed filter
 * @return                 Zero on success, -1 on error
 */
int flush_filters(struct output* restrict output, size_t first_updated)
{
  union gamma_ramps plain;
  union gamma_ramps* last;
  size_t i;
  
  if (first_updated == 0)
    {
      if (make_plain_ramps(&plain, output) < 0)
	return -1;
      last = &plain;
    }
  else
    last = output->table_sums + (first_updated - 1);
  
  for (i = first_updated; i < output->table_size; i++)
    {
      apply_filter(output->table_sums + i, output->table_filters[i].ramps, output->depth, last);
      last = output->table_sums + i;
    }
  
  set_gamma(output, last);
  
  if (first_updated == 0)
    libgamma_gamma_ramps8_destroy(&(plain.u8));
  
  return 0;
}



/**
 * Preserve current gamma ramps at priority 0 for all outputs
 * 
 * @return  Zero on success, -1 on error
 */
int preserve_gamma(void)
{
  size_t i;
  
  for (i = 0; i < outputs_n; i++)
    {
      struct filter filter = {
	.client   = -1,
	.priority = 0,
	.class    = NULL,
	.lifespan = LIFESPAN_UNTIL_REMOVAL,
	.ramps    = NULL
      };
      outputs[i].table_filters = calloc(4, sizeof(*(outputs[i].table_filters)));
      outputs[i].table_sums = calloc(4, sizeof(*(outputs[i].table_sums)));
      outputs[i].table_alloc = 4;
      outputs[i].table_size = 1;
      filter.class = memdup(PKGNAME "::" COMMAND "::preserved", sizeof(PKGNAME "::" COMMAND "::preserved"));
      if (filter.class == NULL)
	return -1;
      filter.ramps = memdup(outputs[i].saved_ramps.u8.red, outputs[i].ramps_size);
      if (filter.ramps == NULL)
	return -1;
      outputs[i].table_filters[0] = filter;
      COPY_RAMP_SIZES(&(outputs[i].table_sums[0].u8), outputs + i);
      if (!gamma_ramps_unmarshal(outputs[i].table_sums, outputs[i].saved_ramps.u8.red, outputs[i].ramps_size))
	return -1;
    }
  
  return 0;
}