rex-edid/src/rex-edid.c

314 lines
10 KiB
C

/*
rex-edid extracts the EDID data and corresponding display name from the X server
Copyright (C) 2018 rexy712
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <xcb/xcb.h>
#include <xcb/randr.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#define REX_EDID_VERSION_MAJ 0u
#define REX_EDID_VERSION_MIN 2u
#define RANDR_MIN_VER_MAJ 1u
#define RANDR_MIN_VER_MIN 3u
/*return 0 if we have a good version*/
int check_randr_version(xcb_connection_t* connection, int* maj, int* min){
xcb_randr_query_version_cookie_t cookie;
xcb_generic_error_t* err;
xcb_randr_query_version_reply_t* reply;
int retval = 0;
cookie = xcb_randr_query_version(connection, RANDR_MIN_VER_MAJ, RANDR_MIN_VER_MIN);
reply = xcb_randr_query_version_reply(connection, cookie, &err);
if(err){
fprintf(stderr, "An error occurred while checking RandR version!\n");
free(reply);
return 2;
}
if(min)
*min = reply->minor_version;
if(maj)
*maj = reply->major_version;
if(reply->major_version < RANDR_MIN_VER_MAJ ||
(reply->major_version == RANDR_MIN_VER_MAJ && reply->minor_version > RANDR_MIN_VER_MIN)){
retval = 1;
}
free(reply);
return retval;
}
/*get the screen we want to work with*/
xcb_screen_t* get_screen(xcb_connection_t* connection, int screen_num){
int i;
xcb_screen_iterator_t iter = xcb_setup_roots_iterator(xcb_get_setup(connection));
for(i = 0;i < screen_num;++i){
xcb_screen_next(&iter);
}
return iter.data;
}
xcb_window_t get_screen_root_window(xcb_connection_t* connection, int screen_num){
return get_screen(connection, screen_num)->root;
}
_Noreturn void help(int ret){
printf("rex-edid is a utility to extract the EDID of all the connected displays.\n");
printf("It was made from a desire for a simpler way to extract the info than parsing the output of `xrandr --props`\n");
printf("\n");
printf("Usage: rex-edid [--help]\n");
printf("\n");
printf("Options:\n");
printf("\t--help|-h\n");
printf("\t\tshow this help message and exit\n");
printf("\t--version\n");
printf("\t\tprint program version and exit\n");
printf("\t--sysfs|-s\n");
printf("\t\tget edid data and device names from sysfs\n");
printf("\t\tNOT intended for use other than by udev rule\n");
printf("\t--xrandr|-x\n");
printf("\t\tget edid data and device names from X using randr extension\n");
printf("\t\trequires randr version %u.%u or newer\n", RANDR_MIN_VER_MAJ, RANDR_MIN_VER_MIN);
printf("\n");
printf("rex-edid Copyright (C) 2018 rexy712\n");
printf("This program comes with ABSOLUTELY NO WARRANTY; for details type refer to the LICENSE file.\n");
printf("This is free software, and you are welcome to redistribute it\n");
printf("under certain conditions; see the LICENSE file for details.\n");
exit(ret);
}
_Noreturn void version(void){
printf("rex-edid version %u.%u\n", REX_EDID_VERSION_MAJ, REX_EDID_VERSION_MIN);
printf("Copyright (C) 2018 rexy712\n");
printf("This program comes with ABSOLUTELY NO WARRANTY; for details type refer to the LICENSE file.\n");
printf("This is free software, and you are welcome to redistribute it\n");
printf("under certain conditions; see the LICENSE file for details.\n");
exit(0);
}
int rex_edid_xrandr(void){
xcb_connection_t* connection; /*connection to x server*/
xcb_window_t root; /*handle on root window of screen???*/
xcb_timestamp_t timestamp; /*timestamp of resource reply???*/
xcb_randr_output_t* randr_outputs; /*list of RandR outputs*/
int num_randr_outputs, /*number of RandR outputs in list*/
screen_num, /*number of starting screen on server*/
randr_ver_min, /*return location for RandR version*/
randr_ver_maj; /*see above*/
int i; /*counter*/
xcb_randr_get_screen_resources_reply_t* randr_resources_reply; /*X server reply to our resource query*/
/*initialize connection to x server*/
connection = xcb_connect(NULL, &screen_num);
if(xcb_connection_has_error(connection)){
fprintf(stderr, "Unable to open connection to X server!\n");
return -1;
}
if(check_randr_version(connection, &randr_ver_maj, &randr_ver_min)){
xcb_disconnect(connection);
fprintf(stderr, "RandR version %d.%d is too old!\nMust have at least RandR version %u.%u\n", randr_ver_maj, randr_ver_min, RANDR_MIN_VER_MAJ, RANDR_MIN_VER_MIN);
return -1;
}
/*get all this bs so we can get what we actually want*/
root = get_screen_root_window(connection, screen_num);
randr_resources_reply = xcb_randr_get_screen_resources_reply(connection, xcb_randr_get_screen_resources(connection, root), NULL);
timestamp = randr_resources_reply->config_timestamp;
/*number of displays*/
num_randr_outputs = xcb_randr_get_screen_resources_outputs_length(randr_resources_reply);
/*list of displays*/
randr_outputs = xcb_randr_get_screen_resources_outputs(randr_resources_reply);
for(i = 0;i < num_randr_outputs;++i){
xcb_randr_get_output_info_reply_t* output_info_reply;
xcb_randr_list_output_properties_reply_t* output_list_prop_reply;
xcb_randr_get_output_property_reply_t* output_get_prop_reply;
xcb_get_atom_name_reply_t* atom_name_reply;
xcb_atom_t* output_atoms;
int num_atoms;
int j;
uint8_t* prop_data;
/*get output properties list(which includes the atoms)*/
output_list_prop_reply = xcb_randr_list_output_properties_reply(connection, xcb_randr_list_output_properties(connection, randr_outputs[i]), NULL);
/*get list of atoms (which includes EDID)*/
output_atoms = xcb_randr_list_output_properties_atoms(output_list_prop_reply);
num_atoms = xcb_randr_list_output_properties_atoms_length(output_list_prop_reply);
/*get output name*/
output_info_reply = xcb_randr_get_output_info_reply(connection, xcb_randr_get_output_info(connection, randr_outputs[i], timestamp), NULL);
printf("%.*s:", xcb_randr_get_output_info_name_length(output_info_reply), xcb_randr_get_output_info_name(output_info_reply));
for(j = 0;j < num_atoms;++j){
unsigned int k;
/*get atom_name*/
atom_name_reply = xcb_get_atom_name_reply(connection, xcb_get_atom_name(connection, output_atoms[j]), NULL);
/*if the atom is the EDID data, we want to get it*/
if(!memcmp(xcb_get_atom_name_name(atom_name_reply), "EDID", 4)){
/*get property data for the current atom*/
output_get_prop_reply = xcb_randr_get_output_property_reply(connection, xcb_randr_get_output_property(connection, randr_outputs[i], output_atoms[j], XCB_GET_PROPERTY_TYPE_ANY, 0, 100, 0, 0), NULL);
prop_data = xcb_randr_get_output_property_data(output_get_prop_reply);
/*print out the EDID in hex (starts at an offset)*/
for(k = 0;k < output_get_prop_reply->num_items;++k){
printf("%02x", prop_data[k]);
}
free(output_get_prop_reply);
}
free(atom_name_reply);
}
printf("\n");
free(output_info_reply);
free(output_list_prop_reply);
}
free(randr_resources_reply);
xcb_disconnect(connection);
return 0;
}
/*get device name from DEVPATH environment variable and prepend '/sys' to it*/
char* get_device_path(int* len){
char* devpath;
int length;
char* tmp;
/*udev stores the device location in sysfs in DEVPATH environment variable*/
devpath = getenv("DEVPATH");
if(!devpath)
return NULL;
length = strlen(devpath) + 5;
tmp = malloc(length);
if(!tmp)
return NULL;
tmp[length-1] = 0;
snprintf(tmp, length, "/sys%s", devpath);
if(len)
*len = length-1;
return tmp;
}
/*extract device name from the device path (the last part of the pathname)*/
char* get_device_name(char* devpath, int strlength){
int i;
for(i = strlength-1;i >= 0;--i){
if(devpath[i] == '/'){
if(i == strlength)
return NULL;
return &(devpath[i])+1;
}
}
return NULL;
}
int rex_edid_sysfs(void){
int devpath_len; /*length of device path name*/
int devname_len; /*length of device name*/
char* devpath; /*name of device path*/
char* devname; /*name of the device*/
DIR* dp; /*pointer to open device directory in sysfs*/
struct dirent* dir; /*accessor to sysfs directory*/
/*set up device names*/
devpath = get_device_path(&devpath_len);
if(!devpath){
fprintf(stderr, "DEVPATH is empty!\n");
return -3;
}
devname = get_device_name(devpath, devpath_len);
devname_len = strlen(devname);
/*open directory*/
dp = opendir(devpath);
if(!dp){
fprintf(stderr, "Unable to open DEVPATH!\n");
return -2;
}
/*check all contents of device directory*/
while((dir = readdir(dp))){
if(!strncmp(dir->d_name, devname, devname_len)){
int edid_len; /*length of full path to edid file*/
char* edid_file; /*buffer for edid path name*/
unsigned char c; /*place to store char from file*/
/*set up edid file path*/
edid_len = strlen(dir->d_name)+1 + devpath_len + 6;
edid_file = malloc(edid_len);
snprintf(edid_file, edid_len, "%s/%s/edid", devpath, dir->d_name);
printf("%s:", dir->d_name + devname_len+1);
/*read in edid data from file*/
FILE* fd = fopen(edid_file, "r");
if(!fd){
fprintf(stderr, "Unable to open edid file\n");
free(edid_file);
printf("\n");
continue;
}
c = fgetc(fd);
while(!feof(fd)){
printf("%02x", c);
c = fgetc(fd);
}
/*cleanup*/
fclose(fd);
free(edid_file);
printf("\n");
}
}
closedir(dp);
free(devpath);
return 0;
}
int main(int argc, char* argv[]){
int i;
if(argc == 1){
help(1);
}
for(i = 1;i < argc;++i){
if(!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h")){
help(0);
}else if(!strcmp(argv[i], "--sysfs") || !strcmp(argv[i], "-s")){
return rex_edid_sysfs();
}else if(!strcmp(argv[i], "--xrandr") || !strcmp(argv[i], "-x")){
return rex_edid_xrandr();
}else if(!strcmp(argv[i], "--version")){
version();
}else{
fprintf(stderr, "Unrecognized option '%s'\n", argv[i]);
help(1);
}
}
return 1;
}