Permalink
Fetching contributors…
Cannot retrieve contributors at this time
870 lines (684 sloc) 37.7 KB
// copyright marazmista @ 29.03.2014
#include "gpu.h"
#include "radeon_profile.h"
#include <cmath>
#include <QFile>
#include <QDebug>
#include <QtConcurrent/QtConcurrent>
extern "C" {
#include <X11/extensions/Xrandr.h>
}
#define EDID_OFFSET_PNP_ID 0x08
#define EDID_OFFSET_MODEL_NUMBER 0x0a
#define EDID_OFFSET_SERIAL_NUMBER 0x0c
#define EDID_OFFSET_DATA_BLOCKS 0x36
#define EDID_OFFSET_LAST_BLOCK 0x6c
#define EDID_DESCRIPTOR_PRODUCT_NAME 0xfc
#define ATOM_VALUE (Atom)4
#define INTEGER_VALUE (Atom)19
#define CARDINAL_VALUE (Atom)6
#define MILLIMETERS_PER_INCH 0.0393700787402
// List of files mapping PnP IDs to vendor names
// List found through pkgfile --verbose --search pnp.ids (on arch linux)
#define PNP_ID_FILE_COUNT 8
static const char * pnpIdFiles [PNP_ID_FILE_COUNT] = {
"/usr/share/misc/pnp.ids",
"/usr/share/libgnome-desktop-3.0/pnp.ids",
"/usr/share/libgnome-desktop/pnp.ids",
"/usr/share/libcinnamon-desktop/pnp.ids",
"/usr/share/dispcalGUI/pnp.ids",
"/usr/share/DisplayCAL/pnp.ids",
"/usr/share/libmate-desktop/pnp.ids",
"/usr/share/hwdata/pnp.ids"
};
bool gpu::daemonConnected() {
return driverHandler->daemonConnected();
}
void gpu::detectCards() {
QStringList out = globalStuff::grabSystemInfo("ls /sys/class/drm/").filter(QRegExp("card\\d$"));
for (char i = 0; i < out.count(); i++) {
QFile f("/sys/class/drm/"+out[i]+"/device/uevent");
if (!f.open(QIODevice::ReadOnly))
continue;
QString line = f.readLine(50).trimmed();
if (line == "DRIVER=radeon") {
GPUSysInfo gsi;
gsi.driverModuleString = "radeon";
gsi.module = DriverModule::RADEON;
gsi.sysName = out[i];
gpuList.append(gsi);
qDebug() << "Card detected (radeon): " << gsi.sysName;
continue;
}
if (line == "DRIVER=amdgpu") {
GPUSysInfo gsi;
gsi.driverModuleString = "amdgpu";
gsi.module = DriverModule::AMDGPU;
gsi.sysName = out[i];
gpuList.append(gsi);
qDebug() << "Card detected (amdgpu): " << gsi.sysName;
continue;
}
}
}
bool gpu::initialize() {
qDebug() << "Initializing device";
detectCards();
if (gpuList.size() == 0) {
qWarning() << "No cards found!";
return false;
}
driverHandler = new dXorg(gpuList.at(0));
defineAvailableDataContainer();
return true;
}
bool gpu::isInitialized() {
return gpuList.count() > 0;
}
void gpu::changeGpu(int index) {
delete driverHandler;
driverHandler = new dXorg(gpuList.at(index));
driverHandler->configure();
defineAvailableDataContainer();
}
void gpu::defineAvailableDataContainer() {
GPUClocks tmpClk = driverHandler->getClocks();
if (tmpClk.coreClk != -1)
gpuData.insert(ValueID::CLK_CORE, RPValue(ValueUnit::MEGAHERTZ, tmpClk.coreClk));
if (tmpClk.coreVolt != -1)
gpuData.insert(ValueID::VOLT_CORE, RPValue(ValueUnit::MILIVOLT, tmpClk.coreVolt));
if (tmpClk.memClk != -1)
gpuData.insert(ValueID::CLK_MEM, RPValue(ValueUnit::MEGAHERTZ, tmpClk.memClk));
if (tmpClk.memVolt != -1)
gpuData.insert(ValueID::VOLT_MEM, RPValue(ValueUnit::MILIVOLT, tmpClk.memVolt));
if (tmpClk.uvdCClk != -1)
gpuData.insert(ValueID::CLK_UVD, RPValue(ValueUnit::MEGAHERTZ, tmpClk.uvdCClk));
if (tmpClk.uvdDClk != -1)
gpuData.insert(ValueID::DCLK_UVD, RPValue(ValueUnit::MEGAHERTZ, tmpClk.uvdDClk));
if (tmpClk.powerLevel != -1)
gpuData.insert(ValueID::POWER_LEVEL, RPValue(ValueUnit::NONE, tmpClk.powerLevel));
GPUFanSpeed tmpPwm = driverHandler->getFanSpeed();
if (tmpPwm.fanSpeedPercent != -1)
gpuData.insert(ValueID::FAN_SPEED_PERCENT, RPValue(ValueUnit::PERCENT, tmpPwm.fanSpeedPercent));
if (tmpPwm.fanSpeedRpm != -1)
gpuData.insert(ValueID::FAN_SPEED_RPM, RPValue(ValueUnit::RPM, tmpPwm.fanSpeedRpm));
float tmpTemp = driverHandler->getTemperature();
if (tmpTemp != -1) {
gpuData.insert(ValueID::TEMPERATURE_CURRENT, RPValue(ValueUnit::CELSIUS, tmpTemp));
gpuData.insert(ValueID::TEMPERATURE_BEFORE_CURRENT, RPValue(ValueUnit::CELSIUS, tmpTemp));
gpuData.insert(ValueID::TEMPERATURE_MIN, RPValue(ValueUnit::CELSIUS, tmpTemp));
gpuData.insert(ValueID::TEMPERATURE_MAX, RPValue(ValueUnit::CELSIUS, tmpTemp));
}
GPUUsage tmpUsage = driverHandler->getGPUUsage();
if (tmpUsage.gpuUsage != -1)
gpuData.insert(ValueID::GPU_USAGE_PERCENT, RPValue(ValueUnit::PERCENT, tmpUsage.gpuUsage));
if (tmpUsage.gpuVramUsage != -1)
gpuData.insert(ValueID::GPU_VRAM_USAGE_MB, RPValue(ValueUnit::MEGABYTE, tmpUsage.gpuVramUsage));
if (tmpUsage.gpuVramUsagePercent != -1)
gpuData.insert(ValueID::GPU_VRAM_USAGE_PERCENT, RPValue(ValueUnit::PERCENT, tmpUsage.gpuVramUsagePercent));
}
void gpu::getClocks() {
GPUClocks tmp = driverHandler->getClocks();
if (gpuData.contains(ValueID::CLK_CORE))
gpuData[ValueID::CLK_CORE].setValue(tmp.coreClk);
if (gpuData.contains(ValueID::VOLT_CORE))
gpuData[ValueID::VOLT_CORE].setValue(tmp.coreVolt);
if (gpuData.contains(ValueID::CLK_MEM))
gpuData[ValueID::CLK_MEM].setValue(tmp.memClk);
if (gpuData.contains(ValueID::VOLT_MEM))
gpuData[ValueID::VOLT_MEM].setValue(tmp.memVolt);
if (gpuData.contains(ValueID::CLK_UVD))
gpuData[ValueID::CLK_UVD].setValue(tmp.uvdCClk);
if (gpuData.contains(ValueID::DCLK_UVD))
gpuData[ValueID::DCLK_UVD].setValue(tmp.uvdDClk);
if (gpuData.contains(ValueID::POWER_LEVEL))
gpuData[ValueID::POWER_LEVEL].setValue(tmp.powerLevel);
}
void gpu::getTemperature() {
if (!gpuData.contains(ValueID::TEMPERATURE_CURRENT))
return;
gpuData[ValueID::TEMPERATURE_BEFORE_CURRENT].setValue(gpuData.value(ValueID::TEMPERATURE_CURRENT).value);
gpuData[ValueID::TEMPERATURE_CURRENT].setValue(driverHandler->getTemperature());
if (gpuData.value(ValueID::TEMPERATURE_MIN, RPValue()).value > gpuData.value(ValueID::TEMPERATURE_CURRENT).value)
gpuData[ValueID::TEMPERATURE_MIN].setValue(gpuData.value(ValueID::TEMPERATURE_CURRENT).value);
if (gpuData.value(ValueID::TEMPERATURE_MAX, RPValue()).value < gpuData.value(ValueID::TEMPERATURE_CURRENT).value)
gpuData[ValueID::TEMPERATURE_MAX].setValue(gpuData.value(ValueID::TEMPERATURE_CURRENT).value);
}
void gpu::getGpuUsage() {
// getting gpu usage seems to be heavy and cause ui lag, so it is done in another thread
futureGpuUsage.setFuture(QtConcurrent::run(driverHandler,&dXorg::getGPUUsage));
}
QList<QTreeWidgetItem *> gpu::getModuleInfo() const {
return driverHandler->getModuleInfo();
}
QStringList gpu::getGLXInfo(QString gpuName) const {
QStringList data, gpus = globalStuff::grabSystemInfo("lspci").filter(QRegExp(".+VGA.+|.+3D.+"));
gpus.removeAt(gpus.indexOf(QRegExp(".+Audio.+"))); //remove radeon audio device
// loop for multi gpu
for (int i = 0; i < gpus.count(); i++)
data << "VGA:"+gpus[i].split(":",QString::SkipEmptyParts)[2];
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
if (!gpuName.isEmpty())
env.insert("DRI_PRIME",gpuName.at(gpuName.length()-1));
QStringList driver = globalStuff::grabSystemInfo("xdriinfo",env).filter("Screen 0:",Qt::CaseInsensitive);
if (!driver.isEmpty()) // because of segfault when no xdriinfo
data << "OpenGL driver:"+ driver.filter("Screen 0:",Qt::CaseInsensitive)[0].split(":",QString::SkipEmptyParts)[1];
data << "Kernel driver: " + gpuList.at(currentGpuIndex).driverModuleString;
data << globalStuff::grabSystemInfo("glxinfo -B",env).filter(QRegExp(".+"));
return data;
}
QString gpu::getCurrentPowerLevel() {
return driverHandler->getCurrentPowerLevel().trimmed();
}
QString gpu::getCurrentPowerProfile() {
return driverHandler->getCurrentPowerProfile().trimmed();
}
void gpu::refreshPowerLevel() {
currentPowerLevel = getCurrentPowerLevel();
currentPowerProfile = getCurrentPowerProfile();
}
void gpu::setPowerProfile(PowerProfiles newPowerProfile) {
driverHandler->setPowerProfile(newPowerProfile);
}
void gpu::setForcePowerLevel(ForcePowerLevels newForcePowerLevel) {
driverHandler->setForcePowerLevel(newForcePowerLevel);
}
void gpu::setPwmValue(unsigned int value) {
driverHandler->setPwmValue(value);
}
void gpu::setPwmManualControl(bool manual) {
driverHandler->setPwmManualControl(manual);
}
void gpu::getFanSpeed() {
if (!gpuData.contains(ValueID::FAN_SPEED_PERCENT))
return;
GPUFanSpeed tmp = driverHandler->getFanSpeed();
gpuData[ValueID::FAN_SPEED_PERCENT].setValue(tmp.fanSpeedPercent);
if (gpuData.contains(ValueID::FAN_SPEED_RPM))
gpuData[ValueID::FAN_SPEED_RPM].setValue(tmp.fanSpeedRpm);
}
const DriverFeatures& gpu::getDriverFeatures() {
return driverHandler->features;
}
const GPUConstParams& gpu::getGpuConstParams() {
return driverHandler->params;
}
const DeviceFilePaths& gpu::getDriverFiles() {
return driverHandler->driverFiles;
}
void gpu::finalize() {
if (gpuData.contains(ValueID::FAN_SPEED_PERCENT))
setPwmManualControl(false);
if (getDriverFeatures().ocCoreAvailable)
resetOverclock();
if (futureGpuUsage.isRunning())
futureGpuUsage.waitForFinished();
}
void gpu::setOverclockValue(const QString &file, const int value) {
driverHandler->setOverclockValue(file, value);
}
void gpu::resetOverclock() {
driverHandler->setOverclockValue(getDriverFiles().sysFs.pp_sclk_od, 0);
driverHandler->setOverclockValue(getDriverFiles().sysFs.pp_mclk_od, 0);
}
int gpu::getCurrentPowerPlayTableId(const QString &file) {
return driverHandler->getCurrentPowerPlayTableId(file);
}
// Function that returns the human readable output of a property value
// For reference:
// http://cgit.freedesktop.org/xorg/app/xrandr/tree/xrandr.c#n2408
QString translateProperty(Display * display,
const int propertyDataFormat, // 8 / 16 / 32 bit
const Atom propertyDataType, // ATOM / INTEGER / CARDINAL
const Atom * propertyRawData){ // Pointer to the property value data array
QString out;
switch(propertyDataType){
case ATOM_VALUE: // Text value, like 'off' or 'None'
if(propertyDataFormat == 32){ // Only 32 bit supported here
char* string = XGetAtomName(display, *propertyRawData);
if(string) // If it is not NULL
out = QString(string);
XFree(string);
}
break;
case INTEGER_VALUE: // Signed numeric value
switch(propertyDataFormat){
case 8: out = QString::number((qint8) *propertyRawData);
case 16: out = QString::number((qint16) *propertyRawData);
case 32: out = QString::number((qint32) *propertyRawData);
}
break;
case CARDINAL_VALUE: // Unsigned numeric value
switch(propertyDataFormat){
case 8: out = QString::number((quint8) *propertyRawData);
case 16: out = QString::number((quint16) *propertyRawData);
case 32: out = QString::number((quint32) *propertyRawData);
}
}
return out.isEmpty() ? QString::number(*propertyRawData) : out; // If no match was found, return as number
}
// Get the real vendor name from the three-letter PNP ID
// See http://www.uefi.org/pnp_id_list
QString translatePnpId(const QString pnpId){
if ( ! pnpId.isEmpty()){
qDebug() << "Searching PnP ID: " << pnpId;
for(int i=0; i < PNP_ID_FILE_COUNT; i++){ // Cycle through the files
QFile pnpIds(pnpIdFiles[i]);
if(pnpIds.exists() && pnpIds.open(QIODevice::ReadOnly)){ // File is available
while ( ! pnpIds.atEnd()){ // Continue reading the file until the file has ended
QString line = pnpIds.readLine();
if (line.startsWith(pnpId)){ // Right line
QStringList parts = line.split(QChar('\t')); // Separate PNP ID from the real name
if (parts.size() == 2){
qDebug() << "Found PnP ID: " << pnpId << "->" << parts.at(1).simplified();
pnpIds.close();
return parts.at(1).simplified(); // Get the real name
}
}
}
}
pnpIds.close();
}
}
return pnpId; // No real name found, return the PNP ID instead
}
// Get the human-readable monitor name (vendor + model) from the EDID
// See http://en.wikipedia.org/wiki/Extended_display_identification_data#EDID_1.3_data_format
// For reference: https://github.com/KDE/libkscreen/blob/master/src/edid.cpp#L262-L286
QString getMonitorName(const quint8 *EDID){
QString monitorName;
//Get the vendor PnP ID
QString pnpId, modelName;
pnpId[0] = 'A' + ((EDID[EDID_OFFSET_PNP_ID + 0] & 0x7c) / 4) - 1;
pnpId[1] = 'A' + ((EDID[EDID_OFFSET_PNP_ID + 0] & 0x3) * 8) + ((EDID[EDID_OFFSET_PNP_ID + 1] & 0xe0) / 32) - 1;
pnpId[2] = 'A' + (EDID[EDID_OFFSET_PNP_ID + 1] & 0x1f) - 1;
//Translate the PnP ID into human-readable vendor name
monitorName.append(translatePnpId(pnpId));
// Get the model name
for (uint i = EDID_OFFSET_DATA_BLOCKS; i <= EDID_OFFSET_LAST_BLOCK; i += 18)
if(EDID[i+3] == EDID_DESCRIPTOR_PRODUCT_NAME)
modelName = QString::fromLocal8Bit((const char*)&EDID[i+5], 13).simplified();
monitorName.append(modelName.isEmpty() ? " - Model unknown" : ' ' + modelName);
return monitorName;
}
// Function that returns the right info struct for the RRMode it receives
XRRModeInfo * getModeInfo(XRRScreenResources * screenResources, RRMode mode){
// Cycle through all the XRRModeInfos of this screen and search the right one
for(int modeInfoIndex=0; modeInfoIndex < screenResources->nmode; modeInfoIndex++){
if(screenResources->modes[modeInfoIndex].id == mode) // If we found the right modeInfo
return &screenResources->modes[modeInfoIndex];// We found it, we can exit the loop
}
return NULL; // We haven't found it
}
// Function that returns the horizontal refresh rate in Hz from a XRRModeInfo
float getHorizontalRefreshRate(XRRModeInfo * modeInfo){
float out = -1;
if(modeInfo && modeInfo->hTotal > 0 && modeInfo->dotClock > 0) // 'modeInfo' and its values must be present
out = (float)modeInfo->dotClock / (float) modeInfo->hTotal;
return out;
}
// Function that returns the vertical refresh rate in Hz from a XRRModeInfo
float getVerticalRefreshRate(XRRModeInfo * modeInfo){
float out = -1;
if(modeInfo && modeInfo->hTotal > 0 && modeInfo->vTotal > 0 && modeInfo->dotClock > 0){ // 'modeInfo' and its values must be present
float vTotal = modeInfo->vTotal;
if(modeInfo->modeFlags & RR_DoubleScan) // https://en.wikipedia.org/wiki/Dual_Scan
vTotal *= 2;
if(modeInfo->modeFlags & RR_Interlace) // https://en.wikipedia.org/wiki/Interlaced_video
vTotal /= 2;
out = (float)modeInfo->dotClock / ((float)modeInfo->hTotal * vTotal);
}
return out;
}
//Utility function for getAspectRatio
bool ratioEqualsValue(const float ratio, const float value){
return std::abs(ratio - value) < 0.01;
}
// Function that takes the resolution OR the width/height ratio
// Returns as string the aspect ratio name if it is one of the most common ones
// Returns <ratio>:1 otherwise
QString getAspectRatio(const float width, const float height = 1){
const float ratio = width / height;
QString out;
// https://en.wikipedia.org/wiki/Aspect_ratio_(image)#Some_common_examples
if(ratioEqualsValue(ratio, 1.78f))
out = "16:9";
else if(ratioEqualsValue(ratio, 1.67f))
out = "5:3";
else if(ratioEqualsValue(ratio, 1.6f))
out = "16:10";
else if(ratioEqualsValue(ratio, 1.5f))
out = "3:2";
else if(ratioEqualsValue(ratio, 1.33f))
out = "4:3";
else if(ratioEqualsValue(ratio, 1.25f))
out = "5:4";
else if(ratioEqualsValue(ratio, 1.2f))
out = "6:5";
return out.isEmpty() ? QString::number(ratio, 'g', 3) + ":1" : out;
}
void addItemToTreeList(QTreeWidgetItem * parent, const QString &leftColumn, const QString &rightColumn) {
parent->addChild(new QTreeWidgetItem(QStringList() << leftColumn << rightColumn));
}
// Returns a detailed list of connections and connected monitors
QList<QTreeWidgetItem *> gpu::getCardConnectors() const {
QList<QTreeWidgetItem *> cardConnectorsList;
#ifdef QT_DEBUG // COMPILED ONLY IN DEBUG MODE
// Old implementation, parsing the output of xrandr
QStringList out = globalStuff::grabSystemInfo("xrandr -q --verbose"), screens;
screens = out.filter(QRegExp("Screen\\s\\d"));
for (int i = 0; i < screens.count(); i++) {
QTreeWidgetItem *item = new QTreeWidgetItem(QStringList() << screens[i].split(':')[0] << screens[i].split(",")[1].remove(" current "));
cardConnectorsList.append(item);
}
cardConnectorsList.append(new QTreeWidgetItem(QStringList() << "------"));
for (int i = 0; i < out.size(); i++) {
if (!out[i].startsWith("\t") && out[i].contains("connected")) {
QString connector = out[i].split(' ')[0],
status = out[i].split(' ')[1],
res = out[i].split(' ')[2].split('+')[0];
if (status == "connected") {
QString monitor, edid = monitor = "";
// find EDID
for (int i = out.indexOf(QRegExp(".+EDID.+"))+1; i < out.count(); i++)
if (out[i].startsWith(("\t\t")))
edid += out[i].remove("\t\t");
else
break;
// Parse EDID
// See http://en.wikipedia.org/wiki/Extended_display_identification_data#EDID_1.3_data_format
if (edid.size() >= 256) {
QStringList hex;
bool found = false, ok = true;
int i2 = 108;
for(int i3 = 0; i3 < 4; i3++) {
if(edid.mid(i2, 2).toInt(&ok, 16) == 0 && ok &&
edid.mid(i2 + 2, 2).toInt(&ok, 16) == 0) {
// Other Monitor Descriptor found
if(ok && edid.mid(i2 + 6, 2).toInt(&ok, 16) == 0xFC && ok) {
// Monitor name found
for(int i4 = i2 + 10; i4 < i2 + 34; i4 += 2)
hex << edid.mid(i4, 2);
found = true;
break;
}
}
if(!ok)
break;
i2 += 36;
}
if (ok && found) {
// Hex -> String
for(i2 = 0; i2 < hex.size(); i2++) {
monitor += QString(hex[i2].toInt(&ok, 16));
if(!ok)
break;
}
if(ok)
monitor = " (" + monitor.left(monitor.indexOf('\n')) + ")";
}
}
status += monitor + " @ " + QString((res.contains('x')) ? res : "unknown");
}
QTreeWidgetItem *item = new QTreeWidgetItem(QStringList() << connector << status);
cardConnectorsList.append(item);
}
}
#endif // QT_DEBUG
// New implementation that uses libXrandr to get the connectors and connected monitors list
// Official docs: http://www.x.org/releases/current/doc/man/man3/Xrandr.3.html
// Wiki page: http://www.x.org/wiki/libraries/libxrandr/
// XRandr source code (useful as reference): http://cgit.freedesktop.org/xorg/app/xrandr/tree/xrandr.c
Display* display = XOpenDisplay(NULL); // Get the connection to the X server.
if( ! display){
qWarning() << "Error loading connectors: can't connect to X server";
return cardConnectorsList;
}
for(int screenIndex = 0; screenIndex < ScreenCount(display); screenIndex++){ // For each screen in this connection
qDebug() << "Analyzing screen " << screenIndex;
// Create root QTreeWidgetItem item for this screen
QTreeWidgetItem * screenItem = new QTreeWidgetItem(QStringList() << QObject::tr("Virtual screen n°%n", NULL, screenIndex));
cardConnectorsList.append(screenItem);
// Add resolution
QString screenResolution = QString::number(DisplayWidth(display,screenIndex));
screenResolution += " x " + QString::number(DisplayHeight(display, screenIndex));
addItemToTreeList(screenItem, QObject::tr("Resolution"), screenResolution);
// Add screen minimum and maximum resolutions
int screenMinWidth, screenMinHeight, screenMaxWidth, screenMaxHeight;
Window screenRoot = RootWindow(display, screenIndex);
XRRGetScreenSizeRange(display, screenRoot, &screenMinWidth, &screenMinHeight, &screenMaxWidth, &screenMaxHeight);
QString screenMinResolution = QString::number(screenMinWidth) + " x " + QString::number(screenMinHeight);
addItemToTreeList(screenItem, QObject::tr("Minimum resolution"), screenMinResolution);
QString screenMaxResolution = QString::number(screenMaxWidth) + " x " + QString::number(screenMaxHeight);
addItemToTreeList(screenItem, QObject::tr("Maximum resolution"), screenMaxResolution);
// Adding screen virtual dimension
addItemToTreeList(screenItem, QObject::tr("Virtual size"), QObject::tr("%n mm x ", NULL, DisplayWidthMM(display, screenIndex)) + QObject::tr("%n mm", NULL, DisplayHeightMM(display, screenIndex)));
// Retrieve screen resources (connectors, configurations, timestamps etc.)
XRRScreenResources * screenResources = XRRGetScreenResources(display, screenRoot);
if (screenResources == NULL){
qWarning() << "Error loading connectors: can't get resources for screen " << QString::number(screenIndex);
continue; // Next screen
}
// Creating root QTreeWidgetItem for this screen's outputs
QTreeWidgetItem * outputListItem = new QTreeWidgetItem(QStringList() << QObject::tr("Outputs"));
screenItem->addChild(outputListItem);
int screenConnectedOutputs = 0, screenActiveOutputs = 0;
//Cycle through outputs of this screen
for(int outputIndex = 0; outputIndex < screenResources->noutput; outputIndex++){
qDebug() << " Analyzing output " << outputIndex;
// Get output info (connection name, current configuration, dimensions, etc)
XRROutputInfo * outputInfo = XRRGetOutputInfo(display, screenResources, screenResources->outputs[outputIndex]);
if(outputInfo == NULL){
qWarning() << screenIndex << "/" << outputIndex << "Can't retrieve info for this output";
continue; // Next output
}
// Creating root QTreeWidgetItem item for this output
QTreeWidgetItem *outputItem = new QTreeWidgetItem(QStringList() << outputInfo->name);
outputListItem->addChild(outputItem);
// Check the output connection state
if(outputInfo->connection != 0){ // No connection
outputItem->setText(1, QObject::tr("Disconnected"));
XRRFreeOutputInfo(outputInfo); // Deallocate the memory of this output's info
continue; // Next output
}
screenConnectedOutputs++;
// Get active configuration info (resolution, refresh rate, offset and brightness)
XRRCrtcInfo * configInfo = XRRGetCrtcInfo(display, screenResources, outputInfo->crtc);
RRMode * activeMode = NULL;
if(configInfo == NULL) { // This output is not active
qDebug() << "Output" << outputIndex << "has no active mode";
addItemToTreeList(outputItem, QObject::tr("Active"), QObject::tr("No"));
} else { // The output is active: add resolution, refresh rate and the offset
addItemToTreeList(outputItem, QObject::tr("Active"), QObject::tr("Yes"));
qDebug() << " Analyzing active mode";
screenActiveOutputs++;
activeMode = &configInfo->mode;
// Add current resolution
QString outputResolution = QString::number(configInfo->width) + " x " + QString::number(configInfo->height);
outputResolution += " (" + getAspectRatio(configInfo->width, configInfo->height) + ')';
addItemToTreeList(outputItem, QObject::tr("Resolution"), outputResolution);
//Add refresh rate
float vRefreshRate = getVerticalRefreshRate(getModeInfo(screenResources, *activeMode));
if(vRefreshRate != -1){
QString outputVRate = QString::number(vRefreshRate, 'g', 3) + " Hz";
addItemToTreeList(outputItem, QObject::tr("Refresh rate"), outputVRate);
}
// Add the position in the current configuration (useful only in multi-head)
// It's the offset from the top left corner
QString outputOffset = QString::number(configInfo->x) + ", " + QString::number(configInfo->y);
addItemToTreeList(outputItem, QObject::tr("Offset"), outputOffset);
// Calculate and add the screen software brightness level
XRRCrtcGamma * gammaInfo = XRRGetCrtcGamma(display, outputInfo->crtc);
if(gammaInfo){
float maxRed = gammaInfo->red[gammaInfo->size-1],
maxGreen = gammaInfo->green[gammaInfo->size-1],
maxBlue = gammaInfo->blue[gammaInfo->size-1],
brightnessPercent = (0.2126 * maxRed + 0.7152 * maxGreen + 0.0722 * maxBlue) * 100 / 0xFFFF;
// Source of the brightness formula: https://en.wikipedia.org/wiki/Relative_luminance
if(brightnessPercent > 0){
QString softBrightness = QString::number(brightnessPercent, 'g', 3) + " %";
addItemToTreeList(outputItem, QObject::tr("Brightness (software)"), softBrightness);
}
XRRFreeGamma(gammaInfo);
}
// We could also insert here the hardware (backlight) brightness level but we need to implement it
// This would require analyzing and parsing the content of /sys/class/backlight
// Maybe in a future commit.
// For more info: https://wiki.archlinux.org/index.php/backlight
// This is an example implementation:
// https://git.gnome.org/browse/gnome-settings-daemon/tree/plugins/power/gsd-backlight-helper.c
}
// Get other details (monitor size, possible configurations and properties)
// Add monitor size
double diagonal = sqrt(pow(outputInfo->mm_width, 2) + pow(outputInfo->mm_height, 2)) * MILLIMETERS_PER_INCH;
addItemToTreeList(outputItem, QObject::tr("Size"), QObject::tr("%n mm x ", NULL, outputInfo->mm_width) + QObject::tr("%n mm ", NULL, outputInfo->mm_height) + QObject::tr("(%n inches)", NULL, diagonal));
// Create the root QTreeWidgetItem of the possible modes (resolution, Refresh rate, HSync, VSync, etc) list
QTreeWidgetItem * modeListItem = new QTreeWidgetItem(QStringList() << QObject::tr("Supported modes"));
outputItem->addChild(modeListItem);
for(int modeIndex = 0; modeIndex < outputInfo->nmode; modeIndex++){ // For each possible mode
XRRModeInfo * modeInfo = getModeInfo(screenResources, outputInfo->modes[modeIndex]);
if(modeInfo == NULL) // Mode info not found
continue; // Proceed to next mode
// Get the name (the resolution) and prepare the details
QString modeName = QString::fromLocal8Bit(modeInfo->name);
if((activeMode != NULL) && (modeInfo->id == *activeMode))
modeName += " (active)"; // This is the currently active mode
// Add the aspect ratio
QString modeDetails = getAspectRatio(modeInfo->width, modeInfo->height) + ", ";
// Gather mode vertical and horizontal refresh rate and clock
// http://en.tldp.org/HOWTO/XFree86-Video-Timings-HOWTO/basic.html
if(modeInfo->dotClock && modeInfo->vTotal && modeInfo->hTotal){ // We need those values
float verticalRefreshRate = getVerticalRefreshRate(modeInfo),
horizontalRefreshRate = getHorizontalRefreshRate(modeInfo);
if(verticalRefreshRate > 0)
modeDetails += QString::number(verticalRefreshRate, 'g', 3) + QObject::tr(" Hz vertical, ");
if(horizontalRefreshRate > 0)
modeDetails += QString::number(horizontalRefreshRate / 1000, 'g', 3) + QObject::tr(" KHz horizontal, ");
if(modeInfo->dotClock > 0)
modeDetails += QString::number((float) modeInfo->dotClock / 1000000, 'g', 3) + QObject::tr(" MHz dot clock");
}
// Check possible mode flags
if(modeInfo->modeFlags & RR_DoubleScan)
modeDetails += ", Double scan";
if(modeInfo->modeFlags & RR_Interlace)
modeDetails += ", Interlaced";
// Check if this is the default configuration
if(modeIndex == outputInfo->npreferred)
modeDetails += " (preferred)";
addItemToTreeList(modeListItem, modeName, modeDetails);
}
// Create the root QTreeWidgetItem of the property list
QTreeWidgetItem * propertyListItem = new QTreeWidgetItem(QStringList() << QObject::tr("Properties"));
outputItem->addChild(propertyListItem);
// Get this output properties (EDID, audio, scaling mode, etc)
int propertyCount;
Atom * properties = XRRListOutputProperties(display, screenResources->outputs[outputIndex], &propertyCount);
// Cycle through this output's properties
for(int propertyIndex = 0; propertyIndex < propertyCount; propertyIndex++){
// Prepare this property's name and value
char * propertyAtomName = XGetAtomName(display, properties[propertyIndex]);
QString propertyName = QString::fromLocal8Bit(propertyAtomName);
XFree(propertyAtomName);
// Get the property raw value
Atom propertyDataType;
int propertyDataFormat;
unsigned long propertyDataSize, propertyDataBytesAfter;
quint8 * propertyRawData;
XRRGetOutputProperty(display,
screenResources->outputs[outputIndex], // Current output
properties[propertyIndex], // Current property
0, 100, False, False, AnyPropertyType,
&propertyDataType, // Property type will be returned here
&propertyDataFormat,
&propertyDataSize, // Length of the property
&propertyDataBytesAfter,
&propertyRawData); // The raw data content of the property
// Translate the property value to human readable
// http://us.download.nvidia.com/XFree86/Linux-x86-ARM/361.16/README/xrandrextension.html
if(propertyName.compare("EDID") == 0){ // EDID found
qDebug() << " Property is EDID, parsing it";
// See http://en.wikipedia.org/wiki/Extended_display_identification_data#EDID_1.3_data_format
if(propertyDataSize < 128){ // EDID is invalid
qWarning() << "EDID is malformed, skipping";
continue;
}
QString readableEDID;
for(unsigned long i = 0; i < propertyDataSize; i++){ // For each byte in raw data
if(((i % 16) == 0) && (i != 0)) // Every 16 bytes (32 HEX symbols) go on new line
readableEDID += '\n';
// Convert each byte into two readable HEX symbols
readableEDID += QString("%1").arg(propertyRawData[i], 2, 16, QChar('0'));
}
addItemToTreeList(propertyListItem, propertyName, readableEDID);
// Parse the EDID to gather info
const quint8 header[8] = {0x00,0xff,0xff,0xff,0xff,0xff,0xff,0x00}; // Fixed EDID header
if (memcmp(propertyRawData, header, 8) != 0) { // If the header is not valid
qWarning() << screenIndex << '/' << outputInfo->name << ": can't parse EDID, invalid header";
} else { // Valid header
// Add the monitor name to the tree as value of the Output Item
outputItem->setText(1, QObject::tr("Connected with ") + getMonitorName(propertyRawData));
// Get the serial number
// For reference: https://github.com/KDE/libkscreen/blob/master/src/edid.cpp#L288-L295
quint32 serialNumber = propertyRawData[EDID_OFFSET_SERIAL_NUMBER];
serialNumber += propertyRawData[EDID_OFFSET_SERIAL_NUMBER + 1] * 0x100;
serialNumber += propertyRawData[EDID_OFFSET_SERIAL_NUMBER + 2] * 0x10000;
serialNumber += propertyRawData[EDID_OFFSET_SERIAL_NUMBER + 3] * 0x1000000;
QString serial = (serialNumber > 0) ? QString::number(serialNumber) : QObject::tr("Not available");
addItemToTreeList(propertyListItem, QObject::tr("Serial number"), serial);
}
//End of EDID
} else { //Not EDID
// Translate the value ( translateProperty() will handle it)
QString propertyValue;
for(unsigned long i = 0; i < propertyDataSize; i++)
propertyValue += translateProperty(display,
propertyDataFormat,
propertyDataType,
(Atom*)&propertyRawData[i]);
// Get the property informations (allows to get ranges)
XRRPropertyInfo *propertyInfo = XRRQueryOutputProperty(display,
screenResources->outputs[outputIndex],
properties[propertyIndex]);
if(propertyInfo == NULL) {
qDebug() << screenIndex << '/' << outputInfo->name << ": propertyInfo is NULL";
} else if(propertyInfo->num_values > 0) { // A range or a list of alternatives is present
// Proceed to print the range or the list alternatives
propertyValue += (propertyInfo->range) ? " (Range: " : " (Supported: ";
for(int valuesIndex = 0; valuesIndex < propertyInfo->num_values; valuesIndex++){
// Until there is another alternative/range available
if(propertyInfo->range) { // This is a range, print the maximum value
propertyValue += translateProperty(display,
32, // Value data has 32-bit format
propertyDataType,
(Atom*)&propertyInfo->values[valuesIndex]);
propertyValue += " - ";
valuesIndex++;
}
propertyValue += translateProperty(display,
32,
propertyDataType,
(Atom*)&propertyInfo->values[valuesIndex]);
if(valuesIndex < propertyInfo->num_values - 1) // Another range is available
propertyValue.append(", ");
}
propertyValue += ')';
// Property alternatives completed: deallocate propertyInfo
free(propertyInfo);
}
// Print the property
addItemToTreeList(propertyListItem, propertyName, propertyValue);
}
// Property completed: deallocate the property raw data
free(propertyRawData);
}
// Output completed: deallocate properties, configInfo and outputInfo
XFree(properties);
XRRFreeCrtcInfo(configInfo);
XRRFreeOutputInfo(outputInfo);
}
// Screen completed: print output count (connected and active) and deallocate screenResources
outputListItem->setText(1, QObject::tr("%n connected, ", NULL, screenConnectedOutputs) + QObject::tr("%n active", NULL, screenActiveOutputs));
XRRFreeScreenResources(screenResources);
}
return cardConnectorsList;
}