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.
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"
22 #include "PlatformDefs.h"
24 #if defined(TARGET_FREEBSD)
25 #include <sys/types.h>
29 using namespace std::chrono_literals
;
31 CXRandR::CXRandR(bool query
)
39 bool CXRandR::Query(bool force
, bool ignoreoff
)
43 return m_outputs
.size() > 0;
47 if (getenv("KODI_BIN_HOME") == NULL
)
52 // we are happy if at least one screen returns results
54 for(unsigned int screennum
=0; screennum
<m_numScreens
; ++screennum
)
56 if(Query(force
, screennum
, ignoreoff
))
62 bool CXRandR::Query(bool force
, int screennum
, bool ignoreoff
)
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");
77 CLog::Log(LOGERROR
, "CXRandR::Query - unable to execute xrandr tool");
82 if (!xmlDoc
.LoadFile(file
))
84 CLog::Log(LOGERROR
, "CXRandR::Query - unable to open xrandr xml");
90 auto* rootElement
= xmlDoc
.RootElement();
91 if (atoi(rootElement
->Attribute("id")) != screennum
)
97 for (auto* output
= rootElement
->FirstChildElement("output"); output
;
98 output
= output
->NextSiblingElement("output"))
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;
119 xoutput
.isRotated
= false;
121 if (!xoutput
.isConnected
)
124 bool hascurrent
= false;
125 for (auto* mode
= output
->FirstChildElement("mode"); mode
;
126 mode
= mode
->NextSiblingElement("mode"))
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
);
140 if (hascurrent
|| !ignoreoff
)
141 m_outputs
.push_back(xoutput
);
143 CLog::Log(LOGWARNING
, "CXRandR::Query - output {} has no current mode, assuming disconnected",
146 return m_outputs
.size() > 0;
149 bool CXRandR::TurnOffOutput(const std::string
& name
)
151 XOutput
*output
= GetOutput(name
);
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());
170 if (WEXITSTATUS(status
) != 0)
176 bool CXRandR::TurnOnOutput(const std::string
& name
)
178 XOutput
*output
= GetOutput(name
);
182 XMode mode
= GetCurrentMode(output
->name
);
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
];
202 if (!mode
.isPreferred
)
205 if (!SetMode(*output
, mode
))
208 XbmcThreads::EndTime
<> timeout(5s
);
209 while (!timeout
.IsTimePast())
214 output
= GetOutput(name
);
215 if (output
&& output
->h
> 0)
218 KODI::TIME::Sleep(200ms
);
224 std::vector
<XOutput
> CXRandR::GetModes(void)
230 void CXRandR::SaveState()
235 bool CXRandR::SetMode(const XOutput
& output
, const XMode
& mode
)
237 if ((output
.name
== "" && mode
.id
== ""))
242 // Make sure the output exists, if not -- complain and exit
243 bool isOutputFound
= false;
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
];
257 "CXRandR::SetMode: asked to change resolution for non existing output: {} mode: {}",
258 output
.name
, mode
.id
);
262 // try to find the same exact mode (same id, resolution, hz)
263 bool isModeFound
= false;
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
)
274 modeFound
= outputFound
.modes
[i
];
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
);
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
)
296 modeFound
= outputFound
.modes
[i
];
297 CLog::Log(LOGWARNING
, "CXRandR::SetMode: found alternative mode (same hz): {} mode: {}.",
298 output
.name
, outputFound
.modes
[i
].id
);
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
)
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
323 "CXRandR::SetMode: asked to change resolution for non existing mode: {} mode: {}",
324 output
.name
, mode
.id
);
328 m_currentOutput
= outputFound
.name
;
329 m_currentMode
= modeFound
.id
;
330 std::string appname
= CCompileInfo::GetAppName();
331 StringUtils::ToLower(appname
);
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());
340 CLog::Log(LOGINFO
, "XRANDR: {}", cmd
);
341 int status
= system(cmd
);
345 if (WEXITSTATUS(status
) != 0)
351 XMode
CXRandR::GetCurrentMode(const std::string
& outputName
)
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
];
374 XMode
CXRandR::GetPreferredMode(const std::string
& outputName
)
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
];
397 void CXRandR::LoadCustomModeLinesToAllOutputs(void)
400 CXBMCTinyXML2 xmlDoc
;
402 if (!xmlDoc
.LoadFile("special://xbmc/userdata/ModeLines.xml"))
407 auto* rootElement
= xmlDoc
.RootElement();
408 if (StringUtils::CompareNoCase(rootElement
->Value(), "modelines") != 0)
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
)
455 bool CXRandR::IsOutputConnected(const std::string
& name
)
460 for (unsigned int i
= 0; i
< m_outputs
.size(); ++i
)
462 if (m_outputs
[i
].name
== name
)
471 XOutput
* CXRandR::GetOutput(const std::string
& outputName
)
475 for (unsigned int i
= 0; i
< m_outputs
.size(); ++i
)
477 if (m_outputs
[i
].name
== outputName
)
479 result
= &m_outputs
[i
];
486 int CXRandR::GetCrtc(int x
, int y
, float &hz
)
489 for (unsigned int i
= 0; i
< m_outputs
.size(); ++i
)
491 if (!m_outputs
[i
].isConnected
)
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
)
518 r.LoadCustomModeLinesToAllOutputs();