diff options
Diffstat (limited to 'libgamma_x_randr_get_crtc_information.c')
-rw-r--r-- | libgamma_x_randr_get_crtc_information.c | 400 |
1 files changed, 400 insertions, 0 deletions
diff --git a/libgamma_x_randr_get_crtc_information.c b/libgamma_x_randr_get_crtc_information.c new file mode 100644 index 0000000..29abd8b --- /dev/null +++ b/libgamma_x_randr_get_crtc_information.c @@ -0,0 +1,400 @@ +/* See LICENSE file for copyright and license details. */ +#define IN_LIBGAMMA_X_RANDR +#include "common.h" + + +/** + * Get the gamma ramp size of a CRTC + * + * @param this Instance of a data structure to fill with the information about the CRTC + * @param crtc The state of the CRTC whose information should be read + * @return Non-zero on error + */ +static int +get_gamma_ramp_size(libgamma_crtc_information_t *restrict out, libgamma_crtc_state_t *restrict crtc) +{ + xcb_connection_t *restrict connection = crtc->partition->site->data; + xcb_randr_crtc_t *restrict crtc_id = crtc->data; + xcb_randr_get_crtc_gamma_size_cookie_t cookie; + xcb_randr_get_crtc_gamma_size_reply_t *restrict reply; + xcb_generic_error_t *error; + + /* Query gamma ramp size */ + out->gamma_size_error = 0; + cookie = xcb_randr_get_crtc_gamma_size(connection, *crtc_id); + reply = xcb_randr_get_crtc_gamma_size_reply(connection, cookie, &error); + if (error) { + out->gamma_size_error = libgamma_x_randr_internal_translate_error(error->error_code, + LIBGAMMA_GAMMA_RAMPS_SIZE_QUERY_FAILED, 1); + return out->gamma_size_error; + } + /* Sanity check gamma ramp size */ + if (reply->size < 2) + out->gamma_size_error = LIBGAMMA_SINGLETON_GAMMA_RAMP; + /* Store gamma ramp size */ + out->red_gamma_size = out->green_gamma_size = out->blue_gamma_size = reply->size; + /* Release resources and return successfulnes */ + free(reply); + return out->gamma_size_error; +} + + +/** + * Read information from the CRTC's output + * + * @param out Instance of a data structure to fill with the information about the CRTC + * @param output The CRTC's output information + * @return Non-zero if at least on error occured + */ +static int +read_output_data(libgamma_crtc_information_t *restrict out, xcb_randr_get_output_info_reply_t *restrict output) +{ + switch (output->connection) { + case XCB_RANDR_CONNECTION_CONNECTED: + /* We have a monitor connected, report that and store information that is provided to us */ + out->active = 1; + out->width_mm = output->mm_width; + out->height_mm = output->mm_height; + switch (output->subpixel_order) { +#define X(CONST, NAME, RENDER_SUFFIX)\ + case XCB_RENDER_SUB_PIXEL_##RENDER_SUFFIX:\ + out->subpixel_order = CONST;\ + break; + LIST_SUBPIXEL_ORDERS(X) +#undef X + default: + out->subpixel_order_error = LIBGAMMA_SUBPIXEL_ORDER_NOT_RECOGNISED; + break; + } + return 0; + + case XCB_RANDR_CONNECTION_UNKNOWN: + /* If we do know whether a monitor is connected report that and assume it is not */ + out->active_error = LIBGAMMA_STATE_UNKNOWN; + /* fall through */ + default: + /* If no monitor is connected, report that on fails that require it */ + out->width_mm_error = LIBGAMMA_NOT_CONNECTED; + out->height_mm_error = LIBGAMMA_NOT_CONNECTED; + out->subpixel_order_error = LIBGAMMA_NOT_CONNECTED; + /* And store that we are not connected */ + out->active = 0; + /* This fuction only failed if we could not figure out whether a monitor is connected */ + return output->connection != XCB_RANDR_CONNECTION_UNKNOWN ? 0 : -1; + } +} + + +/** + * Determine the connector type from the connector name + * + * @param this The CRTC information to use and extend + * @param Non-zero on error + */ +static int +get_connector_type(libgamma_crtc_information_t *restrict this) +{ + /* Since we require the name of the output of get the type of the connected, + copy any reported error on the output's name to the connector's type, + and report failure if there was an error */ + if ((this->connector_type_error = this->connector_name_error)) + return -1; + +#define __select(name, type)\ + do {\ + if (strstr(this->connector_name, name "-") == this->connector_name) {\ + this->connector_type = LIBGAMMA_CONNECTOR_TYPE_##type;\ + return 0;\ + }\ + } while (0) + + /* Check begin on the name of the output to find out what type the connector is of */ + __select ("None", Unknown); + __select ("VGA", VGA); + __select ("DVI-I", DVII); + __select ("DVI-D", DVID); + __select ("DVI-A", DVIA); + __select ("DVI", DVI); + __select ("Composite", Composite); + __select ("S-Video", SVIDEO); + __select ("Component", Component); + __select ("LFP", LFP); + __select ("Proprietary", Unknown); + __select ("HDMI", HDMI); + __select ("DisplayPort", DisplayPort); + +#undef __select + + /* If there was no matching output name pattern report that and exit with an error */ + this->connector_name_error = LIBGAMMA_CONNECTOR_TYPE_NOT_RECOGNISED; + return -1; +} + + +/** + * Get the output name of a CRTC + * + * @param this Instance of a data structure to fill with the information about the CRTC + * @param output The CRTC's output's information + * @return Non-zero on error + */ +static int +get_output_name(libgamma_crtc_information_t *restrict out, xcb_randr_get_output_info_reply_t *restrict output) +{ + char *restrict store; + uint8_t *restrict name; + uint16_t length; + size_t i; + + /* Get the name of the output and the length of that name */ + name = xcb_randr_get_output_info_name(output); + length = output->name_len; /* There is no NUL-termination */ + if (!name) + return out->connector_name_error = LIBGAMMA_REPLY_VALUE_EXTRACTION_FAILED; + + /* Allocate a memory area for a NUL-terminated copy of the name */ + store = out->connector_name = malloc(((size_t)length + 1) * sizeof(char)); + if (!store) { + out->connector_name_error = errno; + return -1; + } + + /* char is guaranteed to be (u)int_least8_t, but it is only guaranteed to be (u)int8_t + * on POSIX, so to be truly portable we will not assume that char is (u)int8_t */ + for (i = 0; i < (size_t)length; i++) + store[i] = (char)name[i]; + store[length] = '\0'; + + return 0; +} + + +/** + * Get the Extended Display Information Data of the monitor connected to the connector of a CRTC + * + * @param out Instance of a data structure to fill with the information about the CRTC + * @param crtc The state of the CRTC whose information should be read + * @param output The CRTC's output + * @return Non-zero on error + */ +static int +get_edid(libgamma_crtc_information_t *restrict out, libgamma_crtc_state_t *restrict crtc, xcb_randr_output_t output) +{ + xcb_connection_t *restrict connection = crtc->partition->site->data; + xcb_randr_list_output_properties_cookie_t prop_cookie; + xcb_randr_list_output_properties_reply_t *restrict prop_reply; + xcb_atom_t *atoms; + xcb_atom_t *atoms_end; + xcb_generic_error_t *error; + xcb_get_atom_name_cookie_t atom_name_cookie; + xcb_get_atom_name_reply_t *restrict atom_name_reply; + char *restrict atom_name; + int atom_name_len; + xcb_randr_get_output_property_cookie_t atom_cookie; + xcb_randr_get_output_property_reply_t *restrict atom_reply; + unsigned char* restrict atom_data; + int length; + + /* Acquire a list of all properties of the output */ + prop_cookie = xcb_randr_list_output_properties(connection, output); + prop_reply = xcb_randr_list_output_properties_reply(connection, prop_cookie, &error); + if (error) + return out->edid_error = libgamma_x_randr_internal_translate_error(error->error_code, LIBGAMMA_LIST_PROPERTIES_FAILED, 1); + + /* Extract the properties form the data structure that holds them, */ + atoms = xcb_randr_list_output_properties_atoms(prop_reply); + /* and get the last one so that we can iterate over them nicely */ + atoms_end = atoms + xcb_randr_list_output_properties_atoms_length(prop_reply); + + if (!atoms) { + free(prop_reply); + return out->edid_error = LIBGAMMA_REPLY_VALUE_EXTRACTION_FAILED; + } + + /* For each property */ + for (; atoms != atoms_end; atoms++) { + /* Acquire the atom name */ + atom_name_cookie = xcb_get_atom_name(connection, *atoms); + atom_name_reply = xcb_get_atom_name_reply(connection, atom_name_cookie, &error); + if (error) + continue; + + /* Extract the atom name from the data structure that holds it */ + atom_name = xcb_get_atom_name_name(atom_name_reply); + /* As well as the length of the name; it is not NUL-termianted */ + atom_name_len = xcb_get_atom_name_name_length(atom_name_reply); + + if (/* Check for errors */ + !atom_name || /* atom_name_len < 1 || */ + /* Check that the length is the expected length for the EDID property */ + atom_name_len != 4 || + /* Check that the property is the EDID property */ + atom_name[0] != 'E' || atom_name[1] != 'D' || atom_name[2] != 'I' || atom_name[3] != 'D') { + free(atom_name_reply); + continue; + } + + /* Acquire the property's value, we know that it is either 128 or 256 byte long */ + atom_cookie = xcb_randr_get_output_property(connection, output, *atoms, XCB_GET_PROPERTY_TYPE_ANY, 0, 256, 0, 0); + atom_reply = xcb_randr_get_output_property_reply(connection, atom_cookie, &error); + /* (*) EDID version 1.0 through 1.4 define it as 128 bytes long, + * but version 2.0 define it as 256 bytes long. However, + * version 2.0 is rare(?) and has been deprecated and replaced + * by version 1.3 (I guess that is with a new version epoch, + * but I do not know.) */ + if (error) { + free(atom_name_reply); + free(prop_reply); + return out->edid_error = LIBGAMMA_PROPERTY_VALUE_QUERY_FAILED; + } + + /* Extract the property's value */ + atom_data = xcb_randr_get_output_property_data(atom_reply); + /* and its actual length */ + length = xcb_randr_get_output_property_data_length(atom_reply); + if (!atom_data || length < 1) { + free(atom_reply); + free(atom_name_reply); + free(prop_reply); + return out->edid_error = LIBGAMMA_REPLY_VALUE_EXTRACTION_FAILED; + } + + /* Store the EDID */ + out->edid_length = (size_t)length; + out->edid = malloc((size_t)length * sizeof(unsigned char)); + if (!out->edid) + out->edid_error = errno; + else + memcpy(out->edid, atom_data, (size_t)length * sizeof(unsigned char)); + + /* Release resouces */ + free(atom_reply); + free(atom_name_reply); + free(prop_reply); + + return out->edid_error; + } + + return out->edid_error = LIBGAMMA_EDID_NOT_FOUND; +} + + +/** + * Read information about a CRTC + * + * @param this Instance of a data structure to fill with the information about the CRTC + * @param crtc The state of the CRTC whose information should be read + * @param fields OR:ed identifiers for the information about the CRTC that should be read + * @return Zero on success, -1 on error; on error refer to the error reports in `this` + */ +int +libgamma_x_randr_get_crtc_information(libgamma_crtc_information_t *restrict this, + libgamma_crtc_state_t *restrict crtc, int32_t fields) +{ +#define _E(FIELD) ((fields & FIELD) ? LIBGAMMA_CRTC_INFO_NOT_SUPPORTED : 0) + + int e = 0; + xcb_randr_get_output_info_reply_t *restrict output_info = NULL; + xcb_randr_output_t output; + int free_edid, free_name; + xcb_connection_t *restrict connection; + libgamma_x_randr_partition_data_t *restrict screen_data; + size_t output_index; + xcb_randr_get_output_info_cookie_t cookie; + xcb_generic_error_t *error; + + /* Wipe all error indicators */ + memset(this, 0, sizeof(libgamma_crtc_information_t)); + + /* We need to free the EDID after us if it is not explicitly requested */ + free_edid = !(fields & LIBGAMMA_CRTC_INFO_EDID); + + /* We need to free the output's name after us if it is not explicitly requested */ + free_name = !(fields & LIBGAMMA_CRTC_INFO_CONNECTOR_NAME); + + /* Jump if the output information is not required */ + if (!(fields & (LIBGAMMA_CRTC_INFO_MACRO_ACTIVE | LIBGAMMA_CRTC_INFO_MACRO_CONNECTOR))) + goto cont; + + /* Get connector and connector information */ + connection = crtc->partition->site->data; + screen_data = crtc->partition->data; + output_index = screen_data->crtc_to_output[crtc->crtc]; + /* `SIZE_MAX` is used for CRTC:s that misses mapping to its output (should not happen), + * because `SIZE_MAX - 1` is the highest theoretical possible value */ + if (output_index == SIZE_MAX) { + e |= this->edid_error = this->gamma_error = this->width_mm_edid_error + = this->height_mm_edid_error = this->connector_type_error + = this->connector_name_error = this->subpixel_order_error + = this->width_mm_error = this->height_mm_error + = this->active_error = LIBGAMMA_CONNECTOR_UNKNOWN; + goto cont; + } + /* Get the output */ + output = screen_data->outputs[output_index]; + /* Query output information */ + cookie = xcb_randr_get_output_info(connection, output, screen_data->config_timestamp); + output_info = xcb_randr_get_output_info_reply(connection, cookie, &error); + if (error) { + e |= this->edid_error = this->gamma_error = this->width_mm_edid_error + = this->height_mm_edid_error = this->connector_type_error + = this->connector_name_error = this->subpixel_order_error + = this->width_mm_error = this->height_mm_error + = this->active_error = LIBGAMMA_OUTPUT_INFORMATION_QUERY_FAILED; + goto cont; + } + + /* Get connector name */ + e |= get_output_name(this, output_info); + /* Get connector type */ + if (fields & LIBGAMMA_CRTC_INFO_CONNECTOR_TYPE) + e |= get_connector_type(this); + /* Get additional output data, excluding EDID */ + e |= read_output_data(this, output_info); + if (fields & LIBGAMMA_CRTC_INFO_MACRO_VIEWPORT) + e |= this->width_mm_error | this->height_mm_error; + e |= (fields & LIBGAMMA_CRTC_INFO_SUBPIXEL_ORDER) ? this->subpixel_order_error : 0; + + /* If we do not want any EDID information, jump */ + if (!(fields & LIBGAMMA_CRTC_INFO_MACRO_EDID)) + goto cont; + /* If there is not monitor that report error in EDID related fields */ + if (!this->active) { + e |= this->edid_error = this->gamma_error = this->width_mm_edid_error + = this->height_mm_edid_error = LIBGAMMA_NOT_CONNECTED; + goto cont; + } + /* Get EDID */ + e |= get_edid(this, crtc, output); + if (!this->edid) { + this->gamma_error = this->width_mm_edid_error = this->height_mm_edid_error = this->edid_error; + goto cont; + } + /* Parse EDID */ + if ((fields & (LIBGAMMA_CRTC_INFO_MACRO_EDID ^ LIBGAMMA_CRTC_INFO_EDID))) + e |= libgamma_internal_parse_edid(this, fields); + +cont: + /* Get gamma ramp size */ + e |= (fields & LIBGAMMA_CRTC_INFO_GAMMA_SIZE) ? get_gamma_ramp_size(this, crtc) : 0; + /* Store gamma ramp depth. */ + this->gamma_depth = 16; + /* X RandR does not support quering gamma ramp support. */ + e |= this->gamma_support_error = _E(LIBGAMMA_CRTC_INFO_GAMMA_SUPPORT); + + /* Free the EDID after us */ + if (free_edid) { + free(this->edid); + this->edid = NULL; + } + /* Free the output name after us */ + if (free_name) { + free(this->connector_name); + this->connector_name = NULL; + } + + free(output_info); + return e ? -1 : 0; + +#undef _E +} |