From c04dcc2e7d834218ef2d4194331e383402495ae1 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 10 Apr 2024 20:07:22 +0200 Subject: Adding upstream version 2:20.4+dfsg. Signed-off-by: Daniel Baumann --- xbmc/windowing/X11/CMakeLists.txt | 37 + xbmc/windowing/X11/GLContext.cpp | 20 + xbmc/windowing/X11/GLContext.h | 42 + xbmc/windowing/X11/GLContextEGL.cpp | 542 ++++++++++++ xbmc/windowing/X11/GLContextEGL.h | 63 ++ xbmc/windowing/X11/GLContextGLX.cpp | 293 +++++++ xbmc/windowing/X11/GLContextGLX.h | 49 ++ xbmc/windowing/X11/OSScreenSaverX11.cpp | 36 + xbmc/windowing/X11/OSScreenSaverX11.h | 28 + xbmc/windowing/X11/OptionalsReg.cpp | 256 ++++++ xbmc/windowing/X11/OptionalsReg.h | 78 ++ xbmc/windowing/X11/VideoSyncGLX.cpp | 277 ++++++ xbmc/windowing/X11/VideoSyncGLX.h | 62 ++ xbmc/windowing/X11/VideoSyncOML.cpp | 83 ++ xbmc/windowing/X11/VideoSyncOML.h | 46 + xbmc/windowing/X11/WinEventsX11.cpp | 673 +++++++++++++++ xbmc/windowing/X11/WinEventsX11.h | 60 ++ xbmc/windowing/X11/WinSystemX11.cpp | 1081 ++++++++++++++++++++++++ xbmc/windowing/X11/WinSystemX11.h | 117 +++ xbmc/windowing/X11/WinSystemX11GLContext.cpp | 358 ++++++++ xbmc/windowing/X11/WinSystemX11GLContext.h | 79 ++ xbmc/windowing/X11/WinSystemX11GLESContext.cpp | 293 +++++++ xbmc/windowing/X11/WinSystemX11GLESContext.h | 62 ++ xbmc/windowing/X11/X11DPMSSupport.cpp | 96 +++ xbmc/windowing/X11/X11DPMSSupport.h | 20 + xbmc/windowing/X11/XRandR.cpp | 518 ++++++++++++ xbmc/windowing/X11/XRandR.h | 109 +++ 27 files changed, 5378 insertions(+) create mode 100644 xbmc/windowing/X11/CMakeLists.txt create mode 100644 xbmc/windowing/X11/GLContext.cpp create mode 100644 xbmc/windowing/X11/GLContext.h create mode 100644 xbmc/windowing/X11/GLContextEGL.cpp create mode 100644 xbmc/windowing/X11/GLContextEGL.h create mode 100644 xbmc/windowing/X11/GLContextGLX.cpp create mode 100644 xbmc/windowing/X11/GLContextGLX.h create mode 100644 xbmc/windowing/X11/OSScreenSaverX11.cpp create mode 100644 xbmc/windowing/X11/OSScreenSaverX11.h create mode 100644 xbmc/windowing/X11/OptionalsReg.cpp create mode 100644 xbmc/windowing/X11/OptionalsReg.h create mode 100644 xbmc/windowing/X11/VideoSyncGLX.cpp create mode 100644 xbmc/windowing/X11/VideoSyncGLX.h create mode 100644 xbmc/windowing/X11/VideoSyncOML.cpp create mode 100644 xbmc/windowing/X11/VideoSyncOML.h create mode 100644 xbmc/windowing/X11/WinEventsX11.cpp create mode 100644 xbmc/windowing/X11/WinEventsX11.h create mode 100644 xbmc/windowing/X11/WinSystemX11.cpp create mode 100644 xbmc/windowing/X11/WinSystemX11.h create mode 100644 xbmc/windowing/X11/WinSystemX11GLContext.cpp create mode 100644 xbmc/windowing/X11/WinSystemX11GLContext.h create mode 100644 xbmc/windowing/X11/WinSystemX11GLESContext.cpp create mode 100644 xbmc/windowing/X11/WinSystemX11GLESContext.h create mode 100644 xbmc/windowing/X11/X11DPMSSupport.cpp create mode 100644 xbmc/windowing/X11/X11DPMSSupport.h create mode 100644 xbmc/windowing/X11/XRandR.cpp create mode 100644 xbmc/windowing/X11/XRandR.h (limited to 'xbmc/windowing/X11') diff --git a/xbmc/windowing/X11/CMakeLists.txt b/xbmc/windowing/X11/CMakeLists.txt new file mode 100644 index 0000000..91e13d6 --- /dev/null +++ b/xbmc/windowing/X11/CMakeLists.txt @@ -0,0 +1,37 @@ +set(SOURCES GLContextEGL.cpp + GLContext.cpp + OptionalsReg.cpp + OSScreenSaverX11.cpp + WinEventsX11.cpp + WinSystemX11.cpp + XRandR.cpp + X11DPMSSupport.cpp) + +set(HEADERS GLContext.h + GLContextEGL.h + OptionalsReg.h + OSScreenSaverX11.h + WinEventsX11.h + WinSystemX11.h + XRandR.h + X11DPMSSupport.h) + +if(GLX_FOUND) + list(APPEND SOURCES GLContextGLX.cpp + VideoSyncGLX.cpp) + list(APPEND HEADERS GLContextGLX.h + VideoSyncGLX.h) +endif() + +if(OPENGL_FOUND) + list(APPEND SOURCES WinSystemX11GLContext.cpp) + list(APPEND HEADERS WinSystemX11GLContext.h) + list(APPEND SOURCES VideoSyncOML.cpp) + list(APPEND HEADERS VideoSyncOML.h) +endif() +if(OPENGLES_FOUND) + list(APPEND SOURCES WinSystemX11GLESContext.cpp) + list(APPEND HEADERS WinSystemX11GLESContext.h) +endif() + +core_add_library(windowing_X11) diff --git a/xbmc/windowing/X11/GLContext.cpp b/xbmc/windowing/X11/GLContext.cpp new file mode 100644 index 0000000..3dac508 --- /dev/null +++ b/xbmc/windowing/X11/GLContext.cpp @@ -0,0 +1,20 @@ +/* + * 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 "GLContext.h" + +bool CGLContext::IsExtSupported(const char* extension) const +{ + std::string name; + + name = " "; + name += extension; + name += " "; + + return m_extensions.find(name) != std::string::npos; +} diff --git a/xbmc/windowing/X11/GLContext.h b/xbmc/windowing/X11/GLContext.h new file mode 100644 index 0000000..95e22ec --- /dev/null +++ b/xbmc/windowing/X11/GLContext.h @@ -0,0 +1,42 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +#include + +class CGLContext +{ +public: + explicit CGLContext(Display *dpy) + { + m_dpy = dpy; + } + virtual ~CGLContext() = default; + virtual bool Refresh(bool force, int screen, Window glWindow, bool &newContext) = 0; + virtual bool CreatePB() { return false; } + virtual void Destroy() = 0; + virtual void Detach() = 0; + virtual void SetVSync(bool enable) = 0; + virtual void SwapBuffers() = 0; + virtual void QueryExtensions() = 0; + virtual uint64_t GetVblankTiming(uint64_t& msc, uint64_t& interval) { return 0; } + bool IsExtSupported(const char* extension) const; + + std::string ExtPrefix() { return m_extPrefix; } + std::string m_extPrefix; + std::string m_extensions; + + Display *m_dpy; + +protected: + bool m_omlSync = true; +}; diff --git a/xbmc/windowing/X11/GLContextEGL.cpp b/xbmc/windowing/X11/GLContextEGL.cpp new file mode 100644 index 0000000..66de374 --- /dev/null +++ b/xbmc/windowing/X11/GLContextEGL.cpp @@ -0,0 +1,542 @@ +/* + * 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. + */ + +// always define GL_GLEXT_PROTOTYPES before include gl headers +#if !defined(GL_GLEXT_PROTOTYPES) + #define GL_GLEXT_PROTOTYPES +#endif + +#include "GLContextEGL.h" + +#include "ServiceBroker.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" +#include "utils/log.h" + +#include +#include + +#include +#include + +#include "PlatformDefs.h" +#include "system_gl.h" + +#define EGL_NO_CONFIG (EGLConfig)0 + +CGLContextEGL::CGLContextEGL(Display* dpy, EGLint renderingApi) + : CGLContext(dpy), m_renderingApi(renderingApi) +{ + m_extPrefix = "EGL_"; + m_eglConfig = EGL_NO_CONFIG; + + m_eglGetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT"); + + const auto settings = CServiceBroker::GetSettingsComponent(); + if (settings) + { + m_omlSync = settings->GetAdvancedSettings()->m_omlSync; + } +} + +CGLContextEGL::~CGLContextEGL() +{ + Destroy(); +} + +bool CGLContextEGL::Refresh(bool force, int screen, Window glWindow, bool &newContext) +{ + m_sync.cont = 0; + + // refresh context + if (m_eglContext && !force) + { + if (m_eglSurface == EGL_NO_SURFACE) + { + m_eglSurface = eglCreateWindowSurface(m_eglDisplay, m_eglConfig, glWindow, NULL); + if (m_eglSurface == EGL_NO_SURFACE) + { + CLog::Log(LOGERROR, "failed to create EGL window surface {}", eglGetError()); + return false; + } + } + + CLog::Log(LOGDEBUG, "CWinSystemX11::RefreshEGLContext: refreshing context"); + eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglMakeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext); + return true; + } + + Destroy(); + newContext = true; + + if (m_eglGetPlatformDisplayEXT) + { + EGLint attribs[] = + { + EGL_PLATFORM_X11_SCREEN_EXT, screen, + EGL_NONE + }; + m_eglDisplay = m_eglGetPlatformDisplayEXT(EGL_PLATFORM_X11_EXT,(EGLNativeDisplayType)m_dpy, + attribs); + } + else + m_eglDisplay = eglGetDisplay((EGLNativeDisplayType)m_dpy); + + if (m_eglDisplay == EGL_NO_DISPLAY) + { + CLog::Log(LOGERROR, "failed to get egl display"); + return false; + } + if (!eglInitialize(m_eglDisplay, NULL, NULL)) + { + CLog::Log(LOGERROR, "failed to initialize egl"); + Destroy(); + return false; + } + if (!eglBindAPI(m_renderingApi)) + { + CLog::Log(LOGERROR, "failed to bind rendering API"); + Destroy(); + return false; + } + + // create context + + XVisualInfo vMask; + XVisualInfo *vInfo = nullptr; + int availableVisuals = 0; + vMask.screen = screen; + XWindowAttributes winAttr; + + if (!XGetWindowAttributes(m_dpy, glWindow, &winAttr)) + { + CLog::Log(LOGWARNING, "Failed to get window attributes"); + Destroy(); + return false; + } + + vMask.visualid = XVisualIDFromVisual(winAttr.visual); + vInfo = XGetVisualInfo(m_dpy, VisualScreenMask | VisualIDMask, &vMask, &availableVisuals); + if (!vInfo) + { + CLog::Log(LOGERROR, "Failed to get VisualInfo of visual 0x{:x}", (unsigned)vMask.visualid); + Destroy(); + return false; + } + + unsigned int visualid = static_cast(vInfo->visualid); + m_eglConfig = GetEGLConfig(m_eglDisplay, vInfo); + XFree(vInfo); + + if (m_eglConfig == EGL_NO_CONFIG) + { + CLog::Log(LOGERROR, "failed to get suitable eglconfig for visual 0x{:x}", visualid); + Destroy(); + return false; + } + + CLog::Log(LOGINFO, "Using visual 0x{:x}", visualid); + + m_eglSurface = eglCreateWindowSurface(m_eglDisplay, m_eglConfig, glWindow, NULL); + if (m_eglSurface == EGL_NO_SURFACE) + { + CLog::Log(LOGERROR, "failed to create EGL window surface {}", eglGetError()); + Destroy(); + return false; + } + + EGLint contextAttributes[] = + { + EGL_CONTEXT_MAJOR_VERSION_KHR, 3, + EGL_CONTEXT_MINOR_VERSION_KHR, 2, + EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR, + EGL_NONE + }; + m_eglContext = eglCreateContext(m_eglDisplay, m_eglConfig, EGL_NO_CONTEXT, contextAttributes); + if (m_eglContext == EGL_NO_CONTEXT) + { + EGLint contextAttributes[] = + { + EGL_CONTEXT_MAJOR_VERSION_KHR, 2, + EGL_NONE + }; + m_eglContext = eglCreateContext(m_eglDisplay, m_eglConfig, EGL_NO_CONTEXT, contextAttributes); + + if (m_eglContext == EGL_NO_CONTEXT) + { + CLog::Log(LOGERROR, "failed to create EGL context"); + Destroy(); + return false; + } + + CLog::Log(LOGWARNING, "Failed to get an OpenGL context supporting core profile 3.2, " + "using legacy mode with reduced feature set"); + } + + if (!eglMakeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext)) + { + CLog::Log(LOGERROR, "Failed to make context current {} {} {}", fmt::ptr(m_eglDisplay), + fmt::ptr(m_eglSurface), fmt::ptr(m_eglContext)); + Destroy(); + return false; + } + + m_eglGetSyncValuesCHROMIUM = (PFNEGLGETSYNCVALUESCHROMIUMPROC)eglGetProcAddress("eglGetSyncValuesCHROMIUM"); + + m_usePB = false; + return true; +} + +bool CGLContextEGL::CreatePB() +{ + const EGLint configAttribs[] = + { + EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, + EGL_BLUE_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_RED_SIZE, 8, + EGL_DEPTH_SIZE, 8, + EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, + EGL_NONE + }; + + const EGLint pbufferAttribs[] = + { + EGL_WIDTH, 9, + EGL_HEIGHT, 9, + EGL_NONE, + }; + + Destroy(); + + if (m_eglGetPlatformDisplayEXT) + { + m_eglDisplay = m_eglGetPlatformDisplayEXT(EGL_PLATFORM_X11_EXT,(EGLNativeDisplayType)m_dpy, + NULL); + } + else + m_eglDisplay = eglGetDisplay((EGLNativeDisplayType)m_dpy); + + if (m_eglDisplay == EGL_NO_DISPLAY) + { + CLog::Log(LOGERROR, "failed to get egl display"); + return false; + } + if (!eglInitialize(m_eglDisplay, NULL, NULL)) + { + CLog::Log(LOGERROR, "failed to initialize egl"); + Destroy(); + return false; + } + if (!eglBindAPI(m_renderingApi)) + { + CLog::Log(LOGERROR, "failed to bind rendering API"); + Destroy(); + return false; + } + + EGLint numConfigs; + + eglChooseConfig(m_eglDisplay, configAttribs, &m_eglConfig, 1, &numConfigs); + m_eglSurface = eglCreatePbufferSurface(m_eglDisplay, m_eglConfig, pbufferAttribs); + if (m_eglSurface == EGL_NO_SURFACE) + { + CLog::Log(LOGERROR, "failed to create EGL window surface {}", eglGetError()); + Destroy(); + return false; + } + + EGLint contextAttributes[] = + { + EGL_CONTEXT_MAJOR_VERSION_KHR, 3, + EGL_CONTEXT_MINOR_VERSION_KHR, 2, + EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR, + EGL_NONE + }; + m_eglContext = eglCreateContext(m_eglDisplay, m_eglConfig, EGL_NO_CONTEXT, contextAttributes); + if (m_eglContext == EGL_NO_CONTEXT) + { + EGLint contextAttributes[] = + { + EGL_CONTEXT_MAJOR_VERSION_KHR, 2, + EGL_NONE + }; + m_eglContext = eglCreateContext(m_eglDisplay, m_eglConfig, EGL_NO_CONTEXT, contextAttributes); + + if (m_eglContext == EGL_NO_CONTEXT) + { + CLog::Log(LOGERROR, "failed to create EGL context"); + Destroy(); + return false; + } + } + + if (!eglMakeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext)) + { + CLog::Log(LOGERROR, "Failed to make context current {} {} {}", fmt::ptr(m_eglDisplay), + fmt::ptr(m_eglSurface), fmt::ptr(m_eglContext)); + Destroy(); + return false; + } + + m_usePB = true; + return true; +} + +void CGLContextEGL::Destroy() +{ + if (m_eglContext) + { + glFinish(); + eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroyContext(m_eglDisplay, m_eglContext); + m_eglContext = EGL_NO_CONTEXT; + } + + if (m_eglSurface) + { + eglDestroySurface(m_eglDisplay, m_eglSurface); + m_eglSurface = EGL_NO_SURFACE; + } + + if (m_eglDisplay) + { + eglTerminate(m_eglDisplay); + m_eglDisplay = EGL_NO_DISPLAY; + } + + m_eglConfig = EGL_NO_CONFIG; +} + +void CGLContextEGL::Detach() +{ + if (m_eglContext) + { + glFinish(); + eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + } + if (m_eglSurface) + { + eglDestroySurface(m_eglDisplay, m_eglSurface); + m_eglSurface = EGL_NO_SURFACE; + } +} + +bool CGLContextEGL::SuitableCheck(EGLDisplay eglDisplay, EGLConfig config) +{ + if (config == EGL_NO_CONFIG) + return false; + + EGLint value; + if (!eglGetConfigAttrib(eglDisplay, config, EGL_RED_SIZE, &value) || value < 8) + return false; + if (!eglGetConfigAttrib(eglDisplay, config, EGL_GREEN_SIZE, &value) || value < 8) + return false; + if (!eglGetConfigAttrib(eglDisplay, config, EGL_BLUE_SIZE, &value) || value < 8) + return false; + if (!eglGetConfigAttrib(eglDisplay, config, EGL_DEPTH_SIZE, &value) || value < 24) + return false; + + return true; +} + +EGLConfig CGLContextEGL::GetEGLConfig(EGLDisplay eglDisplay, XVisualInfo *vInfo) +{ + EGLint numConfigs; + + if (!eglGetConfigs(eglDisplay, nullptr, 0, &numConfigs)) + { + CLog::Log(LOGERROR, "Failed to query number of egl configs"); + return EGL_NO_CONFIG; + } + if (numConfigs == 0) + { + CLog::Log(LOGERROR, "No suitable egl configs found"); + return EGL_NO_CONFIG; + } + + EGLConfig *eglConfigs; + eglConfigs = (EGLConfig*)malloc(numConfigs * sizeof(EGLConfig)); + if (!eglConfigs) + { + CLog::Log(LOGERROR, "eglConfigs malloc failed"); + return EGL_NO_CONFIG; + } + EGLConfig eglConfig = EGL_NO_CONFIG; + if (!eglGetConfigs(eglDisplay, eglConfigs, numConfigs, &numConfigs)) + { + CLog::Log(LOGERROR, "Failed to query egl configs"); + goto Exit; + } + for (EGLint i = 0; i < numConfigs; ++i) + { + if (!SuitableCheck(eglDisplay, eglConfigs[i])) + continue; + + EGLint value; + if (!eglGetConfigAttrib(eglDisplay, eglConfigs[i], EGL_NATIVE_VISUAL_ID, &value)) + { + CLog::Log(LOGERROR, "Failed to query EGL_NATIVE_VISUAL_ID for egl config."); + break; + } + if (value == (EGLint)vInfo->visualid) + { + eglConfig = eglConfigs[i]; + break; + } + } + +Exit: + free(eglConfigs); + return eglConfig; +} + +void CGLContextEGL::SetVSync(bool enable) +{ + eglSwapInterval(m_eglDisplay, enable ? 1 : 0); +} + +void CGLContextEGL::SwapBuffers() +{ + if ((m_eglDisplay == EGL_NO_DISPLAY) || (m_eglSurface == EGL_NO_SURFACE)) + return; + + if (m_usePB) + { + eglSwapBuffers(m_eglDisplay, m_eglSurface); + usleep(20 * 1000); + return; + } + + uint64_t ust1, ust2; + uint64_t msc1, msc2; + uint64_t sbc1, sbc2; + struct timespec nowTs; + uint64_t now; + uint64_t cont = m_sync.cont; + uint64_t interval = m_sync.interval; + + if (m_eglGetSyncValuesCHROMIUM) + { + m_eglGetSyncValuesCHROMIUM(m_eglDisplay, m_eglSurface, &ust1, &msc1, &sbc1); + } + + eglSwapBuffers(m_eglDisplay, m_eglSurface); + + if (!m_eglGetSyncValuesCHROMIUM) + return; + + clock_gettime(CLOCK_MONOTONIC, &nowTs); + now = static_cast(nowTs.tv_sec) * 1000000000ULL + nowTs.tv_nsec; + + m_eglGetSyncValuesCHROMIUM(m_eglDisplay, m_eglSurface, &ust2, &msc2, &sbc2); + + if ((msc1 - m_sync.msc1) > 2) + { + cont = 0; + } + + // we want to block in SwapBuffers + // if a vertical retrace occurs 5 times in a row outside + // of this function, we take action + if (m_sync.cont < 5) + { + if ((msc1 - m_sync.msc1) == 2) + { + cont = 0; + } + else if ((msc1 - m_sync.msc1) == 1) + { + interval = (ust1 - m_sync.ust1) / (msc1 - m_sync.msc1); + cont++; + } + } + else if (m_sync.cont == 5 && m_omlSync) + { + CLog::Log(LOGDEBUG, "CGLContextEGL::SwapBuffers: sync check blocking"); + + if (msc2 == msc1) + { + // if no vertical retrace has occurred in eglSwapBuffers, + // sleep until next vertical retrace + uint64_t lastIncrement = (now / 1000 - ust2); + if (lastIncrement > m_sync.interval) + { + lastIncrement = m_sync.interval; + CLog::Log(LOGWARNING, "CGLContextEGL::SwapBuffers: last msc time greater than interval"); + } + uint64_t sleeptime = m_sync.interval - lastIncrement; + usleep(sleeptime); + cont++; + msc2++; + CLog::Log(LOGDEBUG, "CGLContextEGL::SwapBuffers: sync sleep: {}", sleeptime); + } + } + else if ((m_sync.cont > 5) && (msc2 == m_sync.msc2)) + { + // sleep until next vertical retrace + // this avoids blocking outside of this function + uint64_t lastIncrement = (now / 1000 - ust2); + if (lastIncrement > m_sync.interval) + { + lastIncrement = m_sync.interval; + CLog::Log(LOGWARNING, "CGLContextEGL::SwapBuffers: last msc time greater than interval (1)"); + } + uint64_t sleeptime = m_sync.interval - lastIncrement; + usleep(sleeptime); + msc2++; + } + { + std::unique_lock lock(m_syncLock); + m_sync.ust1 = ust1; + m_sync.ust2 = ust2; + m_sync.msc1 = msc1; + m_sync.msc2 = msc2; + m_sync.interval = interval; + m_sync.cont = cont; + } +} + +uint64_t CGLContextEGL::GetVblankTiming(uint64_t &msc, uint64_t &interval) +{ + struct timespec nowTs; + uint64_t now; + clock_gettime(CLOCK_MONOTONIC, &nowTs); + now = static_cast(nowTs.tv_sec) * 1000000000ULL + nowTs.tv_nsec; + now /= 1000; + + std::unique_lock lock(m_syncLock); + msc = m_sync.msc2; + + interval = (m_sync.cont >= 5) ? m_sync.interval : m_sync.ust2 - m_sync.ust1; + if (interval == 0) + return 0; + + if (now < m_sync.ust2) + { + return 0; + } + + uint64_t ret = now - m_sync.ust2; + while (ret > interval) + { + ret -= interval; + msc++; + } + + return ret; +} + +void CGLContextEGL::QueryExtensions() +{ + std::string extensions = eglQueryString(m_eglDisplay, EGL_EXTENSIONS); + m_extensions = std::string(" ") + extensions + " "; + + CLog::Log(LOGDEBUG, "EGL_EXTENSIONS:{}", m_extensions); +} diff --git a/xbmc/windowing/X11/GLContextEGL.h b/xbmc/windowing/X11/GLContextEGL.h new file mode 100644 index 0000000..441787b --- /dev/null +++ b/xbmc/windowing/X11/GLContextEGL.h @@ -0,0 +1,63 @@ +/* + * 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. + */ + +#pragma once + +#include "GLContext.h" +#include "system_egl.h" +#include "threads/CriticalSection.h" + +#include + +#include +#ifdef HAVE_EGLEXTANGLE +#include +#else +#include +#endif +#include + +class CGLContextEGL : public CGLContext +{ +public: + explicit CGLContextEGL(Display *dpy, EGLint renderingApi); + ~CGLContextEGL() override; + bool Refresh(bool force, int screen, Window glWindow, bool &newContext) override; + bool CreatePB() override; + void Destroy() override; + void Detach() override; + void SetVSync(bool enable) override; + void SwapBuffers() override; + void QueryExtensions() override; + uint64_t GetVblankTiming(uint64_t &msc, uint64_t &interval) override; + + EGLint m_renderingApi; + EGLDisplay m_eglDisplay = EGL_NO_DISPLAY; + EGLSurface m_eglSurface = EGL_NO_SURFACE; + EGLContext m_eglContext = EGL_NO_CONTEXT; + EGLConfig m_eglConfig; +protected: + bool SuitableCheck(EGLDisplay eglDisplay, EGLConfig config); + EGLConfig GetEGLConfig(EGLDisplay eglDisplay, XVisualInfo *vInfo); + PFNEGLGETSYNCVALUESCHROMIUMPROC m_eglGetSyncValuesCHROMIUM = nullptr; + PFNEGLGETPLATFORMDISPLAYEXTPROC m_eglGetPlatformDisplayEXT = nullptr; + + struct Sync + { + uint64_t cont = 0; + uint64_t ust1 = 0; + uint64_t ust2 = 0; + uint64_t msc1 = 0; + uint64_t msc2 = 0; + uint64_t interval = 0; + } m_sync; + + CCriticalSection m_syncLock; + + bool m_usePB = false; +}; diff --git a/xbmc/windowing/X11/GLContextGLX.cpp b/xbmc/windowing/X11/GLContextGLX.cpp new file mode 100644 index 0000000..3c31c22 --- /dev/null +++ b/xbmc/windowing/X11/GLContextGLX.cpp @@ -0,0 +1,293 @@ +/* + * 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 "GLContextGLX.h" + +#include "utils/log.h" + +#include + +#include "system_gl.h" + +using namespace KODI::WINDOWING::X11; + +CGLContextGLX::CGLContextGLX(Display *dpy) : CGLContext(dpy) +{ + m_extPrefix = "GLX_"; + m_vsyncMode = 0; +} + +bool CGLContextGLX::Refresh(bool force, int screen, Window glWindow, bool &newContext) +{ + bool retVal = false; + m_glxWindow = glWindow; + m_nScreen = screen; + + // refresh context + if (m_glxContext && !force) + { + CLog::Log(LOGDEBUG, "CWinSystemX11::RefreshGlxContext: refreshing context"); + glXMakeCurrent(m_dpy, None, NULL); + glXMakeCurrent(m_dpy, glWindow, m_glxContext); + return true; + } + + // create context + + XVisualInfo vMask; + XVisualInfo *visuals; + XVisualInfo *vInfo = NULL; + int availableVisuals = 0; + vMask.screen = screen; + XWindowAttributes winAttr; + + /* Assume a depth of 24 in case the below calls to XGetWindowAttributes() + or XGetVisualInfo() fail. That shouldn't happen unless something is + fatally wrong, but lets prepare for everything. */ + vMask.depth = 24; + + if (XGetWindowAttributes(m_dpy, glWindow, &winAttr)) + { + vMask.visualid = XVisualIDFromVisual(winAttr.visual); + vInfo = XGetVisualInfo(m_dpy, VisualScreenMask | VisualIDMask, &vMask, &availableVisuals); + if (!vInfo) + CLog::Log(LOGWARNING, "Failed to get VisualInfo of visual 0x{:x}", (unsigned)vMask.visualid); + else if(!IsSuitableVisual(vInfo)) + { + CLog::Log(LOGWARNING, + "Visual 0x{:x} of the window is not suitable, looking for another one...", + (unsigned)vInfo->visualid); + vMask.depth = vInfo->depth; + XFree(vInfo); + vInfo = NULL; + } + } + else + CLog::Log(LOGWARNING, "Failed to get window attributes"); + + /* As per glXMakeCurrent documentation, we have to use the same visual as + m_glWindow. Since that was not suitable for use, we try to use another + one with the same depth and hope that the used implementation is less + strict than the documentation. */ + if (!vInfo) + { + visuals = XGetVisualInfo(m_dpy, VisualScreenMask | VisualDepthMask, &vMask, &availableVisuals); + for (int i = 0; i < availableVisuals; i++) + { + if (IsSuitableVisual(&visuals[i])) + { + vMask.visualid = visuals[i].visualid; + vInfo = XGetVisualInfo(m_dpy, VisualScreenMask | VisualIDMask, &vMask, &availableVisuals); + break; + } + } + XFree(visuals); + } + + if (vInfo) + { + CLog::Log(LOGINFO, "Using visual 0x{:x}", (unsigned)vInfo->visualid); + if (m_glxContext) + { + glXMakeCurrent(m_dpy, None, NULL); + glXDestroyContext(m_dpy, m_glxContext); + XSync(m_dpy, False); + } + + if ((m_glxContext = glXCreateContext(m_dpy, vInfo, NULL, True))) + { + // make this context current + glXMakeCurrent(m_dpy, glWindow, m_glxContext); + retVal = true; + newContext = true; + } + else + CLog::Log(LOGERROR, "GLX Error: Could not create context"); + + XFree(vInfo); + } + else + { + CLog::Log(LOGERROR, "GLX Error: vInfo is NULL!"); + } + + return retVal; +} + +void CGLContextGLX::Destroy() +{ + glXMakeCurrent(m_dpy, None, NULL); + glXDestroyContext(m_dpy, m_glxContext); + m_glxContext = 0; +} + +void CGLContextGLX::Detach() +{ + glXMakeCurrent(m_dpy, None, NULL); +} + +bool CGLContextGLX::IsSuitableVisual(XVisualInfo *vInfo) +{ + int value; + + if (glXGetConfig(m_dpy, vInfo, GLX_RGBA, &value) || !value) + return false; + if (glXGetConfig(m_dpy, vInfo, GLX_DOUBLEBUFFER, &value) || !value) + return false; + if (glXGetConfig(m_dpy, vInfo, GLX_RED_SIZE, &value) || value < 8) + return false; + if (glXGetConfig(m_dpy, vInfo, GLX_GREEN_SIZE, &value) || value < 8) + return false; + if (glXGetConfig(m_dpy, vInfo, GLX_BLUE_SIZE, &value) || value < 8) + return false; + if (glXGetConfig(m_dpy, vInfo, GLX_DEPTH_SIZE, &value) || value < 24) + return false; + + return true; +} + +void CGLContextGLX::SetVSync(bool enable) +{ + // turn of current setting first + if(m_glXSwapIntervalEXT) + m_glXSwapIntervalEXT(m_dpy, m_glxWindow, 0); + else if(m_glXSwapIntervalMESA) + m_glXSwapIntervalMESA(0); + + m_iVSyncErrors = 0; + + if(!enable) + return; + + if (m_glXSwapIntervalEXT) + { + m_glXSwapIntervalEXT(m_dpy, m_glxWindow, 1); + m_vsyncMode = 6; + } + if (m_glXSwapIntervalMESA) + { + if(m_glXSwapIntervalMESA(1) == 0) + m_vsyncMode = 2; + else + CLog::Log(LOGWARNING, "{} - glXSwapIntervalMESA failed", __FUNCTION__); + } + if (m_glXWaitVideoSyncSGI && m_glXGetVideoSyncSGI && !m_vsyncMode) + { + unsigned int count; + if(m_glXGetVideoSyncSGI(&count) == 0) + m_vsyncMode = 3; + else + CLog::Log(LOGWARNING, "{} - glXGetVideoSyncSGI failed, glcontext probably not direct", + __FUNCTION__); + } +} + +void CGLContextGLX::SwapBuffers() +{ + if (m_vsyncMode == 3) + { + glFinish(); + unsigned int before = 0, after = 0; + if (m_glXGetVideoSyncSGI(&before) != 0) + CLog::Log(LOGERROR, "{} - glXGetVideoSyncSGI - Failed to get current retrace count", + __FUNCTION__); + + glXSwapBuffers(m_dpy, m_glxWindow); + glFinish(); + + if(m_glXGetVideoSyncSGI(&after) != 0) + CLog::Log(LOGERROR, "{} - glXGetVideoSyncSGI - Failed to get current retrace count", + __FUNCTION__); + + if (after == before) + m_iVSyncErrors = 1; + else + m_iVSyncErrors--; + + if (m_iVSyncErrors > 0) + { + CLog::Log(LOGINFO, "GL: retrace count didn't change after buffer swap, switching to vsync mode 4"); + m_iVSyncErrors = 0; + m_vsyncMode = 4; + } + + if (m_iVSyncErrors < -200) + { + CLog::Log( + LOGINFO, + "GL: retrace count change for {} consecutive buffer swap, switching to vsync mode 2", + -m_iVSyncErrors); + m_iVSyncErrors = 0; + m_vsyncMode = 2; + } + } + else if (m_vsyncMode == 4) + { + glFinish(); + unsigned int before = 0, swap = 0, after = 0; + if (m_glXGetVideoSyncSGI(&before) != 0) + CLog::Log(LOGERROR, "{} - glXGetVideoSyncSGI - Failed to get current retrace count", + __FUNCTION__); + + if(m_glXWaitVideoSyncSGI(2, (before+1)%2, &swap) != 0) + CLog::Log(LOGERROR, "{} - glXWaitVideoSyncSGI - Returned error", __FUNCTION__); + + glXSwapBuffers(m_dpy, m_glxWindow); + glFinish(); + + if (m_glXGetVideoSyncSGI(&after) != 0) + CLog::Log(LOGERROR, "{} - glXGetVideoSyncSGI - Failed to get current retrace count", + __FUNCTION__); + + if (after == before) + CLog::Log(LOGERROR, "{} - glXWaitVideoSyncSGI - Woke up early", __FUNCTION__); + + if (after > before + 1) + m_iVSyncErrors++; + else + m_iVSyncErrors = 0; + + if (m_iVSyncErrors > 30) + { + CLog::Log(LOGINFO, "GL: retrace count seems to be changing due to the swapbuffers call, switching to vsync mode 3"); + m_vsyncMode = 3; + m_iVSyncErrors = 0; + } + } + else + glXSwapBuffers(m_dpy, m_glxWindow); +} + +void CGLContextGLX::QueryExtensions() +{ + m_extensions = " "; + m_extensions += glXQueryExtensionsString(m_dpy, m_nScreen); + m_extensions += " "; + + CLog::Log(LOGDEBUG, "GLX_EXTENSIONS:{}", m_extensions); + + if (IsExtSupported("GLX_SGI_video_sync")) + m_glXWaitVideoSyncSGI = (int (*)(int, int, unsigned int*))glXGetProcAddress((const GLubyte*)"glXWaitVideoSyncSGI"); + else + m_glXWaitVideoSyncSGI = NULL; + + if (IsExtSupported("GLX_SGI_video_sync")) + m_glXGetVideoSyncSGI = (int (*)(unsigned int*))glXGetProcAddress((const GLubyte*)"glXGetVideoSyncSGI"); + else + m_glXGetVideoSyncSGI = NULL; + + if (IsExtSupported("GLX_MESA_swap_control")) + m_glXSwapIntervalMESA = (int (*)(int))glXGetProcAddress((const GLubyte*)"glXSwapIntervalMESA"); + else + m_glXSwapIntervalMESA = NULL; + + if (IsExtSupported("GLX_EXT_swap_control")) + m_glXSwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC)glXGetProcAddress((const GLubyte*)"glXSwapIntervalEXT"); + else + m_glXSwapIntervalEXT = NULL; +} diff --git a/xbmc/windowing/X11/GLContextGLX.h b/xbmc/windowing/X11/GLContextGLX.h new file mode 100644 index 0000000..6fd41b3 --- /dev/null +++ b/xbmc/windowing/X11/GLContextGLX.h @@ -0,0 +1,49 @@ +/* + * 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. + */ + +#pragma once + +#include "GLContext.h" + +#include + +namespace KODI +{ +namespace WINDOWING +{ +namespace X11 +{ + +class CGLContextGLX : public CGLContext +{ +public: + explicit CGLContextGLX(Display *dpy); + bool Refresh(bool force, int screen, Window glWindow, bool &newContext) override; + void Destroy() override; + void Detach() override; + void SetVSync(bool enable) override; + void SwapBuffers() override; + void QueryExtensions() override; + GLXWindow m_glxWindow = 0; + GLXContext m_glxContext = 0; + +protected: + bool IsSuitableVisual(XVisualInfo *vInfo); + + int (*m_glXGetVideoSyncSGI)(unsigned int*); + int (*m_glXWaitVideoSyncSGI)(int, int, unsigned int*); + int (*m_glXSwapIntervalMESA)(int); + PFNGLXSWAPINTERVALEXTPROC m_glXSwapIntervalEXT; + int m_nScreen; + int m_iVSyncErrors; + int m_vsyncMode; +}; + +} +} +} diff --git a/xbmc/windowing/X11/OSScreenSaverX11.cpp b/xbmc/windowing/X11/OSScreenSaverX11.cpp new file mode 100644 index 0000000..3395e46 --- /dev/null +++ b/xbmc/windowing/X11/OSScreenSaverX11.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2017-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 "OSScreenSaverX11.h" + +#include + +using namespace std::chrono_literals; + +COSScreenSaverX11::COSScreenSaverX11(Display* dpy) +: m_dpy(dpy), m_screensaverResetTimer(std::bind(&COSScreenSaverX11::ResetScreenSaver, this)) +{ + assert(m_dpy); +} + +void COSScreenSaverX11::Inhibit() +{ + // disallow the screensaver by periodically calling XResetScreenSaver(), + // for some reason setting a 0 timeout with XSetScreenSaver doesn't work with gnome + m_screensaverResetTimer.Start(5000ms, true); +} + +void COSScreenSaverX11::Uninhibit() +{ + m_screensaverResetTimer.Stop(true); +} + +void COSScreenSaverX11::ResetScreenSaver() +{ + XResetScreenSaver(m_dpy); +} diff --git a/xbmc/windowing/X11/OSScreenSaverX11.h b/xbmc/windowing/X11/OSScreenSaverX11.h new file mode 100644 index 0000000..60c9b45 --- /dev/null +++ b/xbmc/windowing/X11/OSScreenSaverX11.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2017-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. + */ + +#pragma once + +#include "../OSScreenSaver.h" +#include "threads/Timer.h" + +#include + +class COSScreenSaverX11 : public KODI::WINDOWING::IOSScreenSaver +{ +public: + explicit COSScreenSaverX11(Display* dpy); + void Inhibit() override; + void Uninhibit() override; + +private: + void ResetScreenSaver(); + + Display* m_dpy; + CTimer m_screensaverResetTimer; +}; diff --git a/xbmc/windowing/X11/OptionalsReg.cpp b/xbmc/windowing/X11/OptionalsReg.cpp new file mode 100644 index 0000000..ecfd780 --- /dev/null +++ b/xbmc/windowing/X11/OptionalsReg.cpp @@ -0,0 +1,256 @@ +/* + * 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 "OptionalsReg.h" + +//----------------------------------------------------------------------------- +// VAAPI +//----------------------------------------------------------------------------- +#if defined (HAVE_LIBVA) +#include +#include "cores/VideoPlayer/DVDCodecs/Video/VAAPI.h" +#if defined(HAS_GL) +#include "cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVAAPIGL.h" +#endif +#if defined(HAS_GLES) +#include "cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVAAPIGLES.h" +#endif + +using namespace KODI::WINDOWING::X11; + +namespace KODI +{ +namespace WINDOWING +{ +namespace X11 +{ + +class CWinSystemX11GLContext; + +class CVaapiProxy : public VAAPI::IVaapiWinSystem +{ +public: + CVaapiProxy() = default; + virtual ~CVaapiProxy() = default; + VADisplay GetVADisplay() override { return vaGetDisplay(dpy); }; + void *GetEGLDisplay() override { return eglDisplay; }; + + Display *dpy; + void *eglDisplay; +}; + +CVaapiProxy* VaapiProxyCreate() +{ + return new CVaapiProxy(); +} + +void VaapiProxyDelete(CVaapiProxy *proxy) +{ + delete proxy; +} + +void VaapiProxyConfig(CVaapiProxy *proxy, void *dpy, void *eglDpy) +{ + proxy->dpy = static_cast(dpy); + proxy->eglDisplay = eglDpy; +} + +void VAAPIRegister(CVaapiProxy *winSystem, bool deepColor) +{ + VAAPI::CDecoder::Register(winSystem, deepColor); +} + +#if defined(HAS_GL) +void VAAPIRegisterRenderGL(CVaapiProxy* winSystem, bool& general, bool& deepColor) +{ + EGLDisplay eglDpy = winSystem->eglDisplay; + VADisplay vaDpy = vaGetDisplay(winSystem->dpy); + CRendererVAAPIGL::Register(winSystem, vaDpy, eglDpy, general, deepColor); +} +#endif + +#if defined(HAS_GLES) +void VAAPIRegisterRenderGLES(CVaapiProxy* winSystem, bool& general, bool& deepColor) +{ + EGLDisplay eglDpy = winSystem->eglDisplay; + VADisplay vaDpy = vaGetDisplay(winSystem->dpy); + CRendererVAAPIGLES::Register(winSystem, vaDpy, eglDpy, general, deepColor); +} +#endif +} +} +} + +#else +namespace KODI +{ +namespace WINDOWING +{ +namespace X11 +{ + +class CVaapiProxy +{ +}; + +CVaapiProxy* VaapiProxyCreate() +{ + return nullptr; +} + +void VaapiProxyDelete(CVaapiProxy *proxy) +{ +} + +void VaapiProxyConfig(CVaapiProxy *proxy, void *dpy, void *eglDpy) +{ +} + +void VAAPIRegister(CVaapiProxy *winSystem, bool deepColor) +{ +} + +void VAAPIRegisterRenderGL(CVaapiProxy* winSystem, bool& general, bool& deepColor) +{ +} + +void VAAPIRegisterRenderGLES(CVaapiProxy* winSystem, bool& general, bool& deepColor) +{ +} + +} +} +} +#endif + +//----------------------------------------------------------------------------- +// GLX +//----------------------------------------------------------------------------- + +#ifdef HAS_GLX +#include +#include "VideoSyncGLX.h" +#include "GLContextGLX.h" + +namespace KODI +{ +namespace WINDOWING +{ +namespace X11 +{ + +XID GLXGetWindow(void* context) +{ + return static_cast(context)->m_glxWindow; +} + +void* GLXGetContext(void* context) +{ + return static_cast(context)->m_glxContext; +} + +CGLContext* GLXContextCreate(Display *dpy) +{ + return new CGLContextGLX(dpy); +} + + +CVideoSync* GLXVideoSyncCreate(void* clock, CWinSystemX11GLContext& winSystem) +{ + return new CVideoSyncGLX(clock, winSystem); +} + +} +} +} +#else + +namespace KODI +{ +namespace WINDOWING +{ +namespace X11 +{ + +XID GLXGetWindow(void* context) +{ + return 0; +} + +void* GLXGetContext(void* context) +{ + return nullptr; +} + +CGLContext* GLXContextCreate(Display *dpy) +{ + return nullptr; +} + +CVideoSync* GLXVideoSyncCreate(void* clock, CWinSystemX11GLContext& winSystem) +{ + return nullptr; +} + +} +} +} +#endif + +//----------------------------------------------------------------------------- +// VDPAU +//----------------------------------------------------------------------------- + +#if defined (HAVE_LIBVDPAU) +#include "cores/VideoPlayer/DVDCodecs/Video/VDPAU.h" +#include "cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVDPAU.h" + +namespace KODI +{ +namespace WINDOWING +{ +namespace X11 +{ + +void VDPAURegisterRender() +{ + CRendererVDPAU::Register(); +} + +void VDPAURegister() +{ + VDPAU::CDecoder::Register(); +} + +} +} +} +#else + +namespace KODI +{ +namespace WINDOWING +{ +namespace X11 +{ + +void VDPAURegisterRender() +{ + +} + +void VDPAURegister() +{ + +} + +} +} +} +#endif + diff --git a/xbmc/windowing/X11/OptionalsReg.h b/xbmc/windowing/X11/OptionalsReg.h new file mode 100644 index 0000000..2c4f15b --- /dev/null +++ b/xbmc/windowing/X11/OptionalsReg.h @@ -0,0 +1,78 @@ +/* + * 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. + */ + +#pragma once + +#include + +//----------------------------------------------------------------------------- +// VAAPI +//----------------------------------------------------------------------------- + +namespace KODI +{ +namespace WINDOWING +{ +namespace X11 +{ + +class CVaapiProxy; + +CVaapiProxy* VaapiProxyCreate(); +void VaapiProxyDelete(CVaapiProxy *proxy); +void VaapiProxyConfig(CVaapiProxy *proxy, void *dpy, void *eglDpy); +void VAAPIRegister(CVaapiProxy *winSystem, bool deepColor); +#if defined(HAS_GL) +void VAAPIRegisterRenderGL(CVaapiProxy* winSystem, bool& general, bool& deepColor); +#endif +#if defined(HAS_GLES) +void VAAPIRegisterRenderGLES(CVaapiProxy* winSystem, bool& general, bool& deepColor); +#endif +} +} +} + +//----------------------------------------------------------------------------- +// GLX +//----------------------------------------------------------------------------- + +class CVideoSync; +class CGLContext; + +namespace KODI +{ +namespace WINDOWING +{ +namespace X11 +{ + +class CWinSystemX11GLContext; + +XID GLXGetWindow(void* context); +void* GLXGetContext(void* context); +CGLContext* GLXContextCreate(Display *dpy); +CVideoSync* GLXVideoSyncCreate(void *clock, CWinSystemX11GLContext& winSystem); +} +} +} + +//----------------------------------------------------------------------------- +// VDPAU +//----------------------------------------------------------------------------- + +namespace KODI +{ +namespace WINDOWING +{ +namespace X11 +{ +void VDPAURegisterRender(); +void VDPAURegister(); +} +} +} diff --git a/xbmc/windowing/X11/VideoSyncGLX.cpp b/xbmc/windowing/X11/VideoSyncGLX.cpp new file mode 100644 index 0000000..0e29a85 --- /dev/null +++ b/xbmc/windowing/X11/VideoSyncGLX.cpp @@ -0,0 +1,277 @@ +/* + * 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 "VideoSyncGLX.h" + +#include "utils/TimeUtils.h" +#include "utils/XTimeUtils.h" +#include "utils/log.h" +#include "windowing/GraphicContext.h" +#include "windowing/X11/WinSystemX11GLContext.h" + +#include +#include + +#include + +using namespace KODI::WINDOWING::X11; + +using namespace std::chrono_literals; + +Display* CVideoSyncGLX::m_Dpy = NULL; + +void CVideoSyncGLX::OnLostDisplay() +{ + if (!m_displayLost) + { + m_displayLost = true; + m_lostEvent.Wait(); + } +} + +void CVideoSyncGLX::OnResetDisplay() +{ + m_displayReset = true; +} + +bool CVideoSyncGLX::Setup(PUPDATECLOCK func) +{ + std::unique_lock lock(m_winSystem.GetGfxContext()); + + m_glXWaitVideoSyncSGI = NULL; + m_glXGetVideoSyncSGI = NULL; + m_vInfo = NULL; + m_Window = 0; + m_Context = NULL; + UpdateClock = func; + + int singleBufferAttributes[] = { + GLX_RGBA, + GLX_RED_SIZE, 0, + GLX_GREEN_SIZE, 0, + GLX_BLUE_SIZE, 0, + None + }; + + int ReturnV, SwaMask; + unsigned int GlxTest; + XSetWindowAttributes Swa; + + m_vInfo = NULL; + m_Context = NULL; + m_Window = 0; + + CLog::Log(LOGDEBUG, "CVideoReferenceClock: Setting up GLX"); + + static_cast(&m_winSystem)->Register(this); + + m_displayLost = false; + m_displayReset = false; + m_lostEvent.Reset(); + + if (!m_Dpy) + { + m_Dpy = XOpenDisplay(NULL); + if (!m_Dpy) + { + CLog::Log(LOGDEBUG, "CVideoReferenceClock: Unable to open display"); + return false; + } + } + + if (!glXQueryExtension(m_Dpy, NULL, NULL)) + { + CLog::Log(LOGDEBUG, "CVideoReferenceClock: X server does not support GLX"); + return false; + } + + bool ExtensionFound = false; + std::istringstream Extensions(glXQueryExtensionsString(m_Dpy, m_winSystem.GetScreen())); + std::string ExtensionStr; + + while (!ExtensionFound) + { + Extensions >> ExtensionStr; + if (Extensions.fail()) + break; + + if (ExtensionStr == "GLX_SGI_video_sync") + ExtensionFound = true; + } + + if (!ExtensionFound) + { + CLog::Log(LOGDEBUG, "CVideoReferenceClock: X server does not support GLX_SGI_video_sync"); + return false; + } + + m_vInfo = glXChooseVisual(m_Dpy, m_winSystem.GetScreen(), singleBufferAttributes); + if (!m_vInfo) + { + CLog::Log(LOGDEBUG, "CVideoReferenceClock: glXChooseVisual returned NULL"); + return false; + } + + Swa.border_pixel = 0; + Swa.event_mask = StructureNotifyMask; + Swa.colormap = XCreateColormap(m_Dpy, m_winSystem.GetWindow(), m_vInfo->visual, AllocNone ); + SwaMask = CWBorderPixel | CWColormap | CWEventMask; + + m_Window = XCreateWindow(m_Dpy, m_winSystem.GetWindow(), 0, 0, 256, 256, 0, + m_vInfo->depth, InputOutput, m_vInfo->visual, SwaMask, &Swa); + + m_Context = glXCreateContext(m_Dpy, m_vInfo, NULL, True); + if (!m_Context) + { + CLog::Log(LOGDEBUG, "CVideoReferenceClock: glXCreateContext returned NULL"); + return false; + } + + ReturnV = glXMakeCurrent(m_Dpy, m_Window, m_Context); + if (ReturnV != True) + { + CLog::Log(LOGDEBUG, "CVideoReferenceClock: glXMakeCurrent returned {}", ReturnV); + return false; + } + + m_glXWaitVideoSyncSGI = (int (*)(int, int, unsigned int*))glXGetProcAddress((const GLubyte*)"glXWaitVideoSyncSGI"); + if (!m_glXWaitVideoSyncSGI) + { + CLog::Log(LOGDEBUG, "CVideoReferenceClock: glXWaitVideoSyncSGI not found"); + return false; + } + + ReturnV = m_glXWaitVideoSyncSGI(2, 0, &GlxTest); + if (ReturnV) + { + CLog::Log(LOGDEBUG, "CVideoReferenceClock: glXWaitVideoSyncSGI returned {}", ReturnV); + return false; + } + + m_glXGetVideoSyncSGI = (int (*)(unsigned int*))glXGetProcAddress((const GLubyte*)"glXGetVideoSyncSGI"); + if (!m_glXGetVideoSyncSGI) + { + CLog::Log(LOGDEBUG, "CVideoReferenceClock: glXGetVideoSyncSGI not found"); + return false; + } + + ReturnV = m_glXGetVideoSyncSGI(&GlxTest); + if (ReturnV) + { + CLog::Log(LOGDEBUG, "CVideoReferenceClock: glXGetVideoSyncSGI returned {}", ReturnV); + return false; + } + + return true; +} + +void CVideoSyncGLX::Run(CEvent& stopEvent) +{ + unsigned int PrevVblankCount; + unsigned int VblankCount; + int ReturnV; + bool IsReset = false; + int64_t Now; + + //get the current vblank counter + m_glXGetVideoSyncSGI(&VblankCount); + PrevVblankCount = VblankCount; + + while(!stopEvent.Signaled() && !m_displayLost && !m_displayReset) + { + //wait for the next vblank + ReturnV = m_glXWaitVideoSyncSGI(2, (VblankCount + 1) % 2, &VblankCount); + m_glXGetVideoSyncSGI(&VblankCount); //the vblank count returned by glXWaitVideoSyncSGI is not always correct + Now = CurrentHostCounter(); //get the timestamp of this vblank + + if(ReturnV) + { + CLog::Log(LOGDEBUG, "CVideoReferenceClock: glXWaitVideoSyncSGI returned {}", ReturnV); + return; + } + + if (VblankCount > PrevVblankCount) + { + UpdateClock((int)(VblankCount - PrevVblankCount), Now, m_refClock); + IsReset = false; + } + else + { + CLog::Log(LOGDEBUG, "CVideoReferenceClock: Vblank counter has reset"); + + //only try reattaching once + if (IsReset) + return; + + //because of a bug in the nvidia driver, glXWaitVideoSyncSGI breaks when the vblank counter resets + CLog::Log(LOGDEBUG, "CVideoReferenceClock: Detaching glX context"); + ReturnV = glXMakeCurrent(m_Dpy, None, NULL); + if (ReturnV != True) + { + CLog::Log(LOGDEBUG, "CVideoReferenceClock: glXMakeCurrent returned {}", ReturnV); + return; + } + + //sleep here so we don't busy spin when this constantly happens, for example when the display went to sleep + KODI::TIME::Sleep(1s); + + CLog::Log(LOGDEBUG, "CVideoReferenceClock: Attaching glX context"); + ReturnV = glXMakeCurrent(m_Dpy, m_Window, m_Context); + if (ReturnV != True) + { + CLog::Log(LOGDEBUG, "CVideoReferenceClock: glXMakeCurrent returned {}", ReturnV); + return; + } + + m_glXGetVideoSyncSGI(&VblankCount); + + IsReset = true; + } + PrevVblankCount = VblankCount; + } + m_lostEvent.Set(); + while(!stopEvent.Signaled() && m_displayLost && !m_displayReset) + { + KODI::TIME::Sleep(10ms); + } +} + +void CVideoSyncGLX::Cleanup() +{ + CLog::Log(LOGDEBUG, "CVideoReferenceClock: Cleaning up GLX"); + + { + std::unique_lock lock(m_winSystem.GetGfxContext()); + + if (m_vInfo) + { + XFree(m_vInfo); + m_vInfo = NULL; + } + if (m_Context) + { + glXMakeCurrent(m_Dpy, None, NULL); + glXDestroyContext(m_Dpy, m_Context); + m_Context = NULL; + } + if (m_Window) + { + XDestroyWindow(m_Dpy, m_Window); + m_Window = 0; + } + } + + m_lostEvent.Set(); + m_winSystem.Unregister(this); +} + +float CVideoSyncGLX::GetFps() +{ + m_fps = m_winSystem.GetGfxContext().GetFPS(); + return m_fps; +} diff --git a/xbmc/windowing/X11/VideoSyncGLX.h b/xbmc/windowing/X11/VideoSyncGLX.h new file mode 100644 index 0000000..06d348e --- /dev/null +++ b/xbmc/windowing/X11/VideoSyncGLX.h @@ -0,0 +1,62 @@ +/* + * 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. + */ + +#pragma once + +#include "guilib/DispResource.h" +#include "threads/Event.h" +#include "windowing/VideoSync.h" + +#include +#include +#include + +#include "system_gl.h" + + + +namespace KODI +{ +namespace WINDOWING +{ +namespace X11 +{ + +class CWinSystemX11GLContext; + +class CVideoSyncGLX : public CVideoSync, IDispResource +{ +public: + explicit CVideoSyncGLX(void* clock, CWinSystemX11GLContext& winSystem) + : CVideoSync(clock), m_winSystem(winSystem) + { + } + bool Setup(PUPDATECLOCK func) override; + void Run(CEvent& stopEvent) override; + void Cleanup() override; + float GetFps() override; + void OnLostDisplay() override; + void OnResetDisplay() override; + +private: + int (*m_glXWaitVideoSyncSGI) (int, int, unsigned int*); + int (*m_glXGetVideoSyncSGI) (unsigned int*); + + static Display* m_Dpy; + CWinSystemX11GLContext &m_winSystem; + XVisualInfo *m_vInfo; + Window m_Window; + GLXContext m_Context; + volatile bool m_displayLost; + volatile bool m_displayReset; + CEvent m_lostEvent; +}; + +} +} +} diff --git a/xbmc/windowing/X11/VideoSyncOML.cpp b/xbmc/windowing/X11/VideoSyncOML.cpp new file mode 100644 index 0000000..2dc91fe --- /dev/null +++ b/xbmc/windowing/X11/VideoSyncOML.cpp @@ -0,0 +1,83 @@ +/* + * 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 "VideoSyncOML.h" + +#include "utils/TimeUtils.h" +#include "utils/log.h" +#include "windowing/GraphicContext.h" +#include "windowing/X11/WinSystemX11GLContext.h" + +#include + +using namespace KODI::WINDOWING::X11; + +bool CVideoSyncOML::Setup(PUPDATECLOCK func) +{ + CLog::Log(LOGDEBUG, "CVideoSyncOML::{} - setting up OML", __FUNCTION__); + + UpdateClock = func; + + m_abort = false; + + static_cast(&m_winSystem)->Register(this); + + return true; +} + +void CVideoSyncOML::Run(CEvent& stopEvent) +{ + uint64_t interval, timeSinceVblank, msc; + + timeSinceVblank = m_winSystem.GetVblankTiming(msc, interval); + + while (!stopEvent.Signaled() && !m_abort) + { + if (interval == 0) + { + usleep(10000); + } + else + { + usleep(interval - timeSinceVblank + 1000); + } + uint64_t newMsc; + timeSinceVblank = m_winSystem.GetVblankTiming(newMsc, interval); + + if (newMsc == msc) + { + newMsc++; + } + else if (newMsc < msc) + { + timeSinceVblank = interval; + continue; + } + + uint64_t now = CurrentHostCounter(); + UpdateClock(newMsc - msc, now, m_refClock); + msc = newMsc; + } +} + +void CVideoSyncOML::Cleanup() +{ + m_winSystem.Unregister(this); +} + +void CVideoSyncOML::OnResetDisplay() +{ + m_abort = true; +} + +float CVideoSyncOML::GetFps() +{ + m_fps = m_winSystem.GetGfxContext().GetFPS(); + return m_fps; +} + diff --git a/xbmc/windowing/X11/VideoSyncOML.h b/xbmc/windowing/X11/VideoSyncOML.h new file mode 100644 index 0000000..a04bd1d --- /dev/null +++ b/xbmc/windowing/X11/VideoSyncOML.h @@ -0,0 +1,46 @@ +/* + * 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. + */ + +#pragma once + +#include "guilib/DispResource.h" +#include "windowing/VideoSync.h" + +#include + + +namespace KODI +{ +namespace WINDOWING +{ +namespace X11 +{ + +class CWinSystemX11GLContext; + +class CVideoSyncOML : public CVideoSync, IDispResource +{ +public: + explicit CVideoSyncOML(void* clock, CWinSystemX11GLContext& winSystem) + : CVideoSync(clock), m_winSystem(winSystem) + { + } + bool Setup(PUPDATECLOCK func) override; + void Run(CEvent& stopEvent) override; + void Cleanup() override; + float GetFps() override; + void OnResetDisplay() override; + +private: + std::atomic_bool m_abort; + CWinSystemX11GLContext &m_winSystem; +}; + +} +} +} diff --git a/xbmc/windowing/X11/WinEventsX11.cpp b/xbmc/windowing/X11/WinEventsX11.cpp new file mode 100644 index 0000000..faffd99 --- /dev/null +++ b/xbmc/windowing/X11/WinEventsX11.cpp @@ -0,0 +1,673 @@ +/* + * 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 "WinEventsX11.h" + +#include "ServiceBroker.h" +#include "application/AppInboundProtocol.h" +#include "application/Application.h" +#include "cores/AudioEngine/Interfaces/AE.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "input/InputManager.h" +#include "input/mouse/MouseStat.h" +#include "messaging/ApplicationMessenger.h" +#include "utils/CharsetConverter.h" +#include "utils/log.h" +#include "windowing/WinEvents.h" +#include "windowing/X11/WinSystemX11.h" + +#include + +#include +#include +#include +#include + +using namespace KODI::WINDOWING::X11; + +static uint32_t SymMappingsX11[][2] = +{ + {XK_BackSpace, XBMCK_BACKSPACE} +, {XK_Tab, XBMCK_TAB} +, {XK_Clear, XBMCK_CLEAR} +, {XK_Return, XBMCK_RETURN} +, {XK_Pause, XBMCK_PAUSE} +, {XK_Escape, XBMCK_ESCAPE} +, {XK_Delete, XBMCK_DELETE} +// multi-media keys +, {XF86XK_Back, XBMCK_BROWSER_BACK} +, {XF86XK_Forward, XBMCK_BROWSER_FORWARD} +, {XF86XK_Refresh, XBMCK_BROWSER_REFRESH} +, {XF86XK_Stop, XBMCK_BROWSER_STOP} +, {XF86XK_Search, XBMCK_BROWSER_SEARCH} +, {XF86XK_Favorites, XBMCK_BROWSER_FAVORITES} +, {XF86XK_HomePage, XBMCK_BROWSER_HOME} +, {XF86XK_AudioMute, XBMCK_VOLUME_MUTE} +, {XF86XK_AudioLowerVolume, XBMCK_VOLUME_DOWN} +, {XF86XK_AudioRaiseVolume, XBMCK_VOLUME_UP} +, {XF86XK_AudioNext, XBMCK_MEDIA_NEXT_TRACK} +, {XF86XK_AudioPrev, XBMCK_MEDIA_PREV_TRACK} +, {XF86XK_AudioStop, XBMCK_MEDIA_STOP} +, {XF86XK_AudioPause, XBMCK_MEDIA_PLAY_PAUSE} +, {XF86XK_Mail, XBMCK_LAUNCH_MAIL} +, {XF86XK_Select, XBMCK_LAUNCH_MEDIA_SELECT} +, {XF86XK_Launch0, XBMCK_LAUNCH_APP1} +, {XF86XK_Launch1, XBMCK_LAUNCH_APP2} +, {XF86XK_WWW, XBMCK_LAUNCH_FILE_BROWSER} +, {XF86XK_AudioMedia, XBMCK_LAUNCH_MEDIA_CENTER } + // Numeric keypad +, {XK_KP_0, XBMCK_KP0} +, {XK_KP_1, XBMCK_KP1} +, {XK_KP_2, XBMCK_KP2} +, {XK_KP_3, XBMCK_KP3} +, {XK_KP_4, XBMCK_KP4} +, {XK_KP_5, XBMCK_KP5} +, {XK_KP_6, XBMCK_KP6} +, {XK_KP_7, XBMCK_KP7} +, {XK_KP_8, XBMCK_KP8} +, {XK_KP_9, XBMCK_KP9} +, {XK_KP_Separator, XBMCK_KP_PERIOD} +, {XK_KP_Divide, XBMCK_KP_DIVIDE} +, {XK_KP_Multiply, XBMCK_KP_MULTIPLY} +, {XK_KP_Subtract, XBMCK_KP_MINUS} +, {XK_KP_Add, XBMCK_KP_PLUS} +, {XK_KP_Enter, XBMCK_KP_ENTER} +, {XK_KP_Equal, XBMCK_KP_EQUALS} + // Arrows + Home/End pad +, {XK_Up, XBMCK_UP} +, {XK_Down, XBMCK_DOWN} +, {XK_Right, XBMCK_RIGHT} +, {XK_Left, XBMCK_LEFT} +, {XK_Insert, XBMCK_INSERT} +, {XK_Home, XBMCK_HOME} +, {XK_End, XBMCK_END} +, {XK_Page_Up, XBMCK_PAGEUP} +, {XK_Page_Down, XBMCK_PAGEDOWN} + // Function keys +, {XK_F1, XBMCK_F1} +, {XK_F2, XBMCK_F2} +, {XK_F3, XBMCK_F3} +, {XK_F4, XBMCK_F4} +, {XK_F5, XBMCK_F5} +, {XK_F6, XBMCK_F6} +, {XK_F7, XBMCK_F7} +, {XK_F8, XBMCK_F8} +, {XK_F9, XBMCK_F9} +, {XK_F10, XBMCK_F10} +, {XK_F11, XBMCK_F11} +, {XK_F12, XBMCK_F12} +, {XK_F13, XBMCK_F13} +, {XK_F14, XBMCK_F14} +, {XK_F15, XBMCK_F15} + // Key state modifier keys +, {XK_Num_Lock, XBMCK_NUMLOCK} +, {XK_Caps_Lock, XBMCK_CAPSLOCK} +, {XK_Scroll_Lock, XBMCK_SCROLLOCK} +, {XK_Shift_R, XBMCK_RSHIFT} +, {XK_Shift_L, XBMCK_LSHIFT} +, {XK_Control_R, XBMCK_RCTRL} +, {XK_Control_L, XBMCK_LCTRL} +, {XK_Alt_R, XBMCK_RALT} +, {XK_Alt_L, XBMCK_LALT} +, {XK_Meta_R, XBMCK_RMETA} +, {XK_Meta_L, XBMCK_LMETA} +, {XK_Super_L, XBMCK_LSUPER} +, {XK_Super_R, XBMCK_RSUPER} +, {XK_Mode_switch, XBMCK_MODE} +, {XK_Multi_key, XBMCK_COMPOSE} + // Miscellaneous function keys +, {XK_Help, XBMCK_HELP} +, {XK_Print, XBMCK_PRINT} +//, {0, XBMCK_SYSREQ} +, {XK_Break, XBMCK_BREAK} +, {XK_Menu, XBMCK_MENU} +, {XF86XK_PowerOff, XBMCK_POWER} +, {XF86XK_Sleep, XBMCK_SLEEP} +, {XK_EcuSign, XBMCK_EURO} +, {XK_Undo, XBMCK_UNDO} + /* Media keys */ +, {XF86XK_Eject, XBMCK_EJECT} +, {XF86XK_Stop, XBMCK_STOP} +, {XF86XK_AudioRecord, XBMCK_RECORD} +, {XF86XK_AudioRewind, XBMCK_REWIND} +, {XF86XK_Phone, XBMCK_PHONE} +, {XF86XK_AudioPlay, XBMCK_PLAY} +, {XF86XK_AudioRandomPlay, XBMCK_SHUFFLE} +, {XF86XK_AudioForward, XBMCK_FASTFORWARD} +}; + +CWinEventsX11::CWinEventsX11(CWinSystemX11& winSystem) : m_winSystem(winSystem) +{ +} + +CWinEventsX11::~CWinEventsX11() +{ + Quit(); +} + +bool CWinEventsX11::Init(Display *dpy, Window win) +{ + if (m_display) + return true; + + m_display = dpy; + m_window = win; + m_keybuf_len = 32*sizeof(char); + m_keybuf = (char*)malloc(m_keybuf_len); + m_keymodState = 0; + m_wmDeleteMessage = XInternAtom(dpy, "WM_DELETE_WINDOW", False); + m_structureChanged = false; + m_xrrEventPending = false; + + // open input method + char *old_locale = NULL, *old_modifiers = NULL; + char res_name[8]; + const char *p; + + // set resource name to xbmc, not used + strcpy(res_name, "xbmc"); + + // save current locale, this should be "C" + p = setlocale(LC_ALL, NULL); + if (p) + { + old_locale = (char*)malloc(strlen(p) +1); + strcpy(old_locale, p); + } + p = XSetLocaleModifiers(NULL); + if (p) + { + old_modifiers = (char*)malloc(strlen(p) +1); + strcpy(old_modifiers, p); + } + + // set users preferences and open input method + p = setlocale(LC_ALL, ""); + XSetLocaleModifiers(""); + m_xim = XOpenIM(m_display, NULL, res_name, res_name); + + // restore old locale + if (old_locale) + { + setlocale(LC_ALL, old_locale); + free(old_locale); + } + if (old_modifiers) + { + XSetLocaleModifiers(old_modifiers); + free(old_modifiers); + } + + m_xic = NULL; + if (m_xim) + { + m_xic = XCreateIC(m_xim, + XNClientWindow, m_window, + XNFocusWindow, m_window, + XNInputStyle, XIMPreeditNothing | XIMStatusNothing, + XNResourceName, res_name, + XNResourceClass, res_name, + nullptr); + } + + if (!m_xic) + CLog::Log(LOGWARNING,"CWinEventsX11::Init - no input method found"); + + // build Keysym lookup table + for (const auto& symMapping : SymMappingsX11) + { + m_symLookupTable[symMapping[0]] = symMapping[1]; + } + + // register for xrandr events + int iReturn; + XRRQueryExtension(m_display, &m_RREventBase, &iReturn); + int numScreens = XScreenCount(m_display); + for (int i = 0; i < numScreens; i++) + { + XRRSelectInput(m_display, RootWindow(m_display, i), RRScreenChangeNotifyMask | RRCrtcChangeNotifyMask | RROutputChangeNotifyMask | RROutputPropertyNotifyMask); + } + + return true; +} + +void CWinEventsX11::Quit() +{ + free(m_keybuf); + m_keybuf = nullptr; + + if (m_xic) + { + XUnsetICFocus(m_xic); + XDestroyIC(m_xic); + m_xic = nullptr; + } + + if (m_xim) + { + XCloseIM(m_xim); + m_xim = nullptr; + } + + m_symLookupTable.clear(); + + m_display = nullptr; +} + +bool CWinEventsX11::HasStructureChanged() +{ + if (!m_display) + return false; + + bool ret = m_structureChanged; + m_structureChanged = false; + return ret; +} + +void CWinEventsX11::SetXRRFailSafeTimer(std::chrono::milliseconds duration) +{ + if (!m_display) + return; + + m_xrrFailSafeTimer.Set(duration); + m_xrrEventPending = true; +} + +bool CWinEventsX11::MessagePump() +{ + if (!m_display) + return false; + + bool ret = false; + XEvent xevent; + unsigned long serial = 0; + std::shared_ptr appPort = CServiceBroker::GetAppPort(); + + while (m_display && XPending(m_display)) + { + memset(&xevent, 0, sizeof (XEvent)); + XNextEvent(m_display, &xevent); + + if (m_display && (xevent.type == m_RREventBase + RRScreenChangeNotify)) + { + if (xevent.xgeneric.serial == serial) + continue; + + if (m_xrrEventPending) + { + m_winSystem.NotifyXRREvent(); + m_xrrEventPending = false; + serial = xevent.xgeneric.serial; + } + + continue; + } + else if (m_display && (xevent.type == m_RREventBase + RRNotify)) + { + if (xevent.xgeneric.serial == serial) + continue; + + XRRNotifyEvent* rrEvent = reinterpret_cast(&xevent); + if (rrEvent->subtype == RRNotify_OutputChange) + { + XRROutputChangeNotifyEvent* changeEvent = reinterpret_cast(&xevent); + if (changeEvent->connection == RR_Connected || + changeEvent->connection == RR_Disconnected) + { + m_winSystem.NotifyXRREvent(); + CServiceBroker::GetActiveAE()->DeviceChange(); + serial = xevent.xgeneric.serial; + } + } + + continue; + } + + if (XFilterEvent(&xevent, m_window)) + continue; + + switch (xevent.type) + { + case MapNotify: + { + if (appPort) + appPort->SetRenderGUI(true); + break; + } + + case UnmapNotify: + { + if (appPort) + appPort->SetRenderGUI(false); + break; + } + + case FocusIn: + { + if (m_xic) + XSetICFocus(m_xic); + g_application.m_AppFocused = true; + m_keymodState = 0; + if (serial == xevent.xfocus.serial) + break; + m_winSystem.NotifyAppFocusChange(g_application.m_AppFocused); + break; + } + + case FocusOut: + { + if (m_xic) + XUnsetICFocus(m_xic); + g_application.m_AppFocused = false; + m_winSystem.NotifyAppFocusChange(g_application.m_AppFocused); + serial = xevent.xfocus.serial; + break; + } + + case Expose: + { + CServiceBroker::GetGUI()->GetWindowManager().MarkDirty(); + break; + } + + case ConfigureNotify: + { + if (xevent.xconfigure.window != m_window) + break; + + m_structureChanged = true; + XBMC_Event newEvent = {}; + newEvent.type = XBMC_VIDEORESIZE; + newEvent.resize.w = xevent.xconfigure.width; + newEvent.resize.h = xevent.xconfigure.height; + if (appPort) + ret |= appPort->OnEvent(newEvent); + CServiceBroker::GetGUI()->GetWindowManager().MarkDirty(); + break; + } + + case ClientMessage: + { + if ((unsigned int)xevent.xclient.data.l[0] == m_wmDeleteMessage) + if (!g_application.m_bStop) + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_QUIT); + break; + } + + case KeyPress: + { + XBMC_Event newEvent = {}; + newEvent.type = XBMC_KEYDOWN; + KeySym xkeysym; + + // fallback if we have no IM + if (!m_xic) + { + static XComposeStatus state; + char keybuf[32]; + XLookupString(&xevent.xkey, NULL, 0, &xkeysym, NULL); + newEvent.key.keysym.sym = LookupXbmcKeySym(xkeysym); + newEvent.key.keysym.scancode = xevent.xkey.keycode; + if (XLookupString(&xevent.xkey, keybuf, sizeof(keybuf), NULL, &state)) + { + newEvent.key.keysym.unicode = keybuf[0]; + } + ret |= ProcessKey(newEvent); + break; + } + + Status status; + int len; + len = Xutf8LookupString(m_xic, &xevent.xkey, + m_keybuf, m_keybuf_len, + &xkeysym, &status); + if (status == XBufferOverflow) + { + m_keybuf_len = len; + m_keybuf = (char*)realloc(m_keybuf, m_keybuf_len); + if (m_keybuf == nullptr) + throw std::runtime_error("Failed to realloc memory, insufficient memory available"); + len = Xutf8LookupString(m_xic, &xevent.xkey, + m_keybuf, m_keybuf_len, + &xkeysym, &status); + } + switch (status) + { + case XLookupNone: + break; + case XLookupChars: + case XLookupBoth: + { + std::string data(m_keybuf, len); + std::wstring keys; + g_charsetConverter.utf8ToW(data, keys, false); + + if (keys.length() == 0) + { + break; + } + + for (unsigned int i = 0; i < keys.length() - 1; i++) + { + newEvent.key.keysym.sym = XBMCK_UNKNOWN; + newEvent.key.keysym.unicode = keys[i]; + ret |= ProcessKey(newEvent); + } + if (keys.length() > 0) + { + newEvent.key.keysym.scancode = xevent.xkey.keycode; + XLookupString(&xevent.xkey, NULL, 0, &xkeysym, NULL); + newEvent.key.keysym.sym = LookupXbmcKeySym(xkeysym); + newEvent.key.keysym.unicode = keys[keys.length() - 1]; + + ret |= ProcessKey(newEvent); + } + break; + } + + case XLookupKeySym: + { + newEvent.key.keysym.scancode = xevent.xkey.keycode; + newEvent.key.keysym.sym = LookupXbmcKeySym(xkeysym); + ret |= ProcessKey(newEvent); + break; + } + + }// switch status + break; + } //KeyPress + + case KeyRelease: + { + // if we have a queued press directly after, this is a repeat + if (XEventsQueued(m_display, QueuedAfterReading)) + { + XEvent next_event; + XPeekEvent(m_display, &next_event); + if (next_event.type == KeyPress && + next_event.xkey.window == xevent.xkey.window && + next_event.xkey.keycode == xevent.xkey.keycode && + (next_event.xkey.time - xevent.xkey.time < 2)) + continue; + } + + XBMC_Event newEvent = {}; + KeySym xkeysym; + newEvent.type = XBMC_KEYUP; + xkeysym = XLookupKeysym(&xevent.xkey, 0); + newEvent.key.keysym.scancode = xevent.xkey.keycode; + newEvent.key.keysym.sym = LookupXbmcKeySym(xkeysym); + ret |= ProcessKey(newEvent); + break; + } + + case EnterNotify: + { + break; + } + + // lose mouse coverage + case LeaveNotify: + { + CServiceBroker::GetInputManager().SetMouseActive(false); + break; + } + + case MotionNotify: + { + if (xevent.xmotion.window != m_window) + break; + XBMC_Event newEvent = {}; + newEvent.type = XBMC_MOUSEMOTION; + newEvent.motion.x = (int16_t)xevent.xmotion.x; + newEvent.motion.y = (int16_t)xevent.xmotion.y; + if (appPort) + ret |= appPort->OnEvent(newEvent); + break; + } + + case ButtonPress: + { + XBMC_Event newEvent = {}; + newEvent.type = XBMC_MOUSEBUTTONDOWN; + newEvent.button.button = (unsigned char)xevent.xbutton.button; + newEvent.button.x = (int16_t)xevent.xbutton.x; + newEvent.button.y = (int16_t)xevent.xbutton.y; + if (appPort) + ret |= appPort->OnEvent(newEvent); + break; + } + + case ButtonRelease: + { + XBMC_Event newEvent = {}; + newEvent.type = XBMC_MOUSEBUTTONUP; + newEvent.button.button = (unsigned char)xevent.xbutton.button; + newEvent.button.x = (int16_t)xevent.xbutton.x; + newEvent.button.y = (int16_t)xevent.xbutton.y; + if (appPort) + ret |= appPort->OnEvent(newEvent); + break; + } + + default: + { + break; + } + }// switch event.type + }// while + + if (m_display && m_xrrEventPending && m_xrrFailSafeTimer.IsTimePast()) + { + CLog::Log(LOGERROR,"CWinEventsX11::MessagePump - missed XRR Events"); + m_winSystem.NotifyXRREvent(); + m_xrrEventPending = false; + } + + return ret; +} + +bool CWinEventsX11::ProcessKey(XBMC_Event &event) +{ + if (event.type == XBMC_KEYDOWN) + { + // check key modifiers + switch(event.key.keysym.sym) + { + case XBMCK_LSHIFT: + m_keymodState |= XBMCKMOD_LSHIFT; + break; + case XBMCK_RSHIFT: + m_keymodState |= XBMCKMOD_RSHIFT; + break; + case XBMCK_LCTRL: + m_keymodState |= XBMCKMOD_LCTRL; + break; + case XBMCK_RCTRL: + m_keymodState |= XBMCKMOD_RCTRL; + break; + case XBMCK_LALT: + m_keymodState |= XBMCKMOD_LALT; + break; + case XBMCK_RALT: + m_keymodState |= XBMCKMOD_RCTRL; + break; + case XBMCK_LMETA: + m_keymodState |= XBMCKMOD_LMETA; + break; + case XBMCK_RMETA: + m_keymodState |= XBMCKMOD_RMETA; + break; + case XBMCK_MODE: + m_keymodState |= XBMCKMOD_MODE; + break; + default: + break; + } + event.key.keysym.mod = (XBMCMod)m_keymodState; + } + else if (event.type == XBMC_KEYUP) + { + switch(event.key.keysym.sym) + { + case XBMCK_LSHIFT: + m_keymodState &= ~XBMCKMOD_LSHIFT; + break; + case XBMCK_RSHIFT: + m_keymodState &= ~XBMCKMOD_RSHIFT; + break; + case XBMCK_LCTRL: + m_keymodState &= ~XBMCKMOD_LCTRL; + break; + case XBMCK_RCTRL: + m_keymodState &= ~XBMCKMOD_RCTRL; + break; + case XBMCK_LALT: + m_keymodState &= ~XBMCKMOD_LALT; + break; + case XBMCK_RALT: + m_keymodState &= ~XBMCKMOD_RCTRL; + break; + case XBMCK_LMETA: + m_keymodState &= ~XBMCKMOD_LMETA; + break; + case XBMCK_RMETA: + m_keymodState &= ~XBMCKMOD_RMETA; + break; + case XBMCK_MODE: + m_keymodState &= ~XBMCKMOD_MODE; + break; + default: + break; + } + event.key.keysym.mod = (XBMCMod)m_keymodState; + } + + std::shared_ptr appPort = CServiceBroker::GetAppPort(); + if (appPort) + appPort->OnEvent(event); + return true; +} + +XBMCKey CWinEventsX11::LookupXbmcKeySym(KeySym keysym) +{ + // try direct mapping first + std::map::iterator it; + it = m_symLookupTable.find(keysym); + if (it != m_symLookupTable.end()) + { + return (XBMCKey)(it->second); + } + + // try ascii mappings + if (keysym>>8 == 0x00) + return (XBMCKey)tolower(keysym & 0xFF); + + return (XBMCKey)keysym; +} diff --git a/xbmc/windowing/X11/WinEventsX11.h b/xbmc/windowing/X11/WinEventsX11.h new file mode 100644 index 0000000..b2111da --- /dev/null +++ b/xbmc/windowing/X11/WinEventsX11.h @@ -0,0 +1,60 @@ +/* + * 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. + */ + +#pragma once + +#include "threads/SystemClock.h" +#include "windowing/WinEvents.h" +#include "windowing/X11/WinSystemX11.h" + +#include +#include + +#include + +namespace KODI +{ +namespace WINDOWING +{ +namespace X11 +{ + +class CWinEventsX11 : public IWinEvents +{ +public: + CWinEventsX11(CWinSystemX11& winSystem); + ~CWinEventsX11() override; + bool MessagePump() override; + bool Init(Display *dpy, Window win); + void Quit(); + bool HasStructureChanged(); + void PendingResize(int width, int height); + void SetXRRFailSafeTimer(std::chrono::milliseconds duration); + +protected: + XBMCKey LookupXbmcKeySym(KeySym keysym); + bool ProcessKey(XBMC_Event &event); + Display *m_display = nullptr; + Window m_window = 0; + Atom m_wmDeleteMessage; + char *m_keybuf = nullptr; + size_t m_keybuf_len = 0; + XIM m_xim = nullptr; + XIC m_xic = nullptr; + std::map m_symLookupTable; + int m_keymodState; + bool m_structureChanged; + int m_RREventBase; + XbmcThreads::EndTime<> m_xrrFailSafeTimer; + bool m_xrrEventPending; + CWinSystemX11& m_winSystem; +}; + +} +} +} diff --git a/xbmc/windowing/X11/WinSystemX11.cpp b/xbmc/windowing/X11/WinSystemX11.cpp new file mode 100644 index 0000000..43a8ebb --- /dev/null +++ b/xbmc/windowing/X11/WinSystemX11.cpp @@ -0,0 +1,1081 @@ +/* + * 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 "WinSystemX11.h" + +#include "CompileInfo.h" +#include "OSScreenSaverX11.h" +#include "ServiceBroker.h" +#include "WinEventsX11.h" +#include "XRandR.h" +#include "guilib/DispResource.h" +#include "guilib/Texture.h" +#include "input/InputManager.h" +#include "messaging/ApplicationMessenger.h" +#include "settings/DisplaySettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "settings/lib/Setting.h" +#include "utils/StringUtils.h" +#include "utils/TimeUtils.h" +#include "utils/log.h" +#include "windowing/GraphicContext.h" + +#include +#include +#include + +#include +#include + +using namespace KODI::WINDOWING::X11; + +using namespace std::chrono_literals; + +#define EGL_NO_CONFIG (EGLConfig)0 + +CWinSystemX11::CWinSystemX11() : CWinSystemBase() +{ + m_dpy = NULL; + m_bWasFullScreenBeforeMinimize = false; + m_minimized = false; + m_bIgnoreNextFocusMessage = false; + m_bIsInternalXrr = false; + m_delayDispReset = false; + + XSetErrorHandler(XErrorHandler); + + m_winEventsX11 = new CWinEventsX11(*this); + m_winEvents.reset(m_winEventsX11); +} + +CWinSystemX11::~CWinSystemX11() = default; + +bool CWinSystemX11::InitWindowSystem() +{ + const char* env = getenv("DISPLAY"); + if (!env) + { + CLog::Log(LOGDEBUG, "CWinSystemX11::{} - DISPLAY env not set", __FUNCTION__); + return false; + } + + if ((m_dpy = XOpenDisplay(NULL))) + { + bool ret = CWinSystemBase::InitWindowSystem(); + + CServiceBroker::GetSettingsComponent() + ->GetSettings() + ->GetSetting(CSettings::SETTING_VIDEOSCREEN_LIMITEDRANGE) + ->SetVisible(true); + + return ret; + } + else + CLog::Log(LOGERROR, "X11 Error: No Display found"); + + return false; +} + +bool CWinSystemX11::DestroyWindowSystem() +{ + //restore desktop resolution on exit + if (m_bFullScreen) + { + XOutput out; + XMode mode; + out.name = CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP).strOutput; + mode.w = CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP).iWidth; + mode.h = CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP).iHeight; + mode.hz = CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP).fRefreshRate; + mode.id = CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP).strId; + g_xrandr.SetMode(out, mode); + } + + return true; +} + +bool CWinSystemX11::CreateNewWindow(const std::string& name, bool fullScreen, RESOLUTION_INFO& res) +{ + if(!SetFullScreen(fullScreen, res, false)) + return false; + + m_bWindowCreated = true; + return true; +} + +bool CWinSystemX11::DestroyWindow() +{ + if (!m_mainWindow) + return true; + + if (m_invisibleCursor) + { + XUndefineCursor(m_dpy, m_mainWindow); + XFreeCursor(m_dpy, m_invisibleCursor); + m_invisibleCursor = 0; + } + + m_winEventsX11->Quit(); + + XUnmapWindow(m_dpy, m_mainWindow); + XDestroyWindow(m_dpy, m_glWindow); + XDestroyWindow(m_dpy, m_mainWindow); + m_glWindow = 0; + m_mainWindow = 0; + + if (m_icon) + XFreePixmap(m_dpy, m_icon); + + return true; +} + +bool CWinSystemX11::ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop) +{ + m_userOutput = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_VIDEOSCREEN_MONITOR); + XOutput *out = NULL; + if (m_userOutput.compare("Default") != 0) + { + out = g_xrandr.GetOutput(m_userOutput); + if (out) + { + XMode mode = g_xrandr.GetCurrentMode(m_userOutput); + if (!mode.isCurrent) + { + out = NULL; + } + } + } + if (!out) + { + std::vector outputs = g_xrandr.GetModes(); + if (!outputs.empty()) + { + m_userOutput = outputs[0].name; + } + } + + if (!SetWindow(newWidth, newHeight, false, m_userOutput)) + { + return false; + } + + m_nWidth = newWidth; + m_nHeight = newHeight; + m_bFullScreen = false; + m_currentOutput = m_userOutput; + + return true; +} + +void CWinSystemX11::FinishWindowResize(int newWidth, int newHeight) +{ + m_userOutput = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_VIDEOSCREEN_MONITOR); + XOutput *out = NULL; + if (m_userOutput.compare("Default") != 0) + { + out = g_xrandr.GetOutput(m_userOutput); + if (out) + { + XMode mode = g_xrandr.GetCurrentMode(m_userOutput); + if (!mode.isCurrent) + { + out = NULL; + } + } + } + if (!out) + { + std::vector outputs = g_xrandr.GetModes(); + if (!outputs.empty()) + { + m_userOutput = outputs[0].name; + } + } + + XResizeWindow(m_dpy, m_glWindow, newWidth, newHeight); + UpdateCrtc(); + + if (m_userOutput.compare(m_currentOutput) != 0) + { + SetWindow(newWidth, newHeight, false, m_userOutput); + } + + m_nWidth = newWidth; + m_nHeight = newHeight; +} + +bool CWinSystemX11::SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) +{ + XOutput out; + XMode mode; + + if (fullScreen) + { + out.name = res.strOutput; + mode.w = res.iWidth; + mode.h = res.iHeight; + mode.hz = res.fRefreshRate; + mode.id = res.strId; + } + else + { + out.name = CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP).strOutput; + mode.w = CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP).iWidth; + mode.h = CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP).iHeight; + mode.hz = CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP).fRefreshRate; + mode.id = CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP).strId; + } + + XMode currmode = g_xrandr.GetCurrentMode(out.name); + if (!currmode.name.empty()) + { + // flip h/w when rotated + if (m_bIsRotated) + { + int w = mode.w; + mode.w = mode.h; + mode.h = w; + } + + // only call xrandr if mode changes + if (m_mainWindow) + { + if (currmode.w != mode.w || currmode.h != mode.h || + currmode.hz != mode.hz || currmode.id != mode.id) + { + CLog::Log(LOGINFO, "CWinSystemX11::SetFullScreen - calling xrandr"); + + // remember last position of mouse + Window root_return, child_return; + int root_x_return, root_y_return; + int win_x_return, win_y_return; + unsigned int mask_return; + bool isInWin = XQueryPointer(m_dpy, m_mainWindow, &root_return, &child_return, + &root_x_return, &root_y_return, + &win_x_return, &win_y_return, + &mask_return); + + if (isInWin) + { + m_MouseX = win_x_return; + m_MouseY = win_y_return; + } + else + { + m_MouseX = -1; + m_MouseY = -1; + } + + OnLostDevice(); + m_bIsInternalXrr = true; + g_xrandr.SetMode(out, mode); + auto delay = + std::chrono::milliseconds(CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt( + "videoscreen.delayrefreshchange") * + 100); + if (delay > 0ms) + { + m_delayDispReset = true; + m_dispResetTimer.Set(delay); + } + return true; + } + } + } + + if (!SetWindow(res.iWidth, res.iHeight, fullScreen, m_userOutput)) + return false; + + m_nWidth = res.iWidth; + m_nHeight = res.iHeight; + m_bFullScreen = fullScreen; + m_currentOutput = m_userOutput; + + return true; +} + +void CWinSystemX11::UpdateResolutions() +{ + CWinSystemBase::UpdateResolutions(); + + int numScreens = XScreenCount(m_dpy); + g_xrandr.SetNumScreens(numScreens); + + const std::shared_ptr settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + bool switchOnOff = settings->GetBool(CSettings::SETTING_VIDEOSCREEN_BLANKDISPLAYS); + m_userOutput = settings->GetString(CSettings::SETTING_VIDEOSCREEN_MONITOR); + if (m_userOutput.compare("Default") == 0) + switchOnOff = false; + + if(g_xrandr.Query(true, !switchOnOff)) + { + XOutput *out = NULL; + if (m_userOutput.compare("Default") != 0) + { + out = g_xrandr.GetOutput(m_userOutput); + if (out) + { + XMode mode = g_xrandr.GetCurrentMode(m_userOutput); + if (!mode.isCurrent && !switchOnOff) + { + out = NULL; + } + } + } + if (!out) + { + m_userOutput = g_xrandr.GetModes()[0].name; + out = g_xrandr.GetOutput(m_userOutput); + } + + if (switchOnOff) + { + // switch on output + g_xrandr.TurnOnOutput(m_userOutput); + + // switch off other outputs + std::vector outputs = g_xrandr.GetModes(); + for (size_t i=0; iisRotated; + if (!m_bIsRotated) + UpdateDesktopResolution(CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP), out->name, mode.w, mode.h, mode.hz, 0); + else + UpdateDesktopResolution(CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP), out->name, mode.h, mode.w, mode.hz, 0); + CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP).strId = mode.id; + } + else + { + m_userOutput = "No Output"; + m_screen = DefaultScreen(m_dpy); + int w = DisplayWidth(m_dpy, m_screen); + int h = DisplayHeight(m_dpy, m_screen); + UpdateDesktopResolution(CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP), m_userOutput, w, h, 0.0, 0); + } + + // erase previous stored modes + CDisplaySettings::GetInstance().ClearCustomResolutions(); + + CLog::Log(LOGINFO, "Available videomodes (xrandr):"); + + XOutput *out = g_xrandr.GetOutput(m_userOutput); + if (out != NULL) + { + CLog::Log(LOGINFO, "Output '{}' has {} modes", out->name, out->modes.size()); + + for (auto mode : out->modes) + { + CLog::Log(LOGINFO, "ID:{} Name:{} Refresh:{:f} Width:{} Height:{}", mode.id, mode.name, + mode.hz, mode.w, mode.h); + RESOLUTION_INFO res; + res.dwFlags = 0; + + if (mode.IsInterlaced()) + res.dwFlags |= D3DPRESENTFLAG_INTERLACED; + + if (!m_bIsRotated) + { + res.iWidth = mode.w; + res.iHeight = mode.h; + res.iScreenWidth = mode.w; + res.iScreenHeight = mode.h; + } + else + { + res.iWidth = mode.h; + res.iHeight = mode.w; + res.iScreenWidth = mode.h; + res.iScreenHeight = mode.w; + } + + if (mode.h > 0 && mode.w > 0 && out->hmm > 0 && out->wmm > 0) + res.fPixelRatio = ((float)out->wmm/(float)mode.w) / (((float)out->hmm/(float)mode.h)); + else + res.fPixelRatio = 1.0f; + + CLog::Log(LOGINFO, "Pixel Ratio: {:f}", res.fPixelRatio); + + res.strMode = StringUtils::Format("{}: {} @ {:.2f}Hz", out->name, mode.name, mode.hz); + res.strOutput = out->name; + res.strId = mode.id; + res.iSubtitles = mode.h; + res.fRefreshRate = mode.hz; + res.bFullScreen = true; + + CServiceBroker::GetWinSystem()->GetGfxContext().ResetOverscan(res); + CDisplaySettings::GetInstance().AddResolutionInfo(res); + } + } + CDisplaySettings::GetInstance().ApplyCalibrations(); +} + +bool CWinSystemX11::HasCalibration(const RESOLUTION_INFO &resInfo) +{ + XOutput *out = g_xrandr.GetOutput(m_currentOutput); + + // keep calibrations done on a not connected output + if (!StringUtils::EqualsNoCase(out->name, resInfo.strOutput)) + return true; + + // keep calibrations not updated with resolution data + if (resInfo.iWidth == 0) + return true; + + float fPixRatio; + if (resInfo.iHeight>0 && resInfo.iWidth>0 && out->hmm>0 && out->wmm>0) + fPixRatio = ((float)out->wmm/(float)resInfo.iWidth) / (((float)out->hmm/(float)resInfo.iHeight)); + else + fPixRatio = 1.0f; + + if (resInfo.Overscan.left != 0) + return true; + if (resInfo.Overscan.top != 0) + return true; + if (resInfo.Overscan.right != resInfo.iWidth) + return true; + if (resInfo.Overscan.bottom != resInfo.iHeight) + return true; + if (resInfo.fPixelRatio != fPixRatio) + return true; + if (resInfo.iSubtitles != resInfo.iHeight) + return true; + + return false; +} + +bool CWinSystemX11::UseLimitedColor() +{ + return CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOSCREEN_LIMITEDRANGE); +} + +std::vector CWinSystemX11::GetConnectedOutputs() +{ + std::vector outputs; + std::vector outs; + g_xrandr.Query(true); + outs = g_xrandr.GetModes(); + outputs.emplace_back("Default"); + for(unsigned int i=0; i CWinSystemX11::GetOSScreenSaverImpl() +{ + std::unique_ptr ret; + if (m_dpy) + { + ret.reset(new COSScreenSaverX11(m_dpy)); + } + return ret; +} + +void CWinSystemX11::NotifyAppActiveChange(bool bActivated) +{ + if (bActivated && m_bWasFullScreenBeforeMinimize && !m_bFullScreen) + { + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_TOGGLEFULLSCREEN); + + m_bWasFullScreenBeforeMinimize = false; + } + m_minimized = !bActivated; +} + +void CWinSystemX11::NotifyAppFocusChange(bool bGaining) +{ + if (bGaining && m_bWasFullScreenBeforeMinimize && !m_bIgnoreNextFocusMessage && + !m_bFullScreen) + { + m_bWasFullScreenBeforeMinimize = false; + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_TOGGLEFULLSCREEN); + m_minimized = false; + } + if (!bGaining) + m_bIgnoreNextFocusMessage = false; +} + +bool CWinSystemX11::Minimize() +{ + m_bWasFullScreenBeforeMinimize = m_bFullScreen; + if (m_bWasFullScreenBeforeMinimize) + { + m_bIgnoreNextFocusMessage = true; + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_TOGGLEFULLSCREEN); + } + + XIconifyWindow(m_dpy, m_mainWindow, m_screen); + + m_minimized = true; + return true; +} +bool CWinSystemX11::Restore() +{ + return false; +} +bool CWinSystemX11::Hide() +{ + XUnmapWindow(m_dpy, m_mainWindow); + XFlush(m_dpy); + return true; +} +bool CWinSystemX11::Show(bool raise) +{ + XMapWindow(m_dpy, m_mainWindow); + XFlush(m_dpy); + m_minimized = false; + return true; +} + +void CWinSystemX11::NotifyXRREvent() +{ + CLog::Log(LOGDEBUG, "{} - notify display reset event", __FUNCTION__); + + std::unique_lock lock(CServiceBroker::GetWinSystem()->GetGfxContext()); + + if (!g_xrandr.Query(true)) + { + CLog::Log(LOGERROR, "WinSystemX11::RefreshWindow - failed to query xrandr"); + return; + } + + // if external event update resolutions + if (!m_bIsInternalXrr) + { + UpdateResolutions(); + } + + RecreateWindow(); +} + +void CWinSystemX11::RecreateWindow() +{ + m_windowDirty = true; + + std::unique_lock lock(CServiceBroker::GetWinSystem()->GetGfxContext()); + + XOutput *out = g_xrandr.GetOutput(m_userOutput); + XMode mode = g_xrandr.GetCurrentMode(m_userOutput); + + if (out) + CLog::Log(LOGDEBUG, "{} - current output: {}, mode: {}, refresh: {:.3f}", __FUNCTION__, + out->name, mode.id, mode.hz); + else + CLog::Log(LOGWARNING, "{} - output name not set", __FUNCTION__); + + RESOLUTION_INFO res; + unsigned int i; + bool found(false); + for (i = RES_DESKTOP; i < CDisplaySettings::GetInstance().ResolutionInfoSize(); ++i) + { + res = CDisplaySettings::GetInstance().GetResolutionInfo(i); + if (StringUtils::EqualsNoCase(CDisplaySettings::GetInstance().GetResolutionInfo(i).strId, mode.id)) + { + found = true; + break; + } + } + + if (!found) + { + CLog::Log(LOGERROR, "CWinSystemX11::RecreateWindow - could not find resolution"); + i = RES_DESKTOP; + } + + if (CServiceBroker::GetWinSystem()->GetGfxContext().IsFullScreenRoot()) + CServiceBroker::GetWinSystem()->GetGfxContext().SetVideoResolution((RESOLUTION)i, true); + else + CServiceBroker::GetWinSystem()->GetGfxContext().SetVideoResolution(RES_WINDOW, true); +} + +void CWinSystemX11::OnLostDevice() +{ + CLog::Log(LOGDEBUG, "{} - notify display change event", __FUNCTION__); + + { + std::unique_lock lock(m_resourceSection); + for (std::vector::iterator i = m_resources.begin(); i != m_resources.end(); ++i) + (*i)->OnLostDisplay(); + } + + m_winEventsX11->SetXRRFailSafeTimer(3s); +} + +void CWinSystemX11::Register(IDispResource *resource) +{ + std::unique_lock lock(m_resourceSection); + m_resources.push_back(resource); +} + +void CWinSystemX11::Unregister(IDispResource* resource) +{ + std::unique_lock lock(m_resourceSection); + std::vector::iterator i = find(m_resources.begin(), m_resources.end(), resource); + if (i != m_resources.end()) + m_resources.erase(i); +} + +int CWinSystemX11::XErrorHandler(Display* dpy, XErrorEvent* error) +{ + char buf[1024]; + XGetErrorText(error->display, error->error_code, buf, sizeof(buf)); + CLog::Log(LOGERROR, + "CWinSystemX11::XErrorHandler: {}, type:{}, serial:{}, error_code:{}, request_code:{} " + "minor_code:{}", + buf, error->type, error->serial, (int)error->error_code, (int)error->request_code, + (int)error->minor_code); + + return 0; +} + +bool CWinSystemX11::SetWindow(int width, int height, bool fullscreen, const std::string &output, int *winstate) +{ + bool changeWindow = false; + bool changeSize = false; + float mouseX = 0.5; + float mouseY = 0.5; + + if (!m_mainWindow) + { + CServiceBroker::GetInputManager().SetMouseActive(false); + } + + if (m_mainWindow && ((m_bFullScreen != fullscreen) || m_currentOutput.compare(output) != 0 || m_windowDirty)) + { + // set mouse to last known position + // we can't trust values after an xrr event + if (m_bIsInternalXrr && m_MouseX >= 0 && m_MouseY >= 0) + { + mouseX = (float)m_MouseX/m_nWidth; + mouseY = (float)m_MouseY/m_nHeight; + } + else if (!m_windowDirty) + { + Window root_return, child_return; + int root_x_return, root_y_return; + int win_x_return, win_y_return; + unsigned int mask_return; + bool isInWin = XQueryPointer(m_dpy, m_mainWindow, &root_return, &child_return, + &root_x_return, &root_y_return, + &win_x_return, &win_y_return, + &mask_return); + + if (isInWin) + { + mouseX = (float)win_x_return/m_nWidth; + mouseY = (float)win_y_return/m_nHeight; + } + } + + CServiceBroker::GetInputManager().SetMouseActive(false); + OnLostDevice(); + DestroyWindow(); + m_windowDirty = true; + } + + // create main window + if (!m_mainWindow) + { + Colormap cmap; + XSetWindowAttributes swa; + XVisualInfo *vi; + int x0 = 0; + int y0 = 0; + + XOutput *out = g_xrandr.GetOutput(output); + if (!out) + out = g_xrandr.GetOutput(m_currentOutput); + if (out) + { + m_screen = out->screen; + x0 = out->x; + y0 = out->y; + } + + vi = GetVisual(); + if (!vi) + { + CLog::Log(LOGERROR, "Failed to find matching visual"); + return false; + } + + cmap = XCreateColormap(m_dpy, RootWindow(m_dpy, vi->screen), vi->visual, AllocNone); + + bool hasWM = HasWindowManager(); + + int def_vis = (vi->visual == DefaultVisual(m_dpy, vi->screen)); + swa.override_redirect = hasWM ? False : True; + swa.border_pixel = fullscreen ? 0 : 5; + swa.background_pixel = def_vis ? BlackPixel(m_dpy, vi->screen) : 0; + swa.colormap = cmap; + swa.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask | + ButtonPressMask | ButtonReleaseMask | PointerMotionMask | + PropertyChangeMask | StructureNotifyMask | KeymapStateMask | + EnterWindowMask | LeaveWindowMask | ExposureMask; + unsigned long mask = CWBackPixel | CWBorderPixel | CWColormap | CWOverrideRedirect | CWEventMask; + + m_mainWindow = XCreateWindow(m_dpy, RootWindow(m_dpy, vi->screen), + x0, y0, width, height, 0, vi->depth, + InputOutput, vi->visual, + mask, &swa); + + swa.override_redirect = False; + swa.border_pixel = 0; + swa.event_mask = ExposureMask; + mask = CWBackPixel | CWBorderPixel | CWColormap | CWOverrideRedirect | CWColormap | CWEventMask; + + m_glWindow = XCreateWindow(m_dpy, m_mainWindow, + 0, 0, width, height, 0, vi->depth, + InputOutput, vi->visual, + mask, &swa); + + if (fullscreen && hasWM) + { + Atom fs = XInternAtom(m_dpy, "_NET_WM_STATE_FULLSCREEN", True); + XChangeProperty(m_dpy, m_mainWindow, XInternAtom(m_dpy, "_NET_WM_STATE", True), XA_ATOM, 32, PropModeReplace, (unsigned char *) &fs, 1); + // disable desktop compositing for KDE, when Kodi is in full-screen mode + int one = 1; + Atom composite = XInternAtom(m_dpy, "_KDE_NET_WM_BLOCK_COMPOSITING", True); + if (composite != None) + { + XChangeProperty(m_dpy, m_mainWindow, XInternAtom(m_dpy, "_KDE_NET_WM_BLOCK_COMPOSITING", True), XA_CARDINAL, 32, + PropModeReplace, (unsigned char*) &one, 1); + } + composite = XInternAtom(m_dpy, "_NET_WM_BYPASS_COMPOSITOR", True); + if (composite != None) + { + // standard way for Gnome 3 + XChangeProperty(m_dpy, m_mainWindow, XInternAtom(m_dpy, "_NET_WM_BYPASS_COMPOSITOR", True), XA_CARDINAL, 32, + PropModeReplace, (unsigned char*) &one, 1); + } + } + + // define invisible cursor + Pixmap bitmapNoData; + XColor black; + static char noData[] = { 0,0,0,0,0,0,0,0 }; + black.red = black.green = black.blue = 0; + + bitmapNoData = XCreateBitmapFromData(m_dpy, m_mainWindow, noData, 8, 8); + m_invisibleCursor = XCreatePixmapCursor(m_dpy, bitmapNoData, bitmapNoData, + &black, &black, 0, 0); + XFreePixmap(m_dpy, bitmapNoData); + XDefineCursor(m_dpy,m_mainWindow, m_invisibleCursor); + XFree(vi); + + //init X11 events + m_winEventsX11->Init(m_dpy, m_mainWindow); + + changeWindow = true; + changeSize = true; + } + + if (!m_winEventsX11->HasStructureChanged() && ((width != m_nWidth) || (height != m_nHeight))) + { + changeSize = true; + } + + if (changeSize || changeWindow) + { + XResizeWindow(m_dpy, m_mainWindow, width, height); + } + + if ((width != m_nWidth) || (height != m_nHeight) || changeWindow) + { + XResizeWindow(m_dpy, m_glWindow, width, height); + } + + if (changeWindow) + { + m_icon = None; + { + CreateIconPixmap(); + XWMHints *wm_hints; + XClassHint *class_hints; + XTextProperty windowName, iconName; + + std::string titleString = CCompileInfo::GetAppName(); + const std::string& classString = titleString; + char *title = const_cast(titleString.c_str()); + + XStringListToTextProperty(&title, 1, &windowName); + XStringListToTextProperty(&title, 1, &iconName); + + wm_hints = XAllocWMHints(); + wm_hints->initial_state = NormalState; + wm_hints->icon_pixmap = m_icon; + wm_hints->flags = StateHint | IconPixmapHint; + + class_hints = XAllocClassHint(); + class_hints->res_class = const_cast(classString.c_str()); + class_hints->res_name = const_cast(classString.c_str()); + + XSetWMProperties(m_dpy, m_mainWindow, &windowName, &iconName, + NULL, 0, NULL, wm_hints, + class_hints); + XFree(class_hints); + XFree(wm_hints); + XFree(iconName.value); + XFree(windowName.value); + + // register interest in the delete window message + Atom wmDeleteMessage = XInternAtom(m_dpy, "WM_DELETE_WINDOW", False); + XSetWMProtocols(m_dpy, m_mainWindow, &wmDeleteMessage, 1); + } + + // placement of window may follow mouse + XWarpPointer(m_dpy, None, m_mainWindow, 0, 0, 0, 0, mouseX*width, mouseY*height); + + XMapRaised(m_dpy, m_glWindow); + XMapRaised(m_dpy, m_mainWindow); + + // discard events generated by creating the window, i.e. xrr events + XSync(m_dpy, True); + + if (winstate) + *winstate = 1; + } + + UpdateCrtc(); + + return true; +} + +bool CWinSystemX11::CreateIconPixmap() +{ + int depth; + XImage *img = NULL; + Visual *vis; + XWindowAttributes wndattribs; + XVisualInfo visInfo; + double rRatio; + double gRatio; + double bRatio; + int outIndex = 0; + unsigned int i,j; + unsigned char *buf; + uint32_t *newBuf = 0; + size_t numNewBufBytes; + + // Get visual Info + XGetWindowAttributes(m_dpy, m_glWindow, &wndattribs); + visInfo.visualid = wndattribs.visual->visualid; + int nvisuals = 0; + XVisualInfo* visuals = XGetVisualInfo(m_dpy, VisualIDMask, &visInfo, &nvisuals); + if (nvisuals != 1) + { + CLog::Log(LOGERROR, "CWinSystemX11::CreateIconPixmap - could not find visual"); + return false; + } + visInfo = visuals[0]; + XFree(visuals); + + depth = visInfo.depth; + vis = visInfo.visual; + + if (depth < 15) + { + CLog::Log(LOGERROR, "CWinSystemX11::CreateIconPixmap - no suitable depth"); + return false; + } + + rRatio = vis->red_mask / 255.0; + gRatio = vis->green_mask / 255.0; + bRatio = vis->blue_mask / 255.0; + + std::unique_ptr iconTexture = + CTexture::LoadFromFile("special://xbmc/media/icon256x256.png"); + + if (!iconTexture) + return false; + + buf = iconTexture->GetPixels(); + + if (depth>=24) + numNewBufBytes = (4 * (iconTexture->GetWidth() * iconTexture->GetHeight())); + else + numNewBufBytes = (2 * (iconTexture->GetWidth() * iconTexture->GetHeight())); + + newBuf = (uint32_t*)malloc(numNewBufBytes); + if (!newBuf) + { + CLog::Log(LOGERROR, "CWinSystemX11::CreateIconPixmap - malloc failed"); + return false; + } + + for (i=0; iGetHeight();++i) + { + for (j=0; jGetWidth();++j) + { + unsigned int pos = i*iconTexture->GetPitch()+j*4; + unsigned int r, g, b; + r = (buf[pos+2] * rRatio); + g = (buf[pos+1] * gRatio); + b = (buf[pos+0] * bRatio); + r &= vis->red_mask; + g &= vis->green_mask; + b &= vis->blue_mask; + newBuf[outIndex] = r | g | b; + ++outIndex; + } + } + img = XCreateImage(m_dpy, vis, depth,ZPixmap, 0, (char *)newBuf, + iconTexture->GetWidth(), iconTexture->GetHeight(), + (depth>=24)?32:16, 0); + if (!img) + { + CLog::Log(LOGERROR, "CWinSystemX11::CreateIconPixmap - could not create image"); + free(newBuf); + return false; + } + if (!XInitImage(img)) + { + CLog::Log(LOGERROR, "CWinSystemX11::CreateIconPixmap - init image failed"); + XDestroyImage(img); + return false; + } + + // set byte order + union + { + char c[sizeof(short)]; + short s; + } order; + order.s = 1; + if ((1 == order.c[0])) + { + img->byte_order = LSBFirst; + } + else + { + img->byte_order = MSBFirst; + } + + // create icon pixmap from image + m_icon = XCreatePixmap(m_dpy, m_glWindow, img->width, img->height, depth); + GC gc = XCreateGC(m_dpy, m_glWindow, 0, NULL); + XPutImage(m_dpy, m_icon, gc, img, 0, 0, 0, 0, img->width, img->height); + XFreeGC(m_dpy, gc); + XDestroyImage(img); // this also frees newBuf + + return true; +} + +bool CWinSystemX11::HasWindowManager() +{ + Window wm_check; + unsigned char *data; + int status, real_format; + Atom real_type, prop; + unsigned long items_read, items_left; + + prop = XInternAtom(m_dpy, "_NET_SUPPORTING_WM_CHECK", True); + if (prop == None) + return false; + status = XGetWindowProperty(m_dpy, DefaultRootWindow(m_dpy), prop, + 0L, 1L, False, XA_WINDOW, &real_type, &real_format, + &items_read, &items_left, &data); + if(status != Success || ! items_read) + { + if(status == Success) + XFree(data); + return false; + } + + wm_check = ((Window*)data)[0]; + XFree(data); + + status = XGetWindowProperty(m_dpy, wm_check, prop, + 0L, 1L, False, XA_WINDOW, &real_type, &real_format, + &items_read, &items_left, &data); + + if(status != Success || !items_read) + { + if(status == Success) + XFree(data); + return false; + } + + if(wm_check != ((Window*)data)[0]) + { + XFree(data); + return false; + } + + XFree(data); + + prop = XInternAtom(m_dpy, "_NET_WM_NAME", True); + if (prop == None) + { + CLog::Log(LOGDEBUG,"Window Manager Name: "); + return true; + } + + status = XGetWindowProperty(m_dpy, wm_check, prop, + 0L, (~0L), False, AnyPropertyType, &real_type, &real_format, + &items_read, &items_left, &data); + + if(status == Success && items_read) + { + const char* s; + + s = reinterpret_cast(data); + CLog::Log(LOGDEBUG, "Window Manager Name: {}", s); + } + else + CLog::Log(LOGDEBUG,"Window Manager Name: "); + + if(status == Success) + XFree(data); + + return true; +} + +void CWinSystemX11::UpdateCrtc() +{ + XWindowAttributes winattr; + int posx, posy; + float fps = 0.0f; + Window child; + XGetWindowAttributes(m_dpy, m_mainWindow, &winattr); + XTranslateCoordinates(m_dpy, m_mainWindow, RootWindow(m_dpy, m_screen), winattr.x, winattr.y, + &posx, &posy, &child); + + m_crtc = g_xrandr.GetCrtc(posx+winattr.width/2, posy+winattr.height/2, fps); + CServiceBroker::GetWinSystem()->GetGfxContext().SetFPS(fps); +} + +bool CWinSystemX11::MessagePump() +{ + return m_winEvents->MessagePump(); +} diff --git a/xbmc/windowing/X11/WinSystemX11.h b/xbmc/windowing/X11/WinSystemX11.h new file mode 100644 index 0000000..f65538d --- /dev/null +++ b/xbmc/windowing/X11/WinSystemX11.h @@ -0,0 +1,117 @@ +/* + * 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. + */ + +#pragma once + +#include "settings/lib/ISettingCallback.h" +#include "threads/CriticalSection.h" +#include "threads/SystemClock.h" +#include "utils/Stopwatch.h" +#include "windowing/WinSystem.h" + +#include +#include + +#include +#include + +class IDispResource; + +namespace KODI +{ +namespace WINDOWING +{ +namespace X11 +{ + +class CWinEventsX11; + +class CWinSystemX11 : public CWinSystemBase +{ +public: + CWinSystemX11(); + ~CWinSystemX11() override; + + const std::string GetName() override { return "x11"; } + + // CWinSystemBase + bool InitWindowSystem() override; + bool DestroyWindowSystem() override; + bool CreateNewWindow(const std::string& name, bool fullScreen, RESOLUTION_INFO& res) override; + bool DestroyWindow() override; + bool ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop) override; + void FinishWindowResize(int newWidth, int newHeight) override; + bool SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) override; + void UpdateResolutions() override; + void ShowOSMouse(bool show) override; + + void NotifyAppActiveChange(bool bActivated) override; + void NotifyAppFocusChange(bool bGaining) override; + + bool Minimize() override; + bool Restore() override; + bool Hide() override; + bool Show(bool raise = true) override; + void Register(IDispResource *resource) override; + void Unregister(IDispResource *resource) override; + bool HasCalibration(const RESOLUTION_INFO &resInfo) override; + bool UseLimitedColor() override; + + std::vector GetConnectedOutputs() override; + + // Local to WinSystemX11 only + Display* GetDisplay() { return m_dpy; } + int GetScreen() { return m_screen; } + void NotifyXRREvent(); + bool IsCurrentOutput(const std::string& output); + void RecreateWindow(); + int GetCrtc() { return m_crtc; } + + // winevents override + bool MessagePump() override; + +protected: + std::unique_ptr GetOSScreenSaverImpl() override; + + virtual bool SetWindow(int width, int height, bool fullscreen, const std::string &output, int *winstate = NULL) = 0; + virtual XVisualInfo* GetVisual() = 0; + + void OnLostDevice(); + + Window m_glWindow = 0, m_mainWindow = 0; + int m_screen = 0; + Display *m_dpy; + Cursor m_invisibleCursor = 0; + Pixmap m_icon; + bool m_bIsRotated; + bool m_bWasFullScreenBeforeMinimize; + bool m_minimized; + bool m_bIgnoreNextFocusMessage; + CCriticalSection m_resourceSection; + std::vector m_resources; + bool m_delayDispReset; + XbmcThreads::EndTime<> m_dispResetTimer; + std::string m_currentOutput; + std::string m_userOutput; + bool m_windowDirty; + bool m_bIsInternalXrr; + int m_MouseX, m_MouseY; + int m_crtc; + CWinEventsX11 *m_winEventsX11; + +private: + bool IsSuitableVisual(XVisualInfo *vInfo); + static int XErrorHandler(Display* dpy, XErrorEvent* error); + bool CreateIconPixmap(); + bool HasWindowManager(); + void UpdateCrtc(); +}; + +} +} +} diff --git a/xbmc/windowing/X11/WinSystemX11GLContext.cpp b/xbmc/windowing/X11/WinSystemX11GLContext.cpp new file mode 100644 index 0000000..ad27aca --- /dev/null +++ b/xbmc/windowing/X11/WinSystemX11GLContext.cpp @@ -0,0 +1,358 @@ +/* + * 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 "WinSystemX11GLContext.h" + +#include "GLContextEGL.h" +#include "OptionalsReg.h" +#include "VideoSyncOML.h" +#include "X11DPMSSupport.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationSkinHandling.h" +#include "cores/RetroPlayer/process/X11/RPProcessInfoX11.h" +#include "cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGL.h" +#include "cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.h" +#include "cores/VideoPlayer/Process/X11/ProcessInfoX11.h" +#include "cores/VideoPlayer/VideoRenderers/LinuxRendererGL.h" +#include "cores/VideoPlayer/VideoRenderers/RenderFactory.h" +#include "guilib/DispResource.h" +#include "rendering/gl/ScreenshotSurfaceGL.h" +#include "windowing/GraphicContext.h" +#include "windowing/WindowSystemFactory.h" + +#include +#include + +#include +#include + +using namespace KODI; +using namespace KODI::WINDOWING::X11; + + +void CWinSystemX11GLContext::Register() +{ + KODI::WINDOWING::CWindowSystemFactory::RegisterWindowSystem(CreateWinSystem, "x11"); +} + +std::unique_ptr CWinSystemX11GLContext::CreateWinSystem() +{ + return std::make_unique(); +} + +CWinSystemX11GLContext::~CWinSystemX11GLContext() +{ + delete m_pGLContext; +} + +void CWinSystemX11GLContext::PresentRenderImpl(bool rendered) +{ + if (rendered) + m_pGLContext->SwapBuffers(); + + if (m_delayDispReset && m_dispResetTimer.IsTimePast()) + { + m_delayDispReset = false; + std::unique_lock lock(m_resourceSection); + // tell any shared resources + for (std::vector::iterator i = m_resources.begin(); i != m_resources.end(); ++i) + (*i)->OnResetDisplay(); + } +} + +void CWinSystemX11GLContext::SetVSyncImpl(bool enable) +{ + m_pGLContext->SetVSync(enable); +} + +bool CWinSystemX11GLContext::IsExtSupported(const char* extension) const +{ + if(strncmp(extension, m_pGLContext->ExtPrefix().c_str(), 4) != 0) + return CRenderSystemGL::IsExtSupported(extension); + + return m_pGLContext->IsExtSupported(extension); +} + +XID CWinSystemX11GLContext::GetWindow() const +{ + return GLXGetWindow(m_pGLContext); +} + +void* CWinSystemX11GLContext::GetGlxContext() const +{ + return GLXGetContext(m_pGLContext); +} + +EGLDisplay CWinSystemX11GLContext::GetEGLDisplay() const +{ + return static_cast(m_pGLContext)->m_eglDisplay; +} + +EGLSurface CWinSystemX11GLContext::GetEGLSurface() const +{ + return static_cast(m_pGLContext)->m_eglSurface; +} + +EGLContext CWinSystemX11GLContext::GetEGLContext() const +{ + return static_cast(m_pGLContext)->m_eglContext; +} + +EGLConfig CWinSystemX11GLContext::GetEGLConfig() const +{ + return static_cast(m_pGLContext)->m_eglConfig; +} + +bool CWinSystemX11GLContext::SetWindow(int width, int height, bool fullscreen, const std::string &output, int *winstate) +{ + int newwin = 0; + + CWinSystemX11::SetWindow(width, height, fullscreen, output, &newwin); + if (newwin) + { + RefreshGLContext(m_currentOutput.compare(output) != 0); + XSync(m_dpy, False); + CServiceBroker::GetWinSystem()->GetGfxContext().Clear(0); + CServiceBroker::GetWinSystem()->GetGfxContext().Flip(true, false); + ResetVSync(); + + m_windowDirty = false; + m_bIsInternalXrr = false; + + if (!m_delayDispReset) + { + std::unique_lock lock(m_resourceSection); + // tell any shared resources + for (std::vector::iterator i = m_resources.begin(); i != m_resources.end(); ++i) + (*i)->OnResetDisplay(); + } + } + return true; +} + +bool CWinSystemX11GLContext::CreateNewWindow(const std::string& name, bool fullScreen, RESOLUTION_INFO& res) +{ + if(!CWinSystemX11::CreateNewWindow(name, fullScreen, res)) + return false; + + m_pGLContext->QueryExtensions(); + return true; +} + +bool CWinSystemX11GLContext::ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop) +{ + m_newGlContext = false; + CWinSystemX11::ResizeWindow(newWidth, newHeight, newLeft, newTop); + CRenderSystemGL::ResetRenderSystem(newWidth, newHeight); + + if (m_newGlContext) + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appSkin = components.GetComponent(); + appSkin->ReloadSkin(); + } + + return true; +} + +void CWinSystemX11GLContext::FinishWindowResize(int newWidth, int newHeight) +{ + m_newGlContext = false; + CWinSystemX11::FinishWindowResize(newWidth, newHeight); + CRenderSystemGL::ResetRenderSystem(newWidth, newHeight); + + if (m_newGlContext) + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appSkin = components.GetComponent(); + appSkin->ReloadSkin(); + } +} + +bool CWinSystemX11GLContext::SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) +{ + m_newGlContext = false; + CWinSystemX11::SetFullScreen(fullScreen, res, blankOtherDisplays); + CRenderSystemGL::ResetRenderSystem(res.iWidth, res.iHeight); + + if (m_newGlContext) + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appSkin = components.GetComponent(); + appSkin->ReloadSkin(); + } + + return true; +} + +bool CWinSystemX11GLContext::DestroyWindowSystem() +{ + if (m_pGLContext) + m_pGLContext->Destroy(); + return CWinSystemX11::DestroyWindowSystem(); +} + +bool CWinSystemX11GLContext::DestroyWindow() +{ + if (m_pGLContext) + m_pGLContext->Detach(); + return CWinSystemX11::DestroyWindow(); +} + +XVisualInfo* CWinSystemX11GLContext::GetVisual() +{ + int count = 0; + XVisualInfo vTemplate; + XVisualInfo *visual = nullptr; + + int vMask = VisualScreenMask | VisualDepthMask | VisualClassMask; + + vTemplate.screen = m_screen; + vTemplate.depth = 24; + vTemplate.c_class = TrueColor; + + visual = XGetVisualInfo(m_dpy, vMask, &vTemplate, &count); + + if (!visual) + { + vTemplate.depth = 30; + visual = XGetVisualInfo(m_dpy, vMask, &vTemplate, &count); + } + + return visual; +} + +bool CWinSystemX11GLContext::RefreshGLContext(bool force) +{ + bool success = false; + if (m_pGLContext) + { + if (force) + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appSkin = components.GetComponent(); + appSkin->UnloadSkin(); + CRenderSystemGL::DestroyRenderSystem(); + } + success = m_pGLContext->Refresh(force, m_screen, m_glWindow, m_newGlContext); + if (!success) + { + success = m_pGLContext->CreatePB(); + m_newGlContext = true; + } + if (force) + CRenderSystemGL::InitRenderSystem(); + return success; + } + + m_dpms = std::make_shared(); + VIDEOPLAYER::CProcessInfoX11::Register(); + RETRO::CRPProcessInfoX11::Register(); + RETRO::CRPProcessInfoX11::RegisterRendererFactory(new RETRO::CRendererFactoryOpenGL); + CDVDFactoryCodec::ClearHWAccels(); + VIDEOPLAYER::CRendererFactory::ClearRenderer(); + CLinuxRendererGL::Register(); + + CScreenshotSurfaceGL::Register(); + + std::string gpuvendor; + const char* vend = (const char*) glGetString(GL_VENDOR); + if (vend) + gpuvendor = vend; + std::transform(gpuvendor.begin(), gpuvendor.end(), gpuvendor.begin(), ::tolower); + bool isNvidia = (gpuvendor.compare(0, 6, "nvidia") == 0); + bool isIntel = (gpuvendor.compare(0, 5, "intel") == 0); + std::string gli = (getenv("KODI_GL_INTERFACE") != nullptr) ? getenv("KODI_GL_INTERFACE") : ""; + + if (gli != "GLX") + { + m_pGLContext = new CGLContextEGL(m_dpy, EGL_OPENGL_API); + success = m_pGLContext->Refresh(force, m_screen, m_glWindow, m_newGlContext); + if (success) + { + if (!isNvidia) + { + m_vaapiProxy.reset(VaapiProxyCreate()); + VaapiProxyConfig(m_vaapiProxy.get(), GetDisplay(), + static_cast(m_pGLContext)->m_eglDisplay); + bool general = false; + bool deepColor = false; + VAAPIRegisterRenderGL(m_vaapiProxy.get(), general, deepColor); + if (general) + { + VAAPIRegister(m_vaapiProxy.get(), deepColor); + return true; + } + if (isIntel || gli == "EGL") + return true; + } + } + else if (gli == "EGL_PB") + { + success = m_pGLContext->CreatePB(); + if (success) + return true; + } + } + + delete m_pGLContext; + + // fallback for vdpau + m_pGLContext = GLXContextCreate(m_dpy); + success = m_pGLContext->Refresh(force, m_screen, m_glWindow, m_newGlContext); + if (success) + { + VDPAURegister(); + VDPAURegisterRender(); + } + return success; +} + +std::unique_ptr CWinSystemX11GLContext::GetVideoSync(void *clock) +{ + std::unique_ptr pVSync; + + if (dynamic_cast(m_pGLContext)) + { + pVSync.reset(new CVideoSyncOML(clock, *this)); + } + else + { + pVSync.reset(GLXVideoSyncCreate(clock, *this)); + } + + return pVSync; +} + +float CWinSystemX11GLContext::GetFrameLatencyAdjustment() +{ + if (m_pGLContext) + { + uint64_t msc, interval; + float micros = m_pGLContext->GetVblankTiming(msc, interval); + return micros / 1000; + } + return 0; +} + +uint64_t CWinSystemX11GLContext::GetVblankTiming(uint64_t &msc, uint64_t &interval) +{ + if (m_pGLContext) + { + float micros = m_pGLContext->GetVblankTiming(msc, interval); + return micros; + } + msc = 0; + interval = 0; + return 0; +} + +void CWinSystemX11GLContext::delete_CVaapiProxy::operator()(CVaapiProxy *p) const +{ + VaapiProxyDelete(p); +} diff --git a/xbmc/windowing/X11/WinSystemX11GLContext.h b/xbmc/windowing/X11/WinSystemX11GLContext.h new file mode 100644 index 0000000..7d227b2 --- /dev/null +++ b/xbmc/windowing/X11/WinSystemX11GLContext.h @@ -0,0 +1,79 @@ +/* + * 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. + */ + +#pragma once + +#include "WinSystemX11.h" +#include "rendering/gl/RenderSystemGL.h" +#include "system_egl.h" + +#include + +class CGLContext; + +namespace KODI +{ +namespace WINDOWING +{ +namespace X11 +{ + +class CVaapiProxy; + +class CWinSystemX11GLContext : public CWinSystemX11, public CRenderSystemGL +{ +public: + CWinSystemX11GLContext() = default; + ~CWinSystemX11GLContext() override; + + static void Register(); + static std::unique_ptr CreateWinSystem(); + + // Implementation of CWinSystem via CWinSystemX11 + CRenderSystemBase *GetRenderSystem() override { return this; } + bool CreateNewWindow(const std::string& name, bool fullScreen, RESOLUTION_INFO& res) override; + bool ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop) override; + void FinishWindowResize(int newWidth, int newHeight) override; + bool SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) override; + bool DestroyWindowSystem() override; + bool DestroyWindow() override; + + bool IsExtSupported(const char* extension) const override; + + // videosync + std::unique_ptr GetVideoSync(void *clock) override; + float GetFrameLatencyAdjustment() override; + uint64_t GetVblankTiming(uint64_t &msc, uint64_t &interval); + + XID GetWindow() const; + void* GetGlxContext() const; + EGLDisplay GetEGLDisplay() const; + EGLSurface GetEGLSurface() const; + EGLContext GetEGLContext() const; + EGLConfig GetEGLConfig() const; + +protected: + bool SetWindow(int width, int height, bool fullscreen, const std::string &output, int *winstate = NULL) override; + void PresentRenderImpl(bool rendered) override; + void SetVSyncImpl(bool enable) override; + bool RefreshGLContext(bool force); + XVisualInfo* GetVisual() override; + + CGLContext *m_pGLContext = nullptr; + bool m_newGlContext; + + struct delete_CVaapiProxy + { + void operator()(CVaapiProxy *p) const; + }; + std::unique_ptr m_vaapiProxy; +}; + +} +} +} diff --git a/xbmc/windowing/X11/WinSystemX11GLESContext.cpp b/xbmc/windowing/X11/WinSystemX11GLESContext.cpp new file mode 100644 index 0000000..e2367d3 --- /dev/null +++ b/xbmc/windowing/X11/WinSystemX11GLESContext.cpp @@ -0,0 +1,293 @@ +/* + * 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 "WinSystemX11GLESContext.h" + +#include "GLContextEGL.h" +#include "OptionalsReg.h" +#include "X11DPMSSupport.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationSkinHandling.h" +#include "cores/RetroPlayer/process/X11/RPProcessInfoX11.h" +#include "cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGLES.h" +#include "cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.h" +#include "cores/VideoPlayer/Process/X11/ProcessInfoX11.h" +#include "cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.h" +#include "cores/VideoPlayer/VideoRenderers/RenderFactory.h" +#include "guilib/DispResource.h" +#include "utils/log.h" +#include "windowing/GraphicContext.h" +#include "windowing/WindowSystemFactory.h" + +#include + +using namespace KODI; +using namespace KODI::WINDOWING::X11; + +void CWinSystemX11GLESContext::Register() +{ + KODI::WINDOWING::CWindowSystemFactory::RegisterWindowSystem(CreateWinSystem, "x11"); +} + +std::unique_ptr CWinSystemX11GLESContext::CreateWinSystem() +{ + return std::make_unique(); +} + +CWinSystemX11GLESContext::~CWinSystemX11GLESContext() +{ + delete m_pGLContext; +} + +void CWinSystemX11GLESContext::PresentRenderImpl(bool rendered) +{ + if (rendered && m_pGLContext) + m_pGLContext->SwapBuffers(); + + if (m_delayDispReset && m_dispResetTimer.IsTimePast()) + { + m_delayDispReset = false; + std::unique_lock lock(m_resourceSection); + // tell any shared resources + for (std::vector::iterator i = m_resources.begin(); i != m_resources.end(); ++i) + (*i)->OnResetDisplay(); + } +} + +void CWinSystemX11GLESContext::SetVSyncImpl(bool enable) +{ + m_pGLContext->SetVSync(enable); +} + +bool CWinSystemX11GLESContext::IsExtSupported(const char* extension) const +{ + if (strncmp(extension, m_pGLContext->ExtPrefix().c_str(), 4) != 0) + return CRenderSystemGLES::IsExtSupported(extension); + + return m_pGLContext->IsExtSupported(extension); +} + +EGLDisplay CWinSystemX11GLESContext::GetEGLDisplay() const +{ + return m_pGLContext->m_eglDisplay; +} + +EGLSurface CWinSystemX11GLESContext::GetEGLSurface() const +{ + return m_pGLContext->m_eglSurface; +} + +EGLContext CWinSystemX11GLESContext::GetEGLContext() const +{ + return m_pGLContext->m_eglContext; +} + +EGLConfig CWinSystemX11GLESContext::GetEGLConfig() const +{ + return m_pGLContext->m_eglConfig; +} + +bool CWinSystemX11GLESContext::SetWindow(int width, int height, bool fullscreen, const std::string& output, int* winstate) +{ + int newwin = 0; + + CWinSystemX11::SetWindow(width, height, fullscreen, output, &newwin); + if (newwin) + { + RefreshGLContext(m_currentOutput.compare(output) != 0); + XSync(m_dpy, false); + CServiceBroker::GetWinSystem()->GetGfxContext().Clear(0); + CServiceBroker::GetWinSystem()->GetGfxContext().Flip(true, false); + ResetVSync(); + + m_windowDirty = false; + m_bIsInternalXrr = false; + + if (!m_delayDispReset) + { + std::unique_lock lock(m_resourceSection); + // tell any shared resources + for (std::vector::iterator i = m_resources.begin(); i != m_resources.end(); ++i) + (*i)->OnResetDisplay(); + } + } + return true; +} + +bool CWinSystemX11GLESContext::CreateNewWindow(const std::string& name, bool fullScreen, RESOLUTION_INFO& res) +{ + CLog::Log(LOGINFO, "CWinSystemX11GLESContext::CreateNewWindow"); + if (!CWinSystemX11::CreateNewWindow(name, fullScreen, res) || !m_pGLContext) + return false; + + m_pGLContext->QueryExtensions(); + return true; +} + +bool CWinSystemX11GLESContext::ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop) +{ + m_newGlContext = false; + CWinSystemX11::ResizeWindow(newWidth, newHeight, newLeft, newTop); + CRenderSystemGLES::ResetRenderSystem(newWidth, newHeight); + + if (m_newGlContext) + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appSkin = components.GetComponent(); + appSkin->ReloadSkin(); + } + + return true; +} + +void CWinSystemX11GLESContext::FinishWindowResize(int newWidth, int newHeight) +{ + m_newGlContext = false; + CWinSystemX11::FinishWindowResize(newWidth, newHeight); + CRenderSystemGLES::ResetRenderSystem(newWidth, newHeight); + + if (m_newGlContext) + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appSkin = components.GetComponent(); + appSkin->ReloadSkin(); + } +} + +bool CWinSystemX11GLESContext::SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) +{ + m_newGlContext = false; + CWinSystemX11::SetFullScreen(fullScreen, res, blankOtherDisplays); + CRenderSystemGLES::ResetRenderSystem(res.iWidth, res.iHeight); + + if (m_newGlContext) + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appSkin = components.GetComponent(); + appSkin->ReloadSkin(); + } + + return true; +} + +bool CWinSystemX11GLESContext::DestroyWindowSystem() +{ + if (m_pGLContext) + m_pGLContext->Destroy(); + return CWinSystemX11::DestroyWindowSystem(); +} + +bool CWinSystemX11GLESContext::DestroyWindow() +{ + if (m_pGLContext) + m_pGLContext->Detach(); + return CWinSystemX11::DestroyWindow(); +} + +XVisualInfo* CWinSystemX11GLESContext::GetVisual() +{ + EGLDisplay eglDisplay; + + PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = + reinterpret_cast(eglGetProcAddress("eglGetPlatformDisplayEXT")); + if (eglGetPlatformDisplayEXT) + { + EGLint attribs[] = + { + EGL_PLATFORM_X11_SCREEN_EXT, m_screen, + EGL_NONE + }; + eglDisplay = eglGetPlatformDisplayEXT(EGL_PLATFORM_X11_EXT,static_cast(m_dpy), attribs); + } + else + eglDisplay = eglGetDisplay(static_cast(m_dpy)); + + if (eglDisplay == EGL_NO_DISPLAY) + { + CLog::Log(LOGERROR, "failed to get egl display"); + return nullptr; + } + if (!eglInitialize(eglDisplay, nullptr, nullptr)) + { + CLog::Log(LOGERROR, "failed to initialize egl display"); + return nullptr; + } + + GLint att[] = + { + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_BUFFER_SIZE, 32, + EGL_DEPTH_SIZE, 24, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_NONE + }; + EGLint numConfigs; + EGLConfig eglConfig = 0; + if (!eglChooseConfig(eglDisplay, att, &eglConfig, 1, &numConfigs) || numConfigs == 0) + { + CLog::Log(LOGERROR, "Failed to choose a config {}", eglGetError()); + return nullptr; + } + + XVisualInfo x11_visual_info_template; + memset(&x11_visual_info_template, 0, sizeof(XVisualInfo)); + + if (!eglGetConfigAttrib(eglDisplay, eglConfig, + EGL_NATIVE_VISUAL_ID, reinterpret_cast(&x11_visual_info_template.visualid))) + { + CLog::Log(LOGERROR, "Failed to query native visual id"); + return nullptr; + } + int num_visuals; + XVisualInfo* visual = + XGetVisualInfo(m_dpy, VisualIDMask, &x11_visual_info_template, &num_visuals); + return visual; +} + +bool CWinSystemX11GLESContext::RefreshGLContext(bool force) +{ + bool success = false; + if (m_pGLContext) + { + success = m_pGLContext->Refresh(force, m_screen, m_glWindow, m_newGlContext); + if (!success) + { + success = m_pGLContext->CreatePB(); + m_newGlContext = true; + } + return success; + } + + m_dpms = std::make_shared(); + VIDEOPLAYER::CProcessInfoX11::Register(); + RETRO::CRPProcessInfoX11::Register(); + RETRO::CRPProcessInfoX11::RegisterRendererFactory(new RETRO::CRendererFactoryOpenGLES); + CDVDFactoryCodec::ClearHWAccels(); + VIDEOPLAYER::CRendererFactory::ClearRenderer(); + CLinuxRendererGLES::Register(); + + std::string gli = (getenv("KODI_GL_INTERFACE") != nullptr) ? getenv("KODI_GL_INTERFACE") : ""; + + m_pGLContext = new CGLContextEGL(m_dpy, EGL_OPENGL_ES_API); + success = m_pGLContext->Refresh(force, m_screen, m_glWindow, m_newGlContext); + if (!success && gli == "EGL_PB") + { + success = m_pGLContext->CreatePB(); + m_newGlContext = true; + } + + if (!success) + { + delete m_pGLContext; + m_pGLContext = nullptr; + } + return success; +} diff --git a/xbmc/windowing/X11/WinSystemX11GLESContext.h b/xbmc/windowing/X11/WinSystemX11GLESContext.h new file mode 100644 index 0000000..cdb1cc4 --- /dev/null +++ b/xbmc/windowing/X11/WinSystemX11GLESContext.h @@ -0,0 +1,62 @@ +/* + * 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. + */ + +#pragma once + +#include "EGL/egl.h" +#include "WinSystemX11.h" +#include "rendering/gles/RenderSystemGLES.h" + +class CGLContextEGL; + +namespace KODI +{ +namespace WINDOWING +{ +namespace X11 +{ + +class CWinSystemX11GLESContext : public CWinSystemX11, public CRenderSystemGLES +{ +public: + CWinSystemX11GLESContext() = default; + virtual ~CWinSystemX11GLESContext() override; + + static void Register(); + static std::unique_ptr CreateWinSystem(); + + // Implementation of CWinSystem via CWinSystemX11 + CRenderSystemBase* GetRenderSystem() override { return this; } + bool CreateNewWindow(const std::string& name, bool fullScreen, RESOLUTION_INFO& res) override; + bool ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop) override; + void FinishWindowResize(int newWidth, int newHeight) override; + bool SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) override; + bool DestroyWindowSystem() override; + bool DestroyWindow() override; + + bool IsExtSupported(const char* extension) const override; + + EGLDisplay GetEGLDisplay() const; + EGLSurface GetEGLSurface() const; + EGLContext GetEGLContext() const; + EGLConfig GetEGLConfig() const; + +protected: + bool SetWindow(int width, int height, bool fullscreen, const std::string& output, int* winstate = nullptr) override; + void PresentRenderImpl(bool rendered) override; + void SetVSyncImpl(bool enable) override; + bool RefreshGLContext(bool force); + XVisualInfo* GetVisual() override; + + CGLContextEGL* m_pGLContext = nullptr; + bool m_newGlContext; +}; + +} // namespace X11 +} // namespace WINDOWING +} // namespace KODI diff --git a/xbmc/windowing/X11/X11DPMSSupport.cpp b/xbmc/windowing/X11/X11DPMSSupport.cpp new file mode 100644 index 0000000..7512bfa --- /dev/null +++ b/xbmc/windowing/X11/X11DPMSSupport.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2009-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 "X11DPMSSupport.h" + +#include "ServiceBroker.h" +#include "utils/log.h" +#include "windowing/X11/WinSystemX11.h" + +#include +#include + +using namespace KODI::WINDOWING::X11; + +namespace +{ +// Mapping of PowerSavingMode to X11's mode constants. +const CARD16 X_DPMS_MODES[] = +{ + DPMSModeStandby, + DPMSModeSuspend, + DPMSModeOff +}; +} + +CX11DPMSSupport::CX11DPMSSupport() +{ + CWinSystemX11* winSystem = dynamic_cast(CServiceBroker::GetWinSystem()); + if (!winSystem) + return; + + Display* dpy = winSystem->GetDisplay(); + if (!dpy) + return; + + int event_base, error_base; // we ignore these + if (!DPMSQueryExtension(dpy, &event_base, &error_base)) + { + CLog::Log(LOGINFO, "DPMS: X11 extension not present, power-saving will not be available"); + return; + } + + if (!DPMSCapable(dpy)) + { + CLog::Log(LOGINFO, "DPMS: display does not support power-saving"); + return; + } + + m_supportedModes.push_back(SUSPEND); // best compromise + m_supportedModes.push_back(OFF); // next best + m_supportedModes.push_back(STANDBY); // rather lame, < 80% power according to DPMS spec +} + +bool CX11DPMSSupport::EnablePowerSaving(PowerSavingMode mode) +{ + CWinSystemX11* winSystem = dynamic_cast(CServiceBroker::GetWinSystem()); + if (!winSystem) + return false; + + Display* dpy = winSystem->GetDisplay(); + if (!dpy) + return false; + + // This is not needed on my ATI Radeon, but the docs say that DPMSForceLevel + // after a DPMSDisable (from SDL) should not normally work. + DPMSEnable(dpy); + DPMSForceLevel(dpy, X_DPMS_MODES[mode]); + // There shouldn't be any errors if we called DPMSEnable; if they do happen, + // they're asynchronous and messy to detect. + XFlush(dpy); + return true; +} + +bool CX11DPMSSupport::DisablePowerSaving() +{ + CWinSystemX11* winSystem = dynamic_cast(CServiceBroker::GetWinSystem()); + if (!winSystem) + return false; + + Display* dpy = winSystem->GetDisplay(); + if (!dpy) + return false; + + DPMSForceLevel(dpy, DPMSModeOn); + DPMSDisable(dpy); + XFlush(dpy); + + winSystem->RecreateWindow(); + + return true; +} diff --git a/xbmc/windowing/X11/X11DPMSSupport.h b/xbmc/windowing/X11/X11DPMSSupport.h new file mode 100644 index 0000000..c6ce8df --- /dev/null +++ b/xbmc/windowing/X11/X11DPMSSupport.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2009-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 "xbmc/powermanagement/DPMSSupport.h" + +class CX11DPMSSupport : public CDPMSSupport +{ +public: + CX11DPMSSupport(); + ~CX11DPMSSupport() override = default; + +protected: + bool EnablePowerSaving(PowerSavingMode mode) override; + bool DisablePowerSaving() override; +}; 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 + +#include + +#include "PlatformDefs.h" + +#if defined(TARGET_FREEBSD) +#include +#include +#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; screennumAttribute("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 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(); + } +*/ diff --git a/xbmc/windowing/X11/XRandR.h b/xbmc/windowing/X11/XRandR.h new file mode 100644 index 0000000..6f5f74c --- /dev/null +++ b/xbmc/windowing/X11/XRandR.h @@ -0,0 +1,109 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include + +class XMode +{ +public: + XMode() + { + hz=0.0f; + isPreferred=false; + isCurrent=false; + w=h=0; + } + bool operator==(XMode& mode) const + { + if (id != mode.id) + return false; + if (name != mode.name) + return false; + if (hz != mode.hz) + return false; + if (isPreferred != mode.isPreferred) + return false; + if (isCurrent != mode.isCurrent) + return false; + if (w != mode.w) + return false; + if (h != mode.h) + return false; + return true; + } + bool IsInterlaced() + { + return name.back() == 'i'; + } + std::string id; + std::string name; + float hz; + bool isPreferred; + bool isCurrent; + unsigned int w; + unsigned int h; +}; + +class XOutput +{ +public: + XOutput() + { + isConnected = false; + w = h = x = y = wmm = hmm = 0; + } + std::string name; + bool isConnected; + int screen; + int w; + int h; + int x; + int y; + int crtc; + int wmm; + int hmm; + std::vector modes; + bool isRotated; +}; + +class CXRandR +{ +public: + explicit CXRandR(bool query=false); + bool Query(bool force=false, bool ignoreoff=true); + bool Query(bool force, int screennum, bool ignoreoff=true); + std::vector GetModes(void); + XMode GetCurrentMode(const std::string& outputName); + XMode GetPreferredMode(const std::string& outputName); + XOutput *GetOutput(const std::string& outputName); + bool SetMode(const XOutput& output, const XMode& mode); + void LoadCustomModeLinesToAllOutputs(void); + void SaveState(); + void SetNumScreens(unsigned int num); + bool IsOutputConnected(const std::string& name); + bool TurnOffOutput(const std::string& name); + bool TurnOnOutput(const std::string& name); + int GetCrtc(int x, int y, float &hz); + //bool Has1080i(); + //bool Has1080p(); + //bool Has720p(); + //bool Has480p(); + +private: + bool m_bInit; + std::vector m_outputs; + std::string m_currentOutput; + std::string m_currentMode; + unsigned int m_numScreens; +}; + +extern CXRandR g_xrandr; -- cgit v1.2.3