/**
* Copyright © 2014, 2015, 2016, 2017 Mattias Andrée (maandree@kth.se)
*
* 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 <http://www.gnu.org/licenses/>.
*/
#define _GNU_SOURCE /* for strcasestr */
#include <stdlib.h>
#include <stdio.h>
#include <alloca.h>
#include <string.h>
#include <xcb/xcb.h>
/**
* Connection to the X server
*/
static xcb_connection_t* connection;
/**
* Used to store errors in
*/
static xcb_generic_error_t* error;
/**
* Main entry point of the program
*
* @param argc Length of `argv`
* @param argv Command line arguments
* @return Zero on success
*/
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstack-protector"
int main(int argc, char **argv)
{
#pragma GCC diagnostic pop
char* display = NULL;
xcb_screen_iterator_t iter;
int screen_count;
int screen_i;
/* To get all ICC profiles, which are binary encoded, we have
to connect to the display and for each screen look for
properties maching the pattern "_ICC_PROFILE(|_[0-9]*)".
But we should also do it without being case sensitive,
because it is not well defined how the casing should be.
The _ICC_PROFILE atom is the profile for the first CRTC
(with the primary one being the first) within the screen.
_ICC_PROFILE_0 is not a valid atom for a profile,
_ICC_PROFILE_1 is for the secondard, and _ICC_PROFILE_2
is for the tertiary, and so on.
*/
/* Get X connection */
/* This acquires a connection to the
X display indicated by the DISPLAY
environ variable, or as indicated
by the first command line argument
if existent. */
if (argc > 1)
display = *(argv + 1);
connection = xcb_connect(display, NULL);
/* Get screen information */
/* Acquire a list of all screens in the display, */
iter = xcb_setup_roots_iterator(xcb_get_setup(connection));
/* count the list. */
screen_count = iter.rem;
for (screen_i = 0; screen_i < screen_count; screen_i++)
{
/* For each screen */
xcb_screen_t* screen = iter.data;
xcb_list_properties_cookie_t list_cookie;
xcb_list_properties_reply_t* list_reply;
xcb_atom_t* atoms;
xcb_atom_t* atoms_end;
/* We have acquired the screen, got to next in preperate for next iteration. */
xcb_screen_next(&iter);
/* Get root window properties */
/* Acquire a list of all properties on the current screen's root window.
global properties are set here, as well as monitor specific properties
that are actual monitor properties. */
list_cookie = xcb_list_properties(connection, screen->root);
list_reply = xcb_list_properties_reply(connection, list_cookie, &error);
if (error)
{
/* If we were not successful lets print an error
message and close the connection to the display. */
fprintf(stderr, "Screen root window property list query returned %i\n", error->error_code);
xcb_disconnect(connection);
return 1;
}
/* Extract the properties for the data structure that holds them, */
atoms = xcb_list_properties_atoms(list_reply);
/* and get the last one so that we can iterate over them nicely. */
atoms_end = atoms + xcb_list_properties_atoms_length(list_reply);
/* For each property */
for (; atoms != atoms_end; atoms++)
{
xcb_get_atom_name_cookie_t name_cookie;
xcb_get_atom_name_reply_t* name_reply;
char* name;
char* name_;
uint32_t len;
xcb_get_property_cookie_t prop_cookie;
xcb_get_property_reply_t* prop_reply;
int monitor;
/* Get root window property name */
/* Acquire the the atom name. */
name_cookie = xcb_get_atom_name(connection, *atoms);
name_reply = xcb_get_atom_name_reply(connection, name_cookie, &error);
if (error)
{
/* If we were not successful lets print an error
message, free the property list and close the
connection to the display. */
fprintf(stderr, "Screen root window property name query returned %i\n", error->error_code);
free(list_reply);
xcb_disconnect(connection);
return 1;
}
/* Extract the atom name from the data structure that holds it. */
name_ = xcb_get_atom_name_name(name_reply);
/* As well as the length of the name; it is not NUL-termianted.*/
len = (uint32_t)xcb_get_atom_name_name_length(name_reply);
/* NUL-terminate the atom name, */
name = alloca((len + 1U) * sizeof(char));
/* (it is allocated on the stack, so it should not be free:d) */
memcpy(name, name_, len * sizeof(char));
*(name + len) = 0;
/* and free the version that is not NUL-terminated. */
free(name_reply);
/* Check property name pattern */
/* Read the atom name */
if (!strcasecmp(name, "_icc_profile"))
/* _ICC_PROFILE is for monitor 0 */
monitor = 0;
else if (strcasestr(name, "_icc_profile_") == name)
{
/* Skip to the part that should be numerical */
name += strlen("_icc_profile_");
monitor = 0;
if (*name == '\0')
/* Invalid: no index */
continue;
/* Parse index */
while (*name)
{
char c = *name++;
/* with strict format matching. */
if (('0' <= c) && (c <= '9'))
monitor = monitor * 10 - (c & 15);
else
/* Not numerical: did not match */
goto monitor_bad;
}
/* Convert from negative to possitive. */
monitor = -monitor;
/* Check that it is not zero, zero is
not a valid index, it should just be
_ICC_PROFILE in such case. */
if (monitor > 0)
goto monitor_ok;
monitor_bad:
/* Atom name ultimately did not match the, pattern
ignore it, it may be for something else, but it
is propably just invalid. */
continue;
}
else
/* Atom name does not match the pattern,
ignore it, it is for something else. */
continue;
/* Get root window property value */
monitor_ok:
/* Acquire the property's value, partially. */
prop_cookie = xcb_get_property(connection, 0, screen->root, *atoms, XCB_GET_PROPERTY_TYPE_ANY, 0, 0);
prop_reply = xcb_get_property_reply(connection, prop_cookie, &error);
if (error)
{
/* If we were not successful lets print an error
message, free the property and property list
and close the connection to the display. */
fprintf(stderr, "Screen root window property value query returned %i\n", error->error_code);
free(prop_reply);
free(list_reply);
xcb_disconnect(connection);
return 1;
}
/* Get the length of the property's value */
len = prop_reply->bytes_after;
/* Acquire the property's value, fully. */
prop_cookie = xcb_get_property(connection, 0, screen->root, *atoms, XCB_GET_PROPERTY_TYPE_ANY, 0, len);
prop_reply = xcb_get_property_reply(connection, prop_cookie, &error);
if (error)
{
/* If we were not successful lets print an error
message, free the property and property list
and close the connection to the display. */
fprintf(stderr, "Screen root window property value query returned %i\n", error->error_code);
free(prop_reply);
free(list_reply);
xcb_disconnect(connection);
return 1;
}
/* Encode the property's value hexadecimally */
{
/* Allocate memories on the stack to fill with property's
value in with hexadecimal encoding and NUL-termination. */
char* value = alloca((2 * len + 1) * sizeof(char));
/* Get the property's value. */
char* value_ = xcb_get_property_value(prop_reply);
size_t i;
/* Recode */
for (i = 0; i < len; i++)
{
*(value + i * 2 + 0) = "0123456789abcdef"[(*(value_ + i) >> 4) & 15];
*(value + i * 2 + 1) = "0123456789abcdef"[(*(value_ + i) >> 0) & 15];
}
/* NUL-terminate */
*(value + 2 * len) = 0;
/* Print screen, monitor and profile. */
printf("%i: %i: %s\n", screen_i, monitor, value);
}
/* Free the property resources. */
free(prop_reply);
}
/* Free the list is properties. */
free(list_reply);
}
/* Flush standard output to be sure that everything was printed,
should not be necessary, but it is best to be on the safe side. */
fflush(stdout);
/* Free resources */
/* Close connection to the display. */
xcb_disconnect(connection);
return 0;
}