summaryrefslogtreecommitdiff
path: root/ggamma.c
blob: de0192ebcfc142c28e844ad06d16c637ba9f905e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
#include <stdlib.h> /* for getenv(3) */
#include <error.h> /* for error(3) */
#include <stdint.h> /* for uint16_t */

#include <X11/Xlib.h>

#include <X11/extensions/Xrandr.h>

#include <gtk/gtk.h>

#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);
	}
	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;
}