/* 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(struct libgamma_crtc_information *restrict out, struct libgamma_crtc_state *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(struct libgamma_crtc_information *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(struct libgamma_crtc_information *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(struct libgamma_crtc_information *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(struct libgamma_crtc_information *restrict out, struct libgamma_crtc_state *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(struct libgamma_crtc_information *restrict this, struct libgamma_crtc_state *restrict crtc, unsigned long long 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; struct libgamma_x_randr_partition_data *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(*this)); /* 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 }