#include /* for getenv(3) */ #include /* for error(3) */ #include /* for uint16_t */ #include #include #include #define UNUSED __attribute__((unused)) /* Technically, the range of the curve should be [0,2¹⁶), but the X * server will just divide it to fit in [0,gamma_size). So there's no * point in making it awkwardly tall to get the extra precision; just * make it a square, we're not really loosing anything. */ void gtkgamma_set(GtkGammaCurve *widget, int gamma_size, uint16_t *gamma_chan) { gfloat vec[gamma_size]; for (int i = 0; i < gamma_size; i++) vec[i] = gamma_chan[i] * gamma_size / 65535.0; gtk_curve_set_vector(GTK_CURVE(widget->curve), gamma_size, vec); } gboolean gtkgamma_get(GtkGammaCurve *widget, int gamma_size, uint16_t *gamma_chan) { gfloat vec[gamma_size]; gboolean dirty = FALSE; gtk_curve_get_vector(GTK_CURVE(widget->curve), gamma_size, vec); for (int i = 0; i < gamma_size; i++) { uint16_t y = vec[i] * 65535.0 / gamma_size; if (y != gamma_chan[i]) { gamma_chan[i] = y; dirty = TRUE; } } return dirty; } struct gtkgamma_data { GtkGammaCurve *widget; int gamma_size; uint16_t *gamma_chan; void (*cb)(void); guint poll_id; }; gboolean _gtkgamma_poll(struct gtkgamma_data *data) { if (gtkgamma_get(data->widget, data->gamma_size, data->gamma_chan) && data->cb) data->cb(); return TRUE; } void _gtkgamma_destroy(UNUSED GtkObject *object, struct gtkgamma_data *data) { g_source_remove(data->poll_id); free(data); } GtkWidget *gtkgamma_new(int gamma_size, uint16_t *gamma_chan, void (*cb)(void)) { GtkGammaCurve *widget = GTK_GAMMA_CURVE(gtk_gamma_curve_new()); gtk_curve_set_range(GTK_CURVE(widget->curve), /* x */0, gamma_size-1, /* y */0, gamma_size-1); gtkgamma_set(widget, gamma_size, gamma_chan); struct gtkgamma_data *data = malloc(sizeof(struct gtkgamma_data)); data->widget = widget; data->gamma_size = gamma_size; data->gamma_chan = gamma_chan; data->cb = cb; data->poll_id = g_timeout_add(100, (gboolean (*)(gpointer))_gtkgamma_poll, data); g_signal_connect(widget, "destroy", G_CALLBACK(_gtkgamma_destroy), data); return GTK_WIDGET(widget); } int main(int argc, char *argv[]) { char *display_name = getenv("DISPLAY"); GError *err = NULL; GOptionEntry options[] = { {"display", 'd', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &display_name, "X display to set gamma for", "DISPLAY"}, {0}, }; if (!gtk_init_with_args(&argc, &argv, "", options, NULL, &err)) error(1, 0, "Unable to initialize GTK+: %s", err->message); Display *display = XOpenDisplay(display_name); if (!display) error(1, 0, "Cannot open target display %s", display_name); /* RRGetScreenResources[Current] takes a Window, and operates * on the Screen associated with that Window, because taking * the Screen itself as an argument just makes too much * sense. */ XRRScreenResources *res = XRRGetScreenResourcesCurrent(display, RootWindow(display, DefaultScreen(display))); if (res->ncrtc < 1) error(1, 0, "There aren't any CRTCs on display %s's default screen (%d)", display_name, DefaultScreen(display)); // TODO: real CTRC selection //for (int i = 0; i < res->ncrtc; i++) { // RRCrtc crtc = res->crtcs[i]; //} RRCrtc crtc = res->crtcs[0]; int gamma_size = XRRGetCrtcGammaSize(display, crtc); XRRCrtcGamma *gamma = XRRGetCrtcGamma(display, crtc); /* start window */ GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_resizable(GTK_WINDOW(window), FALSE); g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL); // TODO: add a xrandr(1) emulation pane /* start layout */ GtkWidget *layout = gtk_hbox_new(TRUE, 0); gtk_container_add(GTK_CONTAINER(window), layout); gtk_widget_show(layout); void flush(void) { XRRSetCrtcGamma(display, crtc, gamma); XFlush(display); } GtkWidget *curve; /* start red */ curve = gtkgamma_new(gamma_size, gamma->red, flush); gtk_container_add(GTK_CONTAINER(layout), curve); gtk_widget_show(curve); /* end red */ /* start green */ curve = gtkgamma_new(gamma_size, gamma->green, flush); gtk_container_add(GTK_CONTAINER(layout), curve); gtk_widget_show(curve); /* end green */ /* start blue */ curve = gtkgamma_new(gamma_size, gamma->blue, flush); gtk_container_add(GTK_CONTAINER(layout), curve); gtk_widget_show(curve); /* end blue */ /* end layout */ gtk_widget_show(window); /* end window */ gtk_main(); XFree(gamma); XCloseDisplay(display); return 0; }