Use mode ID instead of mode for autoconfig
[x-on-resize.git] / xrandr-auto
bloba97a3cb0666c3cd9b62a75ee7314c5fe5f7a1cb7
1 #!/usr/bin/env nickle
3 autoimport Process;
4 autoload ParseArgs;
6 /* Parsed TILE property contents */
7 typedef struct {
8         int     group_id;
9         int     flags;
10         int     number_h;
11         int     number_v;
12         int     hpos;
13         int     vpos;
14 } tile_t;
16 /* Configuration for one output */
17 typedef struct {
18         string name;
19         string mode;
20         int mode_id;
21         bool connected;
22         bool primary;
23         int width, height;
24         int x, y;
25         bool is_tile;
26         tile_t tile;
27 } output_t;
29 /* Configuration for one monitor (collection of outputs) */
30 typedef struct {
31         string          name;
32         int             group_id;
33         bool            primary;
34         int             leader;
35         int             width;
36         int             height;
37         int             x;
38         int             y;
39         (*output_t)[...]        outputs;
40 } monitor_t;
42 /* Overall screen configuration */
43 typedef struct {
44         int             width;
45         int             height;
46 } screen_t;
48 /* List the existing manually configured monitors
49  */
50 string[]
51 get_existing_monitors() {
52         file    input = popen(popen_direction.read, false,
53                               "xrandr", "xrandr", "--listmonitors");
54         string[...] names = {};
55         while (!File::end(input)) {
56                 string          line = File::fgets(input);
57                 string[]        words = String::wordsplit(line, " \t");
59                 if (words[0] != "Monitors:" && dim(words) >= 2) {
60                         string  name = words[1];
62                         if (name[0] != '+') {
63                                 if (name[0] == '*')
64                                         name = String::substr(name, 1, String::length(name) - 1);
65                                 names[dim(names)] = name;
66                         }
67                 }
68         }
69         return names;
72 const int priority_any = 0;
73 const int priority_preferred = 1;
74 const int priority_current = 2;
76 typedef enum { start, start_output, output, skip_output,
77                mode_get, mode_skip, done } state_t;
79 /* Parse xrandr results to compute the set of available monitors and
80  * current screen size
81  */
82 output_t[]
83 get_outputs(*screen_t screen) {
84         file    input = popen(popen_direction.read, false,
85                               "xrandr", "xrandr", "--verbose", "--prop");
86         output_t[...] outputs = {};
87         output_t output;
88         int mode_priority;
89         state_t state = state_t.start;
90         string line;
91         string[] words;
93         void add_output() {
94                 switch (state) {
95                 case state_t.skip_output:
96                 case state_t.output:
97                 case state_t.mode_skip:
98                 case state_t.mode_get:
99 #                       printf ("add output %s\n", output.name);
100                         outputs[dim(outputs)] = output;
101                 }
102         }
104         void getline() {
105                 if (File::end(input))
106                         line = "DONE";
107                 else
108                         line = File::fgets(input);
110                 words = String::wordsplit(line, " \t");
111         }
113         bool check_output() {
114                 if (dim(words) >= 2) {
115                         switch (words[1]) {
116                         case "connected":
117                         case "disconnected":
118                                 add_output();
119                                 state = state_t.start_output;
120                                 return true;
121                         }
122                 }
123                 return false;
124         }
126         getline();
128         while (state != state_t.done) {
129 #               printf("\tstate %v words: %v\n", state, words);
130                 if (words[0] == "DONE") {
131                         add_output();
132                         state = state_t.done;
133                         continue;
134                 }
135                 switch (state) {
136                 case state_t.start:
137                         if (dim(words) >= 10) {
138                                 screen->width = string_to_integer(words[7]);
139                                 screen->height = string_to_integer(words[9]);
140                                 state = state_t.start_output;
141                         }
142                         getline();
143                         break;
144                 case state_t.start_output:
145                         /* Look for an output */
146                         if (dim(words) >= 2) {
147                                 switch (words[1]) {
148                                 case "connected":
149                                         bool primary = false;
150                                         if (dim(words) >= 3 && words[2] == "primary")
151                                                 primary = true;
152                                         output = (output_t) {
153                                                 .name = words[0],
154                                                 .mode = "",
155                                                 .connected = true,
156                                                 .primary = primary,
157                                                 .width = 0,
158                                                 .height = 0,
159                                                 .is_tile = false,
160                                         };
161                                         state = state_t.output;
162                                         mode_priority = -1;
163                                         break;
164                                 case "disconnected":
165                                         output = (output_t) {
166                                                 .name = words[0],
167                                                 .mode = "",
168                                                 .connected = false,
169                                                 .primary = false,
170                                                 .width = 0,
171                                                 .height = 0,
172                                                 .is_tile = false,
173                                         };
174                                         state = state_t.skip_output;
175                                         break;
176                                 }
177                         }
178                         getline();
179                         break;
180                 case state_t.skip_output:
181                         if (check_output())
182                                 break;
183                         getline();
184                         break;
185                 case state_t.output:
186                         if (check_output())
187                                 break;
189                         /* Look for a mode */
190                         if (String::index(line, "MHz") >= 0) {
191                                 int this_priority = priority_any;
192                                 if (String::index(line, "current") >= 0) {
193                                         this_priority = priority_current;
194                                 } else if (String::index(line, "preferred") >= 0) {
195                                         this_priority = priority_preferred;
196                                 }
197                                 if (this_priority > mode_priority) {
198                                         output.mode = words[0];
199                                         File::sscanf(words[1], "(0x%x)", &output.mode_id);
200                                         state = state_t.mode_get;
201                                         mode_priority = this_priority;
202                                 } else {
203                                         state = state_t.mode_skip;
204                                 }
205                         } else if (words[0] == "TILE:" && dim(words) >= 7) {
206                                 int[6] vals = { [i] = string_to_integer(words[i+1]) };
207                                 output.is_tile = true;
208                                 output.tile = (tile_t) {
209                                         .group_id = vals[0],
210                                         .flags = vals[1],
211                                         .number_h = vals[2],
212                                         .number_v = vals[3],
213                                         .hpos = vals[4],
214                                         .vpos = vals[5],
215                                 };
216                         }
217                         getline();
218                         break;
219                 case state_t.mode_get:
220                 case state_t.mode_skip:
221                         if (words[0] == "h:") {
222                                 if (state == state_t.mode_get) {
223                                         for (int h = 0; h < dim(words) - 1; h++) {
224                                                 if (words[h] == "width") {
225                                                         output.width = string_to_integer(words[h+1]);
226                                                         break;
227                                                 }
228                                         }
229                                 }
230                         } else if (words[0] == "v:") {
231                                 if (state == state_t.mode_get) {
232                                         for (int v = 0; v < dim(words) - 1; v++) {
233                                                 if (words[v] == "height") {
234                                                         output.height = string_to_integer(words[v+1]);
235                                                         break;
236                                                 }
237                                         }
238                                 }
239                         } else {
240                                 state = state_t.output;
241                                 break;
242                         }
243                         getline();
244                         break;
245                 }
246         }
247         return outputs;
251  * Construct the set of monitors from the output information, building
252  * composite monitors from outputs with the TILE property
253  */
254 monitor_t[] get_monitors(&output_t[] outputs) {
255         monitor_t[...]  monitors = {};
257         for (int i = 0; i < dim(outputs); i++) {
258                 *output_t       output = &outputs[i];
260                 if (!output->connected)
261                         continue;
263                 if (output->is_tile) {
264                         int     m;
266                         for (m = 0; m < dim(monitors); m++) {
267                                 if (monitors[m].group_id == output->tile.group_id)
268                                         break;
269                         }
270                         if (m == dim(monitors)) {
271                                 (*output_t)[...] outputs;
272                                 outputs[0] = output;
273                                 monitors[m] = (monitor_t) {
274                                         .name = sprintf("DP-GROUP-%d", output->tile.group_id),
275                                         .group_id = output->tile.group_id,
276                                         .outputs = outputs,
277                                         .primary = false,
278                                         .leader = 0,
279                                         .width = output->tile.number_h * output->width,
280                                         .height = output->tile.number_v * output->height,
281                                         .x = -1,
282                                         .y = -1,
283                                 };
284                         } else {
285                                 &output_t       leader = monitors[m].outputs[monitors[m].leader];
287                                 if (output->tile.vpos < leader.tile.vpos ||
288                                     output->tile.vpos == leader.tile.vpos &&
289                                     output->tile.hpos < leader.tile.hpos)
291                                         monitors[m].leader = dim(monitors[m].outputs);
293                                 monitors[m].outputs[dim(monitors[m].outputs)] = output;
294                         }
295                 } else {
296                         monitors[dim(monitors)] = (monitor_t) {
297                                 .name = output->name,
298                                 .group_id = -1,
299                                 .outputs = ((*output_t)[1]) { output },
300                                 .leader = 0,
301                                 .primary = false,
302                                 .width = output->width,
303                                 .height = output->height,
304                                 .x = -1,
305                                 .y = -1,
306                         };
307                 }
308         }
310         return monitors;
313 bool
314 is_internal_output(&output_t output) {
315         if (String::index(output.name, "eDP") >= 0)
316                 return true;
317         if (String::index(output.name, "LVDS") >= 0)
318                 return true;
319         return false;
322 bool
323 is_internal_monitor(&monitor_t monitor) {
324         for (int o = 0; o < dim(monitor.outputs); o++)
325                 if (is_internal_output(monitor.outputs[o]))
326                         return true;
327         return false;
330 /* Return the current primary monitor */
331 *monitor_t
332 get_primary(&monitor_t[] monitors)
334         for (int m = 0; m < dim(monitors); m++) {
335                 &monitor_t      monitor = &monitors[m];
336                 if (monitor.primary)
337                         return &monitor;
338         }
340         monitors[0].primary = true;
341         return &monitors[0];
344 /* Return the first internal monitor (LVDS or eDP) */
345 *monitor_t
346 get_internal(&monitor_t[] monitors)
348         for (int m = 0; m < dim(monitors); m++) {
349                 &monitor_t      monitor = &monitors[m];
350                 if (is_internal_monitor(&monitor))
351                         return &monitor;
352         }
353         return &monitors[0];
356 /* Set output positions and primary status */
357 void
358 set_output(&monitor_t[] monitors)
360         /* Set output positions and primary value */
361         for (int m = 0; m < dim(monitors); m++) {
362                 *monitor_t      monitor = &monitors[m];
364                 if (monitor->primary)
365                         monitor->outputs[monitor->leader]->primary = true;
367                 if (monitor->group_id >= 0) {
368                         int     tile_width = monitor->outputs[0]->width;
369                         int     tile_height = monitor->outputs[0]->height;
371                         for (int o = 0; o < dim(monitor->outputs); o++) {
372                                 monitor->outputs[o]->x = monitor->x + monitor->outputs[o]->tile.hpos * tile_width;
373                                 monitor->outputs[o]->y = monitor->y + monitor->outputs[o]->tile.vpos * tile_height;
374                         }
375                 } else {
376                         monitor->outputs[0]->x = monitor->x;
377                         monitor->outputs[0]->y = monitor->y;
378                 }
379         }
382 /* Set overall screen size */
383 void
384 set_screen(&output_t[] outputs, *screen_t screen)
386         int     width = 0;
387         int     height = 0;
389         for (int o = 0; o < dim(outputs); o++) {
390                 if (outputs[o].connected) {
391                         int     w = outputs[o].x + outputs[o].width;
392                         int     h = outputs[o].y + outputs[o].height;
394                         if (w > width)
395                                 width = w;
396                         if (h > height)
397                                 height = h;
398                 }
399         }
400         screen->width = width;
401         screen->height = height;
405  * Policy -- select the primary monitor
407  * Pick the largest external monitor if it's bigger than 1080p,
408  * otherwise pick the internal monitor
409  */
410 void
411 set_primary(&monitor_t[] monitors)
413         *monitor_t      primary = &monitors[0];
415         for (int m = 1; m < dim(monitors); m++) {
416                 *monitor_t      monitor = &monitors[m];
418                 if (is_internal_monitor(primary)) {
419                         if (!is_internal_monitor(monitor)) {
420                                 if (monitor->height > 1080)
421                                         primary = monitor;
422                         }
423                 } else {
424                         if (is_internal_monitor(monitor)) {
425                                 if (primary->height <= 1080)
426                                         primary = monitor;
427                         } else {
428                                 if (monitor->height > primary->height)
429                                         primary = monitor;
430                         }
431                 }
432         }
433         primary->primary = true;
437  * Policy -- position the monitors
439  * Place the primary monitor at the upper left corner of the
440  * screen.
442  * If the internal monitor is not primary, place it just below the
443  * primary monitor.
445  * Place all other monitors to the right of the primary monitor
446  */
448 void
449 set_monitor_pos(&monitor_t[] monitors)
451         int     nset = 0;
453         /* Primary monitor goes upper left */
454         *monitor_t      primary = get_primary(&monitors);
456         primary->x = 0;
457         primary->y = 0;
459         /* Set panel position, if not primary */
460         *monitor_t      internal = get_internal(&monitors);
461         if (is_internal_monitor(internal) && !internal->primary) {
462                 internal->x = 0;
463                 internal->y = primary->height;
464         }
466         int     x = primary->width;
468         /* Set remaining positions, right of primary */
469         for (int m = 0; m < dim(monitors); m++) {
470                 *monitor_t      monitor = &monitors[m];
472                 if (monitor->x < 0) {
473                         monitor->x = x;
474                         monitor->y = 0;
475                         x += monitor->width;
476                 }
477         }
481 string
482 tabs(int tab) {
483         static string[] t = { "", "\t", "\t\t", "\t\t\t", "t\t\t\t" };
485         return t[tab];
488 void
489 print_output(*output_t output, int tab) {
490         printf ("%soutput %s\n", tabs(tab), output->name);
491         printf ("%sconnected %v\n", tabs(tab+1), output->connected);
492         if (output->connected) {
493                 printf ("%smode %s (0x%x)\n", tabs(tab+1), output->mode, output->mode_id);
494                 printf ("%sprimary %v\n", tabs(tab+1), output->primary);
495                 printf ("%swidth, height %d,%d\n", tabs(tab+1), output->width, output->height);
496                 printf ("%sx, y %d, %d\n", tabs(tab+1), output->x, output->y);
497         }
500 void
501 print_monitor(*monitor_t monitor, int tab) {
503         printf ("%smonitor %s\n", tabs(tab), monitor->name);
504         printf ("%sgroup_id %d\n", tabs(tab+1), monitor->group_id);
505         printf ("%sprimary %v\n", tabs(tab+1), monitor->primary);
506         printf ("%sleader %d\n", tabs(tab+1), monitor->leader);
507         printf ("%swidth, height %d,%d\n", tabs(tab+1), monitor->width, monitor->height);
508         printf ("%sx, y %d, %d\n", tabs(tab+1), monitor->x, monitor->y);
509         printf ("%sgroup_id %d\n", tabs(tab+1), monitor->group_id);
510         printf ("%soutputs", tabs(tab+1));
511         for (int o = 0; o < dim(monitor->outputs); o++)
512                 printf(" %s", monitor->outputs[o]->name);
513         printf ("\n");
516 string[]
517 output_config(*output_t output) {
518         string[...] config;
520         if (!output->connected) {
521                 config = (string[...]) {
522                         "--output",
523                         output->name,
524                         "--off"
525                 };
526         } else {
527                 config = (string[...]) {
528                         "--output",
529                         output->name,
530                         "--mode",
531                         sprintf ("0x%x", output->mode_id),
532                         "--pos",
533                         sprintf("%dx%d", output->x, output->y)
534                 };
535                 if (output->primary)
536                         config[dim(config)] = "--primary";
537         }
538         return config;
541 string
542 output_names(&(*output_t)[] outputs) {
543         string  name = outputs[0]->name;
545         for (int o = 1; o < dim(outputs); o++)
546                 name = name + "," + outputs[o]->name;
547         return name;
550 string[]
551 monitor_config(*monitor_t monitor) {
552         if (monitor->group_id < 0)
553                 return (string[0]) {};
555         string[...] config = {
556                 "--setmonitor",
557                 monitor->name,
558                 "auto",
559                 output_names(&monitor->outputs),
560         };
562         return config;
565 string[]
566 screen_config(*screen_t screen) {
567         return (string[...]) {
568                 "--fb",
569                 sprintf("%dx%d", screen->width, screen->height),
570                 "--dpi", "115"
571         };
574 string[]
575 cat_args(string[][] args) {
576         string[...] ret = {};
578         for (int a = 0; a < dim(args); a++) {
579                 for (int s = 0; s < dim(args[a]); s++)
580                         ret[dim(ret)] = args[a][s];
581         }
582         return ret;
585 bool    verbose = false;
586 bool    dry_run = false;
588 ParseArgs::argdesc argd = {
589         .args = {
590                 { .var = { .arg_flag = &verbose },
591                   .abbr = 'v',
592                   .name = "verbose",
593                   .desc = "verbose mode"
594                 },
595                 { .var = { .arg_flag = &dry_run },
596                   .abbr = 'n',
597                   .name = "dry-run",
598                   .desc = "don't execute, just print"
599                 },
600         },
601         .unknown = &(int user_argind),
604 void
605 xrandr_auto()
607         ParseArgs::parseargs(&argd, &argv);
609         screen_t orig_screen;
610         output_t[] outputs = get_outputs(&orig_screen);
611         monitor_t[] monitors = get_monitors(&outputs);
612         string[...][...] args = { {"xrandr"} };
613         string[...] existing = get_existing_monitors();
614         screen_t screen;
615         screen_t temp_screen;
617         /* Implement our policy */
618         set_primary(&monitors);
619         set_monitor_pos(&monitors);
621         /* Using the specific configuration, place outputs
622          * and set the overall screen size
623          */
624         set_output(&monitors);
625         set_screen(&outputs, &screen);
627         temp_screen.width = max(orig_screen.width, screen.width);
628         temp_screen.height = max(orig_screen.height, screen.height);
630         if (temp_screen.width != orig_screen.width || temp_screen.height != orig_screen.height)
631                 args[dim(args)] = screen_config(&temp_screen);
633         for (int e = 0; e < dim(existing); e++)
634                 args[dim(args)] = (string[...]) {
635                         "--delmonitor",
636                         existing[e]
637                 };
639         for (int m = 0; m < dim(monitors); m++) {
640                 args[dim(args)] = monitor_config(&monitors[m]);
641         }
643         for (int o = 0; o < dim(outputs); o++) {
644                 args[dim(args)] = output_config(&outputs[o]);
645         }
647         if (temp_screen.height != screen.height || temp_screen.width != screen.width)
648                 args[dim(args)] = screen_config(&screen);
650         if (verbose) {
651                 for (int m = 0; m < dim(monitors); m++)
652                         print_monitor(&monitors[m], 0);
653                 for (int o = 0; o < dim(outputs); o++)
654                         print_output(&outputs[o], 0);
655         }
657         string[] xrandr_args = cat_args(args);
659         if (dry_run || verbose) {
660                 for (int a = 0; a < dim(xrandr_args); a++)
661                         printf("%s ", xrandr_args[a]);
662                 printf ("\n");
663         }
664         if (!dry_run) {
665                 system("xrandr", xrandr_args ...);
666         }
669 xrandr_auto();