[Test] Added tests for CUtil::SplitParams
[xbmc.git] / xbmc / windowing / X11 / XRandR.cpp
blob021e09cd3b42a0dcee94e88879ec1653f19d7b70
1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "XRandR.h"
11 #include "CompileInfo.h"
12 #include "threads/SystemClock.h"
13 #include "utils/StringUtils.h"
14 #include "utils/XBMCTinyXML2.h"
15 #include "utils/XTimeUtils.h"
16 #include "utils/log.h"
18 #include <string.h>
20 #include <sys/wait.h>
22 #include "PlatformDefs.h"
24 #if defined(TARGET_FREEBSD)
25 #include <sys/types.h>
26 #include <sys/wait.h>
27 #endif
29 using namespace std::chrono_literals;
31 CXRandR::CXRandR(bool query)
33 m_bInit = false;
34 m_numScreens = 1;
35 if (query)
36 Query();
39 bool CXRandR::Query(bool force, bool ignoreoff)
41 if (!force)
42 if (m_bInit)
43 return m_outputs.size() > 0;
45 m_bInit = true;
47 if (getenv("KODI_BIN_HOME") == NULL)
48 return false;
50 m_outputs.clear();
51 // query all screens
52 // we are happy if at least one screen returns results
53 bool success = false;
54 for(unsigned int screennum=0; screennum<m_numScreens; ++screennum)
56 if(Query(force, screennum, ignoreoff))
57 success = true;
59 return success;
62 bool CXRandR::Query(bool force, int screennum, bool ignoreoff)
64 std::string cmd;
65 std::string appname = CCompileInfo::GetAppName();
66 StringUtils::ToLower(appname);
67 if (getenv("KODI_BIN_HOME"))
69 cmd = getenv("KODI_BIN_HOME");
70 cmd += "/" + appname + "-xrandr";
71 cmd = StringUtils::Format("{} -q --screen {}", cmd, screennum);
74 FILE* file = popen(cmd.c_str(),"r");
75 if (!file)
77 CLog::Log(LOGERROR, "CXRandR::Query - unable to execute xrandr tool");
78 return false;
81 CXBMCTinyXML2 xmlDoc;
82 if (!xmlDoc.LoadFile(file))
84 CLog::Log(LOGERROR, "CXRandR::Query - unable to open xrandr xml");
85 pclose(file);
86 return false;
88 pclose(file);
90 auto* rootElement = xmlDoc.RootElement();
91 if (atoi(rootElement->Attribute("id")) != screennum)
93 //! @todo ERROR
94 return false;
97 for (auto* output = rootElement->FirstChildElement("output"); output;
98 output = output->NextSiblingElement("output"))
100 XOutput xoutput;
101 xoutput.name = output->Attribute("name");
102 StringUtils::Trim(xoutput.name);
103 xoutput.isConnected = (StringUtils::CompareNoCase(output->Attribute("connected"), "true") == 0);
104 xoutput.screen = screennum;
105 xoutput.w = (output->Attribute("w") != NULL ? atoi(output->Attribute("w")) : 0);
106 xoutput.h = (output->Attribute("h") != NULL ? atoi(output->Attribute("h")) : 0);
107 xoutput.x = (output->Attribute("x") != NULL ? atoi(output->Attribute("x")) : 0);
108 xoutput.y = (output->Attribute("y") != NULL ? atoi(output->Attribute("y")) : 0);
109 xoutput.crtc = (output->Attribute("crtc") != NULL ? atoi(output->Attribute("crtc")) : 0);
110 xoutput.wmm = (output->Attribute("wmm") != NULL ? atoi(output->Attribute("wmm")) : 0);
111 xoutput.hmm = (output->Attribute("hmm") != NULL ? atoi(output->Attribute("hmm")) : 0);
112 if (output->Attribute("rotation") != NULL &&
113 (StringUtils::CompareNoCase(output->Attribute("rotation"), "left") == 0 ||
114 StringUtils::CompareNoCase(output->Attribute("rotation"), "right") == 0))
116 xoutput.isRotated = true;
118 else
119 xoutput.isRotated = false;
121 if (!xoutput.isConnected)
122 continue;
124 bool hascurrent = false;
125 for (auto* mode = output->FirstChildElement("mode"); mode;
126 mode = mode->NextSiblingElement("mode"))
128 XMode xmode;
129 xmode.id = mode->Attribute("id");
130 xmode.name = mode->Attribute("name");
131 xmode.hz = atof(mode->Attribute("hz"));
132 xmode.w = atoi(mode->Attribute("w"));
133 xmode.h = atoi(mode->Attribute("h"));
134 xmode.isPreferred = (StringUtils::CompareNoCase(mode->Attribute("preferred"), "true") == 0);
135 xmode.isCurrent = (StringUtils::CompareNoCase(mode->Attribute("current"), "true") == 0);
136 xoutput.modes.push_back(xmode);
137 if (xmode.isCurrent)
138 hascurrent = true;
140 if (hascurrent || !ignoreoff)
141 m_outputs.push_back(xoutput);
142 else
143 CLog::Log(LOGWARNING, "CXRandR::Query - output {} has no current mode, assuming disconnected",
144 xoutput.name);
146 return m_outputs.size() > 0;
149 bool CXRandR::TurnOffOutput(const std::string& name)
151 XOutput *output = GetOutput(name);
152 if (!output)
153 return false;
155 std::string cmd;
156 std::string appname = CCompileInfo::GetAppName();
157 StringUtils::ToLower(appname);
159 if (getenv("KODI_BIN_HOME"))
161 cmd = getenv("KODI_BIN_HOME");
162 cmd += "/" + appname + "-xrandr";
163 cmd = StringUtils::Format("{} --screen {} --output {} --off", cmd, output->screen, name);
166 int status = system(cmd.c_str());
167 if (status == -1)
168 return false;
170 if (WEXITSTATUS(status) != 0)
171 return false;
173 return true;
176 bool CXRandR::TurnOnOutput(const std::string& name)
178 XOutput *output = GetOutput(name);
179 if (!output)
180 return false;
182 XMode mode = GetCurrentMode(output->name);
183 if (mode.isCurrent)
184 return true;
186 // get preferred mode
187 for (unsigned int j = 0; j < m_outputs.size(); j++)
189 if (m_outputs[j].name == output->name)
191 for (unsigned int i = 0; i < m_outputs[j].modes.size(); i++)
193 if (m_outputs[j].modes[i].isPreferred)
195 mode = m_outputs[j].modes[i];
196 break;
202 if (!mode.isPreferred)
203 return false;
205 if (!SetMode(*output, mode))
206 return false;
208 XbmcThreads::EndTime<> timeout(5s);
209 while (!timeout.IsTimePast())
211 if (!Query(true))
212 return false;
214 output = GetOutput(name);
215 if (output && output->h > 0)
216 return true;
218 KODI::TIME::Sleep(200ms);
221 return false;
224 std::vector<XOutput> CXRandR::GetModes(void)
226 Query();
227 return m_outputs;
230 void CXRandR::SaveState()
232 Query(true);
235 bool CXRandR::SetMode(const XOutput& output, const XMode& mode)
237 if ((output.name == "" && mode.id == ""))
238 return true;
240 Query();
242 // Make sure the output exists, if not -- complain and exit
243 bool isOutputFound = false;
244 XOutput outputFound;
245 for (size_t i = 0; i < m_outputs.size(); i++)
247 if (m_outputs[i].name == output.name)
249 isOutputFound = true;
250 outputFound = m_outputs[i];
254 if (!isOutputFound)
256 CLog::Log(LOGERROR,
257 "CXRandR::SetMode: asked to change resolution for non existing output: {} mode: {}",
258 output.name, mode.id);
259 return false;
262 // try to find the same exact mode (same id, resolution, hz)
263 bool isModeFound = false;
264 XMode modeFound;
265 for (size_t i = 0; i < outputFound.modes.size(); i++)
267 if (outputFound.modes[i].id == mode.id)
269 if (outputFound.modes[i].w == mode.w &&
270 outputFound.modes[i].h == mode.h &&
271 outputFound.modes[i].hz == mode.hz)
273 isModeFound = true;
274 modeFound = outputFound.modes[i];
276 else
278 CLog::Log(LOGERROR,
279 "CXRandR::SetMode: asked to change resolution for mode that exists but with "
280 "different w/h/hz: {} mode: {}. Searching for similar modes...",
281 output.name, mode.id);
282 break;
287 if (!isModeFound)
289 for (size_t i = 0; i < outputFound.modes.size(); i++)
291 if (outputFound.modes[i].w == mode.w &&
292 outputFound.modes[i].h == mode.h &&
293 outputFound.modes[i].hz == mode.hz)
295 isModeFound = true;
296 modeFound = outputFound.modes[i];
297 CLog::Log(LOGWARNING, "CXRandR::SetMode: found alternative mode (same hz): {} mode: {}.",
298 output.name, outputFound.modes[i].id);
303 if (!isModeFound)
305 for (size_t i = 0; i < outputFound.modes.size(); i++)
307 if (outputFound.modes[i].w == mode.w &&
308 outputFound.modes[i].h == mode.h)
310 isModeFound = true;
311 modeFound = outputFound.modes[i];
312 CLog::Log(LOGWARNING,
313 "CXRandR::SetMode: found alternative mode (different hz): {} mode: {}.",
314 output.name, outputFound.modes[i].id);
319 // Let's try finding a mode that is the same
320 if (!isModeFound)
322 CLog::Log(LOGERROR,
323 "CXRandR::SetMode: asked to change resolution for non existing mode: {} mode: {}",
324 output.name, mode.id);
325 return false;
328 m_currentOutput = outputFound.name;
329 m_currentMode = modeFound.id;
330 std::string appname = CCompileInfo::GetAppName();
331 StringUtils::ToLower(appname);
332 char cmd[255];
334 if (getenv("KODI_BIN_HOME"))
335 snprintf(cmd, sizeof(cmd), "%s/%s-xrandr --screen %d --output %s --mode %s",
336 getenv("KODI_BIN_HOME"),appname.c_str(),
337 outputFound.screen, outputFound.name.c_str(), modeFound.id.c_str());
338 else
339 return false;
340 CLog::Log(LOGINFO, "XRANDR: {}", cmd);
341 int status = system(cmd);
342 if (status == -1)
343 return false;
345 if (WEXITSTATUS(status) != 0)
346 return false;
348 return true;
351 XMode CXRandR::GetCurrentMode(const std::string& outputName)
353 Query();
354 XMode result;
356 for (unsigned int j = 0; j < m_outputs.size(); j++)
358 if (m_outputs[j].name == outputName || outputName == "")
360 for (unsigned int i = 0; i < m_outputs[j].modes.size(); i++)
362 if (m_outputs[j].modes[i].isCurrent)
364 result = m_outputs[j].modes[i];
365 break;
371 return result;
374 XMode CXRandR::GetPreferredMode(const std::string& outputName)
376 Query();
377 XMode result;
379 for (unsigned int j = 0; j < m_outputs.size(); j++)
381 if (m_outputs[j].name == outputName || outputName == "")
383 for (unsigned int i = 0; i < m_outputs[j].modes.size(); i++)
385 if (m_outputs[j].modes[i].isPreferred)
387 result = m_outputs[j].modes[i];
388 break;
394 return result;
397 void CXRandR::LoadCustomModeLinesToAllOutputs(void)
399 Query();
400 CXBMCTinyXML2 xmlDoc;
402 if (!xmlDoc.LoadFile("special://xbmc/userdata/ModeLines.xml"))
404 return;
407 auto* rootElement = xmlDoc.RootElement();
408 if (StringUtils::CompareNoCase(rootElement->Value(), "modelines") != 0)
410 //! @todo ERROR
411 return;
414 char cmd[255];
415 std::string name;
416 std::string strModeLine;
418 for (auto* modeline = rootElement->FirstChildElement("modeline"); modeline;
419 modeline = modeline->NextSiblingElement("modeline"))
421 name = modeline->Attribute("label");
422 StringUtils::Trim(name);
423 strModeLine = modeline->FirstChild()->Value();
424 StringUtils::Trim(strModeLine);
425 std::string appname = CCompileInfo::GetAppName();
426 StringUtils::ToLower(appname);
428 if (getenv("KODI_BIN_HOME"))
430 snprintf(cmd, sizeof(cmd), "%s/%s-xrandr --newmode \"%s\" %s > /dev/null 2>&1", getenv("KODI_BIN_HOME"),
431 appname.c_str(), name.c_str(), strModeLine.c_str());
432 if (system(cmd) != 0)
433 CLog::Log(LOGERROR, "Unable to create modeline \"{}\"", name);
436 for (unsigned int i = 0; i < m_outputs.size(); i++)
438 if (getenv("KODI_BIN_HOME"))
440 snprintf(cmd, sizeof(cmd), "%s/%s-xrandr --addmode %s \"%s\" > /dev/null 2>&1", getenv("KODI_BIN_HOME"),
441 appname.c_str(), m_outputs[i].name.c_str(), name.c_str());
442 if (system(cmd) != 0)
443 CLog::Log(LOGERROR, "Unable to add modeline \"{}\"", name);
449 void CXRandR::SetNumScreens(unsigned int num)
451 m_numScreens = num;
452 m_bInit = false;
455 bool CXRandR::IsOutputConnected(const std::string& name)
457 bool result = false;
458 Query();
460 for (unsigned int i = 0; i < m_outputs.size(); ++i)
462 if (m_outputs[i].name == name)
464 result = true;
465 break;
468 return result;
471 XOutput* CXRandR::GetOutput(const std::string& outputName)
473 XOutput *result = 0;
474 Query();
475 for (unsigned int i = 0; i < m_outputs.size(); ++i)
477 if (m_outputs[i].name == outputName)
479 result = &m_outputs[i];
480 break;
483 return result;
486 int CXRandR::GetCrtc(int x, int y, float &hz)
488 int crtc = 0;
489 for (unsigned int i = 0; i < m_outputs.size(); ++i)
491 if (!m_outputs[i].isConnected)
492 continue;
494 if ((m_outputs[i].x <= x && (m_outputs[i].x+m_outputs[i].w) > x) &&
495 (m_outputs[i].y <= y && (m_outputs[i].y+m_outputs[i].h) > y))
497 crtc = m_outputs[i].crtc;
498 for (const auto& mode : m_outputs[i].modes)
500 if (mode.isCurrent)
502 hz = mode.hz;
503 break;
506 break;
509 return crtc;
512 CXRandR g_xrandr;
515 int main()
517 CXRandR r;
518 r.LoadCustomModeLinesToAllOutputs();