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