314 lines
10 KiB
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;
|
|
}
|