displayinfo_linux: parse resolution after the name token, validate bounds, accept any modern resolution
This commit is contained in:
parent
9d51a96aa3
commit
95ea28e6b0
1 changed files with 50 additions and 51 deletions
|
|
@ -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.
|
// 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"
|
#include "displayinfo.h"
|
||||||
|
|
||||||
#if defined(__linux__)
|
#if defined(__linux__)
|
||||||
|
|
||||||
|
#include <cctype>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace wg {
|
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> enumerateDisplays() {
|
||||||
std::vector<DisplayInfo> displays;
|
std::vector<DisplayInfo> displays;
|
||||||
|
|
||||||
FILE *fp = popen("xrandr --query 2>/dev/null", "r");
|
FILE *fp = popen("xrandr --query 2>/dev/null", "r");
|
||||||
if (!fp) {
|
if (!fp) {
|
||||||
// Fallback: return default display
|
|
||||||
DisplayInfo def;
|
DisplayInfo def;
|
||||||
def.name = "default";
|
def.name = "default";
|
||||||
def.friendlyName = "default";
|
def.friendlyName = "default";
|
||||||
|
|
@ -32,78 +65,44 @@ std::vector<DisplayInfo> enumerateDisplays() {
|
||||||
|
|
||||||
char line[512];
|
char line[512];
|
||||||
while (fgets(line, sizeof(line), fp) != nullptr) {
|
while (fgets(line, sizeof(line), fp) != nullptr) {
|
||||||
// Remove trailing newline
|
|
||||||
size_t len = strlen(line);
|
size_t len = strlen(line);
|
||||||
if (len > 0 && line[len - 1] == '\n') {
|
if (len > 0 && line[len - 1] == '\n')
|
||||||
line[len - 1] = '\0';
|
line[len - 1] = '\0';
|
||||||
}
|
|
||||||
|
|
||||||
// Skip lines that don't look like output lines (no "connected")
|
// Only consider lines reporting a connected output.
|
||||||
if (strstr(line, "connected") == nullptr) {
|
if (strstr(line, " connected") == nullptr)
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
// Parse: <name> connected [primary] <resolution>
|
// Extract the output name (first whitespace-delimited token).
|
||||||
char name[128] = {};
|
char name[128] = {};
|
||||||
char resolution[64] = {};
|
|
||||||
int isPrimary = 0;
|
|
||||||
|
|
||||||
// Extract name (first token before space)
|
|
||||||
char *space = strchr(line, ' ');
|
char *space = strchr(line, ' ');
|
||||||
if (!space) continue;
|
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;
|
if (nameLen >= sizeof(name)) nameLen = sizeof(name) - 1;
|
||||||
memcpy(name, line, nameLen);
|
memcpy(name, line, nameLen);
|
||||||
name[nameLen] = '\0';
|
name[nameLen] = '\0';
|
||||||
|
|
||||||
// Check if "primary" is in the line
|
const bool isPrimary = (strstr(line, " primary") != nullptr);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 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;
|
int w = 0, h = 0;
|
||||||
if (sscanf(res, "%dx%d", &w, &h) != 2) {
|
if (!parseResolution(afterName, &w, &h))
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
// Only add if we have valid dimensions
|
|
||||||
if (w <= 0 || h <= 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
DisplayInfo info;
|
DisplayInfo info;
|
||||||
info.name = std::string(name);
|
info.name = std::string(name);
|
||||||
info.friendlyName = std::string(name);
|
info.friendlyName = std::string(name);
|
||||||
info.width = w;
|
info.width = w;
|
||||||
info.height = h;
|
info.height = h;
|
||||||
info.isPrimary = isPrimary != 0;
|
info.isPrimary = isPrimary;
|
||||||
|
|
||||||
displays.push_back(info);
|
displays.push_back(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
pclose(fp);
|
pclose(fp);
|
||||||
|
|
||||||
// If xrandr returned nothing, fallback to default
|
|
||||||
if (displays.empty()) {
|
if (displays.empty()) {
|
||||||
DisplayInfo def;
|
DisplayInfo def;
|
||||||
def.name = "default";
|
def.name = "default";
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue