1 /* See LICENSE for copyright and license details
2  * srandrd - simple randr daemon
3  */
4 #include <fcntl.h>
5 #include <signal.h>
6 #include <stdarg.h>
7 #include <stdint.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <sys/wait.h>
12 #include <unistd.h>
13 #include <X11/Xlib.h>
14 #include <X11/extensions/Xrandr.h>
15 #include <X11/extensions/Xinerama.h>
16 
17 #define OCNE(X) ((XRROutputChangeNotifyEvent*)X)
18 #define EVENT_SIZE 128
19 #define EDID_SIZE 17
20 #define SCREENID_SIZE 3
21 
22 typedef struct OutputConnection OutputConnection;
23 
24 struct OutputConnection
25 {
26 	RROutput output;
27 	int sid;
28 	char *edid;
29 	int edidlen;
30 	OutputConnection *next;
31 };
32 
33 char *CON_EVENTS[] = { "connected", "disconnected", "unknown" };
34 
35 OutputConnection *CONNECTIONS = 0;
36 
37 char **ARGV;
38 int ARGS;
39 
40 OutputConnection *cache_connection(OutputConnection * head, RROutput output, char *edid, int edidlen, int sid);
41 static void xerror(const char *format, ...);
42 static int error_handler(void);
43 static void catch_child(int sig);
44 static void help(int status);
45 static void version(void);
46 int get_screenid(Display * dpy, RROutput output);
47 int get_edid(Display * dpy, RROutput out, char *edid, int edidlen);
48 int iter_crtcs(Display * dpy, void (*f) (Display *, char *, char *, int));
49 void print_crtc(Display * dpy, char *name, char *edid, int sid);
50 pid_t emit(Display * dpy, char *output, char *event, char *edid, char *screenid);
51 void emit_crtc(Display * dpy, char *name, char *edid, int sid);
52 void free_output_connection(OutputConnection * ocon);
53 OutputConnection * get_output_connection(OutputConnection * ocon, RROutput output);
54 OutputConnection * remove_output_connection(OutputConnection * ocon, RROutput output);
55 OutputConnection * add_output_connection(OutputConnection * head, OutputConnection * ocon);
56 void die_if_null(void *ptr);
57 OutputConnection * cache_connection(OutputConnection * head, RROutput output, char *edid, int edidlen, int sid);
58 int process_events(Display * dpy, int verbose, int emit_startup);
59 int main(int argc, char **argv);
60 
61 void
die_if_null(void * ptr)62 die_if_null(void *ptr)
63 {
64 	if (!ptr) {
65 		fprintf(stderr, "Malloc failed.");
66 		error_handler();
67 	}
68 }
69 
70 static void
xerror(const char * format,...)71 xerror(const char *format, ...)
72 {
73 	va_list args;
74 	va_start(args, format);
75 	vfprintf(stderr, format, args);
76 	va_end(args);
77 	exit(EXIT_FAILURE);
78 }
79 
80 static int
error_handler(void)81 error_handler(void)
82 {
83 	exit(EXIT_FAILURE);
84 }
85 
86 static void
catch_child(int sig)87 catch_child(int sig)
88 {
89 	(void) sig;
90 	while (waitpid(-1, NULL, WNOHANG) > 0) {
91 		;
92 	}
93 }
94 
95 static void
help(int status)96 help(int status)
97 {
98 	fprintf(stderr, "Usage: " NAME " [option] command|list\n\n"
99 			"   list  List outputs and EDIDs and terminate\n\n"
100 			"Options:\n"
101 			"   -h  Print this help and exit\n"
102 			"   -n  Don't fork to background\n"
103 			"   -V  Print version information and exit\n"
104 			"   -v  Verbose output\n" "   -e  Emit connected devices at startup\n");
105 	exit(status);
106 }
107 
108 static void
version(void)109 version(void)
110 {
111 	fprintf(stderr, "    This is : " NAME "\n"
112 			"    Version : " VERSION "\n"
113 			"  Builddate : " __DATE__ " " __TIME__ "\n" "  Copyright : " COPYRIGHT "\n" "    License : " LICENSE "\n");
114 	exit(EXIT_SUCCESS);
115 }
116 
117 int
get_sid(Display * dpy,RROutput output)118 get_sid(Display * dpy, RROutput output)
119 {
120 	XRRMonitorInfo *mi;
121 	XRRScreenResources *sr;
122 	XineramaScreenInfo *si;
123 	int i, j, k, sid = -1;
124 	int nmonitors, nscreens;
125 
126 	si = XineramaQueryScreens(dpy, &nscreens);
127 	mi = XRRGetMonitors(dpy, DefaultRootWindow(dpy), True, &nmonitors);
128 	sr = XRRGetScreenResourcesCurrent(dpy, DefaultRootWindow(dpy));
129 
130 	for (j = 0; j < nmonitors; ++j) {
131 		for (k = 0; k < mi[j].noutput && mi[j].outputs[k] != output; ++k) {
132 			;
133 		}
134 		if (k < mi[j].noutput) {
135 			break;
136 		}
137 	}
138 
139 	if (si && mi && j < nmonitors) {
140 		for (i = 0; i < nscreens; ++i) {
141 			if (si[i].x_org == mi[j].x &&
142 				si[i].y_org == mi[j].y && si[i].width == mi[j].width && si[i].height == mi[j].height) {
143 				sid = i;
144 				break;
145 			}
146 		}
147 		XFree(si);
148 		XRRFreeMonitors(mi);
149 	}
150 	XRRFreeScreenResources(sr);
151 
152 	return sid;
153 }
154 
155 int
get_edid(Display * dpy,RROutput out,char * edid,int edidlen)156 get_edid(Display * dpy, RROutput out, char *edid, int edidlen)
157 {
158 	int props = 0, len = 0;
159 	Atom *properties;
160 	Atom atom_edid, real;
161 	int i, format;
162 	uint16_t vendor, product;
163 	uint32_t serial;
164 	unsigned char *p;
165 	unsigned long n, extra;
166 	Bool has_edid_prop = False;
167 
168 	if (edidlen) {
169 		edid[0] = 0;
170 	}
171 	else {
172 		return len;
173 	}
174 	properties = XRRListOutputProperties(dpy, out, &props);
175 	atom_edid = XInternAtom(dpy, RR_PROPERTY_RANDR_EDID, False);
176 	for (i = 0; i < props; i++) {
177 		if (properties[i] == atom_edid) {
178 			has_edid_prop = True;
179 			break;
180 		}
181 	}
182 	XFree(properties);
183 
184 	if (has_edid_prop) {
185 		if (XRRGetOutputProperty(dpy, out, atom_edid, 0L, 128L, False, False,
186 								 AnyPropertyType, &real, &format, &n, &extra, &p) == Success) {
187 			if (n >= 127) {
188 				vendor = (p[9] << 8) | p[8];
189 				product = (p[11] << 8) | p[10];
190 				serial = p[15] << 24 | p[14] << 16 | p[13] << 8 | p[12];
191 				snprintf(edid, edidlen, "%04X%04X%08X", vendor, product, serial);
192 				len = EDID_SIZE;
193 			}
194 			free(p);
195 		}
196 	}
197 	return len;
198 }
199 
200 int
iter_crtcs(Display * dpy,void (* f)(Display *,char *,char *,int))201 iter_crtcs(Display * dpy, void (*f) (Display *, char *, char *, int))
202 {
203 	XRRMonitorInfo *mi;
204 	XRRScreenResources *sr;
205 	XineramaScreenInfo *si;
206 	XRROutputInfo *info;
207 	char edid[EDID_SIZE];
208 	int i, j, k, edidlen, nmonitors, nscreens, sid = -1;
209 
210 	si = XineramaQueryScreens(dpy, &nscreens);
211 	mi = XRRGetMonitors(dpy, DefaultRootWindow(dpy), True, &nmonitors);
212 	sr = XRRGetScreenResourcesCurrent(dpy, DefaultRootWindow(dpy));
213 	if (si && mi) {
214 		for (i = 0; i < nscreens; ++i) {
215 			for (j = 0; j < nmonitors; ++j) {
216 				for (k = 0; k < mi[j].noutput; ++k) {
217 					sid = get_sid(dpy, mi[j].outputs[k]);
218 					if (i != sid) {
219 						continue;
220 					}
221 					info = XRRGetOutputInfo(dpy, sr, mi[j].outputs[k]);
222 					edidlen = get_edid(dpy, mi[j].outputs[k], edid, EDID_SIZE);
223 					CONNECTIONS = cache_connection(CONNECTIONS, mi[j].outputs[k], edid, edidlen, sid);
224 					f(dpy, info->name, edid, sid);
225 					XRRFreeOutputInfo(info);
226 				}
227 				if (i == sid) {
228 					break;
229 				}
230 			}
231 		}
232 		XFree(si);
233 		XRRFreeMonitors(mi);
234 	}
235 	XRRFreeScreenResources(sr);
236 	return EXIT_SUCCESS;
237 }
238 
239 void
print_crtc(Display * dpy,char * name,char * edid,int sid)240 print_crtc(Display * dpy, char *name, char *edid, int sid)
241 {
242 	printf("%s %s\n", name, edid);
243 }
244 
245 pid_t
emit(Display * dpy,char * output,char * event,char * edid,char * screenid)246 emit(Display * dpy, char *output, char *event, char *edid, char *screenid)
247 {
248 	pid_t pid;
249 	if ((pid = fork()) == 0) {
250 		if (dpy) {
251 			close(ConnectionNumber(dpy));
252 		}
253 		setsid();
254 		setenv("SRANDRD_OUTPUT", output, True);
255 		setenv("SRANDRD_EVENT", event, True);
256 		setenv("SRANDRD_EDID", edid, True);
257 		setenv("SRANDRD_SCREENID", screenid, True);
258 		execvp(ARGV[ARGS], &(ARGV[ARGS]));
259 	}
260 	return waitpid(pid, NULL, 0);
261 }
262 
263 void
emit_crtc(Display * dpy,char * name,char * edid,int sid)264 emit_crtc(Display * dpy, char *name, char *edid, int sid)
265 {
266 	char screenid[SCREENID_SIZE];
267 	screenid[0] = 0;
268 	if (sid != -1) {
269 		snprintf(screenid, SCREENID_SIZE, "%d", sid);
270 	}
271 	emit(dpy, name, CON_EVENTS[0], edid, screenid);
272 }
273 
274 void
free_output_connection(OutputConnection * ocon)275 free_output_connection(OutputConnection * ocon)
276 {
277 	if (ocon) {
278 		free(ocon->edid);
279 		free(ocon);
280 	}
281 }
282 
283 OutputConnection *
get_output_connection(OutputConnection * ocon,RROutput output)284 get_output_connection(OutputConnection * ocon, RROutput output)
285 {
286 	OutputConnection *_ocon = ocon;
287 	for (; _ocon && _ocon->output != output; _ocon = _ocon->next);
288 	return _ocon;
289 }
290 
291 OutputConnection *
remove_output_connection(OutputConnection * ocon,RROutput output)292 remove_output_connection(OutputConnection * ocon, RROutput output)
293 {
294 	OutputConnection *_ocon = ocon;
295 	if (_ocon && _ocon->output == output) {
296 		OutputConnection *next = _ocon->next;
297 		free_output_connection(_ocon);
298 		return next;
299 	}
300 	for (; _ocon && _ocon->next && _ocon->next->output != output; _ocon = _ocon->next);
301 	if (_ocon && _ocon->next && _ocon->next->output == output) {
302 		OutputConnection *old_ocon = _ocon->next;
303 		_ocon->next = _ocon->next->next;
304 		free_output_connection(old_ocon);
305 	}
306 	return ocon;
307 }
308 
309 OutputConnection *
add_output_connection(OutputConnection * head,OutputConnection * ocon)310 add_output_connection(OutputConnection * head, OutputConnection * ocon)
311 {
312 	OutputConnection *_ocon = head;
313 	if (!ocon) {
314 		return head;
315 	}
316 	if (!head) {
317 		return ocon;
318 	}
319 	for (; _ocon && _ocon->next; _ocon = _ocon->next);
320 	_ocon->next = ocon;
321 	return head;
322 }
323 
324 OutputConnection *
cache_connection(OutputConnection * head,RROutput output,char * edid,int edidlen,int sid)325 cache_connection(OutputConnection * head, RROutput output, char *edid, int edidlen, int sid)
326 {
327 	OutputConnection *ocon = malloc(sizeof(OutputConnection));
328 	die_if_null(ocon);
329 	memset(ocon, 0, sizeof(OutputConnection));
330 	ocon->output = output;
331 
332 	ocon->edid = malloc(sizeof(char) * EDID_SIZE);
333 	memset(ocon->edid, 0, EDID_SIZE);
334 	die_if_null(ocon->edid);
335 
336 	ocon->edidlen = edidlen;
337 	strncpy(ocon->edid, edid, edidlen);
338 
339 	ocon->sid = sid;
340 
341 	return add_output_connection(head, ocon);
342 }
343 
344 int
process_events(Display * dpy,int verbose,int emit_startup)345 process_events(Display * dpy, int verbose, int emit_startup)
346 {
347 	XRRScreenResources *sr;
348 	XRROutputInfo *info;
349 	XEvent ev;
350 	char edid[EDID_SIZE], screenid[SCREENID_SIZE];
351 	int i, edidlen;
352 
353 	if (emit_startup) {
354 		iter_crtcs(dpy, &emit_crtc);
355 	}
356 
357 	XRRSelectInput(dpy, DefaultRootWindow(dpy), RROutputChangeNotifyMask);
358 	XSync(dpy, False);
359 	XSetIOErrorHandler((XIOErrorHandler) error_handler);
360 	while (1) {
361 		if (!XNextEvent(dpy, &ev)) {
362 			i = -1;
363 			edidlen = 0;
364 			memset(edid, 0, EDID_SIZE);
365 			memset(screenid, 0, SCREENID_SIZE);
366 
367 			sr = XRRGetScreenResources(OCNE(&ev)->display, OCNE(&ev)->window);
368 			if (sr == NULL) {
369 				fprintf(stderr, "Could not get screen resources\n");
370 				continue;
371 			}
372 
373 			info = XRRGetOutputInfo(OCNE(&ev)->display, sr, OCNE(&ev)->output);
374 			if (info == NULL) {
375 				XRRFreeScreenResources(sr);
376 				fprintf(stderr, "Could not get output info\n");
377 				continue;
378 			}
379 
380 			if (strncmp(CON_EVENTS[info->connection], "disconnected", 12) == 0) {
381 				/* retrieve edid and screen information from cache */
382 				OutputConnection *ocon = get_output_connection(CONNECTIONS, OCNE(&ev)->output);
383 				if (ocon) {
384 					i = ocon->sid;
385 					edidlen = ocon->edidlen;
386 					strncpy(edid, ocon->edid, edidlen);
387 					CONNECTIONS = remove_output_connection(CONNECTIONS, OCNE(&ev)->output);
388 				}
389 			}
390 			else {
391 				edidlen = get_edid(OCNE(&ev)->display, OCNE(&ev)->output, edid, EDID_SIZE);
392 				i = get_sid(OCNE(&ev)->display, OCNE(&ev)->output);
393 				CONNECTIONS = cache_connection(CONNECTIONS, OCNE(&ev)->output, edid, edidlen, i);
394 			}
395 			if (i != -1) {
396 				snprintf(screenid, SCREENID_SIZE, "%d", i);
397 			}
398 
399 			if (verbose) {
400 				printf("Event: %s %s\n", info->name, CON_EVENTS[info->connection]);
401 				printf("Time: %lu\n", info->timestamp);
402 				if (info->crtc == 0) {
403 					printf("Size: %lumm x %lumm\n", info->mm_width, info->mm_height);
404 				}
405 				else {
406 					printf("CRTC: %lu\n", info->crtc);
407 					XRRCrtcInfo *crtc = XRRGetCrtcInfo(dpy, sr, info->crtc);
408 					if (crtc != NULL) {
409 						printf("Size: %dx%d\n", crtc->width, crtc->height);
410 						XRRFreeCrtcInfo(crtc);
411 					}
412 				}
413 				if (edidlen) {
414 					printf("EDID (vendor, product, serial): %s\n", edid);
415 				}
416 				else {
417 					printf("EDID (vendor, product, serial): not available\n");
418 				}
419 			}
420 			emit(dpy, info->name, CON_EVENTS[info->connection], edid, screenid);
421 			XRRFreeScreenResources(sr);
422 			XRRFreeOutputInfo(info);
423 		}
424 	}
425 	return EXIT_SUCCESS;
426 }
427 
428 int
main(int argc,char ** argv)429 main(int argc, char **argv)
430 {
431 	Display *dpy;
432 	int daemonize = 1, args = 1, verbose = 0, emit = 0, list = 0;
433 	uid_t uid;
434 
435 	if (argc < 2) {
436 		help(EXIT_FAILURE);
437 	}
438 
439 	for (args = 1; args < argc && *(argv[args]) == '-'; args++) {
440 		switch (argv[args][1]) {
441 		case 'V':
442 			version();
443 		case 'n':
444 			daemonize = 0;
445 			break;
446 		case 'v':
447 			verbose++;
448 			break;
449 		case 'e':
450 			emit++;
451 			break;
452 		case 'h':
453 			help(EXIT_SUCCESS);
454 		default:
455 			printf("%d\n", list);
456 			help(EXIT_FAILURE);
457 		}
458 	}
459 	if (argv[args] == NULL) {
460 		help(EXIT_FAILURE);
461 	}
462 
463 	if (strncmp("list", argv[args], 5) == 0) {
464 		list = 1;
465 	}
466 
467 	if (((uid = getuid()) == 0) || uid != geteuid()) {
468 		xerror("This program may not run as root\n");
469 	}
470 
471 	if ((dpy = XOpenDisplay(NULL)) == NULL) {
472 		xerror("Cannot open display\n");
473 	}
474 
475 	if (list) {
476 		return iter_crtcs(dpy, &print_crtc);
477 	}
478 
479 	if (daemonize) {
480 		switch (fork()) {
481 		case -1:
482 			xerror("Could not fork\n");
483 		case 0:
484 			break;
485 		default:
486 			exit(EXIT_SUCCESS);
487 		}
488 		setsid();
489 
490 		close(STDIN_FILENO);
491 		close(STDERR_FILENO);
492 		close(STDOUT_FILENO);
493 	}
494 	signal(SIGCHLD, catch_child);
495 
496 	ARGV = argv;
497 	ARGS = args;
498 
499 	return process_events(dpy, verbose, emit);
500 }
501 
502 /* vi: ft=c:tw=80:sw=4:ts=4:sts=4:noet
503  */
504