#include /* for getenv(3) */ #include /* for error(3) */ #include /* for uint16_t */ #include #include #include #define UNUSED __attribute__((unused)) struct gamma_channel { int size; uint16_t *data; void (*flush)(void); }; void curve_edited(GtkWidget *raw_curve, UNUSED GdkEvent *event, struct gamma_channel *gamma) { gfloat vec[gamma->size]; gboolean dirty = FALSE; gtk_curve_get_vector(GTK_CURVE(raw_curve), gamma->size, vec); for (int i = 0; i < gamma->size; i++) { uint16_t y = vec[i] * 65535.0 / gamma->size; if (y != gamma->data[i]) { gamma->data[i] = y; dirty = TRUE; } } if (dirty && gamma->flush) gamma->flush(); } GtkWidget *curve_new(struct gamma_channel *gamma) { GtkWidget *curve = gtk_gamma_curve_new(); GtkWidget *raw_curve = GTK_GAMMA_CURVE(curve)->curve; /* 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. */ gtk_curve_set_range(GTK_CURVE(raw_curve), /* x */0, gamma->size-1, /* y */0, gamma->size-1); gfloat vec[gamma->size]; for (int i = 0; i < gamma->size; i++) vec[i] = gamma->data[i] * gamma->size / 65535.0; gtk_curve_set_vector(GTK_CURVE(raw_curve), gamma->size, vec); // TODO: find a better signal g_signal_connect(raw_curve, "event-after", G_CALLBACK(curve_edited), gamma); return curve; } 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); } /* start red */ struct gamma_channel r = { .size = gamma_size, .data = gamma->red, .flush = flush, }; GtkWidget *curve = curve_new(&r); gtk_container_add(GTK_CONTAINER(layout), curve); gtk_widget_show(curve); /* end red */ /* start green */ struct gamma_channel g = { .size = gamma_size, .data = gamma->green, .flush = flush, }; curve = curve_new(&g); gtk_container_add(GTK_CONTAINER(layout), curve); gtk_widget_show(curve); /* end green */ /* start blue */ struct gamma_channel b = { .size = gamma_size, .data = gamma->blue, .flush = flush, }; curve = curve_new(&b); 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; }