summaryrefslogtreecommitdiffstats
path: root/xbmc/windowing/X11/XRandR.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
commitc04dcc2e7d834218ef2d4194331e383402495ae1 (patch)
tree7333e38d10d75386e60f336b80c2443c1166031d /xbmc/windowing/X11/XRandR.cpp
parentInitial commit. (diff)
downloadkodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz
kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xbmc/windowing/X11/XRandR.cpp')
-rw-r--r--xbmc/windowing/X11/XRandR.cpp518
1 files changed, 518 insertions, 0 deletions
diff --git a/xbmc/windowing/X11/XRandR.cpp b/xbmc/windowing/X11/XRandR.cpp
new file mode 100644
index 0000000..ab20da3
--- /dev/null
+++ b/xbmc/windowing/X11/XRandR.cpp
@@ -0,0 +1,518 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "XRandR.h"
+
+#include "CompileInfo.h"
+#include "threads/SystemClock.h"
+#include "utils/StringUtils.h"
+#include "utils/XBMCTinyXML.h"
+#include "utils/XTimeUtils.h"
+#include "utils/log.h"
+
+#include <string.h>
+
+#include <sys/wait.h>
+
+#include "PlatformDefs.h"
+
+#if defined(TARGET_FREEBSD)
+#include <sys/types.h>
+#include <sys/wait.h>
+#endif
+
+using namespace std::chrono_literals;
+
+CXRandR::CXRandR(bool query)
+{
+ m_bInit = false;
+ m_numScreens = 1;
+ if (query)
+ Query();
+}
+
+bool CXRandR::Query(bool force, bool ignoreoff)
+{
+ if (!force)
+ if (m_bInit)
+ return m_outputs.size() > 0;
+
+ m_bInit = true;
+
+ if (getenv("KODI_BIN_HOME") == NULL)
+ return false;
+
+ m_outputs.clear();
+ // query all screens
+ // we are happy if at least one screen returns results
+ bool success = false;
+ for(unsigned int screennum=0; screennum<m_numScreens; ++screennum)
+ {
+ if(Query(force, screennum, ignoreoff))
+ success = true;
+ }
+ return success;
+}
+
+bool CXRandR::Query(bool force, int screennum, bool ignoreoff)
+{
+ std::string cmd;
+ std::string appname = CCompileInfo::GetAppName();
+ StringUtils::ToLower(appname);
+ if (getenv("KODI_BIN_HOME"))
+ {
+ cmd = getenv("KODI_BIN_HOME");
+ cmd += "/" + appname + "-xrandr";
+ cmd = StringUtils::Format("{} -q --screen {}", cmd, screennum);
+ }
+
+ FILE* file = popen(cmd.c_str(),"r");
+ if (!file)
+ {
+ CLog::Log(LOGERROR, "CXRandR::Query - unable to execute xrandr tool");
+ return false;
+ }
+
+
+ CXBMCTinyXML xmlDoc;
+ if (!xmlDoc.LoadFile(file, TIXML_DEFAULT_ENCODING))
+ {
+ CLog::Log(LOGERROR, "CXRandR::Query - unable to open xrandr xml");
+ pclose(file);
+ return false;
+ }
+ pclose(file);
+
+ TiXmlElement *pRootElement = xmlDoc.RootElement();
+ if (atoi(pRootElement->Attribute("id")) != screennum)
+ {
+ //! @todo ERROR
+ return false;
+ }
+
+ for (TiXmlElement* output = pRootElement->FirstChildElement("output"); output; output = output->NextSiblingElement("output"))
+ {
+ XOutput xoutput;
+ xoutput.name = output->Attribute("name");
+ StringUtils::Trim(xoutput.name);
+ xoutput.isConnected = (StringUtils::CompareNoCase(output->Attribute("connected"), "true") == 0);
+ xoutput.screen = screennum;
+ xoutput.w = (output->Attribute("w") != NULL ? atoi(output->Attribute("w")) : 0);
+ xoutput.h = (output->Attribute("h") != NULL ? atoi(output->Attribute("h")) : 0);
+ xoutput.x = (output->Attribute("x") != NULL ? atoi(output->Attribute("x")) : 0);
+ xoutput.y = (output->Attribute("y") != NULL ? atoi(output->Attribute("y")) : 0);
+ xoutput.crtc = (output->Attribute("crtc") != NULL ? atoi(output->Attribute("crtc")) : 0);
+ xoutput.wmm = (output->Attribute("wmm") != NULL ? atoi(output->Attribute("wmm")) : 0);
+ xoutput.hmm = (output->Attribute("hmm") != NULL ? atoi(output->Attribute("hmm")) : 0);
+ if (output->Attribute("rotation") != NULL &&
+ (StringUtils::CompareNoCase(output->Attribute("rotation"), "left") == 0 ||
+ StringUtils::CompareNoCase(output->Attribute("rotation"), "right") == 0))
+ {
+ xoutput.isRotated = true;
+ }
+ else
+ xoutput.isRotated = false;
+
+ if (!xoutput.isConnected)
+ continue;
+
+ bool hascurrent = false;
+ for (TiXmlElement* mode = output->FirstChildElement("mode"); mode; mode = mode->NextSiblingElement("mode"))
+ {
+ XMode xmode;
+ xmode.id = mode->Attribute("id");
+ xmode.name = mode->Attribute("name");
+ xmode.hz = atof(mode->Attribute("hz"));
+ xmode.w = atoi(mode->Attribute("w"));
+ xmode.h = atoi(mode->Attribute("h"));
+ xmode.isPreferred = (StringUtils::CompareNoCase(mode->Attribute("preferred"), "true") == 0);
+ xmode.isCurrent = (StringUtils::CompareNoCase(mode->Attribute("current"), "true") == 0);
+ xoutput.modes.push_back(xmode);
+ if (xmode.isCurrent)
+ hascurrent = true;
+ }
+ if (hascurrent || !ignoreoff)
+ m_outputs.push_back(xoutput);
+ else
+ CLog::Log(LOGWARNING, "CXRandR::Query - output {} has no current mode, assuming disconnected",
+ xoutput.name);
+ }
+ return m_outputs.size() > 0;
+}
+
+bool CXRandR::TurnOffOutput(const std::string& name)
+{
+ XOutput *output = GetOutput(name);
+ if (!output)
+ return false;
+
+ std::string cmd;
+ std::string appname = CCompileInfo::GetAppName();
+ StringUtils::ToLower(appname);
+
+ if (getenv("KODI_BIN_HOME"))
+ {
+ cmd = getenv("KODI_BIN_HOME");
+ cmd += "/" + appname + "-xrandr";
+ cmd = StringUtils::Format("{} --screen {} --output {} --off", cmd, output->screen, name);
+ }
+
+ int status = system(cmd.c_str());
+ if (status == -1)
+ return false;
+
+ if (WEXITSTATUS(status) != 0)
+ return false;
+
+ return true;
+}
+
+bool CXRandR::TurnOnOutput(const std::string& name)
+{
+ XOutput *output = GetOutput(name);
+ if (!output)
+ return false;
+
+ XMode mode = GetCurrentMode(output->name);
+ if (mode.isCurrent)
+ return true;
+
+ // get preferred mode
+ for (unsigned int j = 0; j < m_outputs.size(); j++)
+ {
+ if (m_outputs[j].name == output->name)
+ {
+ for (unsigned int i = 0; i < m_outputs[j].modes.size(); i++)
+ {
+ if (m_outputs[j].modes[i].isPreferred)
+ {
+ mode = m_outputs[j].modes[i];
+ break;
+ }
+ }
+ }
+ }
+
+ if (!mode.isPreferred)
+ return false;
+
+ if (!SetMode(*output, mode))
+ return false;
+
+ XbmcThreads::EndTime<> timeout(5s);
+ while (!timeout.IsTimePast())
+ {
+ if (!Query(true))
+ return false;
+
+ output = GetOutput(name);
+ if (output && output->h > 0)
+ return true;
+
+ KODI::TIME::Sleep(200ms);
+ }
+
+ return false;
+}
+
+std::vector<XOutput> CXRandR::GetModes(void)
+{
+ Query();
+ return m_outputs;
+}
+
+void CXRandR::SaveState()
+{
+ Query(true);
+}
+
+bool CXRandR::SetMode(const XOutput& output, const XMode& mode)
+{
+ if ((output.name == "" && mode.id == ""))
+ return true;
+
+ Query();
+
+ // Make sure the output exists, if not -- complain and exit
+ bool isOutputFound = false;
+ XOutput outputFound;
+ for (size_t i = 0; i < m_outputs.size(); i++)
+ {
+ if (m_outputs[i].name == output.name)
+ {
+ isOutputFound = true;
+ outputFound = m_outputs[i];
+ }
+ }
+
+ if (!isOutputFound)
+ {
+ CLog::Log(LOGERROR,
+ "CXRandR::SetMode: asked to change resolution for non existing output: {} mode: {}",
+ output.name, mode.id);
+ return false;
+ }
+
+ // try to find the same exact mode (same id, resolution, hz)
+ bool isModeFound = false;
+ XMode modeFound;
+ for (size_t i = 0; i < outputFound.modes.size(); i++)
+ {
+ if (outputFound.modes[i].id == mode.id)
+ {
+ if (outputFound.modes[i].w == mode.w &&
+ outputFound.modes[i].h == mode.h &&
+ outputFound.modes[i].hz == mode.hz)
+ {
+ isModeFound = true;
+ modeFound = outputFound.modes[i];
+ }
+ else
+ {
+ CLog::Log(LOGERROR,
+ "CXRandR::SetMode: asked to change resolution for mode that exists but with "
+ "different w/h/hz: {} mode: {}. Searching for similar modes...",
+ output.name, mode.id);
+ break;
+ }
+ }
+ }
+
+ if (!isModeFound)
+ {
+ for (size_t i = 0; i < outputFound.modes.size(); i++)
+ {
+ if (outputFound.modes[i].w == mode.w &&
+ outputFound.modes[i].h == mode.h &&
+ outputFound.modes[i].hz == mode.hz)
+ {
+ isModeFound = true;
+ modeFound = outputFound.modes[i];
+ CLog::Log(LOGWARNING, "CXRandR::SetMode: found alternative mode (same hz): {} mode: {}.",
+ output.name, outputFound.modes[i].id);
+ }
+ }
+ }
+
+ if (!isModeFound)
+ {
+ for (size_t i = 0; i < outputFound.modes.size(); i++)
+ {
+ if (outputFound.modes[i].w == mode.w &&
+ outputFound.modes[i].h == mode.h)
+ {
+ isModeFound = true;
+ modeFound = outputFound.modes[i];
+ CLog::Log(LOGWARNING,
+ "CXRandR::SetMode: found alternative mode (different hz): {} mode: {}.",
+ output.name, outputFound.modes[i].id);
+ }
+ }
+ }
+
+ // Let's try finding a mode that is the same
+ if (!isModeFound)
+ {
+ CLog::Log(LOGERROR,
+ "CXRandR::SetMode: asked to change resolution for non existing mode: {} mode: {}",
+ output.name, mode.id);
+ return false;
+ }
+
+ m_currentOutput = outputFound.name;
+ m_currentMode = modeFound.id;
+ std::string appname = CCompileInfo::GetAppName();
+ StringUtils::ToLower(appname);
+ char cmd[255];
+
+ if (getenv("KODI_BIN_HOME"))
+ snprintf(cmd, sizeof(cmd), "%s/%s-xrandr --screen %d --output %s --mode %s",
+ getenv("KODI_BIN_HOME"),appname.c_str(),
+ outputFound.screen, outputFound.name.c_str(), modeFound.id.c_str());
+ else
+ return false;
+ CLog::Log(LOGINFO, "XRANDR: {}", cmd);
+ int status = system(cmd);
+ if (status == -1)
+ return false;
+
+ if (WEXITSTATUS(status) != 0)
+ return false;
+
+ return true;
+}
+
+XMode CXRandR::GetCurrentMode(const std::string& outputName)
+{
+ Query();
+ XMode result;
+
+ for (unsigned int j = 0; j < m_outputs.size(); j++)
+ {
+ if (m_outputs[j].name == outputName || outputName == "")
+ {
+ for (unsigned int i = 0; i < m_outputs[j].modes.size(); i++)
+ {
+ if (m_outputs[j].modes[i].isCurrent)
+ {
+ result = m_outputs[j].modes[i];
+ break;
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+XMode CXRandR::GetPreferredMode(const std::string& outputName)
+{
+ Query();
+ XMode result;
+
+ for (unsigned int j = 0; j < m_outputs.size(); j++)
+ {
+ if (m_outputs[j].name == outputName || outputName == "")
+ {
+ for (unsigned int i = 0; i < m_outputs[j].modes.size(); i++)
+ {
+ if (m_outputs[j].modes[i].isPreferred)
+ {
+ result = m_outputs[j].modes[i];
+ break;
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+void CXRandR::LoadCustomModeLinesToAllOutputs(void)
+{
+ Query();
+ CXBMCTinyXML xmlDoc;
+
+ if (!xmlDoc.LoadFile("special://xbmc/userdata/ModeLines.xml"))
+ {
+ return;
+ }
+
+ TiXmlElement *pRootElement = xmlDoc.RootElement();
+ if (StringUtils::CompareNoCase(pRootElement->Value(), "modelines") != 0)
+ {
+ //! @todo ERROR
+ return;
+ }
+
+ char cmd[255];
+ std::string name;
+ std::string strModeLine;
+
+ for (TiXmlElement* modeline = pRootElement->FirstChildElement("modeline"); modeline; modeline = modeline->NextSiblingElement("modeline"))
+ {
+ name = modeline->Attribute("label");
+ StringUtils::Trim(name);
+ strModeLine = modeline->FirstChild()->Value();
+ StringUtils::Trim(strModeLine);
+ std::string appname = CCompileInfo::GetAppName();
+ StringUtils::ToLower(appname);
+
+ if (getenv("KODI_BIN_HOME"))
+ {
+ snprintf(cmd, sizeof(cmd), "%s/%s-xrandr --newmode \"%s\" %s > /dev/null 2>&1", getenv("KODI_BIN_HOME"),
+ appname.c_str(), name.c_str(), strModeLine.c_str());
+ if (system(cmd) != 0)
+ CLog::Log(LOGERROR, "Unable to create modeline \"{}\"", name);
+ }
+
+ for (unsigned int i = 0; i < m_outputs.size(); i++)
+ {
+ if (getenv("KODI_BIN_HOME"))
+ {
+ snprintf(cmd, sizeof(cmd), "%s/%s-xrandr --addmode %s \"%s\" > /dev/null 2>&1", getenv("KODI_BIN_HOME"),
+ appname.c_str(), m_outputs[i].name.c_str(), name.c_str());
+ if (system(cmd) != 0)
+ CLog::Log(LOGERROR, "Unable to add modeline \"{}\"", name);
+ }
+ }
+ }
+}
+
+void CXRandR::SetNumScreens(unsigned int num)
+{
+ m_numScreens = num;
+ m_bInit = false;
+}
+
+bool CXRandR::IsOutputConnected(const std::string& name)
+{
+ bool result = false;
+ Query();
+
+ for (unsigned int i = 0; i < m_outputs.size(); ++i)
+ {
+ if (m_outputs[i].name == name)
+ {
+ result = true;
+ break;
+ }
+ }
+ return result;
+}
+
+XOutput* CXRandR::GetOutput(const std::string& outputName)
+{
+ XOutput *result = 0;
+ Query();
+ for (unsigned int i = 0; i < m_outputs.size(); ++i)
+ {
+ if (m_outputs[i].name == outputName)
+ {
+ result = &m_outputs[i];
+ break;
+ }
+ }
+ return result;
+}
+
+int CXRandR::GetCrtc(int x, int y, float &hz)
+{
+ int crtc = 0;
+ for (unsigned int i = 0; i < m_outputs.size(); ++i)
+ {
+ if (!m_outputs[i].isConnected)
+ continue;
+
+ if ((m_outputs[i].x <= x && (m_outputs[i].x+m_outputs[i].w) > x) &&
+ (m_outputs[i].y <= y && (m_outputs[i].y+m_outputs[i].h) > y))
+ {
+ crtc = m_outputs[i].crtc;
+ for (const auto& mode : m_outputs[i].modes)
+ {
+ if (mode.isCurrent)
+ {
+ hz = mode.hz;
+ break;
+ }
+ }
+ break;
+ }
+ }
+ return crtc;
+}
+
+CXRandR g_xrandr;
+
+/*
+ int main()
+ {
+ CXRandR r;
+ r.LoadCustomModeLinesToAllOutputs();
+ }
+*/