displayinfo_linux: parse resolution after the name token, validate bounds, accept any modern resolution

This commit is contained in:
Zac Gaetano 2026-05-07 00:19:47 -04:00
parent 9d51a96aa3
commit 95ea28e6b0

View file

@ -1,25 +1,58 @@
// src/wg/displayinfo_linux.cpp Linux implementation of display enumeration.
// src/wg/displayinfo_linux.cpp - Linux implementation of display enumeration.
//
// Uses xrandr subprocess to query connected displays.
// Parses lines matching: <output-name> connected [primary] <WxH>+<x>+<y>
// Parses lines matching: <output-name> connected [primary] <WxH>+<x>+<y> ...
#include "displayinfo.h"
#if defined(__linux__)
#include <cctype>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
namespace wg {
// Parse "WxH" out of a region of a string. Returns true on success and fills
// (w, h) with values that satisfy 640 <= w <= 10000, 480 <= h <= 10000.
//
// Searches the whole input. Caller is expected to have already stripped the
// leading output-name token so we don't pick up digits in names like "DP-1".
static bool parseResolution(const char *s, int *w, int *h) {
while (*s) {
if (std::isdigit(static_cast<unsigned char>(*s))) {
int width = 0;
int height = 0;
int consumed = 0;
if (std::sscanf(s, "%dx%d%n", &width, &height, &consumed) == 2
&& consumed > 0) {
// Make sure the next char is end, '+', ' ' or '_' so we don't
// accept things like "12x3" mid-token.
char next = s[consumed];
bool boundary = (next == '\0' || next == ' ' || next == '+'
|| next == '_' || next == '\t' || next == ',');
if (boundary
&& width >= 640 && width <= 10000
&& height >= 480 && height <= 10000) {
*w = width;
*h = height;
return true;
}
}
}
++s;
}
return false;
}
std::vector<DisplayInfo> enumerateDisplays() {
std::vector<DisplayInfo> displays;
FILE *fp = popen("xrandr --query 2>/dev/null", "r");
if (!fp) {
// Fallback: return default display
DisplayInfo def;
def.name = "default";
def.friendlyName = "default";
@ -32,78 +65,44 @@ std::vector<DisplayInfo> enumerateDisplays() {
char line[512];
while (fgets(line, sizeof(line), fp) != nullptr) {
// Remove trailing newline
size_t len = strlen(line);
if (len > 0 && line[len - 1] == '\n') {
if (len > 0 && line[len - 1] == '\n')
line[len - 1] = '\0';
}
// Skip lines that don't look like output lines (no "connected")
if (strstr(line, "connected") == nullptr) {
// Only consider lines reporting a connected output.
if (strstr(line, " connected") == nullptr)
continue;
}
// Parse: <name> connected [primary] <resolution>
// Extract the output name (first whitespace-delimited token).
char name[128] = {};
char resolution[64] = {};
int isPrimary = 0;
// Extract name (first token before space)
char *space = strchr(line, ' ');
if (!space) continue;
size_t nameLen = space - line;
size_t nameLen = static_cast<size_t>(space - line);
if (nameLen >= sizeof(name)) nameLen = sizeof(name) - 1;
memcpy(name, line, nameLen);
name[nameLen] = '\0';
// Check if "primary" is in the line
if (strstr(line, "primary") != nullptr) {
isPrimary = 1;
}
// Extract resolution: look for pattern like "1920x1080"
char *res = strstr(line, "1280x720");
if (!res) res = strstr(line, "1920x1080");
if (!res) res = strstr(line, "2560x1440");
if (!res) res = strstr(line, "3840x2160");
if (!res) {
// More general: look for digits followed by 'x' and more digits
res = line;
while (*res) {
if (isdigit(*res)) {
char *xpos = strchr(res, 'x');
if (xpos && isdigit(xpos[1])) {
break;
}
}
res++;
}
if (!*res) continue;
}
const bool isPrimary = (strstr(line, " primary") != nullptr);
// Search for the resolution AFTER the name token so we don't pick up
// digits embedded in names like "DP-1" or "eDP-1".
const char *afterName = space;
int w = 0, h = 0;
if (sscanf(res, "%dx%d", &w, &h) != 2) {
if (!parseResolution(afterName, &w, &h))
continue;
}
// Only add if we have valid dimensions
if (w <= 0 || h <= 0) {
continue;
}
DisplayInfo info;
info.name = std::string(name);
info.name = std::string(name);
info.friendlyName = std::string(name);
info.width = w;
info.height = h;
info.isPrimary = isPrimary != 0;
info.width = w;
info.height = h;
info.isPrimary = isPrimary;
displays.push_back(info);
}
pclose(fp);
// If xrandr returned nothing, fallback to default
if (displays.empty()) {
DisplayInfo def;
def.name = "default";