aboutsummaryrefslogtreecommitdiffstats
path: root/libcoopgamma.c
diff options
context:
space:
mode:
Diffstat (limited to 'libcoopgamma.c')
-rw-r--r--libcoopgamma.c2538
1 files changed, 2538 insertions, 0 deletions
diff --git a/libcoopgamma.c b/libcoopgamma.c
new file mode 100644
index 0000000..30a68fe
--- /dev/null
+++ b/libcoopgamma.c
@@ -0,0 +1,2538 @@
+/* See LICENSE file for copyright and license details. */
+#include "libcoopgamma.h"
+
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+
+#if !defined(COOPGAMMAD)
+# define COOPGAMMAD "coopgammad"
+#endif
+
+
+#if defined(__clang__)
+# pragma GCC diagnostic ignored "-Wdocumentation"
+# pragma GCC diagnostic ignored "-Wcovered-switch-default"
+# pragma GCC diagnostic ignored "-Wcast-align"
+#endif
+
+
+#if defined(__GNUC__)
+# define NAME_OF_THE_PROCESS (argv0)
+extern const char *argv0;
+const char *argv0 __attribute__((weak)) = "libcoopgamma";
+#else
+# define NAME_OF_THE_PROCESS ("libcoopgamma")
+#endif
+
+
+
+#define SUBBUF\
+ (buf ? &buf[off] : NULL)
+
+#define NNSUBBUF\
+ (buf + off)
+
+#define MARSHAL_PROLOGUE\
+ char *restrict buf = vbuf;\
+ size_t off = 0;
+
+#define UNMARSHAL_PROLOGUE\
+ const char *restrict buf = vbuf;\
+ size_t off = 0;
+
+#define MARSHAL_EPILOGUE\
+ return off
+
+#define UNMARSHAL_EPILOGUE\
+ return *np = off, LIBCOOPGAMMA_SUCCESS
+
+#define marshal_prim(datum, type)\
+ ((buf != NULL ? *(type *)&buf[off] = (datum) : 0), off += sizeof(type))
+
+#define unmarshal_prim(datum, type)\
+ ((datum) = *(const type *)&buf[off], off += sizeof(type))
+
+#define marshal_version(version)\
+ marshal_prim(version, int)
+
+#define unmarshal_version(version)\
+ do {\
+ int version__;\
+ unmarshal_prim(version__, int);\
+ if (version__ < (version))\
+ return LIBCOOPGAMMA_INCOMPATIBLE_DOWNGRADE;\
+ if (version__ > (version))\
+ return LIBCOOPGAMMA_INCOMPATIBLE_UPGRADE;\
+ } while (0)
+
+#define marshal_buffer(data, n)\
+ ((buf ? (memcpy(buf + off, (data), (n)), 0) : 0), off += (n))
+
+#define unmarshal_buffer(data, n)\
+ do {\
+ (data) = malloc(n);\
+ if (!(data))\
+ return LIBCOOPGAMMA_ERRNO_SET;\
+ memcpy((data), &buf[off], (n));\
+ off += (n);\
+ } while (0)
+
+#define marshal_string(datum)\
+ (!(datum) ? marshal_prim(0, char) :\
+ (marshal_prim(1, char), marshal_buffer((datum), strlen(datum) + 1)))
+
+#define unmarshal_string(datum)\
+ do {\
+ char nonnull__;\
+ unmarshal_prim(nonnull__, char);\
+ if (nonnull__)\
+ unmarshal_buffer((datum), strlen(buf + off) + 1);\
+ else\
+ (datum) = NULL;\
+ } while (0)
+
+
+
+#define copy_errno(ctx)\
+ (!errno ? NULL :\
+ ((ctx)->error.number = (uint64_t)errno,\
+ (ctx)->error.custom = 0,\
+ (ctx)->error.server_side = 0,\
+ free((ctx)->error.description),\
+ (ctx)->error.description = NULL))
+
+
+#define SYNC_CALL(send_call, recv_call, fail_return)\
+ libcoopgamma_async_context_t async;\
+ if (send_call < 0) {\
+ reflush:\
+ if (errno != EINTR)\
+ return fail_return;\
+ if (libcoopgamma_flush(ctx) < 0)\
+ goto reflush;\
+ }\
+ resync:\
+ if (libcoopgamma_synchronise(ctx, &async, (size_t)1, &(size_t){0}) < 0) {\
+ if (errno != EINTR && errno)\
+ return fail_return;\
+ goto resync;\
+ }\
+ return recv_call
+
+
+#define INTEGRAL_DEPTHS\
+ case LIBCOOPGAMMA_UINT8:\
+ case LIBCOOPGAMMA_UINT16:\
+ case LIBCOOPGAMMA_UINT32:\
+ case LIBCOOPGAMMA_UINT64:
+
+
+
+/**
+ * Initialise a `libcoopgamma_ramps8_t`, `libcoopgamma_ramps16_t`, `libcoopgamma_ramps32_t`,
+ * `libcoopgamma_ramps64_t`, `libcoopgamma_rampsf_t`, or `libcoopgamma_rampsd_t`
+ *
+ * `this->red_size`, `this->green_size`, and `this->blue_size` must already be set
+ *
+ * @param this The record to initialise
+ * @param width The `sizeof(*(this->red))`
+ * @return Zero on success, -1 on error
+ */
+int
+libcoopgamma_ramps_initialise_(void *restrict this, size_t width)
+{
+ libcoopgamma_ramps8_t *restrict this8 = (libcoopgamma_ramps8_t *restrict)this;
+ this8->red = this8->green = this8->blue = NULL;
+ this8->red = malloc((this8->red_size + this8->green_size + this8->blue_size) * width);
+ if (!this8->red)
+ return -1;
+ this8->green = this8->red + this8->red_size * width;
+ this8->blue = this8->green + this8->green_size * width;
+ return 0;
+}
+
+
+/**
+ * Release all resources allocated to a `libcoopgamma_ramps8_t`, `libcoopgamma_ramps16_t`,
+ * `libcoopgamma_ramps32_t`, `libcoopgamma_ramps64_t`, `libcoopgamma_rampsf_t`,
+ * `libcoopgamma_rampsd_t`, or `libcoopgamma_ramps_t`, the allocation of the record
+ * itself is not freed
+ *
+ * Always call this function after failed call to `libcoopgamma_ramps_initialise`
+ * or failed call to `libcoopgamma_ramps_unmarshal`
+ *
+ * @param this The record to destroy
+ */
+void
+libcoopgamma_ramps_destroy(void *restrict this)
+{
+ libcoopgamma_ramps8_t *restrict this8 = (libcoopgamma_ramps8_t *restrict)this;
+ free(this8->red);
+ this8->red = this8->green = this8->blue = NULL;
+}
+
+
+/**
+ * Marshal a `libcoopgamma_ramps8_t`, `libcoopgamma_ramps16_t`, `libcoopgamma_ramps32_t`,
+ * `libcoopgamma_ramps64_t`, `libcoopgamma_rampsf_t`, or `libcoopgamma_rampsd_t` into a buffer
+ *
+ * @param this The record to marshal
+ * @param vbuf The output buffer, `NULL` to only measure
+ * how large this buffer has to be
+ * @param width The `sizeof(*(this->red))`
+ * @return The number of marshalled bytes, or if `buf == NULL`,
+ * how many bytes would be marshalled if `buf != NULL`
+ */
+size_t
+libcoopgamma_ramps_marshal_(const void *restrict this, void *restrict vbuf, size_t width)
+{
+ const libcoopgamma_ramps8_t *restrict this8 = (const libcoopgamma_ramps8_t *restrict)this;
+ MARSHAL_PROLOGUE;
+ marshal_version(LIBCOOPGAMMA_RAMPS_VERSION);
+ marshal_prim(this8->red_size, size_t);
+ marshal_prim(this8->green_size, size_t);
+ marshal_prim(this8->blue_size, size_t);
+ marshal_buffer(this8->red, (this8->red_size + this8->green_size + this8->blue_size) * width);
+ MARSHAL_EPILOGUE;
+}
+
+
+/**
+ * Unmarshal a `libcoopgamma_ramps8_t`, `libcoopgamma_ramps16_t`, `libcoopgamma_ramps32_t`,
+ * `libcoopgamma_ramps64_t`, `libcoopgamma_rampsf_t`, or `libcoopgamma_rampsd_t` from a buffer
+ *
+ * @param this The output parameter for unmarshalled record
+ * @param vbuf The buffer with the marshalled record
+ * @param np Output parameter for the number of unmarshalled bytes, undefined on failure
+ * @param width The `sizeof(*(this->red))`
+ * @return `LIBCOOPGAMMA_SUCCESS` (0), `LIBCOOPGAMMA_INCOMPATIBLE_DOWNGRADE`,
+ * `LIBCOOPGAMMA_INCOMPATIBLE_UPGRADE`, or `LIBCOOPGAMMA_ERRNO_SET`
+ */
+int
+libcoopgamma_ramps_unmarshal_(void *restrict this, const void *restrict vbuf,
+ size_t *restrict np, size_t width)
+{
+ libcoopgamma_ramps8_t *restrict this8 = (libcoopgamma_ramps8_t *restrict)this;
+ UNMARSHAL_PROLOGUE;
+ unmarshal_version(LIBCOOPGAMMA_RAMPS_VERSION);
+ unmarshal_prim(this8->red_size, size_t);
+ unmarshal_prim(this8->green_size, size_t);
+ unmarshal_prim(this8->blue_size, size_t);
+ unmarshal_buffer(this8->red, (this8->red_size + this8->green_size + this8->blue_size) * width);
+ this8->green = this8->red + this8->red_size * width;
+ this8->blue = this8->green + this8->green_size * width;
+ UNMARSHAL_EPILOGUE;
+}
+
+
+
+/**
+ * Initialise a `libcoopgamma_filter_t`
+ *
+ * @param this The record to initialise
+ * @return Zero on success, -1 on error
+ */
+int
+libcoopgamma_filter_initialise(libcoopgamma_filter_t *restrict this)
+{
+ memset(this, 0, sizeof(*this));
+ return 0;
+}
+
+
+/**
+ * Release all resources allocated to a `libcoopgamma_filter_t`,
+ * the allocation of the record itself is not freed
+ *
+ * Always call this function after failed call to `libcoopgamma_filter_initialise`
+ * or failed call to `libcoopgamma_filter_unmarshal`
+ *
+ * @param this The record to destroy
+ */
+void
+libcoopgamma_filter_destroy(libcoopgamma_filter_t *restrict this)
+{
+ free(this->crtc);
+ free(this->class);
+ free(this->ramps.u8.red);
+ memset(this, 0, sizeof(*this));
+}
+
+
+/**
+ * Marshal a `libcoopgamma_filter_t` into a buffer
+ *
+ * @param this The record to marshal
+ * @param vbuf The output buffer, `NULL` to only measure
+ * how large this buffer has to be
+ * @return The number of marshalled bytes, or if `buf == NULL`,
+ * how many bytes would be marshalled if `buf != NULL`
+ */
+size_t
+libcoopgamma_filter_marshal(const libcoopgamma_filter_t *restrict this, void *restrict vbuf)
+{
+ MARSHAL_PROLOGUE;
+ marshal_version(LIBCOOPGAMMA_FILTER_VERSION);
+ marshal_version(LIBCOOPGAMMA_DEPTH_VERSION);
+ marshal_version(LIBCOOPGAMMA_LIFESPAN_VERSION);
+ marshal_prim(this->depth, libcoopgamma_depth_t);
+ marshal_prim(this->priority, int64_t);
+ marshal_string(this->crtc);
+ marshal_string(this->class);
+ marshal_prim(this->lifespan, libcoopgamma_lifespan_t);
+ switch (this->depth) {
+ case LIBCOOPGAMMA_UINT8: off += libcoopgamma_ramps_marshal(&this->ramps.u8, SUBBUF); break;
+ case LIBCOOPGAMMA_UINT16: off += libcoopgamma_ramps_marshal(&this->ramps.u16, SUBBUF); break;
+ case LIBCOOPGAMMA_UINT32: off += libcoopgamma_ramps_marshal(&this->ramps.u32, SUBBUF); break;
+ case LIBCOOPGAMMA_UINT64: off += libcoopgamma_ramps_marshal(&this->ramps.u64, SUBBUF); break;
+ case LIBCOOPGAMMA_FLOAT: off += libcoopgamma_ramps_marshal(&this->ramps.f, SUBBUF); break;
+ case LIBCOOPGAMMA_DOUBLE: off += libcoopgamma_ramps_marshal(&this->ramps.d, SUBBUF); break;
+ default:
+ break;
+ }
+ MARSHAL_EPILOGUE;
+}
+
+
+/**
+ * Unmarshal a `libcoopgamma_filter_t` from a buffer
+ *
+ * @param this The output parameter for unmarshalled record
+ * @param vbuf The buffer with the marshalled record
+ * @param np Output parameter for the number of unmarshalled bytes, undefined on failure
+ * @return `LIBCOOPGAMMA_SUCCESS` (0), `LIBCOOPGAMMA_INCOMPATIBLE_DOWNGRADE`,
+ * `LIBCOOPGAMMA_INCOMPATIBLE_UPGRADE`, or `LIBCOOPGAMMA_ERRNO_SET`
+ */
+int
+libcoopgamma_filter_unmarshal(libcoopgamma_filter_t *restrict this, const void *restrict vbuf, size_t *restrict np)
+{
+ int r = LIBCOOPGAMMA_SUCCESS;
+ size_t n = 0;
+ UNMARSHAL_PROLOGUE;
+ memset(this, 0, sizeof(*this));
+ unmarshal_version(LIBCOOPGAMMA_FILTER_VERSION);
+ unmarshal_version(LIBCOOPGAMMA_DEPTH_VERSION);
+ unmarshal_version(LIBCOOPGAMMA_LIFESPAN_VERSION);
+ unmarshal_prim(this->depth, libcoopgamma_depth_t);
+ unmarshal_prim(this->priority, int64_t);
+ unmarshal_string(this->crtc);
+ unmarshal_string(this->class);
+ unmarshal_prim(this->lifespan, libcoopgamma_lifespan_t);
+ switch (this->depth) {
+ case LIBCOOPGAMMA_UINT8: r = libcoopgamma_ramps_unmarshal(&(this->ramps.u8), NNSUBBUF, &n); break;
+ case LIBCOOPGAMMA_UINT16: r = libcoopgamma_ramps_unmarshal(&(this->ramps.u16), NNSUBBUF, &n); break;
+ case LIBCOOPGAMMA_UINT32: r = libcoopgamma_ramps_unmarshal(&(this->ramps.u32), NNSUBBUF, &n); break;
+ case LIBCOOPGAMMA_UINT64: r = libcoopgamma_ramps_unmarshal(&(this->ramps.u64), NNSUBBUF, &n); break;
+ case LIBCOOPGAMMA_FLOAT: r = libcoopgamma_ramps_unmarshal(&(this->ramps.f), NNSUBBUF, &n); break;
+ case LIBCOOPGAMMA_DOUBLE: r = libcoopgamma_ramps_unmarshal(&(this->ramps.d), NNSUBBUF, &n); break;
+ default:
+ break;
+ }
+ if (r != LIBCOOPGAMMA_SUCCESS)
+ return r;
+ off += n;
+ UNMARSHAL_EPILOGUE;
+}
+
+
+
+/**
+ * Initialise a `libcoopgamma_crtc_info_t`
+ *
+ * @param this The record to initialise
+ * @return Zero on success, -1 on error
+ */
+int
+libcoopgamma_crtc_info_initialise(libcoopgamma_crtc_info_t *restrict this)
+{
+ memset(this, 0, sizeof(*this));
+ return 0;
+}
+
+
+/**
+ * Release all resources allocated to a `libcoopgamma_crtc_info_t`,
+ * the allocation of the record itself is not freed
+ *
+ * Always call this function after failed call to `libcoopgamma_crtc_info_initialise`
+ * or failed call to `libcoopgamma_crtc_info_unmarshal`
+ *
+ * @param this The record to destroy
+ */
+void
+libcoopgamma_crtc_info_destroy(libcoopgamma_crtc_info_t *restrict this)
+{
+ (void) this;
+}
+
+
+/**
+ * Marshal a `libcoopgamma_crtc_info_t` into a buffer
+ *
+ * @param this The record to marshal
+ * @param vbuf The output buffer, `NULL` to only measure
+ * how large this buffer has to be
+ * @return The number of marshalled bytes, or if `buf == NULL`,
+ * how many bytes would be marshalled if `buf != NULL`
+ */
+size_t
+libcoopgamma_crtc_info_marshal(const libcoopgamma_crtc_info_t *restrict this, void *restrict vbuf)
+{
+ MARSHAL_PROLOGUE;
+ marshal_version(LIBCOOPGAMMA_CRTC_INFO_VERSION);
+ marshal_version(LIBCOOPGAMMA_DEPTH_VERSION);
+ marshal_version(LIBCOOPGAMMA_SUPPORT_VERSION);
+ marshal_version(LIBCOOPGAMMA_COLOURSPACE_VERSION);
+ marshal_prim(this->cooperative, int);
+ marshal_prim(this->depth, libcoopgamma_depth_t);
+ marshal_prim(this->red_size, size_t);
+ marshal_prim(this->green_size, size_t);
+ marshal_prim(this->blue_size, size_t);
+ marshal_prim(this->supported, libcoopgamma_support_t);
+ marshal_prim(this->colourspace, libcoopgamma_colourspace_t);
+ marshal_prim(this->have_gamut, int);
+ marshal_prim(this->red_x, unsigned);
+ marshal_prim(this->red_y, unsigned);
+ marshal_prim(this->green_x, unsigned);
+ marshal_prim(this->green_y, unsigned);
+ marshal_prim(this->blue_x, unsigned);
+ marshal_prim(this->blue_y, unsigned);
+ marshal_prim(this->white_x, unsigned);
+ marshal_prim(this->white_y, unsigned);
+ MARSHAL_EPILOGUE;
+}
+
+
+/**
+ * Unmarshal a `libcoopgamma_crtc_info_t` from a buffer
+ *
+ * @param this The output parameter for unmarshalled record
+ * @param vbuf The buffer with the marshalled record
+ * @param np Output parameter for the number of unmarshalled bytes, undefined on failure
+ * @return `LIBCOOPGAMMA_SUCCESS` (0), `LIBCOOPGAMMA_INCOMPATIBLE_DOWNGRADE`,
+ * `LIBCOOPGAMMA_INCOMPATIBLE_UPGRADE`, or `LIBCOOPGAMMA_ERRNO_SET`
+ */
+int
+libcoopgamma_crtc_info_unmarshal(libcoopgamma_crtc_info_t *restrict this, const void *restrict vbuf, size_t *restrict np)
+{
+ UNMARSHAL_PROLOGUE;
+ unmarshal_version(LIBCOOPGAMMA_CRTC_INFO_VERSION);
+ unmarshal_version(LIBCOOPGAMMA_DEPTH_VERSION);
+ unmarshal_version(LIBCOOPGAMMA_SUPPORT_VERSION);
+ unmarshal_version(LIBCOOPGAMMA_COLOURSPACE_VERSION);
+ unmarshal_prim(this->cooperative, int);
+ unmarshal_prim(this->depth, libcoopgamma_depth_t);
+ unmarshal_prim(this->red_size, size_t);
+ unmarshal_prim(this->green_size, size_t);
+ unmarshal_prim(this->blue_size, size_t);
+ unmarshal_prim(this->supported, libcoopgamma_support_t);
+ unmarshal_prim(this->colourspace, libcoopgamma_colourspace_t);
+ unmarshal_prim(this->have_gamut, int);
+ unmarshal_prim(this->red_x, unsigned);
+ unmarshal_prim(this->red_y, unsigned);
+ unmarshal_prim(this->green_x, unsigned);
+ unmarshal_prim(this->green_y, unsigned);
+ unmarshal_prim(this->blue_x, unsigned);
+ unmarshal_prim(this->blue_y, unsigned);
+ unmarshal_prim(this->white_x, unsigned);
+ unmarshal_prim(this->white_y, unsigned);
+ UNMARSHAL_EPILOGUE;
+}
+
+
+
+/**
+ * Initialise a `libcoopgamma_filter_query_t`
+ *
+ * @param this The record to initialise
+ * @return Zero on success, -1 on error
+ */
+int
+libcoopgamma_filter_query_initialise(libcoopgamma_filter_query_t *restrict this)
+{
+ this->crtc = NULL;
+ this->coalesce = 0;
+ this->high_priority = INT64_MAX;
+ this->low_priority = INT64_MIN;
+ return 0;
+}
+
+
+/**
+ * Release all resources allocated to a `libcoopgamma_filter_query_t`,
+ * the allocation of the record itself is not freed
+ *
+ * Always call this function after failed call to `libcoopgamma_filter_query_initialise`
+ * or failed call to `libcoopgamma_filter_query_unmarshal`
+ *
+ * @param this The record to destroy
+ */
+void
+libcoopgamma_filter_query_destroy(libcoopgamma_filter_query_t *restrict this)
+{
+ free(this->crtc);
+ this->crtc = NULL;
+}
+
+
+/**
+ * Marshal a `libcoopgamma_filter_query_t` into a buffer
+ *
+ * @param this The record to marshal
+ * @param vbuf The output buffer, `NULL` to only measure
+ * how large this buffer has to be
+ * @return The number of marshalled bytes, or if `buf == NULL`,
+ * how many bytes would be marshalled if `buf != NULL`
+ */
+size_t
+libcoopgamma_filter_query_marshal(const libcoopgamma_filter_query_t *restrict this, void* restrict vbuf)
+{
+ MARSHAL_PROLOGUE;
+ marshal_version(LIBCOOPGAMMA_FILTER_QUERY_VERSION);
+ marshal_string(this->crtc);
+ marshal_prim(this->coalesce, int);
+ marshal_prim(this->high_priority, int64_t);
+ marshal_prim(this->low_priority, int64_t);
+ MARSHAL_EPILOGUE;
+}
+
+
+/**
+ * Unmarshal a `libcoopgamma_filter_query_t` from a buffer
+ *
+ * @param this The output parameter for unmarshalled record
+ * @param vbuf The buffer with the marshalled record
+ * @param np Output parameter for the number of unmarshalled bytes, undefined on failure
+ * @return `LIBCOOPGAMMA_SUCCESS` (0), `LIBCOOPGAMMA_INCOMPATIBLE_DOWNGRADE`,
+ * `LIBCOOPGAMMA_INCOMPATIBLE_UPGRADE`, or `LIBCOOPGAMMA_ERRNO_SET`
+ */
+int
+libcoopgamma_filter_query_unmarshal(libcoopgamma_filter_query_t *restrict this, const void *restrict vbuf, size_t *restrict np)
+{
+ UNMARSHAL_PROLOGUE;
+ this->crtc = NULL;
+ unmarshal_version(LIBCOOPGAMMA_FILTER_QUERY_VERSION);
+ unmarshal_string(this->crtc);
+ unmarshal_prim(this->coalesce, int);
+ unmarshal_prim(this->high_priority, int64_t);
+ unmarshal_prim(this->low_priority, int64_t);
+ UNMARSHAL_EPILOGUE;
+}
+
+
+
+/**
+ * Initialise a `libcoopgamma_queried_filter_t`
+ *
+ * @param this The record to initialise
+ * @return Zero on success, -1 on error
+ */
+int
+libcoopgamma_queried_filter_initialise(libcoopgamma_queried_filter_t *restrict this)
+{
+ memset(this, 0, sizeof(*this));
+ return 0;
+}
+
+
+/**
+ * Release all resources allocated to a `libcoopgamma_queried_filter_t`,
+ * the allocation of the record itself is not freed
+ *
+ * Always call this function after failed call to `libcoopgamma_queried_filter_initialise`
+ * or failed call to `libcoopgamma_queried_filter_unmarshal`
+ *
+ * @param this The record to destroy
+ */
+void
+libcoopgamma_queried_filter_destroy(libcoopgamma_queried_filter_t *restrict this)
+{
+ free(this->class);
+ this->class = NULL;
+ libcoopgamma_ramps_destroy(&this->ramps.u8);
+}
+
+
+/**
+ * Marshal a `libcoopgamma_queried_filter_t` into a buffer
+ *
+ * @param this The record to marshal
+ * @param vbuf The output buffer, `NULL` to only measure
+ * how large this buffer has to be
+ * @param depth The type used of ramp stops
+ * @return The number of marshalled bytes, or if `buf == NULL`,
+ * how many bytes would be marshalled if `buf != NULL`
+ */
+size_t
+libcoopgamma_queried_filter_marshal(const libcoopgamma_queried_filter_t *restrict this,
+ void *restrict vbuf, libcoopgamma_depth_t depth)
+{
+ MARSHAL_PROLOGUE;
+ marshal_version(LIBCOOPGAMMA_QUERIED_FILTER_VERSION);
+ marshal_prim(this->priority, int64_t);
+ marshal_string(this->class);
+ switch (depth) {
+ case LIBCOOPGAMMA_UINT8: off += libcoopgamma_ramps_marshal(&this->ramps.u8, SUBBUF); break;
+ case LIBCOOPGAMMA_UINT16: off += libcoopgamma_ramps_marshal(&this->ramps.u16, SUBBUF); break;
+ case LIBCOOPGAMMA_UINT32: off += libcoopgamma_ramps_marshal(&this->ramps.u32, SUBBUF); break;
+ case LIBCOOPGAMMA_UINT64: off += libcoopgamma_ramps_marshal(&this->ramps.u64, SUBBUF); break;
+ case LIBCOOPGAMMA_FLOAT: off += libcoopgamma_ramps_marshal(&this->ramps.f, SUBBUF); break;
+ case LIBCOOPGAMMA_DOUBLE: off += libcoopgamma_ramps_marshal(&this->ramps.d, SUBBUF); break;
+ default:
+ break;
+ }
+ MARSHAL_EPILOGUE;
+}
+
+
+/**
+ * Unmarshal a `libcoopgamma_queried_filter_t` from a buffer
+ *
+ * @param this The output parameter for unmarshalled record
+ * @param vbuf The buffer with the marshalled record
+ * @param np Output parameter for the number of unmarshalled bytes, undefined on failure
+ * @param depth The type used of ramp stops
+ * @return `LIBCOOPGAMMA_SUCCESS` (0), `LIBCOOPGAMMA_INCOMPATIBLE_DOWNGRADE`,
+ * `LIBCOOPGAMMA_INCOMPATIBLE_UPGRADE`, or `LIBCOOPGAMMA_ERRNO_SET`
+ */
+int libcoopgamma_queried_filter_unmarshal(libcoopgamma_queried_filter_t *restrict this, const void *restrict vbuf,
+ size_t *restrict np, libcoopgamma_depth_t depth)
+{
+ int r = LIBCOOPGAMMA_SUCCESS;
+ size_t n = 0;
+ UNMARSHAL_PROLOGUE;
+ memset(this, 0, sizeof(*this));
+ unmarshal_version(LIBCOOPGAMMA_QUERIED_FILTER_VERSION);
+ unmarshal_prim(this->priority, int64_t);
+ unmarshal_string(this->class);
+ switch (depth) {
+ case LIBCOOPGAMMA_UINT8: r = libcoopgamma_ramps_unmarshal(&this->ramps.u8, NNSUBBUF, &n); break;
+ case LIBCOOPGAMMA_UINT16: r = libcoopgamma_ramps_unmarshal(&this->ramps.u16, NNSUBBUF, &n); break;
+ case LIBCOOPGAMMA_UINT32: r = libcoopgamma_ramps_unmarshal(&this->ramps.u32, NNSUBBUF, &n); break;
+ case LIBCOOPGAMMA_UINT64: r = libcoopgamma_ramps_unmarshal(&this->ramps.u64, NNSUBBUF, &n); break;
+ case LIBCOOPGAMMA_FLOAT: r = libcoopgamma_ramps_unmarshal(&this->ramps.f, NNSUBBUF, &n); break;
+ case LIBCOOPGAMMA_DOUBLE: r = libcoopgamma_ramps_unmarshal(&this->ramps.d, NNSUBBUF, &n); break;
+ default:
+ break;
+ }
+ if (r != LIBCOOPGAMMA_SUCCESS)
+ return r;
+ off += n;
+ UNMARSHAL_EPILOGUE;
+}
+
+
+
+/**
+ * Initialise a `libcoopgamma_filter_table_t`
+ *
+ * @param this The record to initialise
+ * @return Zero on success, -1 on error
+ */
+int
+libcoopgamma_filter_table_initialise(libcoopgamma_filter_table_t *restrict this)
+{
+ memset(this, 0, sizeof(*this));
+ return 0;
+}
+
+
+/**
+ * Release all resources allocated to a `libcoopgamma_filter_table_t`,
+ * the allocation of the record itself is not freed
+ *
+ * Always call this function after failed call to `libcoopgamma_filter_table_initialise`
+ * or failed call to `libcoopgamma_filter_table_unmarshal`
+ *
+ * @param this The record to destroy
+ */
+void
+libcoopgamma_filter_table_destroy(libcoopgamma_filter_table_t *restrict this)
+{
+ while (this->filter_count)
+ libcoopgamma_queried_filter_destroy(this->filters + --this->filter_count);
+ free(this->filters);
+ this->filters = NULL;
+}
+
+
+/**
+ * Marshal a `libcoopgamma_filter_table_t` into a buffer
+ *
+ * @param this The record to marshal
+ * @param vbuf The output buffer, `NULL` to only measure
+ * how large this buffer has to be
+ * @return The number of marshalled bytes, or if `buf == NULL`,
+ * how many bytes would be marshalled if `buf != NULL`
+ */
+size_t
+libcoopgamma_filter_table_marshal(const libcoopgamma_filter_table_t *restrict this, void *restrict vbuf)
+{
+ size_t i;
+ MARSHAL_PROLOGUE;
+ marshal_version(LIBCOOPGAMMA_FILTER_TABLE_VERSION);
+ marshal_version(LIBCOOPGAMMA_DEPTH_VERSION);
+ marshal_prim(this->depth, libcoopgamma_depth_t);
+ marshal_prim(this->red_size, size_t);
+ marshal_prim(this->green_size, size_t);
+ marshal_prim(this->blue_size, size_t);
+ marshal_prim(this->filter_count, size_t);
+ for (i = 0; i < this->filter_count; i++)
+ off += libcoopgamma_queried_filter_marshal(&this->filters[i], SUBBUF, this->depth);
+ MARSHAL_EPILOGUE;
+}
+
+
+/**
+ * Unmarshal a `libcoopgamma_filter_table_t` from a buffer
+ *
+ * @param this The output parameter for unmarshalled record
+ * @param vbuf The buffer with the marshalled record
+ * @param np Output parameter for the number of unmarshalled bytes, undefined on failure
+ * @return `LIBCOOPGAMMA_SUCCESS` (0), `LIBCOOPGAMMA_INCOMPATIBLE_DOWNGRADE`,
+ * `LIBCOOPGAMMA_INCOMPATIBLE_UPGRADE`, or `LIBCOOPGAMMA_ERRNO_SET`
+ */
+int
+libcoopgamma_filter_table_unmarshal(libcoopgamma_filter_table_t *restrict this, const void *restrict vbuf, size_t *restrict np)
+{
+ size_t i, n, fn;
+ int r;
+ UNMARSHAL_PROLOGUE;
+ this->filter_count = 0;
+ this->filters = NULL;
+ unmarshal_version(LIBCOOPGAMMA_FILTER_TABLE_VERSION);
+ unmarshal_version(LIBCOOPGAMMA_DEPTH_VERSION);
+ unmarshal_prim(this->depth, libcoopgamma_depth_t);
+ unmarshal_prim(this->red_size, size_t);
+ unmarshal_prim(this->green_size, size_t);
+ unmarshal_prim(this->blue_size, size_t);
+ unmarshal_prim(fn, size_t);
+ this->filters = malloc(fn * sizeof(*this->filters));
+ if (!this->filters)
+ return LIBCOOPGAMMA_ERRNO_SET;
+ for (i = 0; i < fn; i++) {
+ r = libcoopgamma_queried_filter_unmarshal(&this->filters[i], NNSUBBUF, &n, this->depth);
+ if (r != LIBCOOPGAMMA_SUCCESS)
+ return r;
+ off += n;
+ this->filter_count += 1;
+ }
+ UNMARSHAL_EPILOGUE;
+}
+
+
+
+/**
+ * Initialise a `libcoopgamma_error_t`
+ *
+ * @param this The record to initialise
+ * @return Zero on success, -1 on error
+ */
+int
+libcoopgamma_error_initialise(libcoopgamma_error_t *restrict this)
+{
+ this->number = 0;
+ this->custom = 0;
+ this->description = NULL;
+ return 0;
+}
+
+
+/**
+ * Release all resources allocated to a `libcoopgamma_error_t`,
+ * the allocation of the record itself is not freed
+ *
+ * Always call this function after failed call to `libcoopgamma_error_initialise`
+ * or failed call to `libcoopgamma_error_unmarshal`
+ *
+ * @param this The record to destroy
+ */
+void
+libcoopgamma_error_destroy(libcoopgamma_error_t *restrict this)
+{
+ free(this->description);
+ this->description = NULL;
+}
+
+
+/**
+ * Marshal a `libcoopgamma_error_t` into a buffer
+ *
+ * @param this The record to marshal
+ * @param vbuf The output buffer, `NULL` to only measure
+ * how large this buffer has to be
+ * @return The number of marshalled bytes, or if `buf == NULL`,
+ * how many bytes would be marshalled if `buf != NULL`
+ */
+size_t
+libcoopgamma_error_marshal(const libcoopgamma_error_t *restrict this, void *restrict vbuf)
+{
+ MARSHAL_PROLOGUE;
+ marshal_version(LIBCOOPGAMMA_ERROR_VERSION);
+ marshal_prim(this->number, uint64_t);
+ marshal_prim(this->custom, int);
+ marshal_prim(this->server_side, int);
+ marshal_string(this->description);
+ MARSHAL_EPILOGUE;
+}
+
+
+/**
+ * Unmarshal a `libcoopgamma_error_t` from a buffer
+ *
+ * @param this The output parameter for unmarshalled record
+ * @param vbuf The buffer with the marshalled record
+ * @param np Output parameter for the number of unmarshalled bytes, undefined on failure
+ * @return `LIBCOOPGAMMA_SUCCESS` (0), `LIBCOOPGAMMA_INCOMPATIBLE_DOWNGRADE`,
+ * `LIBCOOPGAMMA_INCOMPATIBLE_UPGRADE`, or `LIBCOOPGAMMA_ERRNO_SET`
+ */
+int
+libcoopgamma_error_unmarshal(libcoopgamma_error_t *restrict this, const void *restrict vbuf, size_t *restrict np)
+{
+ UNMARSHAL_PROLOGUE;
+ this->description = NULL;
+ unmarshal_version(LIBCOOPGAMMA_ERROR_VERSION);
+ unmarshal_prim(this->number, uint64_t);
+ unmarshal_prim(this->custom, int);
+ unmarshal_prim(this->server_side, int);
+ unmarshal_string(this->description);
+ UNMARSHAL_EPILOGUE;
+}
+
+
+
+/**
+ * Initialise a `libcoopgamma_context_t`
+ *
+ * @param this The record to initialise
+ * @return Zero on success, -1 on error
+ */
+int
+libcoopgamma_context_initialise(libcoopgamma_context_t *restrict this)
+{
+ memset(this, 0, sizeof(*this));
+ this->fd = -1;
+ this->blocking = 1;
+ return 0;
+}
+
+
+/**
+ * Release all resources allocated to a `libcoopgamma_context_t`,
+ * the allocation of the record itself is not freed
+ *
+ * Always call this function after failed call to `libcoopgamma_context_initialise`
+ * or failed call to `libcoopgamma_context_unmarshal`
+ *
+ * @param this The record to destroy
+ * @param disconnect Disconnect from the server?
+ */
+void
+libcoopgamma_context_destroy(libcoopgamma_context_t *restrict this, int disconnect)
+{
+ if (disconnect && this->fd >= 0) {
+ shutdown(this->fd, SHUT_RDWR);
+ close(this->fd);
+ }
+ this->fd = -1;
+ libcoopgamma_error_destroy(&this->error);
+ free(this->outbound);
+ free(this->inbound);
+ this->outbound = NULL;
+ this->inbound = NULL;
+}
+
+
+/**
+ * Marshal a `libcoopgamma_context_t` into a buffer
+ *
+ * @param this The record to marshal
+ * @param vbuf The output buffer, `NULL` to only measure
+ * how large this buffer has to be
+ * @return The number of marshalled bytes, or if `buf == NULL`,
+ * how many bytes would be marshalled if `buf != NULL`
+ */
+size_t
+libcoopgamma_context_marshal(const libcoopgamma_context_t *restrict this, void *restrict vbuf)
+{
+ MARSHAL_PROLOGUE;
+ marshal_version(LIBCOOPGAMMA_CONTEXT_VERSION);
+ marshal_prim(this->fd, int);
+ off += libcoopgamma_error_marshal(&this->error, SUBBUF);
+ marshal_prim(this->message_id, uint32_t);
+ marshal_prim(this->outbound_head - this->outbound_tail, size_t);
+ marshal_buffer(this->outbound + this->outbound_tail, this->outbound_head - this->outbound_tail);
+ marshal_prim(this->inbound_head - this->inbound_tail, size_t);
+ marshal_buffer(this->inbound + this->inbound_tail, this->inbound_head - this->inbound_tail);
+ marshal_prim(this->length, size_t);
+ marshal_prim(this->curline, size_t);
+ marshal_prim(this->in_response_to, uint32_t);
+ marshal_prim(this->have_all_headers, int);
+ marshal_prim(this->bad_message, int);
+ marshal_prim(this->blocking, int);
+ MARSHAL_EPILOGUE;
+}
+
+
+/**
+ * Unmarshal a `libcoopgamma_context_t` from a buffer
+ *
+ * @param this The output parameter for unmarshalled record
+ * @param vbuf The buffer with the marshalled record
+ * @param np Output parameter for the number of unmarshalled bytes, undefined on failure
+ * @return `LIBCOOPGAMMA_SUCCESS` (0), `LIBCOOPGAMMA_INCOMPATIBLE_DOWNGRADE`,
+ * `LIBCOOPGAMMA_INCOMPATIBLE_UPGRADE`, or `LIBCOOPGAMMA_ERRNO_SET`
+ */
+int
+libcoopgamma_context_unmarshal(libcoopgamma_context_t *restrict this, const void *restrict vbuf, size_t *restrict np)
+{
+ size_t n;
+ int r;
+ UNMARSHAL_PROLOGUE;
+ memset(this, 0, sizeof(*this));
+ unmarshal_version(LIBCOOPGAMMA_CONTEXT_VERSION);
+ unmarshal_prim(this->fd, int);
+ r = libcoopgamma_error_unmarshal(&this->error, NNSUBBUF, &n);
+ if (r != LIBCOOPGAMMA_SUCCESS)
+ return r;
+ off += n;
+ unmarshal_prim(this->message_id, uint32_t);
+ unmarshal_prim(this->outbound_head, size_t);
+ this->outbound_size = this->outbound_head;
+ unmarshal_buffer(this->outbound, this->outbound_head);
+ unmarshal_prim(this->inbound_head, size_t);
+ this->inbound_size = this->inbound_head;
+ unmarshal_buffer(this->inbound, this->inbound_head);
+ unmarshal_prim(this->length, size_t);
+ unmarshal_prim(this->curline, size_t);
+ unmarshal_prim(this->in_response_to, uint32_t);
+ unmarshal_prim(this->have_all_headers, int);
+ unmarshal_prim(this->bad_message, int);
+ unmarshal_prim(this->blocking, int);
+ UNMARSHAL_EPILOGUE;
+}
+
+
+
+/**
+ * Initialise a `libcoopgamma_async_context_t`
+ *
+ * @param this The record to initialise
+ * @return Zero on success, -1 on error
+ */
+int
+libcoopgamma_async_context_initialise(libcoopgamma_async_context_t *restrict this)
+{
+ this->message_id = 0;
+ this->coalesce = 0;
+ return 0;
+}
+
+
+/**
+ * Release all resources allocated to a `libcoopgamma_async_context_t`,
+ * the allocation of the record itself is not freed
+ *
+ * Always call this function after failed call to `libcoopgamma_async_context_initialise`
+ * or failed call to `libcoopgamma_async_context_unmarshal`
+ *
+ * @param this The record to destroy
+ */
+void
+libcoopgamma_async_context_destroy(libcoopgamma_async_context_t *restrict this)
+{
+ (void) this;
+}
+
+
+/**
+ * Marshal a `libcoopgamma_async_context_t` into a buffer
+ *
+ * @param this The record to marshal
+ * @param vbuf The output buffer, `NULL` to only measure
+ * how large this buffer has to be
+ * @return The number of marshalled bytes, or if `buf == NULL`,
+ * how many bytes would be marshalled if `buf != NULL`
+ */
+size_t
+libcoopgamma_async_context_marshal(const libcoopgamma_async_context_t *restrict this, void *restrict vbuf)
+{
+ MARSHAL_PROLOGUE;
+ marshal_version(LIBCOOPGAMMA_ASYNC_CONTEXT_VERSION);
+ marshal_prim(this->message_id, uint32_t);
+ marshal_prim(this->coalesce, int);
+ MARSHAL_EPILOGUE;
+}
+
+
+/**
+ * Unmarshal a `libcoopgamma_async_context_t` from a buffer
+ *
+ * @param this The output parameter for unmarshalled record
+ * @param vbuf The buffer with the marshalled record
+ * @param np Output parameter for the number of unmarshalled bytes, undefined on failure
+ * @return `LIBCOOPGAMMA_SUCCESS` (0), `LIBCOOPGAMMA_INCOMPATIBLE_DOWNGRADE`,
+ * `LIBCOOPGAMMA_INCOMPATIBLE_UPGRADE`, or `LIBCOOPGAMMA_ERRNO_SET`
+ */
+int
+libcoopgamma_async_context_unmarshal(libcoopgamma_async_context_t *restrict this, const void *restrict vbuf, size_t *restrict np)
+{
+ UNMARSHAL_PROLOGUE;
+ unmarshal_version(LIBCOOPGAMMA_ASYNC_CONTEXT_VERSION);
+ unmarshal_prim(this->message_id, uint32_t);
+ unmarshal_prim(this->coalesce, int);
+ UNMARSHAL_EPILOGUE;
+}
+
+
+
+/**
+ * List all recognised adjustment method
+ *
+ * SIGCHLD must not be ignored or blocked
+ *
+ * @return A `NULL`-terminated list of names. You should only free
+ * the outer pointer, inner pointers are subpointers of the
+ * outer pointer and cannot be freed. `NULL` on error.
+ */
+char **
+libcoopgamma_get_methods(void)
+{
+ char num[5]; /* The size is base on the fact that we have limited `n` in the loop below */
+ char **methods = NULL;
+ char *method;
+ char **rc;
+ char *buffer;
+ int n = 0;
+ size_t size = 0;
+ void *new;
+
+ methods = malloc(4 * sizeof(*methods));
+ if (!methods)
+ goto fail;
+
+ for (n = 0; n < 10000 /* just to be safe */; n++) {
+ if (n >= 4 && (n & -n) == n) {
+ new = realloc(methods, (size_t)(n << 1) * sizeof(*methods));
+ if (!new)
+ goto fail;
+ methods = new;
+ }
+ sprintf(num, "%i", n);
+ if (libcoopgamma_get_method_and_site(num, NULL, &method, NULL))
+ goto fail;
+ if (!strcmp(method, num)) {
+ free(method);
+ break;
+ }
+ methods[n] = method;
+ size += strlen(method) + 1;
+ }
+
+ rc = malloc((size_t)(n + 1) * sizeof(char *) + size);
+ if (!rc)
+ goto fail;
+ buffer = ((char *)rc) + (size_t)(n + 1) * sizeof(char *);
+ rc[n] = NULL;
+ while (n--) {
+ rc[n] = buffer;
+ buffer = stpcpy(buffer, methods[n]) + 1;
+ free(methods[n]);
+ }
+ free(methods);
+
+ return rc;
+
+fail:
+ while (n--)
+ free(methods[n]);
+ free(methods);
+ return NULL;
+}
+
+
+/**
+ * Run coopgammad with -q or -qq and return the response
+ *
+ * SIGCHLD must not be ignored or blocked
+ *
+ * @param method The adjustment method, `NULL` for automatic
+ * @param site The site, `NULL` for automatic
+ * @param arg "-q" or "-qq", which shall be passed to coopgammad?
+ * @return The output of coopgammad, `NULL` on error. This
+ * will be NUL-terminated and will not contain any
+ * other `NUL` bytes.
+ */
+static char *
+libcoopgamma_query(const char *restrict method, const char *restrict site, const char *restrict arg)
+{
+ const char *(args[7]) = {COOPGAMMAD, arg};
+ size_t i = 2, n = 0, size = 0;
+ int pipe_rw[2] = { -1, -1 };
+ pid_t pid;
+ int saved_errno, status;
+ char *msg = NULL;
+ ssize_t got;
+ void *new;
+
+ if (method) args[i++] = "-m", args[i++] = method;
+ if (site) args[i++] = "-s", args[i++] = site;
+ args[i] = NULL;
+
+ if (pipe(pipe_rw) < 0)
+ goto fail;
+
+ switch ((pid = fork())) {
+ case -1:
+ goto fail;
+
+ case 0:
+ /* Child */
+ close(pipe_rw[0]);
+ if (pipe_rw[1] != STDOUT_FILENO) {
+ close(STDOUT_FILENO);
+ if (dup2(pipe_rw[1], STDOUT_FILENO) < 0)
+ goto fail_child;
+ close(pipe_rw[1]);
+ }
+#if defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wcast-qual"
+#endif
+ execvp(COOPGAMMAD, (char* const*)(args));
+#if defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
+ fail_child:
+ saved_errno = errno;
+ perror(NAME_OF_THE_PROCESS);
+ if (write(STDOUT_FILENO, &saved_errno, sizeof(int)) != sizeof(int))
+ perror(NAME_OF_THE_PROCESS);
+ exit(1);
+
+ default:
+ /* Parent */
+ close(pipe_rw[1]), pipe_rw[1] = -1;
+ for (;;) {
+ if (n == size) {
+ new = realloc(msg, size = (n ? (n << 1) : 256));
+ if (!new)
+ goto fail;
+ msg = new;
+ }
+ got = read(pipe_rw[0], msg + n, size - n);
+ if (got < 0) {
+ if (errno == EINTR)
+ continue;
+ goto fail;
+ } else if (got == 0) {
+ break;
+ }
+ n += (size_t)got;
+ }
+ close(pipe_rw[0]), pipe_rw[0] = -1;
+ if (waitpid(pid, &status, 0) < 0)
+ goto fail;
+ if (status) {
+ errno = EINVAL;
+ if (n == sizeof(int) && *(int *)msg)
+ errno = *(int*)msg;
+ }
+ break;
+ }
+
+ if (n == size) {
+ new = realloc(msg, n + 1);
+ if (!new)
+ goto fail;
+ msg = new;
+ }
+ msg[n] = '\0';
+
+ if (strchr(msg, '\0') != msg + n) {
+ errno = EBADMSG;
+ goto fail;
+ }
+
+ return msg;
+
+fail:
+ saved_errno = errno;
+ if (pipe_rw[0] >= 0)
+ close(pipe_rw[0]);
+ if (pipe_rw[1] >= 0)
+ close(pipe_rw[1]);
+ free(msg);
+ errno = saved_errno;
+ return NULL;
+}
+
+
+/**
+ * Get the adjustment method and site
+ *
+ * SIGCHLD must not be ignored or blocked
+ *
+ * @param method The adjustment method, `NULL` for automatic
+ * @param site The site, `NULL` for automatic
+ * @param methodp Output pointer for the selected adjustment method,
+ * which cannot be `NULL`. It is safe to call
+ * this function with this parameter set to `NULL`.
+ * @param sitep Output pointer for the selected site, which will
+ * be `NULL` the method only supports one site or if
+ * `site == NULL` and no site can be selected
+ * automatically. It is safe to call this function
+ * with this parameter set to `NULL`.
+ * @return Zero on success, -1 on error
+ */
+int
+libcoopgamma_get_method_and_site(const char *restrict method, const char *restrict site,
+ char **restrict methodp, char **restrict sitep)
+{
+ int saved_errno;
+ char *raw;
+ char *p;
+ char *q;
+
+ raw = libcoopgamma_query(method, site, "-q");
+ if (!raw)
+ return -1;
+
+ if (methodp) *methodp = NULL;
+ if (sitep) *sitep = NULL;
+
+ p = strchr(raw, '\n');
+ if (!p) {
+ errno = EBADMSG;
+ goto fail;
+ }
+ *p++ = '\0';
+
+ if (methodp) {
+ *methodp = malloc(strlen(raw) + 1);
+ if (!*methodp)
+ goto fail;
+ strcpy(*methodp, raw);
+ }
+
+ if (site && *(q = strchr(p, '\0') - 1)) {
+ if (*q != '\n') {
+ errno = EBADMSG;
+ goto fail;
+ }
+ *q = '\0';
+ *sitep = malloc(strlen(p) + 1);
+ if (!*sitep)
+ goto fail;
+ strcpy(*sitep, p);
+ }
+
+ free(raw);
+ return 0;
+
+fail:
+ saved_errno = errno;
+ if (methodp) {
+ free(*methodp);
+ *methodp = NULL;
+ }
+ free(raw);
+ errno = saved_errno;
+ return -1;
+}
+
+
+/**
+ * Get the PID file of the coopgamma server
+ *
+ * SIGCHLD must not be ignored or blocked
+ *
+ * @param method The adjustment method, `NULL` for automatic
+ * @param site The site, `NULL` for automatic
+ * @return The pathname of the server's PID file, `NULL` on error
+ * or if there server does not use PID files. The later
+ * case is detected by checking that `errno` is set to 0.
+ */
+char *
+libcoopgamma_get_pid_file(const char *restrict method, const char *restrict site)
+{
+ char *path;
+ size_t n;
+
+ path = libcoopgamma_get_socket_file(method, site);
+ if (!path)
+ return NULL;
+
+ n = strlen(path);
+ if (n < 7 || strcmp(path + n - 7, ".socket")) {
+ free(path);
+ errno = EBADMSG;
+ return NULL;
+ }
+
+ strcpy(path + n - 7, ".pid");
+ return path;
+}
+
+
+/**
+ * Get the socket file of the coopgamma server
+ *
+ * SIGCHLD must not be ignored or blocked
+ *
+ * @param method The adjustment method, `NULL` for automatic
+ * @param site The site, `NULL` for automatic
+ * @return The pathname of the server's socket, `NULL` on error
+ * or if there server does have its own socket. The later
+ * case is detected by checking that `errno` is set to 0,
+ * and is the case when communicating with a server in a
+ * multi-server display server like mds.
+ */
+char *
+libcoopgamma_get_socket_file(const char *restrict method, const char *restrict site)
+{
+ char *raw;
+ char *p;
+
+ raw = libcoopgamma_query(method, site, "-qq");
+ if (!raw)
+ return NULL;
+
+ p = strchr(raw, '\0') - 1;
+ if (p < raw || *p != '\n') {
+ errno = EBADMSG;
+ goto fail;
+ }
+ *p = '\0';
+ if (!*raw) {
+ errno = EBADMSG;
+ goto fail;
+ }
+
+ return raw;
+fail:
+ free(raw);
+ return NULL;
+}
+
+
+
+/**
+ * Connect to a coopgamma server, and start it if necessary
+ *
+ * Use `libcoopgamma_context_destroy` to disconnect
+ *
+ * SIGCHLD must not be ignored or blocked
+ *
+ * @param method The adjustment method, `NULL` for automatic
+ * @param site The site, `NULL` for automatic
+ * @param ctx The state of the library, must be initialised
+ * @return Zero on success, -1 on error. On error, `errno` is set
+ * to 0 if the server could not be initialised.
+ */
+int
+libcoopgamma_connect(const char *restrict method, const char *restrict site, libcoopgamma_context_t *restrict ctx)
+{
+ const char *(args[6]) = {COOPGAMMAD};
+ struct sockaddr_un address;
+ char* path;
+ int saved_errno;
+ int tries = 0, status;
+ pid_t pid;
+ size_t i = 1;
+
+ ctx->blocking = 1;
+
+ if (method) args[i++] = "-m", args[i++] = method;
+ if (site) args[i++] = "-s", args[i++] = site;
+ args[i] = NULL;
+
+ path = libcoopgamma_get_socket_file(method, site);
+ if (!path)
+ return -1;
+ if (strlen(path) >= sizeof(address.sun_path)) {
+ free(path);
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ address.sun_family = AF_UNIX;
+ strcpy(address.sun_path, path);
+ free(path);
+ if ((ctx->fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
+ return -1;
+
+retry:
+ if (connect(ctx->fd, (struct sockaddr *)&address, (socklen_t)sizeof(address)) < 0) {
+ if ((errno == ECONNREFUSED || errno == ENOENT || errno == ENOTDIR) && !tries++) {
+ switch ((pid = fork())) {
+ case -1:
+ goto fail;
+
+ case 0:
+ /* Child */
+ close(ctx->fd);
+#if defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wcast-qual"
+#endif
+ execvp(COOPGAMMAD, (char *const *)(args));
+#if defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
+ perror(NAME_OF_THE_PROCESS);
+ exit(1);
+
+ default:
+ /* Parent */
+ if (waitpid(pid, &status, 0) < 0)
+ goto fail;
+ if (status) {
+ errno = 0;
+ goto fail;
+ }
+ break;
+ }
+ goto retry;
+ }
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ saved_errno = errno;
+ close(ctx->fd);
+ ctx->fd = -1;
+ errno = saved_errno;
+ return -1;
+}
+
+
+/**
+ * By default communication is blocking, this function
+ * can be used to switch between blocking and nonblocking
+ *
+ * After setting the communication to nonblocking,
+ * `libcoopgamma_flush`, `libcoopgamma_synchronise` and
+ * and request-sending functions can fail with EAGAIN and
+ * EWOULDBLOCK. It is safe to continue with `libcoopgamma_flush`
+ * (for `libcoopgamma_flush` it selfand equest-sending functions)
+ * or `libcoopgamma_synchronise` just like EINTR failure.
+ *
+ * @param ctx The state of the library, must be connected
+ * @param nonblocking Nonblocking mode?
+ * @return Zero on success, -1 on error
+ */
+int
+libcoopgamma_set_nonblocking(libcoopgamma_context_t *restrict ctx, int nonblocking)
+{
+ int flags = fcntl(ctx->fd, F_GETFL);
+ if (flags == -1)
+ return -1;
+ if (nonblocking)
+ flags |= O_NONBLOCK;
+ else
+ flags &= ~O_NONBLOCK;
+ if (fcntl(ctx->fd, F_SETFL, flags) == -1)
+ return -1;
+ ctx->blocking = !nonblocking;
+ return 0;
+}
+
+
+/**
+ * Send all pending outbound data
+ *
+ * If this function or another function that sends a request
+ * to the server fails with EINTR, call this function to
+ * complete the transfer. The `async` parameter will always
+ * be in a properly configured state if a function fails
+ * with EINTR.
+ *
+ * @param ctx The state of the library, must be connected
+ * @return Zero on success, -1 on error
+ */
+int
+libcoopgamma_flush(libcoopgamma_context_t *restrict ctx)
+{
+ ssize_t sent;
+ size_t chunksize = ctx->outbound_head - ctx->outbound_tail;
+ size_t sendsize;
+
+ while (ctx->outbound_tail < ctx->outbound_head) {
+ sendsize = ctx->outbound_head - ctx->outbound_tail;
+ sendsize = sendsize < chunksize ? sendsize : chunksize;
+ sent = send(ctx->fd, ctx->outbound + ctx->outbound_tail, sendsize, MSG_NOSIGNAL);
+ if (sent < 0) {
+ if (errno == EPIPE)
+ errno = ECONNRESET;
+ if (errno != EMSGSIZE)
+ return -1;
+ if (!(chunksize >>= 1))
+ return -1;
+ continue;
+ }
+
+#ifdef DEBUG_MODE
+ fprintf(stderr, "\033[31m");
+ fwrite(ctx->outbound + ctx->outbound_tail, (size_t)sent, 1, stderr);
+ fprintf(stderr, "\033[m");
+ fflush(stderr);
+#endif
+
+ ctx->outbound_tail += (size_t)sent;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Wait for the next message to be received
+ *
+ * @param ctx The state of the library, must be connected
+ * @param pending Information for each pending request
+ * @param n The number of elements in `pending`
+ * @param selected The index of the element in `pending` which corresponds
+ * to the first inbound message, note that this only means
+ * that the message is not for any of the other request,
+ * if the message is corrupt any of the listed requests can
+ * be selected even if it is not for any of the requests.
+ * Functions that parse the message will detect such corruption.
+ * @return Zero on success, -1 on error. If the the message is ignored,
+ * which happens if corresponding `libcoopgamma_async_context_t`
+ * is not listed, -1 is returned and `errno` is set to 0. If -1
+ * is returned, `errno` is set to `ENOTRECOVERABLE` you have
+ * received a corrupt message and the context has been tainted
+ * beyond recover.
+ */
+int
+libcoopgamma_synchronise(libcoopgamma_context_t *restrict ctx, libcoopgamma_async_context_t *restrict pending,
+ size_t n, size_t *restrict selected)
+{
+ char temp[3 * sizeof(size_t) + 1];
+ ssize_t got;
+ size_t i;
+ char *p;
+ char *line;
+ char *value;
+ struct pollfd pollfd;
+ size_t new_size;
+ void *new;
+
+ if (ctx->inbound_head == ctx->inbound_tail) {
+ ctx->inbound_head = ctx->inbound_tail = ctx->curline = 0;
+ } else if (ctx->inbound_tail > 0) {
+ memmove(ctx->inbound, ctx->inbound + ctx->inbound_tail, ctx->inbound_head -= ctx->inbound_tail);
+ ctx->curline -= ctx->inbound_tail;
+ ctx->inbound_tail = 0;
+ }
+
+ pollfd.fd = ctx->fd;
+ pollfd.events = POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI;
+
+ if (ctx->inbound_head)
+ goto skip_recv;
+ for (;;) {
+ if (ctx->inbound_head == ctx->inbound_size) {
+ new_size = ctx->inbound_size ? (ctx->inbound_size << 1) : 1024;
+ new = realloc(ctx->inbound, new_size);
+ if (!new)
+ return -1;
+ ctx->inbound = new;
+ ctx->inbound_size = new_size;
+ }
+
+ if (ctx->blocking) {
+ pollfd.revents = 0;
+ if (poll(&pollfd, (nfds_t)1, -1) < 0)
+ return -1;
+ }
+ got = recv(ctx->fd, ctx->inbound + ctx->inbound_head, ctx->inbound_size - ctx->inbound_head, 0);
+ if (got <= 0) {
+ if (got == 0)
+ errno = ECONNRESET;
+ return -1;
+ }
+
+#ifdef DEBUG_MODE
+ fprintf(stderr, "\033[32m");
+ fwrite(ctx->inbound + ctx->inbound_head, (size_t)got, 1, stderr);
+ fprintf(stderr, "\033[m");
+ fflush(stderr);
+#endif
+
+ ctx->inbound_head += (size_t)got;
+
+ skip_recv:
+ while (!ctx->have_all_headers) {
+ line = ctx->inbound + ctx->curline;
+ p = memchr(line, '\n', ctx->inbound_head - ctx->curline);
+ if (!p)
+ break;
+ if (memchr(line, '\0', (size_t)(p - line)) != NULL)
+ ctx->bad_message = 1;
+ *p++ = '\0';
+ ctx->curline = (size_t)(p - ctx->inbound);
+ if (!*line) {
+ ctx->have_all_headers = 1;
+ } else if (strstr(line, "In response to: ") == line) {
+ value = line + (sizeof("In response to: ") - 1);
+ ctx->in_response_to = (uint32_t)atol(value);
+ } else if (strstr(line, "Length: ") == line) {
+ value = line + (sizeof("Length: ") - 1);
+ ctx->length = (size_t)atol(value);
+ sprintf(temp, "%zu", ctx->length);
+ if (strcmp(value, temp))
+ goto fatal;
+ }
+ }
+
+ if (ctx->have_all_headers && ctx->inbound_head >= ctx->curline + ctx->length) {
+ ctx->curline += ctx->length;
+ if (ctx->bad_message) {
+ ctx->bad_message = 0;
+ ctx->have_all_headers = 0;
+ ctx->length = 0;
+ ctx->inbound_tail = ctx->curline;
+ errno = EBADMSG;
+ return -1;
+ }
+ for (i = 0; i < n; i++) {
+ if (pending[i].message_id == ctx->in_response_to) {
+ *selected = i;
+ return 0;
+ }
+ }
+ *selected = 0;
+ ctx->bad_message = 0;
+ ctx->have_all_headers = 0;
+ ctx->length = 0;
+ ctx->inbound_tail = ctx->curline;
+ errno = 0;
+ return -1;
+ }
+ }
+
+fatal:
+ errno = ENOTRECOVERABLE;
+ return -1;
+}
+
+
+/**
+ * Send a message to the server and wait for response
+ *
+ * @param resp:char** Output parameter for the response,
+ * will be NUL-terminated
+ * @param ctx:libcoopgamma_context_t* The state of the library
+ * @param payload:void* Data to append to the end of the message
+ * @param payload_size:size_t Byte-size of `payload`
+ * @param format:string-literal Message formatting string
+ * @param ... Message formatting arguments
+ *
+ * On error, the macro goes to `fail`.
+ */
+#define SEND_MESSAGE(ctx, payload, payload_size, format, ...)\
+ do {\
+ ssize_t n__;\
+ char *msg__;\
+ snprintf(NULL, (size_t)0, format "%zn", __VA_ARGS__, &n__);\
+ msg__ = malloc((size_t)n__ + (payload_size) + (size_t)1);\
+ if (!msg__)\
+ goto fail;\
+ sprintf(msg__, format, __VA_ARGS__);\
+ if (payload)\
+ memcpy(msg__ + n__, (payload), (payload_size));\
+ if (send_message((ctx), msg__, (size_t)n__ + (payload_size)) < 0)\
+ goto fail;\
+ } while (0)
+
+
+/**
+ * Send a message to the server and wait for response
+ *
+ * @param ctx The state of the library
+ * @param msg The message to send
+ * @param n The length of `msg`
+ * @return Zero on success, -1 on error
+ */
+static int
+send_message(libcoopgamma_context_t *restrict ctx, char *msg, size_t n)
+{
+ void *new;
+ if (ctx->outbound_head == ctx->outbound_tail) {
+ free(ctx->outbound);
+ ctx->outbound = msg;
+ ctx->outbound_tail = 0;
+ ctx->outbound_head = n;
+ ctx->outbound_size = n;
+ } else {
+ if (ctx->outbound_head + n > ctx->outbound_size) {
+ memmove(ctx->outbound, ctx->outbound + ctx->outbound_tail, ctx->outbound_head -= ctx->outbound_tail);
+ ctx->outbound_tail = 0;
+ }
+ if (ctx->outbound_head + n > ctx->outbound_size) {
+ new = realloc(ctx->outbound, ctx->outbound_head + n);
+ if (!new) {
+ free(msg);
+ return -1;
+ }
+ ctx->outbound = new;
+ ctx->outbound_size = ctx->outbound_head + n;
+ }
+ memcpy(ctx->outbound + ctx->outbound_head, msg, n);
+ ctx->outbound_head += n;
+ free(msg);
+ }
+ ctx->message_id += 1;
+ return libcoopgamma_flush(ctx);
+}
+
+
+/**
+ * Get the next header of the inbound message
+ *
+ * All headers must be read before the payload is read
+ *
+ * @param ctx The state of the library, must be connected
+ * @return The next header line, can never be `NULL`,
+ * the empty string marks the end of the headers.
+ * This memory segment must not be freed.
+ */
+static char *
+next_header(libcoopgamma_context_t *restrict ctx)
+{
+ char *rc = ctx->inbound + ctx->inbound_tail;
+ ctx->inbound_tail += strlen(rc) + 1;
+ return rc;
+}
+
+
+/**
+ * Get the payload of the inbound message
+ *
+ * Calling this function marks that the inbound message
+ * has been fully ready. You must call this function
+ * even if you do not expect a payload
+ *
+ * @param ctx The state of the library, must be connected
+ * @param n Output parameter for the size of the payload
+ * @return The payload (not NUL-terminated), `NULL` if
+ * there is no payload. Failure is impossible.
+ * This memory segment must not be freed.
+ */
+static char *
+next_payload(libcoopgamma_context_t *restrict ctx, size_t *n)
+{
+ char *rc;
+ ctx->have_all_headers = 0;
+ if ((*n = ctx->length)) {
+ rc = ctx->inbound + ctx->inbound_tail;
+ ctx->inbound_tail += *n;
+ ctx->length = 0;
+ return rc;
+ }
+ return NULL;
+}
+
+
+/**
+ * Tell the library that you will not be parsing a receive message
+ *
+ * @param ctx The state of the library, must be connected
+ */
+void
+libcoopgamma_skip_message(libcoopgamma_context_t *restrict ctx)
+{
+ size_t _n;
+ while (*next_header(ctx));
+ (void) next_payload(ctx, &_n);
+}
+
+
+/**
+ * Check whether the server sent an error, if so copy it to `ctx`
+ *
+ * This function will also reports EBADMSG if the message ID
+ * that the message is a response to does not match the request
+ * information, or if it is missing
+ *
+ * @param ctx The state of the library, must be connected
+ * @param async Information about the request
+ * @return 1 if the server sent an error (even indicating success),
+ * 0 on success, -1 on failure. Information about failure
+ * is copied `ctx`.
+ */
+static int
+check_error(libcoopgamma_context_t *restrict ctx, libcoopgamma_async_context_t *restrict async)
+{
+ char temp[3 * sizeof(uint64_t) + 1];
+ size_t old_tail = ctx->inbound_tail;
+ char *line;
+ char *value;
+ int command_ok = 0;
+ int have_in_response_to = 0;
+ int have_error = 0;
+ int bad = 0;
+ char *payload;
+ size_t n;
+
+ for (;;) {
+ line = next_header(ctx);
+ value = strchr(line, ':') + 2;
+ if (!*line) {
+ break;
+ } else if (!strcmp(line, "Command: error")) {
+ command_ok = 1;
+ } else if (strstr(line, "In response to: ") == line) {
+ uint32_t id = (uint32_t)atol(value);
+ have_in_response_to = 1 + !!have_in_response_to;
+ if (id != async->message_id) {
+ bad = 1;
+ } else {
+ sprintf(temp, "%" PRIu32, id);
+ if (strcmp(value, temp))
+ bad = 1;
+ }
+ } else if (strstr(line, "Error: ") == line) {
+ have_error = 1 + !!have_error;
+ ctx->error.server_side = 1;
+ ctx->error.custom = (strstr(value, "custom") == value);
+ if (ctx->error.custom) {
+ if (value[6] == '\0') {
+ ctx->error.number = 0;
+ continue;
+ } else if (value[6] != ' ') {
+ bad = 1;
+ continue;
+ }
+ value += 7;
+ }
+ ctx->error.number = (uint64_t)atoll(value);
+ sprintf(temp, "%" PRIu64, ctx->error.number);
+ if (strcmp(value, temp))
+ bad = 1;
+ }
+ }
+
+ if (!command_ok) {
+ ctx->inbound_tail = old_tail;
+ return 0;
+ }
+
+ payload = next_payload(ctx, &n);
+ if (payload) {
+ if (memchr(payload, '\0', n) || payload[n - 1] != '\n')
+ goto badmsg;
+ ctx->error.description = malloc(n);
+ if (ctx->error.description == NULL)
+ goto fail;
+ memcpy(ctx->error.description, payload, n - 1);
+ ctx->error.description[n - 1] = '\0';
+ }
+
+ if (bad || have_in_response_to != 1 || have_error != 1)
+ goto badmsg;
+
+ return 1;
+
+badmsg:
+ errno = EBADMSG;
+fail:
+ copy_errno(ctx);
+ return -1;
+}
+
+
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wnonnull"
+#endif
+
+
+
+/**
+ * List all available CRTC:s, send request part
+ *
+ * Cannot be used before connecting to the server
+ *
+ * @param ctx The state of the library, must be connected
+ * @param async Information about the request, that is needed to
+ * identify and parse the response, is stored here
+ * @return Zero on success, -1 on error
+ */
+int
+libcoopgamma_get_crtcs_send(libcoopgamma_context_t *restrict ctx, libcoopgamma_async_context_t *restrict async)
+{
+ async->message_id = ctx->message_id;
+ SEND_MESSAGE(ctx, NULL, (size_t)0,
+ "Command: enumerate-crtcs\n"
+ "Message ID: %" PRIu32 "\n"
+ "\n",
+ ctx->message_id);
+
+ return 0;
+fail:
+ copy_errno(ctx);
+ return -1;
+}
+
+
+/**
+ * List all available CRTC:s, receive response part
+ *
+ * @param ctx The state of the library, must be connected
+ * @param async Information about the request
+ * @return A `NULL`-terminated list of names. You should only free
+ * the outer pointer, inner pointers are subpointers of the
+ * outer pointer and cannot be freed. `NULL` on error, in
+ * which case `ctx->error` (rather than `errno`) is read
+ * for information about the error.
+ */
+char **
+libcoopgamma_get_crtcs_recv(libcoopgamma_context_t *restrict ctx, libcoopgamma_async_context_t *restrict async)
+{
+ char *line;
+ char *payload;
+ char *end;
+ int command_ok = 0;
+ size_t i, n, lines, len, length;
+ char **rc;
+
+ if (check_error(ctx, async))
+ return NULL;
+
+ for (;;) {
+ line = next_header(ctx);
+ if (!*line)
+ break;
+ else if (!strcmp(line, "Command: crtc-enumeration"))
+ command_ok = 1;
+ }
+
+ payload = next_payload(ctx, &n);
+
+ if (!command_ok || (n > 0 && payload[n - 1] != '\n')) {
+ errno = EBADMSG;
+ copy_errno(ctx);
+ return NULL;
+ }
+
+ line = payload;
+ end = payload + n;
+ lines = length = 0;
+ while (line != end) {
+ lines += 1;
+ length += len = (size_t)(strchr(line, '\n') + 1 - line);
+ line += len;
+ line[-1] = '\0';
+ }
+
+ rc = malloc((lines + 1) * sizeof(char *) + length);
+ if (!rc) {
+ copy_errno(ctx);
+ return NULL;
+ }
+
+ line = ((char *)rc) + (lines + 1) * sizeof(char *);
+ memcpy(line, payload, length);
+ rc[lines] = NULL;
+ for (i = 0; i < lines; i++) {
+ rc[i] = line;
+ line = strchr(line, '\0') + 1;
+ }
+
+ return rc;
+}
+
+
+/**
+ * List all available CRTC:s, synchronous version
+ *
+ * This is a synchronous request function, as such,
+ * you have to ensure that communication is blocking
+ * (default), and that there are not asynchronous
+ * requests waiting, it also means that EINTR:s are
+ * silently ignored and there no wait to cancel the
+ * operation without disconnection from the server
+ *
+ * @param ctx The state of the library, must be connected
+ * @return A `NULL`-terminated list of names. You should only free
+ * the outer pointer, inner pointers are subpointers of the
+ * outer pointer and cannot be freed. `NULL` on error, in
+ * which case `ctx->error` (rather than `errno`) is read
+ * for information about the error.
+ */
+char **
+libcoopgamma_get_crtcs_sync(libcoopgamma_context_t *restrict ctx)
+{
+ SYNC_CALL(libcoopgamma_get_crtcs_send(ctx, &async),
+ libcoopgamma_get_crtcs_recv(ctx, &async), (copy_errno(ctx), NULL));
+}
+
+
+
+/**
+ * Retrieve information about a CRTC:s gamma ramps, send request part
+ *
+ * Cannot be used before connecting to the server
+ *
+ * @param crtc The name of the CRTC
+ * @param ctx The state of the library, must be connected
+ * @param async Information about the request, that is needed to
+ * identify and parse the response, is stored here
+ * @return Zero on success, -1 on error
+ */
+int
+libcoopgamma_get_gamma_info_send(const char *restrict crtc, libcoopgamma_context_t *restrict ctx,
+ libcoopgamma_async_context_t *restrict async)
+{
+#if defined(__GNUC__) && !defined(__clang__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wnonnull-compare"
+#endif
+ if (crtc == NULL || strchr(crtc, '\n')) {
+ errno = EINVAL;
+ goto fail;
+ }
+#if defined(__GNUC__) && !defined(__clang__)
+# pragma GCC diagnostic pop
+#endif
+
+ async->message_id = ctx->message_id;
+ SEND_MESSAGE(ctx, NULL, (size_t)0,
+ "Command: get-gamma-info\n"
+ "Message ID: %" PRIu32 "\n"
+ "CRTC: %s\n"
+ "\n",
+ ctx->message_id, crtc);
+
+ return 0;
+fail:
+ copy_errno(ctx);
+ return 0;
+}
+
+
+/**
+ * Retrieve information about a CRTC:s gamma ramps, receive response part
+ *
+ * @param info Output parameter for the information, must be initialised
+ * @param ctx The state of the library, must be connected
+ * @param async Information about the request
+ * @return Zero on success, -1 on error, in which case `ctx->error`
+ * (rather than `errno`) is read for information about the error
+ */
+int
+libcoopgamma_get_gamma_info_recv(libcoopgamma_crtc_info_t *restrict info, libcoopgamma_context_t *restrict ctx,
+ libcoopgamma_async_context_t *restrict async)
+{
+ char temp[3 * sizeof(size_t) + 1];
+ char *line;
+ char *value;
+ size_t _n;
+ int have_cooperative = 0, have_gamma_support = 0, have_colourspace = 0;
+ int have_depth = 0, have_white_x = 0, have_white_y = 0;
+ int have_red_size = 0, have_red_x = 0, have_red_y = 0;
+ int have_green_size = 0, have_green_x = 0, have_green_y = 0;
+ int have_blue_size = 0, have_blue_x = 0, have_blue_y = 0;
+ int bad = 0, r = 0, g = 0, b = 0, x = 0;
+ size_t *outz;
+ unsigned *outu;
+ int *have;
+
+ if (check_error(ctx, async))
+ return -1;
+
+ info->cooperative = 0; /* Should be in the response, but ... */
+ info->colourspace = LIBCOOPGAMMA_UNKNOWN;
+
+ for (;;) {
+ line = next_header(ctx);
+ value = strchr(line, ':') + 2;
+ if (!*line) {
+ break;
+ } else if (strstr(line, "Cooperative: ") == line) {
+ have_cooperative = 1 + !!have_cooperative;
+ if (!strcmp(value, "yes")) info->cooperative = 1;
+ else if (!strcmp(value, "no")) info->cooperative = 0;
+ else
+ bad = 1;
+ } else if (strstr(line, "Depth: ") == line) {
+ have_depth = 1 + !!have_depth;
+ if (!strcmp(value, "8")) info->depth = LIBCOOPGAMMA_UINT8;
+ else if (!strcmp(value, "16")) info->depth = LIBCOOPGAMMA_UINT16;
+ else if (!strcmp(value, "32")) info->depth = LIBCOOPGAMMA_UINT32;
+ else if (!strcmp(value, "64")) info->depth = LIBCOOPGAMMA_UINT64;
+ else if (!strcmp(value, "f")) info->depth = LIBCOOPGAMMA_FLOAT;
+ else if (!strcmp(value, "d")) info->depth = LIBCOOPGAMMA_DOUBLE;
+ else
+ bad = 1;
+ } else if (strstr(line, "Gamma support: ") == line) {
+ have_gamma_support = 1 + !!have_gamma_support;
+ if (!strcmp(value, "yes")) info->supported = LIBCOOPGAMMA_YES;
+ else if (!strcmp(value, "no")) info->supported = LIBCOOPGAMMA_NO;
+ else if (!strcmp(value, "maybe")) info->supported = LIBCOOPGAMMA_MAYBE;
+ else
+ bad = 1;
+ } else if ((r = (strstr(line, "Red size: ") == line)) ||
+ (g = (strstr(line, "Green size: ") == line)) ||
+ strstr(line, "Blue size: ") == line) {
+ if (r) have_red_size = 1 + !!have_red_size, outz = &info->red_size;
+ else if (g) have_green_size = 1 + !!have_green_size, outz = &info->green_size;
+ else have_blue_size = 1 + !!have_blue_size, outz = &info->blue_size;
+ *outz = (size_t)atol(value);
+ sprintf(temp, "%zu", *outz);
+ if (strcmp(value, temp))
+ bad = 1;
+ } else if ((x = r = (strstr(line, "Red x: ") == line)) ||
+ (r = (strstr(line, "Red y: ") == line)) ||
+ (x = g = (strstr(line, "Green x: ") == line)) ||
+ (g = (strstr(line, "Green y: ") == line)) ||
+ (x = b = (strstr(line, "Blue x: ") == line)) ||
+ (b = (strstr(line, "Blue y: ") == line)) ||
+ (x = (strstr(line, "White x: ") == line)) ||
+ strstr(line, "White y: ") == line) {
+ if (r && x) have = &have_red_x, outu = &info->red_x;
+ else if (r) have = &have_red_y, outu = &info->red_y;
+ else if (g && x) have = &have_green_x, outu = &info->green_x;
+ else if (g) have = &have_green_y, outu = &info->green_y;
+ else if (b && x) have = &have_blue_x, outu = &info->blue_x;
+ else if (b) have = &have_blue_y, outu = &info->blue_y;
+ else if (x) have = &have_white_x, outu = &info->white_x;
+ else have = &have_white_y, outu = &info->white_y;
+ *have = 1 + !!*have;
+ *outu = (unsigned)atoi(value);
+ sprintf(temp, "%u", *outu);
+ if (strcmp(value, temp))
+ bad = 1;
+ } else if (strstr(line, "Colour space: ") == line) {
+ have_colourspace = 1 + !!have_colourspace;
+ if (!strcmp(value, "sRGB")) info->colourspace = LIBCOOPGAMMA_SRGB;
+ else if (!strcmp(value, "RGB")) info->colourspace = LIBCOOPGAMMA_RGB;
+ else if (!strcmp(value, "non-RGB")) info->colourspace = LIBCOOPGAMMA_NON_RGB;
+ else if (!strcmp(value, "grey")) info->colourspace = LIBCOOPGAMMA_GREY;
+ else
+ info->colourspace = LIBCOOPGAMMA_UNKNOWN;
+ }
+ }
+
+ (void) next_payload(ctx, &_n);
+
+ info->have_gamut = (have_red_x && have_green_x && have_blue_x && have_white_x &&
+ have_red_y && have_green_y && have_blue_y && have_white_y);
+
+ if (bad || have_gamma_support != 1 || have_red_x > 1 || have_red_y > 1 ||
+ have_green_x > 1 || have_green_y > 1 || have_blue_x > 1 || have_blue_y > 1 ||
+ have_white_x > 1 || have_white_y > 1 || have_colourspace > 1) {
+ errno = EBADMSG;
+ copy_errno(ctx);
+ return -1;
+ }
+ if (info->supported != LIBCOOPGAMMA_NO) {
+ if (have_cooperative > 1 || have_depth != 1 || have_gamma_support != 1 ||
+ have_red_size != 1 || have_green_size != 1 || have_blue_size != 1) {
+ errno = EBADMSG;
+ copy_errno(ctx);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+
+/**
+ * Retrieve information about a CRTC:s gamma ramps, synchronous version
+ *
+ * This is a synchronous request function, as such,
+ * you have to ensure that communication is blocking
+ * (default), and that there are not asynchronous
+ * requests waiting, it also means that EINTR:s are
+ * silently ignored and there no wait to cancel the
+ * operation without disconnection from the server
+ *
+ * @param crtc The name of the CRTC
+ * @param info Output parameter for the information, must be initialised
+ * @param ctx The state of the library, must be connected
+ * @return Zero on success, -1 on error, in which case `ctx->error`
+ * (rather than `errno`) is read for information about the error
+ */
+int
+libcoopgamma_get_gamma_info_sync(const char *restrict ctrc, libcoopgamma_crtc_info_t *restrict info,
+ libcoopgamma_context_t *restrict ctx)
+{
+ SYNC_CALL(libcoopgamma_get_gamma_info_send(ctrc, ctx, &async),
+ libcoopgamma_get_gamma_info_recv(info, ctx, &async), (copy_errno(ctx), -1));
+}
+
+
+
+/**
+ * Retrieve the current gamma ramp adjustments, send request part
+ *
+ * Cannot be used before connecting to the server
+ *
+ * @param query The query to send
+ * @param ctx The state of the library, must be connected
+ * @param async Information about the request, that is needed to
+ * identify and parse the response, is stored here
+ * @return Zero on success, -1 on error
+ */
+int
+libcoopgamma_get_gamma_send(const libcoopgamma_filter_query_t *restrict query, libcoopgamma_context_t *restrict ctx,
+ libcoopgamma_async_context_t *restrict async)
+{
+#if defined(__GNUC__) && !defined(__clang__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wnonnull-compare"
+#endif
+ if (!query || !query->crtc || strchr(query->crtc, '\n')) {
+ errno = EINVAL;
+ goto fail;
+ }
+#if defined(__GNUC__) && !defined(__clang__)
+# pragma GCC diagnostic pop
+#endif
+
+ async->message_id = ctx->message_id;
+ async->coalesce = query->coalesce;
+ SEND_MESSAGE(ctx, NULL, (size_t)0,
+ "Command: get-gamma\n"
+ "Message ID: %" PRIu32 "\n"
+ "CRTC: %s\n"
+ "Coalesce: %s\n"
+ "High priority: %" PRIi64 "\n"
+ "Low priority: %" PRIi64 "\n"
+ "\n",
+ ctx->message_id, query->crtc, query->coalesce ? "yes" : "no",
+ query->high_priority, query->low_priority);
+
+ return 0;
+fail:
+ copy_errno(ctx);
+ return -1;
+}
+
+
+/**
+ * Retrieve the current gamma ramp adjustments, receive response part
+ *
+ * @param table Output for the response, must be initialised
+ * @param ctx The state of the library, must be connected
+ * @param async Information about the request
+ * @return Zero on success, -1 on error, in which case `ctx->error`
+ * (rather than `errno`) is read for information about the error
+ */
+int
+libcoopgamma_get_gamma_recv(libcoopgamma_filter_table_t *restrict table, libcoopgamma_context_t *restrict ctx,
+ libcoopgamma_async_context_t *restrict async)
+{
+ char temp[3 * sizeof(size_t) + 1];
+ char *line;
+ char *value;
+ char *payload;
+ size_t i, n, width, clutsize;
+ int have_depth = 0;
+ int have_red_size = 0;
+ int have_green_size = 0;
+ int have_blue_size = 0;
+ int have_tables = 0;
+ int bad = 0, r = 0, g = 0;
+ size_t *out;
+ size_t off, len;
+
+ if (check_error(ctx, async))
+ return -1;
+
+ libcoopgamma_filter_table_destroy(table);
+
+ for (;;) {
+ line = next_header(ctx);
+ value = strchr(line, ':') + 2;
+ if (!*line) {
+ break;
+ } else if (strstr(line, "Depth: ") == line) {
+ have_depth = 1 + !!have_depth;
+ if (!strcmp(value, "8")) table->depth = LIBCOOPGAMMA_UINT8;
+ else if (!strcmp(value, "16")) table->depth = LIBCOOPGAMMA_UINT16;
+ else if (!strcmp(value, "32")) table->depth = LIBCOOPGAMMA_UINT32;
+ else if (!strcmp(value, "64")) table->depth = LIBCOOPGAMMA_UINT64;
+ else if (!strcmp(value, "f")) table->depth = LIBCOOPGAMMA_FLOAT;
+ else if (!strcmp(value, "d")) table->depth = LIBCOOPGAMMA_DOUBLE;
+ else
+ bad = 1;
+ } else if ((r = (strstr(line, "Red size: ") == line)) ||
+ (g = (strstr(line, "Green size: ") == line)) ||
+ strstr(line, "Blue size: ") == line) {
+ if (r) have_red_size = 1 + !!have_red_size, out = &table->red_size;
+ else if (g) have_green_size = 1 + !!have_green_size, out = &table->green_size;
+ else have_blue_size = 1 + !!have_blue_size, out = &table->blue_size;
+ *out = (size_t)atol(value);
+ sprintf(temp, "%zu", *out);
+ if (strcmp(value, temp))
+ bad = 1;
+ } else if (strstr(line, "Tables: ") == line) {
+ have_tables = 1 + have_tables;
+ table->filter_count = (size_t)atol(value);
+ sprintf(temp, "%zu", table->filter_count);
+ if (strcmp(value, temp))
+ bad = 1;
+ }
+ }
+
+ payload = next_payload(ctx, &n);
+
+ if (bad || have_depth != 1 || have_red_size != 1 || have_green_size != 1 ||
+ have_blue_size != 1 || (async->coalesce ? have_tables > 1 : !have_tables) ||
+ ((!payload || !n) && (async->coalesce || table->filter_count > 0)) ||
+ (n > 0 && have_tables && !table->filter_count) ||
+ (async->coalesce && have_tables && table->filter_count != 1))
+ goto bad;
+
+ switch (table->depth) {
+ case LIBCOOPGAMMA_FLOAT: width = sizeof(float); break;
+ case LIBCOOPGAMMA_DOUBLE: width = sizeof(double); break;
+ default: INTEGRAL_DEPTHS
+ if (table->depth <= 0 || (table->depth & 7))
+ goto bad;
+ width = (size_t)(table->depth / 8);
+ break;
+ }
+
+ clutsize = table->red_size + table->green_size + table->blue_size;
+ clutsize *= width;
+
+ if (async->coalesce) {
+ if (n != clutsize)
+ goto bad;
+ table->filters = malloc(sizeof(*(table->filters)));
+ if (!table->filters)
+ goto fail;
+ table->filters->priority = 0;
+ table->filters->class = NULL;
+ table->filters->ramps.u8.red_size = table->red_size;
+ table->filters->ramps.u8.green_size = table->green_size;
+ table->filters->ramps.u8.blue_size = table->blue_size;
+ if (libcoopgamma_ramps_initialise_(&table->filters->ramps, width) < 0)
+ goto fail;
+ memcpy(table->filters->ramps.u8.red, payload, clutsize);
+ table->filter_count = 1;
+ } else if (!table->filter_count) {
+ table->filters = NULL;
+ } else {
+ off = 0;
+ table->filters = calloc(table->filter_count, sizeof(*table->filters));
+ if (!table->filters)
+ goto fail;
+ for (i = 0; i < table->filter_count; i++) {
+ if (off + sizeof(int64_t) > n)
+ goto bad;
+ table->filters[i].priority = *(int64_t *)(payload + off);
+ off += sizeof(int64_t);
+ if (!memchr(payload + off, '\0', n - off))
+ goto bad;
+ len = strlen(payload + off) + 1;
+ table->filters[i].class = malloc(len);
+ if (!table->filters[i].class)
+ goto fail;
+ memcpy(table->filters[i].class, payload + off, len);
+ off += len;
+ if (off + clutsize > n)
+ goto bad;
+ table->filters[i].ramps.u8.red_size = table->red_size;
+ table->filters[i].ramps.u8.green_size = table->green_size;
+ table->filters[i].ramps.u8.blue_size = table->blue_size;
+ if (libcoopgamma_ramps_initialise_(&(table->filters[i].ramps), width) < 0)
+ goto fail;
+ memcpy(table->filters->ramps.u8.red, payload + off, clutsize);
+ off += clutsize;
+ }
+ if (off != n)
+ goto bad;
+ }
+
+ return 0;
+bad:
+ errno = EBADMSG;
+fail:
+ copy_errno(ctx);
+ return -1;
+}
+
+
+/**
+ * Retrieve the current gamma ramp adjustments, synchronous version
+ *
+ * This is a synchronous request function, as such,
+ * you have to ensure that communication is blocking
+ * (default), and that there are not asynchronous
+ * requests waiting, it also means that EINTR:s are
+ * silently ignored and there no wait to cancel the
+ * operation without disconnection from the server
+ *
+ * @param query The query to send
+ * @param table Output for the response, must be initialised
+ * @param ctx The state of the library, must be connected
+ * @return Zero on success, -1 on error, in which case `ctx->error`
+ * (rather than `errno`) is read for information about the error
+ */
+int
+libcoopgamma_get_gamma_sync(const libcoopgamma_filter_query_t *restrict query, libcoopgamma_filter_table_t *restrict table,
+ libcoopgamma_context_t *restrict ctx)
+{
+ SYNC_CALL(libcoopgamma_get_gamma_send(query, ctx, &async),
+ libcoopgamma_get_gamma_recv(table, ctx, &async), (copy_errno(ctx), -1));
+}
+
+
+
+/**
+ * Apply, update, or remove a gamma ramp adjustment, send request part
+ *
+ * Cannot be used before connecting to the server
+ *
+ * @param filter The filter to apply, update, or remove, gamma ramp meta-data must match the CRTC's
+ * @param ctx The state of the library, must be connected
+ * @param async Information about the request, that is needed to
+ * identify and parse the response, is stored here
+ * @return Zero on success, -1 on error
+ */
+int
+libcoopgamma_set_gamma_send(const libcoopgamma_filter_t *restrict filter, libcoopgamma_context_t *restrict ctx,
+ libcoopgamma_async_context_t *restrict async)
+{
+ const void *payload = NULL;
+ const char *lifespan;
+ char priority[sizeof("Priority: \n") + 3 * sizeof(int64_t)] = {'\0'};
+ char length [sizeof("Length: \n") + 3 * sizeof(size_t) ] = {'\0'};
+ size_t payload_size = 0, stopwidth = 0;
+
+#if defined(__GNUC__) && !defined(__clang__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wnonnull-compare"
+#endif
+ if (!filter || !filter->crtc || strchr(filter->crtc, '\n') || !filter->class || strchr(filter->class, '\n')) {
+ errno = EINVAL;
+ goto fail;
+ }
+#if defined(__GNUC__) && !defined(__clang__)
+# pragma GCC diagnostic pop
+#endif
+
+ switch (filter->lifespan) {
+ case LIBCOOPGAMMA_REMOVE: lifespan = "remove"; break;
+ case LIBCOOPGAMMA_UNTIL_DEATH: lifespan = "until-death"; break;
+ case LIBCOOPGAMMA_UNTIL_REMOVAL: lifespan = "until-removal"; break;
+ default:
+ errno = EINVAL;
+ goto fail;
+ }
+
+ if (filter->lifespan != LIBCOOPGAMMA_REMOVE) {
+ switch (filter->depth) {
+ case LIBCOOPGAMMA_FLOAT: stopwidth = sizeof(float); break;
+ case LIBCOOPGAMMA_DOUBLE: stopwidth = sizeof(double); break;
+ default: INTEGRAL_DEPTHS
+ if (filter->depth <= 0 || (filter->depth & 7)) {
+ errno = EINVAL;
+ goto fail;
+ }
+ stopwidth = (size_t)(filter->depth / 8);
+ break;
+ }
+
+ payload_size = filter->ramps.u8.red_size;
+ payload_size += filter->ramps.u8.green_size;
+ payload_size += filter->ramps.u8.blue_size;
+ payload_size *= stopwidth;
+ payload = filter->ramps.u8.red;
+ sprintf(priority, "Priority: %" PRIi64 "\n", filter->priority);
+ sprintf(length, "Length: %zu\n", payload_size);
+ }
+
+ async->message_id = ctx->message_id;
+ SEND_MESSAGE(ctx, payload, payload_size,
+ "Command: set-gamma\n"
+ "Message ID: %" PRIu32 "\n"
+ "CRTC: %s\n"
+ "Class: %s\n"
+ "Lifespan: %s\n"
+ "%s"
+ "%s"
+ "\n",
+ ctx->message_id, filter->crtc, filter->class, lifespan, priority, length);
+
+ return 0;
+fail:
+ copy_errno(ctx);
+ return -1;
+}
+
+
+/**
+ * Apply, update, or remove a gamma ramp adjustment, receive response part
+ *
+ * @param ctx The state of the library, must be connected
+ * @param async Information about the request
+ * @return Zero on success, -1 on error, in which case `ctx->error`
+ * (rather than `errno`) is read for information about the error
+ */
+int
+libcoopgamma_set_gamma_recv(libcoopgamma_context_t *restrict ctx, libcoopgamma_async_context_t *restrict async)
+{
+ size_t _n = 0;
+
+ if (check_error(ctx, async))
+ return -(ctx->error.custom || ctx->error.number);
+
+ while (*next_header(ctx));
+ (void) next_payload(ctx, &_n);
+
+ errno = EBADMSG;
+ copy_errno(ctx);
+ return -1;
+}
+
+
+/**
+ * Apply, update, or remove a gamma ramp adjustment, synchronous version
+ *
+ * This is a synchronous request function, as such,
+ * you have to ensure that communication is blocking
+ * (default), and that there are not asynchronous
+ * requests waiting, it also means that EINTR:s are
+ * silently ignored and there no wait to cancel the
+ * operation without disconnection from the server
+ *
+ * @param filter The filter to apply, update, or remove, gamma ramp meta-data must match the CRTC's
+ * @param depth The datatype for the stops in the gamma ramps, must match the CRTC's
+ * @param ctx The state of the library, must be connected
+ * @return Zero on success, -1 on error, in which case `ctx->error`
+ * (rather than `errno`) is read for information about the error
+ */
+int
+libcoopgamma_set_gamma_sync(const libcoopgamma_filter_t *restrict filter, libcoopgamma_context_t *restrict ctx)
+{
+ SYNC_CALL(libcoopgamma_set_gamma_send(filter, ctx, &async),
+ libcoopgamma_set_gamma_recv(ctx, &async), (copy_errno(ctx), -1));
+}
+
+
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif