Public paste
Undefined
By: Guest | Date: Aug 6 2020 15:21 | Format: None | Expires: never | Size: 9.67 KB | Hits: 749

  1. #include <cassert>
  2. #include <iomanip>
  3. #include <iostream>
  4. #include <getopt.h>
  5. #include <map>
  6. #include <regex>
  7. #include <unordered_set>
  8. #include <unistd.h>
  9. extern "C" {
  10. #include <X11/Xlib.h>
  11. #include <X11/extensions/Xrandr.h>
  12. }
  13.  
  14. using namespace std;
  15. enum LONG_OPTS { LIST_OUTPUTS = 1000, LIST_MODES = 1001 };
  16. static const string CSI = "\x1b["; // ANSI escape code
  17. static const string BOLD = CSI + "1m";
  18. static const string UNDERLINE = CSI + "4m";
  19. static const string REVERSE = CSI + "7m";
  20. static const string RESET = CSI + "0m";
  21.  
  22. bool gatherData();
  23. void listModes(const string &output);
  24. void listOutputs();
  25. double refreshRate(const XRRModeInfo *modeInfo);
  26. RRMode findMode(const string &output, const string &wantedMode);
  27. bool setMode(const string &output, const string &wantedMode);
  28. void showUsage(string name);
  29.  
  30.  
  31. map<RRMode, const XRRModeInfo*> modes;
  32. map<const string, const XRROutputInfo*> outputs;
  33. map<const string, RRMode> currentModes;
  34. map<const string, RRCrtc> crtcs;
  35. string primaryOutput;
  36. Display *display = nullptr;
  37. XRRScreenResources *screenRes = nullptr;
  38. RRCrtc crtc = 0;
  39.  
  40.  
  41. int main(int argc, char *argv[])  {
  42.         bool flagHelp = false;
  43.         bool flagListOutputs = false;
  44.         bool flagListModes = false;
  45.         bool flagSetMode = true;
  46.         string output;
  47.         string wantedMode;
  48.        
  49.         for (;;) {
  50.                 int index = 0;
  51.                 static struct option LongOptions[] = {
  52.                 { "help", no_argument, nullptr, 'h' },
  53.                 { "list-outputs", no_argument, nullptr, LIST_OUTPUTS },
  54.                 { "list-modes", no_argument, nullptr, LIST_MODES },
  55.                 { "output", required_argument, 0, 'o' },
  56.                 };
  57.                
  58.                 int c = getopt_long(argc, argv, "ho:", LongOptions, &index);
  59.                
  60.                 if (c == -1) { break; }
  61.                
  62.                 switch (c) {
  63.                 case 'h': flagHelp = true; break;
  64.                 case 'o': output = optarg; break;
  65.                 case LIST_OUTPUTS: flagListOutputs = true; flagSetMode = false; break;
  66.                 case LIST_MODES: flagListModes = true; flagSetMode = false; break;
  67.                 }
  68.         }
  69.        
  70.         if (optind < argc) {
  71.                 wantedMode = argv[optind];
  72.         } else {
  73.                 flagSetMode = false;
  74.         }
  75.        
  76.         if (flagHelp) { showUsage(argv[0]); return 0; }
  77.        
  78.         if (not (flagListModes or flagListOutputs or flagSetMode)) {
  79.                 flagListModes = true;
  80.         }
  81.        
  82.         if (not gatherData()) { return -1; }
  83.         if (output.empty()) {
  84.                 output = primaryOutput;
  85.                 if (output.empty()) {
  86.                         cerr << "Error: cannot determine primary output" << endl;
  87.                         return -2;
  88.                 }
  89.         } else if (outputs.find(output) == outputs.end()) {
  90.                 cerr << "Error: no output named \"" << output << "\"" << endl;
  91.                 return -3;
  92.         }
  93.        
  94.         if (flagListModes) { listModes(output); }
  95.         if (flagListOutputs) { listOutputs(); }
  96.         if (flagSetMode) { return setMode(output, wantedMode) ? 0 : -4; }
  97.        
  98.         return 0;
  99. }
  100.  
  101.  
  102. bool gatherData() {
  103.         display = XOpenDisplay(nullptr);
  104.         if (display == nullptr) {
  105.                 cerr << "Error: can't open display " << XDisplayName(nullptr) << endl;
  106.                 return false;
  107.         }
  108.        
  109.         int screen = DefaultScreen(display);
  110.         if (screen < 0 or screen >= ScreenCount(display)) {
  111.                 cerr << "Error: can't open default screen" << endl;
  112.                 return false;
  113.         }
  114.        
  115.         Window window = RootWindow(display, screen);
  116.         screenRes = XRRGetScreenResourcesCurrent(display, window);
  117.        
  118.         for (int i = 0; i < screenRes->nmode; ++i) {
  119.                 const XRRModeInfo *modeInfo = screenRes->modes + i;
  120.                 modes.insert({ modeInfo->id, modeInfo });
  121.         }
  122.        
  123.         RROutput primary = XRRGetOutputPrimary(display, window);
  124.        
  125.         for (int i = 0; i < screenRes->noutput; ++i) {
  126.                 const XRROutputInfo *outputInfo = XRRGetOutputInfo(display, screenRes, screenRes->outputs[i]);
  127.                 outputs.insert({ outputInfo->name, outputInfo });
  128.                
  129.                 if (screenRes->outputs[i] == primary) {
  130.                         primaryOutput = outputInfo->name;
  131.                 }
  132.                
  133.                 crtcs.insert({ outputInfo->name, outputInfo->crtc });
  134.                 if (outputInfo->crtc) {
  135.                         const XRRCrtcInfo *crtcInfo = XRRGetCrtcInfo(display, screenRes, outputInfo->crtc);
  136.                         currentModes.insert({ outputInfo->name, crtcInfo->mode });
  137.                 }
  138.         }
  139.        
  140.         return true;
  141. }
  142.  
  143.  
  144. void listModes(const string &output) {
  145.         cout << "Modes for " << output << ":" << endl;
  146.         if (modes.size() > 0) {
  147.                 // insert modes into a multi-dimensional map for sorting
  148.                
  149.                 const XRROutputInfo *outputInfo = outputs[output];
  150.                 map<int, map<int, multimap<double, RRMode>>> modeMap;
  151.                 RRMode currentMode = 0;
  152.                 unordered_set<RRMode> preferredModes;
  153.                
  154.                 auto it = currentModes.find(output);
  155.                 if (it != currentModes.end()) {
  156.                         currentMode = it->second;
  157.                 }
  158.                
  159.                 for (int i = 0; i < outputInfo->nmode; ++i) {
  160.                         const XRRModeInfo *mode = modes[outputInfo->modes[i]];
  161.                         modeMap[mode->width][mode->height].insert({ refreshRate(mode), mode->id });
  162.                        
  163.                         if (i < outputInfo->npreferred) {
  164.                                 preferredModes.insert(mode->id);
  165.                         }
  166.                 }
  167.                
  168.                 for (auto it = modeMap.crbegin(); it != modeMap.crend(); ++it) {
  169.                         string width = to_string(it->first);
  170.                         auto widthMap = it->second;
  171.                         for (auto it = widthMap.crbegin(); it != widthMap.crend(); ++it) {
  172.                                 string height = to_string(it->first);
  173.                                 string wxh = width + "x" + height + "@...";
  174.                                 cout << "  " << left << setw(15) << wxh;
  175.                                
  176.                                 auto rateMap = it->second;
  177.                                 for (auto it = rateMap.crbegin(); it != rateMap.crend(); ++it) {
  178.                                         double rate = it->first;
  179.                                         RRMode mode = it->second;
  180.                                        
  181.                                         string attrStart = "";
  182.                                         string attrEnd = "";
  183.                                        
  184.                                         if (currentMode == mode)            { attrStart += REVERSE; }
  185.                                         if (preferredModes.count(mode) > 0) { attrStart += BOLD; }
  186.                                         if (not attrStart.empty())          { attrEnd += RESET; }
  187.                                        
  188.                                         char buffer[8];
  189.                                         snprintf(buffer, sizeof(buffer), "%.2f", rate);
  190.                                         string rateString = buffer;
  191.                                         if (rateString.substr(rateString.size() - 3, 3) == ".00") {
  192.                                                 rateString = rateString.substr(0, rateString.size() - 3);
  193.                                         }
  194.                                         string padString(6 - rateString.size(), ' ');
  195.                                        
  196.                                         cout << "  " << attrStart << rateString << attrEnd << padString;
  197.                                 }
  198.                                
  199.                                 cout << endl;
  200.                         }
  201.                 }
  202.         } else {
  203.                 cout << "  (no modes)" << endl;
  204.         }
  205.         cout << endl;
  206. }
  207.  
  208.  
  209. void listOutputs() {
  210.         cout << "Outputs:" << endl;
  211.         if (outputs.size() > 0) {
  212.                 for (auto it = outputs.cbegin(); it != outputs.cend(); ++it) {
  213.                         string attrStart = "";
  214.                         string attrEnd = "";
  215.                        
  216.                         if (it->first == primaryOutput) {
  217.                                 attrStart = BOLD;
  218.                                 attrEnd = RESET;
  219.                         }
  220.                        
  221.                         cout << "  " << attrStart << it->first << attrEnd << endl;
  222.                 }
  223.         } else {
  224.                 cout << "  (no outputs)" << endl;
  225.         }
  226.         cout << endl;
  227. }
  228.  
  229.  
  230. double refreshRate(const XRRModeInfo *modeInfo) {
  231.         int pixels = modeInfo->hTotal * modeInfo->vTotal;
  232.        
  233.         if (pixels > 0) {
  234.                 if (modeInfo->modeFlags & RR_Interlace) {
  235.                         pixels >>= 1;
  236.                 }
  237.                 if (modeInfo->modeFlags & RR_DoubleScan) {
  238.                         pixels <<= 1;
  239.                 }
  240.                
  241.                 return modeInfo->dotClock / (double)pixels;
  242.         }
  243.        
  244.         return 0.0;
  245. }
  246.  
  247.  
  248. RRMode findMode(const string &output, const string &wantedMode) {
  249.         static const regex ModeRegex(R"((?:(\d+)x(\d+)(?:@(\d+(?:\.\d*))?)?|(\d+(?:\.\d*)?)))");
  250.         smatch m;
  251.         ulong width = 0;
  252.         ulong height = 0;
  253.         double rate = 0;
  254.        
  255.         // The regular expression accepts the following mode specifications:
  256.         // WIDTHxHEIGHT, e.g. 3840x2160. The values are in sub matches 1 and 2
  257.         // WITDHxHEIGHT@RATE, e.g. 3840x2160x60. The values are in sub matches 1, 2 and 3
  258.         // RATE, e.g. 60. The value is in sub match 4
  259.         if (regex_match(wantedMode, m, ModeRegex)) {
  260.                 assert(m.size() == 5);
  261.                 if (m[1].length() > 0) {
  262.                         assert(m[2].length() > 0);
  263.                         assert(m[4].length() == 0);
  264.                        
  265.                         width = stoul(m[1].str());
  266.                         height = stoul(m[2].str());
  267.                         if (m[3].length() > 0) {
  268.                                 rate = stod(m[3].str());
  269.                         }
  270.                 } else {
  271.                         assert(m[1].length() == 0 and m[2].length() == 0 and m[3].length() == 0);
  272.                         assert(m[4].length() > 0);
  273.                         rate = stod(m[4].str());
  274.                 }
  275.         } else {
  276.                 cerr << "Error: cannot parse mode \"" << wantedMode << "\"" << endl;
  277.                 return 0;
  278.         }
  279.        
  280.         const XRRModeInfo *currentModeInfo = modes[currentModes[output]];
  281.         if (width == 0) {
  282.                 width = currentModeInfo->width;
  283.                 height = currentModeInfo->height;
  284.         } else if (rate == 0) {
  285.                 rate = refreshRate(currentModeInfo);
  286.         }
  287.        
  288.         const XRROutputInfo *outputInfo = outputs[output];
  289.         map<int, map<int, multimap<double, RRMode>>> modeMap;
  290.        
  291.         for (int i = 0; i < outputInfo->nmode; ++i) {
  292.                 const XRRModeInfo *mode = modes[outputInfo->modes[i]];
  293.                 modeMap[mode->width][mode->height].insert({ refreshRate(mode), mode->id });
  294.         }
  295.        
  296.         if (modeMap.find(width) == modeMap.end() or
  297.                 modeMap[width].find(height) == modeMap[width].end()) {
  298.                 cerr << "Error: invalid resolution: " << width << "x" << height << endl;
  299.                 return 0;
  300.         }
  301.        
  302.         const multimap<double, RRMode> &rateMap = modeMap[width][height];
  303.         double chosenDelta = 1e99;
  304.         RRMode chosenMode = 0;
  305.         for (auto it : rateMap) {
  306.                 double delta = abs(it.first - rate);
  307.                 if (delta < chosenDelta) {
  308.                         chosenDelta = delta;
  309.                         chosenMode = it.second;
  310.                 }
  311.         }
  312.        
  313.         if (chosenMode == 0) {
  314.                 cerr << "Error: no appropriate mode found" << endl;
  315.                 return 0;
  316.         }
  317.        
  318.         return chosenMode;
  319. }
  320.  
  321.  
  322. bool setMode(const string &output, const string &wantedMode) {
  323.         RRMode mode = findMode(output, wantedMode);
  324.         if (mode == 0) { return false; }
  325.        
  326.         auto it = crtcs.find(output);
  327.         if (it == crtcs.end()) {
  328.                 cerr << "Error: output \"" << output << "\" is disabled" << endl;
  329.                 return false;
  330.         }
  331.        
  332.         RRCrtc crtc = it->second;
  333.         const XRRCrtcInfo *crtcInfo = XRRGetCrtcInfo(display, screenRes, crtc);
  334.         Status status = XRRSetCrtcConfig(display, screenRes, crtc, crtcInfo->timestamp, crtcInfo->x,
  335.                                          crtcInfo->y, mode, crtcInfo->rotation, crtcInfo->outputs,
  336.                                          crtcInfo->noutput);
  337.        
  338.         return status == 0;
  339. }
  340.  
  341.  
  342. void showUsage(string name) {
  343.         cout << "Usage: " << name << " [-o OUTPUT] [COLUMNSxLINES@]RATE" << endl;
  344.         cout << "       " << name << " --list-outputs" << endl;
  345.         cout << "       " << name << " --list-modes" << endl;
  346. }
  347.