summaryrefslogtreecommitdiffstats
path: root/xbmc/windowing
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--xbmc/windowing/CMakeLists.txt16
-rw-r--r--xbmc/windowing/GraphicContext.cpp1011
-rw-r--r--xbmc/windowing/GraphicContext.h232
-rw-r--r--xbmc/windowing/OSScreenSaver.cpp98
-rw-r--r--xbmc/windowing/OSScreenSaver.h118
-rw-r--r--xbmc/windowing/Resolution.cpp474
-rw-r--r--xbmc/windowing/Resolution.h107
-rw-r--r--xbmc/windowing/VideoSync.h31
-rw-r--r--xbmc/windowing/WinEvents.h19
-rw-r--r--xbmc/windowing/WinSystem.cpp278
-rw-r--r--xbmc/windowing/WinSystem.h203
-rw-r--r--xbmc/windowing/WindowSystemFactory.cpp42
-rw-r--r--xbmc/windowing/WindowSystemFactory.h38
-rw-r--r--xbmc/windowing/X11/CMakeLists.txt37
-rw-r--r--xbmc/windowing/X11/GLContext.cpp20
-rw-r--r--xbmc/windowing/X11/GLContext.h42
-rw-r--r--xbmc/windowing/X11/GLContextEGL.cpp542
-rw-r--r--xbmc/windowing/X11/GLContextEGL.h63
-rw-r--r--xbmc/windowing/X11/GLContextGLX.cpp293
-rw-r--r--xbmc/windowing/X11/GLContextGLX.h49
-rw-r--r--xbmc/windowing/X11/OSScreenSaverX11.cpp36
-rw-r--r--xbmc/windowing/X11/OSScreenSaverX11.h28
-rw-r--r--xbmc/windowing/X11/OptionalsReg.cpp256
-rw-r--r--xbmc/windowing/X11/OptionalsReg.h78
-rw-r--r--xbmc/windowing/X11/VideoSyncGLX.cpp277
-rw-r--r--xbmc/windowing/X11/VideoSyncGLX.h62
-rw-r--r--xbmc/windowing/X11/VideoSyncOML.cpp83
-rw-r--r--xbmc/windowing/X11/VideoSyncOML.h46
-rw-r--r--xbmc/windowing/X11/WinEventsX11.cpp673
-rw-r--r--xbmc/windowing/X11/WinEventsX11.h60
-rw-r--r--xbmc/windowing/X11/WinSystemX11.cpp1081
-rw-r--r--xbmc/windowing/X11/WinSystemX11.h117
-rw-r--r--xbmc/windowing/X11/WinSystemX11GLContext.cpp358
-rw-r--r--xbmc/windowing/X11/WinSystemX11GLContext.h79
-rw-r--r--xbmc/windowing/X11/WinSystemX11GLESContext.cpp293
-rw-r--r--xbmc/windowing/X11/WinSystemX11GLESContext.h62
-rw-r--r--xbmc/windowing/X11/X11DPMSSupport.cpp96
-rw-r--r--xbmc/windowing/X11/X11DPMSSupport.h20
-rw-r--r--xbmc/windowing/X11/XRandR.cpp518
-rw-r--r--xbmc/windowing/X11/XRandR.h109
-rw-r--r--xbmc/windowing/XBMC_events.h134
-rw-r--r--xbmc/windowing/android/AndroidUtils.cpp440
-rw-r--r--xbmc/windowing/android/AndroidUtils.h56
-rw-r--r--xbmc/windowing/android/CMakeLists.txt18
-rw-r--r--xbmc/windowing/android/OSScreenSaverAndroid.cpp21
-rw-r--r--xbmc/windowing/android/OSScreenSaverAndroid.h19
-rw-r--r--xbmc/windowing/android/VideoSyncAndroid.cpp78
-rw-r--r--xbmc/windowing/android/VideoSyncAndroid.h34
-rw-r--r--xbmc/windowing/android/WinEventsAndroid.cpp191
-rw-r--r--xbmc/windowing/android/WinEventsAndroid.h43
-rw-r--r--xbmc/windowing/android/WinSystemAndroid.cpp329
-rw-r--r--xbmc/windowing/android/WinSystemAndroid.h85
-rw-r--r--xbmc/windowing/android/WinSystemAndroidGLESContext.cpp270
-rw-r--r--xbmc/windowing/android/WinSystemAndroidGLESContext.h63
-rw-r--r--xbmc/windowing/gbm/CMakeLists.txt26
-rw-r--r--xbmc/windowing/gbm/GBMDPMSSupport.cpp43
-rw-r--r--xbmc/windowing/gbm/GBMDPMSSupport.h22
-rw-r--r--xbmc/windowing/gbm/GBMUtils.cpp107
-rw-r--r--xbmc/windowing/gbm/GBMUtils.h177
-rw-r--r--xbmc/windowing/gbm/OptionalsReg.cpp139
-rw-r--r--xbmc/windowing/gbm/OptionalsReg.h36
-rw-r--r--xbmc/windowing/gbm/VideoLayerBridge.h27
-rw-r--r--xbmc/windowing/gbm/VideoSyncGbm.cpp131
-rw-r--r--xbmc/windowing/gbm/VideoSyncGbm.h43
-rw-r--r--xbmc/windowing/gbm/WinSystemGbm.cpp445
-rw-r--r--xbmc/windowing/gbm/WinSystemGbm.h96
-rw-r--r--xbmc/windowing/gbm/WinSystemGbmEGLContext.cpp141
-rw-r--r--xbmc/windowing/gbm/WinSystemGbmEGLContext.h58
-rw-r--r--xbmc/windowing/gbm/WinSystemGbmGLContext.cpp174
-rw-r--r--xbmc/windowing/gbm/WinSystemGbmGLContext.h48
-rw-r--r--xbmc/windowing/gbm/WinSystemGbmGLESContext.cpp167
-rw-r--r--xbmc/windowing/gbm/WinSystemGbmGLESContext.h48
-rw-r--r--xbmc/windowing/gbm/drm/CMakeLists.txt21
-rw-r--r--xbmc/windowing/gbm/drm/DRMAtomic.cpp344
-rw-r--r--xbmc/windowing/gbm/drm/DRMAtomic.h81
-rw-r--r--xbmc/windowing/gbm/drm/DRMConnector.cpp99
-rw-r--r--xbmc/windowing/gbm/drm/DRMConnector.h53
-rw-r--r--xbmc/windowing/gbm/drm/DRMCrtc.cpp26
-rw-r--r--xbmc/windowing/gbm/drm/DRMCrtc.h46
-rw-r--r--xbmc/windowing/gbm/drm/DRMEncoder.cpp23
-rw-r--r--xbmc/windowing/gbm/drm/DRMEncoder.h43
-rw-r--r--xbmc/windowing/gbm/drm/DRMLegacy.cpp141
-rw-r--r--xbmc/windowing/gbm/drm/DRMLegacy.h39
-rw-r--r--xbmc/windowing/gbm/drm/DRMObject.cpp133
-rw-r--r--xbmc/windowing/gbm/drm/DRMObject.h71
-rw-r--r--xbmc/windowing/gbm/drm/DRMPlane.cpp118
-rw-r--r--xbmc/windowing/gbm/drm/DRMPlane.h58
-rw-r--r--xbmc/windowing/gbm/drm/DRMUtils.cpp740
-rw-r--r--xbmc/windowing/gbm/drm/DRMUtils.h103
-rw-r--r--xbmc/windowing/gbm/drm/OffScreenModeSetting.cpp52
-rw-r--r--xbmc/windowing/gbm/drm/OffScreenModeSetting.h38
-rw-r--r--xbmc/windowing/ios/CMakeLists.txt8
-rw-r--r--xbmc/windowing/ios/VideoSyncIos.cpp95
-rw-r--r--xbmc/windowing/ios/VideoSyncIos.h43
-rw-r--r--xbmc/windowing/ios/WinEventsIOS.h20
-rw-r--r--xbmc/windowing/ios/WinEventsIOS.mm55
-rw-r--r--xbmc/windowing/ios/WinSystemIOS.h97
-rw-r--r--xbmc/windowing/ios/WinSystemIOS.mm501
-rw-r--r--xbmc/windowing/linux/CMakeLists.txt16
-rw-r--r--xbmc/windowing/linux/OSScreenSaverFreedesktop.cpp78
-rw-r--r--xbmc/windowing/linux/OSScreenSaverFreedesktop.h40
-rw-r--r--xbmc/windowing/linux/WinSystemEGL.cpp36
-rw-r--r--xbmc/windowing/linux/WinSystemEGL.h37
-rw-r--r--xbmc/windowing/osx/CMakeLists.txt17
-rw-r--r--xbmc/windowing/osx/CocoaDPMSSupport.cpp55
-rw-r--r--xbmc/windowing/osx/CocoaDPMSSupport.h20
-rw-r--r--xbmc/windowing/osx/OSScreenSaverOSX.cpp31
-rw-r--r--xbmc/windowing/osx/OSScreenSaverOSX.h24
-rw-r--r--xbmc/windowing/osx/OpenGL/CMakeLists.txt14
-rw-r--r--xbmc/windowing/osx/OpenGL/OSXGLView.h18
-rw-r--r--xbmc/windowing/osx/OpenGL/OSXGLView.mm111
-rw-r--r--xbmc/windowing/osx/OpenGL/OSXGLWindow.h19
-rw-r--r--xbmc/windowing/osx/OpenGL/OSXGLWindow.mm245
-rw-r--r--xbmc/windowing/osx/OpenGL/WinSystemOSXGL.h35
-rw-r--r--xbmc/windowing/osx/OpenGL/WinSystemOSXGL.mm79
-rw-r--r--xbmc/windowing/osx/SDL/CMakeLists.txt10
-rw-r--r--xbmc/windowing/osx/SDL/WinEventsSDL.cpp242
-rw-r--r--xbmc/windowing/osx/SDL/WinEventsSDL.h24
-rw-r--r--xbmc/windowing/osx/SDL/WinSystemOSXSDL.h111
-rw-r--r--xbmc/windowing/osx/SDL/WinSystemOSXSDL.mm1852
-rw-r--r--xbmc/windowing/osx/VideoSyncOsx.h46
-rw-r--r--xbmc/windowing/osx/VideoSyncOsx.mm148
-rw-r--r--xbmc/windowing/osx/WinEventsOSX.h34
-rw-r--r--xbmc/windowing/osx/WinEventsOSX.mm54
-rw-r--r--xbmc/windowing/osx/WinEventsOSXImpl.h25
-rw-r--r--xbmc/windowing/osx/WinEventsOSXImpl.mm364
-rw-r--r--xbmc/windowing/osx/WinSystemOSX.h120
-rw-r--r--xbmc/windowing/osx/WinSystemOSX.mm1305
-rw-r--r--xbmc/windowing/tvos/CMakeLists.txt10
-rw-r--r--xbmc/windowing/tvos/OSScreenSaverTVOS.h19
-rw-r--r--xbmc/windowing/tvos/OSScreenSaverTVOS.mm21
-rw-r--r--xbmc/windowing/tvos/VideoSyncTVos.cpp93
-rw-r--r--xbmc/windowing/tvos/VideoSyncTVos.h44
-rw-r--r--xbmc/windowing/tvos/WinEventsTVOS.h35
-rw-r--r--xbmc/windowing/tvos/WinEventsTVOS.mm76
-rw-r--r--xbmc/windowing/tvos/WinSystemTVOS.h107
-rw-r--r--xbmc/windowing/tvos/WinSystemTVOS.mm453
-rw-r--r--xbmc/windowing/wayland/CMakeLists.txt67
-rw-r--r--xbmc/windowing/wayland/Connection.cpp39
-rw-r--r--xbmc/windowing/wayland/Connection.h39
-rw-r--r--xbmc/windowing/wayland/InputProcessorKeyboard.cpp170
-rw-r--r--xbmc/windowing/wayland/InputProcessorKeyboard.h78
-rw-r--r--xbmc/windowing/wayland/InputProcessorPointer.cpp136
-rw-r--r--xbmc/windowing/wayland/InputProcessorPointer.h76
-rw-r--r--xbmc/windowing/wayland/InputProcessorTouch.cpp130
-rw-r--r--xbmc/windowing/wayland/InputProcessorTouch.h82
-rw-r--r--xbmc/windowing/wayland/OSScreenSaverIdleInhibitUnstableV1.cpp52
-rw-r--r--xbmc/windowing/wayland/OSScreenSaverIdleInhibitUnstableV1.h39
-rw-r--r--xbmc/windowing/wayland/OptionalsReg.cpp136
-rw-r--r--xbmc/windowing/wayland/OptionalsReg.h37
-rw-r--r--xbmc/windowing/wayland/Output.cpp145
-rw-r--r--xbmc/windowing/wayland/Output.h152
-rw-r--r--xbmc/windowing/wayland/Registry.cpp164
-rw-r--r--xbmc/windowing/wayland/Registry.h162
-rw-r--r--xbmc/windowing/wayland/Seat.cpp275
-rw-r--r--xbmc/windowing/wayland/Seat.h203
-rw-r--r--xbmc/windowing/wayland/SeatInputProcessing.cpp83
-rw-r--r--xbmc/windowing/wayland/SeatInputProcessing.h135
-rw-r--r--xbmc/windowing/wayland/SeatSelection.cpp199
-rw-r--r--xbmc/windowing/wayland/SeatSelection.h49
-rw-r--r--xbmc/windowing/wayland/ShellSurface.cpp35
-rw-r--r--xbmc/windowing/wayland/ShellSurface.h92
-rw-r--r--xbmc/windowing/wayland/ShellSurfaceWlShell.cpp101
-rw-r--r--xbmc/windowing/wayland/ShellSurfaceWlShell.h63
-rw-r--r--xbmc/windowing/wayland/ShellSurfaceXdgShell.cpp151
-rw-r--r--xbmc/windowing/wayland/ShellSurfaceXdgShell.h73
-rw-r--r--xbmc/windowing/wayland/ShellSurfaceXdgShellUnstableV6.cpp152
-rw-r--r--xbmc/windowing/wayland/ShellSurfaceXdgShellUnstableV6.h77
-rw-r--r--xbmc/windowing/wayland/Signals.h151
-rw-r--r--xbmc/windowing/wayland/Util.cpp88
-rw-r--r--xbmc/windowing/wayland/Util.h54
-rw-r--r--xbmc/windowing/wayland/VideoSyncWpPresentation.cpp83
-rw-r--r--xbmc/windowing/wayland/VideoSyncWpPresentation.h47
-rw-r--r--xbmc/windowing/wayland/WinEventsWayland.cpp277
-rw-r--r--xbmc/windowing/wayland/WinEventsWayland.h49
-rw-r--r--xbmc/windowing/wayland/WinSystemWayland.cpp1568
-rw-r--r--xbmc/windowing/wayland/WinSystemWayland.h300
-rw-r--r--xbmc/windowing/wayland/WinSystemWaylandEGLContext.cpp150
-rw-r--r--xbmc/windowing/wayland/WinSystemWaylandEGLContext.h55
-rw-r--r--xbmc/windowing/wayland/WinSystemWaylandEGLContextGL.cpp125
-rw-r--r--xbmc/windowing/wayland/WinSystemWaylandEGLContextGL.h47
-rw-r--r--xbmc/windowing/wayland/WinSystemWaylandEGLContextGLES.cpp114
-rw-r--r--xbmc/windowing/wayland/WinSystemWaylandEGLContextGLES.h47
-rw-r--r--xbmc/windowing/wayland/WindowDecorationHandler.h41
-rw-r--r--xbmc/windowing/wayland/WindowDecorator.cpp1043
-rw-r--r--xbmc/windowing/wayland/WindowDecorator.h262
-rw-r--r--xbmc/windowing/wayland/XkbcommonKeymap.cpp333
-rw-r--r--xbmc/windowing/wayland/XkbcommonKeymap.h140
-rw-r--r--xbmc/windowing/win10/CMakeLists.txt12
-rw-r--r--xbmc/windowing/win10/WinEventsWin10.cpp658
-rw-r--r--xbmc/windowing/win10/WinEventsWin10.h83
-rw-r--r--xbmc/windowing/win10/WinSystemWin10.cpp660
-rw-r--r--xbmc/windowing/win10/WinSystemWin10.h156
-rw-r--r--xbmc/windowing/win10/WinSystemWin10DX.cpp205
-rw-r--r--xbmc/windowing/win10/WinSystemWin10DX.h90
-rw-r--r--xbmc/windowing/windows/CMakeLists.txt14
-rw-r--r--xbmc/windowing/windows/VideoSyncD3D.cpp137
-rw-r--r--xbmc/windowing/windows/VideoSyncD3D.h37
-rw-r--r--xbmc/windowing/windows/Win32DPMSSupport.cpp52
-rw-r--r--xbmc/windowing/windows/Win32DPMSSupport.h20
-rw-r--r--xbmc/windowing/windows/WinEventsWin32.cpp1074
-rw-r--r--xbmc/windowing/windows/WinEventsWin32.h32
-rw-r--r--xbmc/windowing/windows/WinKeyMap.h181
-rw-r--r--xbmc/windowing/windows/WinSystemWin32.cpp1368
-rw-r--r--xbmc/windowing/windows/WinSystemWin32.h215
-rw-r--r--xbmc/windowing/windows/WinSystemWin32DX.cpp447
-rw-r--r--xbmc/windowing/windows/WinSystemWin32DX.h96
207 files changed, 34773 insertions, 0 deletions
diff --git a/xbmc/windowing/CMakeLists.txt b/xbmc/windowing/CMakeLists.txt
new file mode 100644
index 0000000..21c7611
--- /dev/null
+++ b/xbmc/windowing/CMakeLists.txt
@@ -0,0 +1,16 @@
+set(SOURCES GraphicContext.cpp
+ OSScreenSaver.cpp
+ Resolution.cpp
+ WindowSystemFactory.cpp
+ WinSystem.cpp)
+
+set(HEADERS GraphicContext.h
+ OSScreenSaver.h
+ Resolution.h
+ WinEvents.h
+ WindowSystemFactory.h
+ WinSystem.h
+ XBMC_events.h
+ VideoSync.h)
+
+core_add_library(windowing)
diff --git a/xbmc/windowing/GraphicContext.cpp b/xbmc/windowing/GraphicContext.cpp
new file mode 100644
index 0000000..423148c
--- /dev/null
+++ b/xbmc/windowing/GraphicContext.cpp
@@ -0,0 +1,1011 @@
+/*
+ * 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 "GraphicContext.h"
+
+#include "ServiceBroker.h"
+#include "WinSystem.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationPlayer.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/TextureManager.h"
+#include "guilib/gui3d.h"
+#include "input/InputManager.h"
+#include "messaging/ApplicationMessenger.h"
+#include "rendering/RenderSystem.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/DisplaySettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "settings/lib/Setting.h"
+#include "utils/log.h"
+
+#include <cassert>
+#include <mutex>
+
+CGraphicContext::CGraphicContext() = default;
+CGraphicContext::~CGraphicContext() = default;
+
+void CGraphicContext::SetOrigin(float x, float y)
+{
+ if (!m_origins.empty())
+ m_origins.push(CPoint(x,y) + m_origins.top());
+ else
+ m_origins.push(CPoint(x,y));
+
+ AddTransform(TransformMatrix::CreateTranslation(x, y));
+}
+
+void CGraphicContext::RestoreOrigin()
+{
+ if (!m_origins.empty())
+ m_origins.pop();
+ RemoveTransform();
+}
+
+// add a new clip region, intersecting with the previous clip region.
+bool CGraphicContext::SetClipRegion(float x, float y, float w, float h)
+{ // transform from our origin
+ CPoint origin;
+ if (!m_origins.empty())
+ origin = m_origins.top();
+
+ // ok, now intersect with our old clip region
+ CRect rect(x, y, x + w, y + h);
+ rect += origin;
+ if (!m_clipRegions.empty())
+ {
+ // intersect with original clip region
+ rect.Intersect(m_clipRegions.top());
+ }
+
+ if (rect.IsEmpty())
+ return false;
+
+ m_clipRegions.push(rect);
+
+ // here we could set the hardware clipping, if applicable
+ return true;
+}
+
+void CGraphicContext::RestoreClipRegion()
+{
+ if (!m_clipRegions.empty())
+ m_clipRegions.pop();
+
+ // here we could reset the hardware clipping, if applicable
+}
+
+void CGraphicContext::ClipRect(CRect &vertex, CRect &texture, CRect *texture2)
+{
+ // this is the software clipping routine. If the graphics hardware is set to do the clipping
+ // (eg via SetClipPlane in D3D for instance) then this routine is unneeded.
+ if (!m_clipRegions.empty())
+ {
+ // take a copy of the vertex rectangle and intersect
+ // it with our clip region (moved to the same coordinate system)
+ CRect clipRegion(m_clipRegions.top());
+ if (!m_origins.empty())
+ clipRegion -= m_origins.top();
+ CRect original(vertex);
+ vertex.Intersect(clipRegion);
+ // and use the original to compute the texture coordinates
+ if (original != vertex)
+ {
+ float scaleX = texture.Width() / original.Width();
+ float scaleY = texture.Height() / original.Height();
+ texture.x1 += (vertex.x1 - original.x1) * scaleX;
+ texture.y1 += (vertex.y1 - original.y1) * scaleY;
+ texture.x2 += (vertex.x2 - original.x2) * scaleX;
+ texture.y2 += (vertex.y2 - original.y2) * scaleY;
+ if (texture2)
+ {
+ scaleX = texture2->Width() / original.Width();
+ scaleY = texture2->Height() / original.Height();
+ texture2->x1 += (vertex.x1 - original.x1) * scaleX;
+ texture2->y1 += (vertex.y1 - original.y1) * scaleY;
+ texture2->x2 += (vertex.x2 - original.x2) * scaleX;
+ texture2->y2 += (vertex.y2 - original.y2) * scaleY;
+ }
+ }
+ }
+}
+
+CRect CGraphicContext::GetClipRegion()
+{
+ if (m_clipRegions.empty())
+ return CRect(0, 0, m_iScreenWidth, m_iScreenHeight);
+ CRect clipRegion(m_clipRegions.top());
+ if (!m_origins.empty())
+ clipRegion -= m_origins.top();
+ return clipRegion;
+}
+
+void CGraphicContext::AddGUITransform()
+{
+ m_transforms.push(m_finalTransform);
+ m_finalTransform = m_guiTransform;
+}
+
+TransformMatrix CGraphicContext::AddTransform(const TransformMatrix &matrix)
+{
+ m_transforms.push(m_finalTransform);
+ m_finalTransform.matrix *= matrix;
+ return m_finalTransform.matrix;
+}
+
+void CGraphicContext::SetTransform(const TransformMatrix &matrix)
+{
+ m_transforms.push(m_finalTransform);
+ m_finalTransform.matrix = matrix;
+}
+
+void CGraphicContext::SetTransform(const TransformMatrix &matrix, float scaleX, float scaleY)
+{
+ m_transforms.push(m_finalTransform);
+ m_finalTransform.matrix = matrix;
+ m_finalTransform.scaleX = scaleX;
+ m_finalTransform.scaleY = scaleY;
+}
+
+void CGraphicContext::RemoveTransform()
+{
+ if (!m_transforms.empty())
+ {
+ m_finalTransform = m_transforms.top();
+ m_transforms.pop();
+ }
+}
+
+bool CGraphicContext::SetViewPort(float fx, float fy, float fwidth, float fheight, bool intersectPrevious /* = false */)
+{
+ // transform coordinates - we may have a rotation which changes the positioning of the
+ // minimal and maximal viewport extents. We currently go to the maximal extent.
+ float x[4], y[4];
+ x[0] = x[3] = fx;
+ x[1] = x[2] = fx + fwidth;
+ y[0] = y[1] = fy;
+ y[2] = y[3] = fy + fheight;
+ float minX = (float)m_iScreenWidth;
+ float maxX = 0;
+ float minY = (float)m_iScreenHeight;
+ float maxY = 0;
+ for (int i = 0; i < 4; i++)
+ {
+ float z = 0;
+ ScaleFinalCoords(x[i], y[i], z);
+ if (x[i] < minX) minX = x[i];
+ if (x[i] > maxX) maxX = x[i];
+ if (y[i] < minY) minY = y[i];
+ if (y[i] > maxY) maxY = y[i];
+ }
+
+ int newLeft = (int)(minX + 0.5f);
+ int newTop = (int)(minY + 0.5f);
+ int newRight = (int)(maxX + 0.5f);
+ int newBottom = (int)(maxY + 0.5f);
+ if (intersectPrevious)
+ {
+ CRect oldviewport = m_viewStack.top();
+ // do the intersection
+ int oldLeft = (int)oldviewport.x1;
+ int oldTop = (int)oldviewport.y1;
+ int oldRight = (int)oldviewport.x2;
+ int oldBottom = (int)oldviewport.y2;
+ if (newLeft >= oldRight || newTop >= oldBottom || newRight <= oldLeft || newBottom <= oldTop)
+ { // empty intersection - return false to indicate no rendering should occur
+ return false;
+ }
+ // ok, they intersect, do the intersection
+ if (newLeft < oldLeft) newLeft = oldLeft;
+ if (newTop < oldTop) newTop = oldTop;
+ if (newRight > oldRight) newRight = oldRight;
+ if (newBottom > oldBottom) newBottom = oldBottom;
+ }
+ // check range against screen size
+ if (newRight <= 0 || newBottom <= 0 ||
+ newTop >= m_iScreenHeight || newLeft >= m_iScreenWidth ||
+ newLeft >= newRight || newTop >= newBottom)
+ { // no intersection with the screen
+ return false;
+ }
+ // intersection with the screen
+ if (newLeft < 0) newLeft = 0;
+ if (newTop < 0) newTop = 0;
+ if (newRight > m_iScreenWidth) newRight = m_iScreenWidth;
+ if (newBottom > m_iScreenHeight) newBottom = m_iScreenHeight;
+
+ assert(newLeft < newRight);
+ assert(newTop < newBottom);
+
+ CRect newviewport((float)newLeft, (float)newTop, (float)newRight, (float)newBottom);
+
+ m_viewStack.push(newviewport);
+
+ newviewport = StereoCorrection(newviewport);
+ CServiceBroker::GetRenderSystem()->SetViewPort(newviewport);
+
+
+ UpdateCameraPosition(m_cameras.top(), m_stereoFactors.top());
+ return true;
+}
+
+void CGraphicContext::RestoreViewPort()
+{
+ if (m_viewStack.size() <= 1) return;
+
+ m_viewStack.pop();
+ CRect viewport = StereoCorrection(m_viewStack.top());
+ CServiceBroker::GetRenderSystem()->SetViewPort(viewport);
+
+ UpdateCameraPosition(m_cameras.top(), m_stereoFactors.top());
+}
+
+CPoint CGraphicContext::StereoCorrection(const CPoint &point) const
+{
+ CPoint res(point);
+
+ if(m_stereoMode == RENDER_STEREO_MODE_SPLIT_HORIZONTAL)
+ {
+ const RESOLUTION_INFO info = GetResInfo();
+
+ if(m_stereoView == RENDER_STEREO_VIEW_RIGHT)
+ res.y += info.iHeight + info.iBlanking;
+ }
+ if(m_stereoMode == RENDER_STEREO_MODE_SPLIT_VERTICAL)
+ {
+ const RESOLUTION_INFO info = GetResInfo();
+
+ if(m_stereoView == RENDER_STEREO_VIEW_RIGHT)
+ res.x += info.iWidth + info.iBlanking;
+ }
+ return res;
+}
+
+CRect CGraphicContext::StereoCorrection(const CRect &rect) const
+{
+ CRect res(StereoCorrection(rect.P1())
+ , StereoCorrection(rect.P2()));
+ return res;
+}
+
+void CGraphicContext::SetScissors(const CRect &rect)
+{
+ m_scissors = rect;
+ m_scissors.Intersect(CRect(0,0,(float)m_iScreenWidth, (float)m_iScreenHeight));
+ CServiceBroker::GetRenderSystem()->SetScissors(StereoCorrection(m_scissors));
+}
+
+const CRect &CGraphicContext::GetScissors() const
+{
+ return m_scissors;
+}
+
+void CGraphicContext::ResetScissors()
+{
+ m_scissors.SetRect(0, 0, (float)m_iScreenWidth, (float)m_iScreenHeight);
+ CServiceBroker::GetRenderSystem()->SetScissors(StereoCorrection(m_scissors));
+}
+
+const CRect CGraphicContext::GetViewWindow() const
+{
+ if (m_bCalibrating || m_bFullScreenVideo)
+ {
+ CRect rect;
+ RESOLUTION_INFO info = GetResInfo();
+ rect.x1 = (float)info.Overscan.left;
+ rect.y1 = (float)info.Overscan.top;
+ rect.x2 = (float)info.Overscan.right;
+ rect.y2 = (float)info.Overscan.bottom;
+ return rect;
+ }
+ return m_videoRect;
+}
+
+void CGraphicContext::SetViewWindow(float left, float top, float right, float bottom)
+{
+ m_videoRect.x1 = ScaleFinalXCoord(left, top);
+ m_videoRect.y1 = ScaleFinalYCoord(left, top);
+ m_videoRect.x2 = ScaleFinalXCoord(right, bottom);
+ m_videoRect.y2 = ScaleFinalYCoord(right, bottom);
+}
+
+void CGraphicContext::SetFullScreenVideo(bool bOnOff)
+{
+ std::unique_lock<CCriticalSection> lock(*this);
+
+ m_bFullScreenVideo = bOnOff;
+
+ if (m_bFullScreenRoot)
+ {
+ bool bTriggerUpdateRes = false;
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ if (m_bFullScreenVideo)
+ bTriggerUpdateRes = true;
+ else
+ {
+ bool allowDesktopRes = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_VIDEOPLAYER_ADJUSTREFRESHRATE) == ADJUST_REFRESHRATE_ALWAYS;
+ if (!allowDesktopRes)
+ {
+ if (appPlayer->IsPlayingVideo())
+ bTriggerUpdateRes = true;
+ }
+ }
+
+ bool allowResolutionChangeOnStop = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_VIDEOPLAYER_ADJUSTREFRESHRATE) != ADJUST_REFRESHRATE_ON_START;
+ RESOLUTION targetResolutionOnStop = RES_DESKTOP;
+ if (bTriggerUpdateRes)
+ appPlayer->TriggerUpdateResolution();
+ else if (CDisplaySettings::GetInstance().GetCurrentResolution() > RES_DESKTOP)
+ {
+ targetResolutionOnStop = CDisplaySettings::GetInstance().GetCurrentResolution();
+ }
+
+ if (allowResolutionChangeOnStop && !bTriggerUpdateRes)
+ {
+ SetVideoResolution(targetResolutionOnStop, false);
+ }
+ }
+ else
+ SetVideoResolution(RES_WINDOW, false);
+}
+
+bool CGraphicContext::IsFullScreenVideo() const
+{
+ return m_bFullScreenVideo;
+}
+
+bool CGraphicContext::IsCalibrating() const
+{
+ return m_bCalibrating;
+}
+
+void CGraphicContext::SetCalibrating(bool bOnOff)
+{
+ m_bCalibrating = bOnOff;
+}
+
+bool CGraphicContext::IsValidResolution(RESOLUTION res)
+{
+ if (res >= RES_WINDOW && (size_t) res < CDisplaySettings::GetInstance().ResolutionInfoSize())
+ {
+ return true;
+ }
+
+ return false;
+}
+
+// call SetVideoResolutionInternal and ensure its done from mainthread
+void CGraphicContext::SetVideoResolution(RESOLUTION res, bool forceUpdate)
+{
+ if (CServiceBroker::GetAppMessenger()->IsProcessThread())
+ {
+ SetVideoResolutionInternal(res, forceUpdate);
+ }
+ else
+ {
+ CServiceBroker::GetAppMessenger()->SendMsg(TMSG_SETVIDEORESOLUTION, res, forceUpdate ? 1 : 0);
+ }
+}
+
+void CGraphicContext::SetVideoResolutionInternal(RESOLUTION res, bool forceUpdate)
+{
+ RESOLUTION lastRes = m_Resolution;
+
+ // If the user asked us to guess, go with desktop
+ if (!IsValidResolution(res))
+ {
+ res = RES_DESKTOP;
+ }
+
+ // If we are switching to the same resolution and same window/full-screen, no need to do anything
+ if (!forceUpdate && res == lastRes && m_bFullScreenRoot == CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_fullScreen)
+ {
+ return;
+ }
+
+ if (res >= RES_DESKTOP)
+ {
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_fullScreen = true;
+ m_bFullScreenRoot = true;
+ }
+ else
+ {
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_fullScreen = false;
+ m_bFullScreenRoot = false;
+ }
+
+ std::unique_lock<CCriticalSection> lock(*this);
+
+ // FIXME Wayland windowing needs some way to "deny" resolution updates since what Kodi
+ // requests might not get actually set by the compositor.
+ // So in theory, m_iScreenWidth etc. would not need to be updated at all before the
+ // change is confirmed.
+ // But other windowing code expects these variables to be already set when
+ // SetFullScreen() is called, so set them anyway and remember the old values.
+ int origScreenWidth = m_iScreenWidth;
+ int origScreenHeight = m_iScreenHeight;
+ float origFPSOverride = m_fFPSOverride;
+
+ UpdateInternalStateWithResolution(res);
+ RESOLUTION_INFO info_org = CDisplaySettings::GetInstance().GetResolutionInfo(res);
+
+ bool switched = false;
+ if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_fullScreen)
+ {
+#if defined (TARGET_DARWIN) || defined (TARGET_WINDOWS)
+ bool blankOtherDisplays = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOSCREEN_BLANKDISPLAYS);
+ switched = CServiceBroker::GetWinSystem()->SetFullScreen(true, info_org, blankOtherDisplays);
+#else
+ switched = CServiceBroker::GetWinSystem()->SetFullScreen(true, info_org, false);
+#endif
+ }
+ else if (lastRes >= RES_DESKTOP )
+ switched = CServiceBroker::GetWinSystem()->SetFullScreen(false, info_org, false);
+ else
+ switched = CServiceBroker::GetWinSystem()->ResizeWindow(info_org.iWidth, info_org.iHeight, -1, -1);
+
+ if (switched)
+ {
+ m_scissors.SetRect(0, 0, (float)m_iScreenWidth, (float)m_iScreenHeight);
+
+ // make sure all stereo stuff are correctly setup
+ SetStereoView(RENDER_STEREO_VIEW_OFF);
+
+ // update anyone that relies on sizing information
+ CServiceBroker::GetInputManager().SetMouseResolution(info_org.iWidth, info_org.iHeight, 1, 1);
+
+ CGUIComponent *gui = CServiceBroker::GetGUI();
+ if (gui)
+ gui->GetWindowManager().SendMessage(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_WINDOW_RESIZE);
+ }
+ else
+ {
+ // Reset old state
+ m_iScreenWidth = origScreenWidth;
+ m_iScreenHeight = origScreenHeight;
+ m_fFPSOverride = origFPSOverride;
+ if (IsValidResolution(lastRes))
+ {
+ m_Resolution = lastRes;
+ }
+ else
+ {
+ // FIXME Resolution has become invalid
+ // This happens e.g. when switching monitors and the new monitor has fewer
+ // resolutions than the old one. Fall back to RES_DESKTOP and hope that
+ // the real resolution is set soon.
+ // Again, must be fixed as part of a greater refactor.
+ m_Resolution = RES_DESKTOP;
+ }
+ }
+}
+
+void CGraphicContext::ApplyVideoResolution(RESOLUTION res)
+{
+ if (!IsValidResolution(res))
+ {
+ CLog::LogF(LOGWARNING, "Asked to apply invalid resolution {}, falling back to RES_DESKTOP",
+ res);
+ res = RES_DESKTOP;
+ }
+
+ if (res >= RES_DESKTOP)
+ {
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_fullScreen = true;
+ m_bFullScreenRoot = true;
+ }
+ else
+ {
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_fullScreen = false;
+ m_bFullScreenRoot = false;
+ }
+
+ std::unique_lock<CCriticalSection> lock(*this);
+
+ UpdateInternalStateWithResolution(res);
+
+ m_scissors.SetRect(0, 0, (float)m_iScreenWidth, (float)m_iScreenHeight);
+
+ // make sure all stereo stuff are correctly setup
+ SetStereoView(RENDER_STEREO_VIEW_OFF);
+
+ // update anyone that relies on sizing information
+ RESOLUTION_INFO info_org = CDisplaySettings::GetInstance().GetResolutionInfo(res);
+ CServiceBroker::GetInputManager().SetMouseResolution(info_org.iWidth, info_org.iHeight, 1, 1);
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_WINDOW_RESIZE);
+}
+
+void CGraphicContext::UpdateInternalStateWithResolution(RESOLUTION res)
+{
+ RESOLUTION_INFO info_mod = GetResInfo(res);
+
+ m_iScreenWidth = info_mod.iWidth;
+ m_iScreenHeight = info_mod.iHeight;
+ m_Resolution = res;
+ m_fFPSOverride = 0;
+}
+
+void CGraphicContext::ApplyModeChange(RESOLUTION res)
+{
+ ApplyVideoResolution(res);
+ CServiceBroker::GetWinSystem()->FinishModeChange(res);
+}
+
+void CGraphicContext::ApplyWindowResize(int newWidth, int newHeight)
+{
+ CServiceBroker::GetWinSystem()->SetWindowResolution(newWidth, newHeight);
+ ApplyVideoResolution(RES_WINDOW);
+ CServiceBroker::GetWinSystem()->FinishWindowResize(newWidth, newHeight);
+}
+
+RESOLUTION CGraphicContext::GetVideoResolution() const
+{
+ return m_Resolution;
+}
+
+void CGraphicContext::ResetOverscan(RESOLUTION_INFO &res)
+{
+ res.Overscan.left = 0;
+ res.Overscan.top = 0;
+ res.Overscan.right = res.iWidth;
+ res.Overscan.bottom = res.iHeight;
+}
+
+void CGraphicContext::ResetOverscan(RESOLUTION res, OVERSCAN &overscan)
+{
+ overscan.left = 0;
+ overscan.top = 0;
+
+ RESOLUTION_INFO info = GetResInfo(res);
+ overscan.right = info.iWidth;
+ overscan.bottom = info.iHeight;
+}
+
+void CGraphicContext::ResetScreenParameters(RESOLUTION res)
+{
+ RESOLUTION_INFO& info = CDisplaySettings::GetInstance().GetResolutionInfo(res);
+
+ info.iSubtitles = info.iHeight;
+ info.fPixelRatio = 1.0f;
+ info.iScreenWidth = info.iWidth;
+ info.iScreenHeight = info.iHeight;
+ ResetOverscan(res, info.Overscan);
+}
+
+void CGraphicContext::Clear(UTILS::COLOR::Color color)
+{
+ CServiceBroker::GetRenderSystem()->ClearBuffers(color);
+}
+
+void CGraphicContext::CaptureStateBlock()
+{
+ CServiceBroker::GetRenderSystem()->CaptureStateBlock();
+}
+
+void CGraphicContext::ApplyStateBlock()
+{
+ CServiceBroker::GetRenderSystem()->ApplyStateBlock();
+}
+
+const RESOLUTION_INFO CGraphicContext::GetResInfo(RESOLUTION res) const
+{
+ RESOLUTION_INFO info = CDisplaySettings::GetInstance().GetResolutionInfo(res);
+
+ if(m_stereoMode == RENDER_STEREO_MODE_SPLIT_HORIZONTAL)
+ {
+ if((info.dwFlags & D3DPRESENTFLAG_MODE3DTB) == 0)
+ {
+ info.fPixelRatio /= 2;
+ info.iBlanking = 0;
+ info.dwFlags |= D3DPRESENTFLAG_MODE3DTB;
+ }
+ info.iHeight = (info.iHeight - info.iBlanking) / 2;
+ info.Overscan.top /= 2;
+ info.Overscan.bottom = (info.Overscan.bottom - info.iBlanking) / 2;
+ info.iSubtitles = (info.iSubtitles - info.iBlanking) / 2;
+ }
+
+ if(m_stereoMode == RENDER_STEREO_MODE_SPLIT_VERTICAL)
+ {
+ if((info.dwFlags & D3DPRESENTFLAG_MODE3DSBS) == 0)
+ {
+ info.fPixelRatio *= 2;
+ info.iBlanking = 0;
+ info.dwFlags |= D3DPRESENTFLAG_MODE3DSBS;
+ }
+ info.iWidth = (info.iWidth - info.iBlanking) / 2;
+ info.Overscan.left /= 2;
+ info.Overscan.right = (info.Overscan.right - info.iBlanking) / 2;
+ }
+
+ if (res == m_Resolution && m_fFPSOverride != 0)
+ {
+ info.fRefreshRate = m_fFPSOverride;
+ }
+
+ return info;
+}
+
+void CGraphicContext::SetResInfo(RESOLUTION res, const RESOLUTION_INFO& info)
+{
+ RESOLUTION_INFO& curr = CDisplaySettings::GetInstance().GetResolutionInfo(res);
+ curr.Overscan = info.Overscan;
+ curr.iSubtitles = info.iSubtitles;
+ curr.fPixelRatio = info.fPixelRatio;
+
+ if(info.dwFlags & D3DPRESENTFLAG_MODE3DSBS)
+ {
+ curr.Overscan.right = info.Overscan.right * 2 + info.iBlanking;
+ if((curr.dwFlags & D3DPRESENTFLAG_MODE3DSBS) == 0)
+ curr.fPixelRatio /= 2.0f;
+ }
+
+ if(info.dwFlags & D3DPRESENTFLAG_MODE3DTB)
+ {
+ curr.Overscan.bottom = info.Overscan.bottom * 2 + info.iBlanking;
+ curr.iSubtitles = info.iSubtitles * 2 + info.iBlanking;
+ if((curr.dwFlags & D3DPRESENTFLAG_MODE3DTB) == 0)
+ curr.fPixelRatio *= 2.0f;
+ }
+}
+
+const RESOLUTION_INFO CGraphicContext::GetResInfo() const
+{
+ return GetResInfo(m_Resolution);
+}
+
+void CGraphicContext::GetGUIScaling(const RESOLUTION_INFO &res, float &scaleX, float &scaleY, TransformMatrix *matrix /* = NULL */)
+{
+ if (m_Resolution != RES_INVALID)
+ {
+ // calculate necessary scalings
+ RESOLUTION_INFO info = GetResInfo();
+ float fFromWidth = (float)res.iWidth;
+ float fFromHeight = (float)res.iHeight;
+ auto fToPosX = info.Overscan.left + info.guiInsets.left;
+ auto fToPosY = info.Overscan.top + info.guiInsets.top;
+ auto fToWidth = info.Overscan.right - info.guiInsets.right - fToPosX;
+ auto fToHeight = info.Overscan.bottom - info.guiInsets.bottom - fToPosY;
+
+ float fZoom = (100 + CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_LOOKANDFEEL_SKINZOOM)) * 0.01f;
+
+ fZoom -= 1.0f;
+ fToPosX -= fToWidth * fZoom * 0.5f;
+ fToWidth *= fZoom + 1.0f;
+
+ // adjust for aspect ratio as zoom is given in the vertical direction and we don't
+ // do aspect ratio corrections in the gui code
+ fZoom = fZoom / info.fPixelRatio;
+ fToPosY -= fToHeight * fZoom * 0.5f;
+ fToHeight *= fZoom + 1.0f;
+
+ scaleX = fFromWidth / fToWidth;
+ scaleY = fFromHeight / fToHeight;
+ if (matrix)
+ {
+ TransformMatrix guiScaler = TransformMatrix::CreateScaler(fToWidth / fFromWidth, fToHeight / fFromHeight, fToHeight / fFromHeight);
+ TransformMatrix guiOffset = TransformMatrix::CreateTranslation(fToPosX, fToPosY);
+ *matrix = guiOffset * guiScaler;
+ }
+ }
+ else
+ {
+ scaleX = scaleY = 1.0f;
+ if (matrix)
+ matrix->Reset();
+ }
+}
+
+void CGraphicContext::SetScalingResolution(const RESOLUTION_INFO &res, bool needsScaling)
+{
+ m_windowResolution = res;
+ if (needsScaling && m_Resolution != RES_INVALID)
+ GetGUIScaling(res, m_guiTransform.scaleX, m_guiTransform.scaleY, &m_guiTransform.matrix);
+ else
+ {
+ m_guiTransform.Reset();
+ }
+
+ // reset our origin and camera
+ while (!m_origins.empty())
+ m_origins.pop();
+ m_origins.push(CPoint(0, 0));
+ while (!m_cameras.empty())
+ m_cameras.pop();
+ m_cameras.push(CPoint(0.5f*m_iScreenWidth, 0.5f*m_iScreenHeight));
+ while (!m_stereoFactors.empty())
+ m_stereoFactors.pop();
+ m_stereoFactors.push(0.0f);
+
+ // and reset the final transform
+ m_finalTransform = m_guiTransform;
+}
+
+void CGraphicContext::SetRenderingResolution(const RESOLUTION_INFO &res, bool needsScaling)
+{
+ std::unique_lock<CCriticalSection> lock(*this);
+
+ SetScalingResolution(res, needsScaling);
+ UpdateCameraPosition(m_cameras.top(), m_stereoFactors.top());
+}
+
+void CGraphicContext::SetStereoView(RENDER_STEREO_VIEW view)
+{
+ m_stereoView = view;
+
+ while(!m_viewStack.empty())
+ m_viewStack.pop();
+
+ CRect viewport(0.0f, 0.0f, (float)m_iScreenWidth, (float)m_iScreenHeight);
+
+ m_viewStack.push(viewport);
+
+ viewport = StereoCorrection(viewport);
+ CServiceBroker::GetRenderSystem()->SetStereoMode(m_stereoMode, m_stereoView);
+ CServiceBroker::GetRenderSystem()->SetViewPort(viewport);
+ CServiceBroker::GetRenderSystem()->SetScissors(viewport);
+}
+
+void CGraphicContext::InvertFinalCoords(float &x, float &y) const
+{
+ m_finalTransform.matrix.InverseTransformPosition(x, y);
+}
+
+float CGraphicContext::ScaleFinalXCoord(float x, float y) const
+{
+ return m_finalTransform.matrix.TransformXCoord(x, y, 0);
+}
+
+float CGraphicContext::ScaleFinalYCoord(float x, float y) const
+{
+ return m_finalTransform.matrix.TransformYCoord(x, y, 0);
+}
+
+float CGraphicContext::ScaleFinalZCoord(float x, float y) const
+{
+ return m_finalTransform.matrix.TransformZCoord(x, y, 0);
+}
+
+void CGraphicContext::ScaleFinalCoords(float &x, float &y, float &z) const
+{
+ m_finalTransform.matrix.TransformPosition(x, y, z);
+}
+
+float CGraphicContext::GetScalingPixelRatio() const
+{
+ // assume the resolutions are different - we want to return the aspect ratio of the video resolution
+ // but only once it's been corrected for the skin -> screen coordinates scaling
+ return GetResInfo().fPixelRatio * (m_finalTransform.scaleY / m_finalTransform.scaleX);
+}
+
+void CGraphicContext::SetCameraPosition(const CPoint &camera)
+{
+ // offset the camera from our current location (this is in XML coordinates) and scale it up to
+ // the screen resolution
+ CPoint cam(camera);
+ if (!m_origins.empty())
+ cam += m_origins.top();
+
+ cam.x *= (float)m_iScreenWidth / m_windowResolution.iWidth;
+ cam.y *= (float)m_iScreenHeight / m_windowResolution.iHeight;
+
+ m_cameras.push(cam);
+ UpdateCameraPosition(m_cameras.top(), m_stereoFactors.top());
+}
+
+void CGraphicContext::RestoreCameraPosition()
+{ // remove the top camera from the stack
+ assert(m_cameras.size());
+ m_cameras.pop();
+ UpdateCameraPosition(m_cameras.top(), m_stereoFactors.top());
+}
+
+void CGraphicContext::SetStereoFactor(float factor)
+{
+ m_stereoFactors.push(factor);
+ UpdateCameraPosition(m_cameras.top(), m_stereoFactors.top());
+}
+
+void CGraphicContext::RestoreStereoFactor()
+{ // remove the top factor from the stack
+ assert(m_stereoFactors.size());
+ m_stereoFactors.pop();
+ UpdateCameraPosition(m_cameras.top(), m_stereoFactors.top());
+}
+
+CRect CGraphicContext::GenerateAABB(const CRect &rect) const
+{
+// ------------------------
+// |(x1, y1) (x2, y2)|
+// | |
+// |(x3, y3) (x4, y4)|
+// ------------------------
+
+ float x1 = rect.x1, x2 = rect.x2, x3 = rect.x1, x4 = rect.x2;
+ float y1 = rect.y1, y2 = rect.y1, y3 = rect.y2, y4 = rect.y2;
+
+ float z = 0.0f;
+ ScaleFinalCoords(x1, y1, z);
+ CServiceBroker::GetRenderSystem()->Project(x1, y1, z);
+
+ z = 0.0f;
+ ScaleFinalCoords(x2, y2, z);
+ CServiceBroker::GetRenderSystem()->Project(x2, y2, z);
+
+ z = 0.0f;
+ ScaleFinalCoords(x3, y3, z);
+ CServiceBroker::GetRenderSystem()->Project(x3, y3, z);
+
+ z = 0.0f;
+ ScaleFinalCoords(x4, y4, z);
+ CServiceBroker::GetRenderSystem()->Project(x4, y4, z);
+
+ return CRect( std::min(std::min(std::min(x1, x2), x3), x4),
+ std::min(std::min(std::min(y1, y2), y3), y4),
+ std::max(std::max(std::max(x1, x2), x3), x4),
+ std::max(std::max(std::max(y1, y2), y3), y4));
+}
+
+// NOTE: This routine is currently called (twice) every time there is a <camera>
+// tag in the skin. It actually only has to be called before we render
+// something, so another option is to just save the camera coordinates
+// and then have a routine called before every draw that checks whether
+// the camera has changed, and if so, changes it. Similarly, it could set
+// the world transform at that point as well (or even combine world + view
+// to cut down on one setting)
+void CGraphicContext::UpdateCameraPosition(const CPoint &camera, const float &factor)
+{
+ float stereoFactor = 0.f;
+ if ( m_stereoMode != RENDER_STEREO_MODE_OFF
+ && m_stereoMode != RENDER_STEREO_MODE_MONO
+ && m_stereoView != RENDER_STEREO_VIEW_OFF)
+ {
+ RESOLUTION_INFO res = GetResInfo();
+ RESOLUTION_INFO desktop = GetResInfo(RES_DESKTOP);
+ float scaleRes = (static_cast<float>(res.iWidth) / static_cast<float>(desktop.iWidth));
+ float scaleX = static_cast<float>(CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_LOOKANDFEEL_STEREOSTRENGTH)) * scaleRes;
+ stereoFactor = factor * (m_stereoView == RENDER_STEREO_VIEW_LEFT ? scaleX : -scaleX);
+ }
+ CServiceBroker::GetRenderSystem()->SetCameraPosition(camera, m_iScreenWidth, m_iScreenHeight, stereoFactor);
+}
+
+bool CGraphicContext::RectIsAngled(float x1, float y1, float x2, float y2) const
+{ // need only test 3 points, as they must be co-planer
+ if (m_finalTransform.matrix.TransformZCoord(x1, y1, 0)) return true;
+ if (m_finalTransform.matrix.TransformZCoord(x2, y2, 0)) return true;
+ if (m_finalTransform.matrix.TransformZCoord(x1, y2, 0)) return true;
+ return false;
+}
+
+const TransformMatrix &CGraphicContext::GetGUIMatrix() const
+{
+ return m_finalTransform.matrix;
+}
+
+float CGraphicContext::GetGUIScaleX() const
+{
+ return m_finalTransform.scaleX;
+}
+
+float CGraphicContext::GetGUIScaleY() const
+{
+ return m_finalTransform.scaleY;
+}
+
+UTILS::COLOR::Color CGraphicContext::MergeAlpha(UTILS::COLOR::Color color) const
+{
+ UTILS::COLOR::Color alpha = m_finalTransform.matrix.TransformAlpha((color >> 24) & 0xff);
+ if (alpha > 255) alpha = 255;
+ return ((alpha << 24) & 0xff000000) | (color & 0xffffff);
+}
+
+UTILS::COLOR::Color CGraphicContext::MergeColor(UTILS::COLOR::Color color) const
+{
+ return m_finalTransform.matrix.TransformColor(color);
+}
+
+int CGraphicContext::GetWidth() const
+{
+ return m_iScreenWidth;
+}
+
+int CGraphicContext::GetHeight() const
+{
+ return m_iScreenHeight;
+}
+
+float CGraphicContext::GetFPS() const
+{
+ if (m_Resolution != RES_INVALID)
+ {
+ RESOLUTION_INFO info = GetResInfo();
+ if (info.fRefreshRate > 0)
+ return info.fRefreshRate;
+ }
+ return 60.0f;
+}
+
+float CGraphicContext::GetDisplayLatency() const
+{
+ float latency = CServiceBroker::GetWinSystem()->GetDisplayLatency();
+ if (latency < 0.0f)
+ {
+ // fallback
+ latency = (CServiceBroker::GetWinSystem()->NoOfBuffers() + 1) / GetFPS() * 1000.0f;
+ }
+
+ return latency;
+}
+
+bool CGraphicContext::IsFullScreenRoot () const
+{
+ return m_bFullScreenRoot;
+}
+
+void CGraphicContext::ToggleFullScreen()
+{
+ RESOLUTION uiRes;
+
+ if (m_bFullScreenRoot)
+ {
+ uiRes = RES_WINDOW;
+ }
+ else
+ {
+ if (CDisplaySettings::GetInstance().GetCurrentResolution() > RES_DESKTOP)
+ uiRes = CDisplaySettings::GetInstance().GetCurrentResolution();
+ else
+ uiRes = RES_DESKTOP;
+ }
+
+ CDisplaySettings::GetInstance().SetCurrentResolution(uiRes, true);
+}
+
+void CGraphicContext::SetMediaDir(const std::string &strMediaDir)
+{
+ CServiceBroker::GetGUI()->GetTextureManager().SetTexturePath(strMediaDir);
+ m_strMediaDir = strMediaDir;
+}
+
+const std::string& CGraphicContext::GetMediaDir() const
+{
+ return m_strMediaDir;
+
+}
+
+void CGraphicContext::Flip(bool rendered, bool videoLayer)
+{
+ CServiceBroker::GetRenderSystem()->PresentRender(rendered, videoLayer);
+
+ if(m_stereoMode != m_nextStereoMode)
+ {
+ m_stereoMode = m_nextStereoMode;
+ SetVideoResolution(GetVideoResolution(), true);
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_RENDERER_RESET);
+ }
+}
+
+void CGraphicContext::GetAllowedResolutions(std::vector<RESOLUTION> &res)
+{
+ res.clear();
+
+ res.push_back(RES_WINDOW);
+ res.push_back(RES_DESKTOP);
+ for (size_t r = (size_t) RES_CUSTOM; r < CDisplaySettings::GetInstance().ResolutionInfoSize(); r++)
+ {
+ res.push_back((RESOLUTION) r);
+ }
+}
+
+void CGraphicContext::SetFPS(float fps)
+{
+ m_fFPSOverride = fps;
+}
diff --git a/xbmc/windowing/GraphicContext.h b/xbmc/windowing/GraphicContext.h
new file mode 100644
index 0000000..9cd4f32
--- /dev/null
+++ b/xbmc/windowing/GraphicContext.h
@@ -0,0 +1,232 @@
+/*
+ * 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 "Resolution.h"
+#include "rendering/RenderSystem.h"
+#include "threads/CriticalSection.h"
+#include "utils/ColorUtils.h"
+#include "utils/Geometry.h" // for CRect/CPoint
+#include "utils/TransformMatrix.h" // for the members m_guiTransform etc.
+
+#include <map>
+#include <stack>
+#include <string>
+#include <vector>
+
+// required by clients
+#include "ServiceBroker.h"
+#include "WinSystem.h"
+
+#define D3DPRESENTFLAG_INTERLACED 1
+#define D3DPRESENTFLAG_WIDESCREEN 2
+#define D3DPRESENTFLAG_PROGRESSIVE 4
+#define D3DPRESENTFLAG_MODE3DSBS 8
+#define D3DPRESENTFLAG_MODE3DTB 16
+
+/* what types are important for mode setting */
+#define D3DPRESENTFLAG_MODEMASK ( D3DPRESENTFLAG_INTERLACED \
+ | D3DPRESENTFLAG_MODE3DSBS \
+ | D3DPRESENTFLAG_MODE3DTB )
+
+enum VIEW_TYPE { VIEW_TYPE_NONE = 0,
+ VIEW_TYPE_LIST,
+ VIEW_TYPE_ICON,
+ VIEW_TYPE_BIG_LIST,
+ VIEW_TYPE_BIG_ICON,
+ VIEW_TYPE_WIDE,
+ VIEW_TYPE_BIG_WIDE,
+ VIEW_TYPE_WRAP,
+ VIEW_TYPE_BIG_WRAP,
+ VIEW_TYPE_INFO,
+ VIEW_TYPE_BIG_INFO,
+ VIEW_TYPE_AUTO,
+ VIEW_TYPE_MAX };
+
+enum AdjustRefreshRate
+{
+ ADJUST_REFRESHRATE_OFF = 0,
+ ADJUST_REFRESHRATE_ALWAYS,
+ ADJUST_REFRESHRATE_ON_STARTSTOP,
+ ADJUST_REFRESHRATE_ON_START,
+};
+
+class CGraphicContext : public CCriticalSection
+{
+public:
+ CGraphicContext(void);
+ virtual ~CGraphicContext();
+
+ // methods related to windowing
+ float GetFPS() const;
+ void SetFPS(float fps);
+ float GetDisplayLatency() const;
+ bool IsFullScreenRoot() const;
+ void ToggleFullScreen();
+ void SetFullScreenVideo(bool bOnOff);
+ bool IsFullScreenVideo() const;
+ bool IsValidResolution(RESOLUTION res);
+ void SetVideoResolution(RESOLUTION res, bool forceUpdate);
+ void ApplyModeChange(RESOLUTION res);
+ void ApplyWindowResize(int newWidth, int newHeight);
+ RESOLUTION GetVideoResolution() const;
+ const RESOLUTION_INFO GetResInfo() const;
+ const RESOLUTION_INFO GetResInfo(RESOLUTION res) const;
+ void SetResInfo(RESOLUTION res, const RESOLUTION_INFO& info);
+
+ void Flip(bool rendered, bool videoLayer);
+
+ // gfx context interface
+ int GetWidth() const;
+ int GetHeight() const;
+ bool SetViewPort(float fx, float fy , float fwidth, float fheight, bool intersectPrevious = false);
+ void RestoreViewPort();
+ void SetScissors(const CRect &rect);
+ void ResetScissors();
+ const CRect &GetScissors() const;
+ const CRect GetViewWindow() const;
+ void SetViewWindow(float left, float top, float right, float bottom);
+ bool IsCalibrating() const;
+ void SetCalibrating(bool bOnOff);
+ void ResetOverscan(RESOLUTION res, OVERSCAN &overscan);
+ void ResetOverscan(RESOLUTION_INFO &resinfo);
+ void ResetScreenParameters(RESOLUTION res);
+ void CaptureStateBlock();
+ void ApplyStateBlock();
+ void Clear(UTILS::COLOR::Color color = 0);
+ void GetAllowedResolutions(std::vector<RESOLUTION> &res);
+
+ /* \brief Get UI scaling information from a given resolution to the screen resolution.
+ Takes account of overscan and UI zooming.
+ \param res the resolution to scale from.
+ \param scaleX [out] the scaling amount in the X direction.
+ \param scaleY [out] the scaling amount in the Y direction.
+ \param matrix [out] if non-NULL, a suitable transformation from res to screen resolution is set.
+ */
+ void GetGUIScaling(const RESOLUTION_INFO &res, float &scaleX, float &scaleY, TransformMatrix *matrix = NULL);
+ void SetRenderingResolution(const RESOLUTION_INFO &res, bool needsScaling); ///< Sets scaling up for rendering
+ void SetScalingResolution(const RESOLUTION_INFO &res, bool needsScaling); ///< Sets scaling up for skin loading etc.
+ float GetScalingPixelRatio() const;
+ void InvertFinalCoords(float &x, float &y) const;
+ float ScaleFinalXCoord(float x, float y) const;
+ float ScaleFinalYCoord(float x, float y) const;
+ float ScaleFinalZCoord(float x, float y) const;
+ void ScaleFinalCoords(float &x, float &y, float &z) const;
+ bool RectIsAngled(float x1, float y1, float x2, float y2) const;
+ const TransformMatrix &GetGUIMatrix() const;
+ float GetGUIScaleX() const;
+ float GetGUIScaleY() const;
+ UTILS::COLOR::Color MergeAlpha(UTILS::COLOR::Color color) const;
+ UTILS::COLOR::Color MergeColor(UTILS::COLOR::Color color) const;
+ void SetOrigin(float x, float y);
+ void RestoreOrigin();
+ void SetCameraPosition(const CPoint &camera);
+ void SetStereoView(RENDER_STEREO_VIEW view);
+ RENDER_STEREO_VIEW GetStereoView() { return m_stereoView; }
+ void SetStereoMode(RENDER_STEREO_MODE mode) { m_nextStereoMode = mode; }
+ RENDER_STEREO_MODE GetStereoMode() { return m_stereoMode; }
+ void RestoreCameraPosition();
+ void SetStereoFactor(float factor);
+ void RestoreStereoFactor();
+ /*! \brief Set a region in which to clip all rendering
+ Anything that is rendered after setting a clip region will be clipped so that no part renders
+ outside of the clip region. Successive calls to SetClipRegion intersect the clip region, which
+ means the clip region may eventually become an empty set. In this case SetClipRegion returns false
+ to indicate that no rendering need be performed.
+
+ This call must be matched with a RestoreClipRegion call unless SetClipRegion returns false.
+
+ Usage should be of the form:
+
+ if (SetClipRegion(x, y, w, h))
+ {
+ ...
+ perform rendering
+ ...
+ RestoreClipRegion();
+ }
+
+ \param x the left-most coordinate of the clip region
+ \param y the top-most coordinate of the clip region
+ \param w the width of the clip region
+ \param h the height of the clip region
+ \returns true if the region is set and the result is non-empty. Returns false if the resulting region is empty.
+ \sa RestoreClipRegion
+ */
+ bool SetClipRegion(float x, float y, float w, float h);
+ void RestoreClipRegion();
+ void ClipRect(CRect &vertex, CRect &texture, CRect *diffuse = NULL);
+ CRect GetClipRegion();
+ void AddGUITransform();
+ TransformMatrix AddTransform(const TransformMatrix &matrix);
+ void SetTransform(const TransformMatrix &matrix);
+ void SetTransform(const TransformMatrix &matrix, float scaleX, float scaleY);
+ void RemoveTransform();
+
+ /* modifies final coordinates according to stereo mode if needed */
+ CRect StereoCorrection(const CRect &rect) const;
+ CPoint StereoCorrection(const CPoint &point) const;
+
+ CRect GenerateAABB(const CRect &rect) const;
+
+ //@todo move those somewhere else
+ const std::string& GetMediaDir() const;
+ void SetMediaDir(const std::string& strMediaDir);
+
+protected:
+
+ void UpdateCameraPosition(const CPoint &camera, const float &factor);
+ void SetVideoResolutionInternal(RESOLUTION res, bool forceUpdate);
+ void ApplyVideoResolution(RESOLUTION res);
+ void UpdateInternalStateWithResolution(RESOLUTION res);
+
+ int m_iScreenHeight = 576;
+ int m_iScreenWidth = 720;
+ std::string m_strMediaDir;
+ CRect m_videoRect;
+ bool m_bFullScreenRoot = true;
+ bool m_bFullScreenVideo = false;
+ bool m_bCalibrating = false;
+ RESOLUTION m_Resolution = RES_INVALID;
+ float m_fFPSOverride = 0.0f;
+
+ RESOLUTION_INFO m_windowResolution;
+ std::stack<CPoint> m_cameras;
+ std::stack<CPoint> m_origins;
+ std::stack<CRect> m_clipRegions;
+ std::stack<float> m_stereoFactors;
+ std::stack<CRect> m_viewStack;
+ CRect m_scissors;
+
+ class UITransform
+ {
+ public:
+ UITransform() : matrix() {}
+ UITransform(const TransformMatrix& m, const float sX = 1.0f, const float sY = 1.0f)
+ : matrix(m), scaleX(sX), scaleY(sY)
+ {
+ }
+ void Reset()
+ {
+ matrix.Reset();
+ scaleX = scaleY = 1.0f;
+ }
+
+ TransformMatrix matrix;
+ float scaleX = 1.0f;
+ float scaleY = 1.0f;
+ };
+
+ UITransform m_guiTransform;
+ UITransform m_finalTransform;
+ std::stack<UITransform> m_transforms;
+ RENDER_STEREO_VIEW m_stereoView = RENDER_STEREO_VIEW_OFF;
+ RENDER_STEREO_MODE m_stereoMode = RENDER_STEREO_MODE_OFF;
+ RENDER_STEREO_MODE m_nextStereoMode = RENDER_STEREO_MODE_OFF;
+};
diff --git a/xbmc/windowing/OSScreenSaver.cpp b/xbmc/windowing/OSScreenSaver.cpp
new file mode 100644
index 0000000..3651314
--- /dev/null
+++ b/xbmc/windowing/OSScreenSaver.cpp
@@ -0,0 +1,98 @@
+/*
+ * 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 "OSScreenSaver.h"
+
+#include "utils/log.h"
+
+using namespace KODI::WINDOWING;
+
+COSScreenSaverManager::COSScreenSaverManager(std::unique_ptr<IOSScreenSaver> impl)
+: m_impl{std::move(impl)}
+{
+}
+
+COSScreenSaverInhibitor COSScreenSaverManager::CreateInhibitor()
+{
+ COSScreenSaverInhibitor inhibitor{this};
+ if (m_inhibitionCount++ == 0)
+ {
+ // Inhibit if this was first inhibitor
+ CLog::Log(LOGDEBUG, "Inhibiting OS screen saver");
+ m_impl->Inhibit();
+ }
+ return inhibitor;
+}
+
+bool COSScreenSaverManager::IsInhibited()
+{
+ return (m_inhibitionCount > 0);
+}
+
+void COSScreenSaverManager::RemoveInhibitor()
+{
+ if (--m_inhibitionCount == 0)
+ {
+ CLog::Log(LOGDEBUG, "Uninhibiting OS screen saver");
+ // Uninhibit if this was last inhibitor
+ m_impl->Uninhibit();
+ }
+}
+
+COSScreenSaverInhibitor::COSScreenSaverInhibitor() noexcept
+: m_active{false}, m_manager{}
+{
+}
+
+COSScreenSaverInhibitor::COSScreenSaverInhibitor(COSScreenSaverManager* manager)
+: m_active{true}, m_manager{manager}
+{
+}
+
+COSScreenSaverInhibitor::COSScreenSaverInhibitor(COSScreenSaverInhibitor&& other) noexcept
+: m_active{false}, m_manager{}
+{
+ *this = std::move(other);
+}
+
+COSScreenSaverInhibitor& COSScreenSaverInhibitor::operator=(COSScreenSaverInhibitor&& other) noexcept
+{
+ Release();
+ m_active = other.m_active;
+ m_manager = other.m_manager;
+ other.m_active = false;
+ other.m_manager = nullptr;
+ return *this;
+}
+
+bool COSScreenSaverInhibitor::IsActive() const
+{
+ return m_active;
+}
+
+COSScreenSaverInhibitor::operator bool() const
+{
+ return IsActive();
+}
+
+void COSScreenSaverInhibitor::Release()
+{
+ if (m_active)
+ {
+ m_manager->RemoveInhibitor();
+ m_active = false;
+ }
+}
+
+COSScreenSaverInhibitor::~COSScreenSaverInhibitor() noexcept
+{
+ Release();
+}
+
+
+
diff --git a/xbmc/windowing/OSScreenSaver.h b/xbmc/windowing/OSScreenSaver.h
new file mode 100644
index 0000000..367d6db
--- /dev/null
+++ b/xbmc/windowing/OSScreenSaver.h
@@ -0,0 +1,118 @@
+/*
+ * 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 <memory>
+#include <utility>
+
+namespace KODI
+{
+namespace WINDOWING
+{
+
+class COSScreenSaverManager;
+
+/**
+ * Inhibit the OS screen saver as long as this object is alive
+ *
+ * Destroy or call \ref Release to stop this inhibitor from being active.
+ * The OS screen saver may still be inhibited as long as other inhibitors are
+ * active though.
+ *
+ * \note Make sure to release or destroy the inhibitor before the \ref
+ * COSScreenSaverManager is destroyed
+ */
+class COSScreenSaverInhibitor
+{
+public:
+ COSScreenSaverInhibitor() noexcept;
+ COSScreenSaverInhibitor(COSScreenSaverInhibitor&& other) noexcept;
+ COSScreenSaverInhibitor& operator=(COSScreenSaverInhibitor&& other) noexcept;
+ ~COSScreenSaverInhibitor() noexcept;
+ void Release();
+ bool IsActive() const;
+ operator bool() const;
+
+private:
+ friend class COSScreenSaverManager;
+ explicit COSScreenSaverInhibitor(COSScreenSaverManager* manager);
+ bool m_active;
+ COSScreenSaverManager* m_manager;
+
+ COSScreenSaverInhibitor(COSScreenSaverInhibitor const& other) = delete;
+ COSScreenSaverInhibitor& operator=(COSScreenSaverInhibitor const& other) = delete;
+};
+
+/**
+ * Interface for OS screen saver control implementations
+ */
+class IOSScreenSaver
+{
+public:
+ virtual ~IOSScreenSaver() = default;
+ /**
+ * Do not allow the OS screen saver to become active
+ *
+ * Calling this function multiple times without calling \ref Unhibit
+ * MUST NOT produce any side-effects.
+ */
+ virtual void Inhibit() = 0;
+ /**
+ * Allow the OS screen saver to become active again
+ *
+ * Calling this function multiple times or at all without calling \ref Inhibit
+ * MUST NOT produce any side-effects.
+ */
+ virtual void Uninhibit() = 0;
+};
+
+/**
+ * Dummy implementation of IOSScreenSaver
+ */
+class CDummyOSScreenSaver : public IOSScreenSaver
+{
+public:
+ void Inhibit() override {}
+ void Uninhibit() override {}
+};
+
+/**
+ * Manage the OS screen saver
+ *
+ * This class keeps track of a number of \ref COSScreenSaverInhibitor instances
+ * and keeps the OS screen saver inhibited as long as at least one of them
+ * exists and is active.
+ */
+class COSScreenSaverManager
+{
+public:
+ /**
+ * Create manager with backing OS-specific implementation
+ */
+ explicit COSScreenSaverManager(std::unique_ptr<IOSScreenSaver> impl);
+ /**
+ * Create inhibitor that prevents the OS screen saver from becoming active as
+ * long as it is alive
+ */
+ COSScreenSaverInhibitor CreateInhibitor();
+ /**
+ * Check whether the OS screen saver is currently inhibited
+ */
+ bool IsInhibited();
+
+private:
+ friend class COSScreenSaverInhibitor;
+ void RemoveInhibitor();
+
+ unsigned int m_inhibitionCount{0u};
+ std::unique_ptr<IOSScreenSaver> m_impl;
+};
+
+}
+}
diff --git a/xbmc/windowing/Resolution.cpp b/xbmc/windowing/Resolution.cpp
new file mode 100644
index 0000000..8d9af06
--- /dev/null
+++ b/xbmc/windowing/Resolution.cpp
@@ -0,0 +1,474 @@
+/*
+ * 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 "Resolution.h"
+
+#include "GraphicContext.h"
+#include "ServiceBroker.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/DisplaySettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/MathUtils.h"
+#include "utils/Variant.h"
+#include "utils/log.h"
+
+#include <cstdlib>
+#include <limits>
+
+namespace
+{
+
+const char* SETTING_VIDEOSCREEN_WHITELIST_PULLDOWN{"videoscreen.whitelistpulldown"};
+const char* SETTING_VIDEOSCREEN_WHITELIST_DOUBLEREFRESHRATE{
+ "videoscreen.whitelistdoublerefreshrate"};
+
+} // namespace
+
+EdgeInsets::EdgeInsets(float l, float t, float r, float b) : left(l), top(t), right(r), bottom(b)
+{
+}
+
+RESOLUTION_INFO::RESOLUTION_INFO(int width, int height, float aspect, const std::string &mode) :
+ strMode(mode)
+{
+ iWidth = width;
+ iHeight = height;
+ iBlanking = 0;
+ iScreenWidth = width;
+ iScreenHeight = height;
+ fPixelRatio = aspect ? ((float)width)/height / aspect : 1.0f;
+ bFullScreen = true;
+ fRefreshRate = 0;
+ dwFlags = iSubtitles = 0;
+}
+
+RESOLUTION_INFO::RESOLUTION_INFO(const RESOLUTION_INFO& res)
+ : Overscan(res.Overscan),
+ guiInsets(res.guiInsets),
+ strMode(res.strMode),
+ strOutput(res.strOutput),
+ strId(res.strId)
+{
+ bFullScreen = res.bFullScreen;
+ iWidth = res.iWidth; iHeight = res.iHeight;
+ iScreenWidth = res.iScreenWidth; iScreenHeight = res.iScreenHeight;
+ iSubtitles = res.iSubtitles; dwFlags = res.dwFlags;
+ fPixelRatio = res.fPixelRatio; fRefreshRate = res.fRefreshRate;
+ iBlanking = res.iBlanking;
+}
+
+float RESOLUTION_INFO::DisplayRatio() const
+{
+ return iWidth * fPixelRatio / iHeight;
+}
+
+RESOLUTION CResolutionUtils::ChooseBestResolution(float fps, int width, int height, bool is3D)
+{
+ RESOLUTION res = CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution();
+ float weight = 0.0f;
+
+ if (!FindResolutionFromOverride(fps, width, is3D, res, weight, false)) //find a refreshrate from overrides
+ {
+ if (!FindResolutionFromOverride(fps, width, is3D, res, weight, true)) //if that fails find it from a fallback
+ {
+ FindResolutionFromWhitelist(fps, width, height, is3D, res); //find a refreshrate from whitelist
+ }
+ }
+
+ CLog::Log(LOGINFO, "Display resolution ADJUST : {} ({}) (weight: {:.3f})",
+ CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(res).strMode, res, weight);
+ return res;
+}
+
+void CResolutionUtils::FindResolutionFromWhitelist(float fps, int width, int height, bool is3D, RESOLUTION &resolution)
+{
+ RESOLUTION_INFO curr = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(resolution);
+ CLog::Log(LOGINFO,
+ "[WHITELIST] Searching the whitelist for: width: {}, height: {}, fps: {:0.3f}, 3D: {}",
+ width, height, fps, is3D ? "true" : "false");
+
+ std::vector<CVariant> indexList = CServiceBroker::GetSettingsComponent()->GetSettings()->GetList(CSettings::SETTING_VIDEOSCREEN_WHITELIST);
+
+ bool noWhiteList = indexList.empty();
+
+ if (noWhiteList)
+ {
+ CLog::Log(LOGDEBUG,
+ "[WHITELIST] Using the default whitelist because the user whitelist is empty");
+ std::vector<RESOLUTION> candidates;
+ RESOLUTION_INFO info;
+ std::string resString;
+ CServiceBroker::GetWinSystem()->GetGfxContext().GetAllowedResolutions(candidates);
+ for (const auto& c : candidates)
+ {
+ info = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(c);
+ if (info.iScreenHeight >= curr.iScreenHeight && info.iScreenWidth >= curr.iScreenWidth &&
+ (info.dwFlags & D3DPRESENTFLAG_MODEMASK) == (curr.dwFlags & D3DPRESENTFLAG_MODEMASK))
+ {
+ // do not add half refreshrates (25, 29.97 by default) as kodi cannot cope with
+ // them on playback start. Especially interlaced content is not properly detected
+ // and this causes ugly double switching.
+ // This won't allow 25p / 30p playback on native refreshrate by default
+ if ((info.fRefreshRate > 30) || (MathUtils::FloatEquals(info.fRefreshRate, 24.0f, 0.1f)))
+ {
+ resString = CDisplaySettings::GetInstance().GetStringFromRes(c);
+ indexList.emplace_back(resString);
+ }
+ }
+ }
+ }
+
+ CLog::Log(LOGDEBUG, "[WHITELIST] Searching for an exact resolution with an exact refresh rate");
+
+ unsigned int penalty = std::numeric_limits<unsigned int>::max();
+ bool found = false;
+
+ for (const auto& mode : indexList)
+ {
+ auto i = CDisplaySettings::GetInstance().GetResFromString(mode.asString());
+ const RESOLUTION_INFO info = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(i);
+
+ // allow resolutions that are exact and have the correct refresh rate
+ // allow macroblock alignment / padding errors (e.g. 1080 mod16 == 8)
+ if (((height == info.iScreenHeight && width <= info.iScreenWidth + 8) ||
+ (width == info.iScreenWidth && height <= info.iScreenHeight + 8)) &&
+ (info.dwFlags & D3DPRESENTFLAG_MODEMASK) == (curr.dwFlags & D3DPRESENTFLAG_MODEMASK) &&
+ MathUtils::FloatEquals(info.fRefreshRate, fps, 0.01f))
+ {
+ CLog::Log(LOGDEBUG,
+ "[WHITELIST] Matched an exact resolution with an exact refresh rate {} ({})",
+ info.strMode, i);
+ unsigned int pen = abs(info.iScreenHeight - height) + abs(info.iScreenWidth - width);
+ if (pen < penalty)
+ {
+ resolution = i;
+ found = true;
+ penalty = pen;
+ }
+ }
+ }
+
+ if (!found)
+ CLog::Log(LOGDEBUG, "[WHITELIST] No match for an exact resolution with an exact refresh rate");
+
+ if (noWhiteList || CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ SETTING_VIDEOSCREEN_WHITELIST_DOUBLEREFRESHRATE))
+ {
+ CLog::Log(LOGDEBUG,
+ "[WHITELIST] Searching for an exact resolution with double the refresh rate");
+
+ for (const auto& mode : indexList)
+ {
+ auto i = CDisplaySettings::GetInstance().GetResFromString(mode.asString());
+ const RESOLUTION_INFO info = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(i);
+
+ // allow resolutions that are exact and have double the refresh rate
+ // allow macroblock alignment / padding errors (e.g. 1080 mod16 == 8)
+ if (((height == info.iScreenHeight && width <= info.iScreenWidth + 8) ||
+ (width == info.iScreenWidth && height <= info.iScreenHeight + 8)) &&
+ (info.dwFlags & D3DPRESENTFLAG_MODEMASK) == (curr.dwFlags & D3DPRESENTFLAG_MODEMASK) &&
+ MathUtils::FloatEquals(info.fRefreshRate, fps * 2, 0.01f))
+ {
+ CLog::Log(LOGDEBUG,
+ "[WHITELIST] Matched an exact resolution with double the refresh rate {} ({})",
+ info.strMode, i);
+ unsigned int pen = abs(info.iScreenHeight - height) + abs(info.iScreenWidth - width);
+ if (pen < penalty)
+ {
+ resolution = i;
+ found = true;
+ penalty = pen;
+ }
+ }
+ }
+ if (found)
+ return;
+
+ CLog::Log(LOGDEBUG,
+ "[WHITELIST] No match for an exact resolution with double the refresh rate");
+ }
+ else if (found)
+ return;
+
+ if (noWhiteList || CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ SETTING_VIDEOSCREEN_WHITELIST_PULLDOWN))
+ {
+ CLog::Log(LOGDEBUG,
+ "[WHITELIST] Searching for an exact resolution with a 3:2 pulldown refresh rate");
+
+ for (const auto& mode : indexList)
+ {
+ auto i = CDisplaySettings::GetInstance().GetResFromString(mode.asString());
+ const RESOLUTION_INFO info = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(i);
+
+ // allow resolutions that are exact and have 2.5 times the refresh rate
+ // allow macroblock alignment / padding errors (e.g. 1080 mod16 == 8)
+ if (((height == info.iScreenHeight && width <= info.iScreenWidth + 8) ||
+ (width == info.iScreenWidth && height <= info.iScreenHeight + 8)) &&
+ (info.dwFlags & D3DPRESENTFLAG_MODEMASK) == (curr.dwFlags & D3DPRESENTFLAG_MODEMASK) &&
+ MathUtils::FloatEquals(info.fRefreshRate, fps * 2.5f, 0.01f))
+ {
+ CLog::Log(
+ LOGDEBUG,
+ "[WHITELIST] Matched an exact resolution with a 3:2 pulldown refresh rate {} ({})",
+ info.strMode, i);
+ unsigned int pen = abs(info.iScreenHeight - height) + abs(info.iScreenWidth - width);
+ if (pen < penalty)
+ {
+ resolution = i;
+ found = true;
+ penalty = pen;
+ }
+ }
+ }
+ if (found)
+ return;
+
+ CLog::Log(LOGDEBUG, "[WHITELIST] No match for a resolution with a 3:2 pulldown refresh rate");
+ }
+
+
+ CLog::Log(LOGDEBUG, "[WHITELIST] Searching for a desktop resolution with an exact refresh rate");
+
+ const RESOLUTION_INFO desktop_info = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(CDisplaySettings::GetInstance().GetCurrentResolution());
+
+ for (const auto& mode : indexList)
+ {
+ auto i = CDisplaySettings::GetInstance().GetResFromString(mode.asString());
+ const RESOLUTION_INFO info = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(i);
+
+ // allow resolutions that are desktop resolution but have the correct refresh rate
+ if (info.iScreenWidth == desktop_info.iScreenWidth &&
+ (info.dwFlags & D3DPRESENTFLAG_MODEMASK) == (desktop_info.dwFlags & D3DPRESENTFLAG_MODEMASK) &&
+ MathUtils::FloatEquals(info.fRefreshRate, fps, 0.01f))
+ {
+ CLog::Log(LOGDEBUG,
+ "[WHITELIST] Matched a desktop resolution with an exact refresh rate {} ({})",
+ info.strMode, i);
+ resolution = i;
+ return;
+ }
+ }
+
+ CLog::Log(LOGDEBUG, "[WHITELIST] No match for a desktop resolution with an exact refresh rate");
+
+ if (noWhiteList || CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ SETTING_VIDEOSCREEN_WHITELIST_DOUBLEREFRESHRATE))
+ {
+ CLog::Log(LOGDEBUG,
+ "[WHITELIST] Searching for a desktop resolution with double the refresh rate");
+
+ for (const auto& mode : indexList)
+ {
+ auto i = CDisplaySettings::GetInstance().GetResFromString(mode.asString());
+ const RESOLUTION_INFO info = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(i);
+
+ // allow resolutions that are desktop resolution but have double the refresh rate
+ if (info.iScreenWidth == desktop_info.iScreenWidth &&
+ (info.dwFlags & D3DPRESENTFLAG_MODEMASK) ==
+ (desktop_info.dwFlags & D3DPRESENTFLAG_MODEMASK) &&
+ MathUtils::FloatEquals(info.fRefreshRate, fps * 2, 0.01f))
+ {
+ CLog::Log(LOGDEBUG,
+ "[WHITELIST] Matched a desktop resolution with double the refresh rate {} ({})",
+ info.strMode, i);
+ resolution = i;
+ return;
+ }
+ }
+
+ CLog::Log(LOGDEBUG,
+ "[WHITELIST] No match for a desktop resolution with double the refresh rate");
+ }
+
+ if (noWhiteList || CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ SETTING_VIDEOSCREEN_WHITELIST_PULLDOWN))
+ {
+ CLog::Log(LOGDEBUG,
+ "[WHITELIST] Searching for a desktop resolution with a 3:2 pulldown refresh rate");
+
+ for (const auto& mode : indexList)
+ {
+ auto i = CDisplaySettings::GetInstance().GetResFromString(mode.asString());
+ const RESOLUTION_INFO info = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(i);
+
+ // allow resolutions that are desktop resolution but have 2.5 times the refresh rate
+ if (info.iScreenWidth == desktop_info.iScreenWidth &&
+ (info.dwFlags & D3DPRESENTFLAG_MODEMASK) ==
+ (desktop_info.dwFlags & D3DPRESENTFLAG_MODEMASK) &&
+ MathUtils::FloatEquals(info.fRefreshRate, fps * 2.5f, 0.01f))
+ {
+ CLog::Log(
+ LOGDEBUG,
+ "[WHITELIST] Matched a desktop resolution with a 3:2 pulldown refresh rate {} ({})",
+ info.strMode, i);
+ resolution = i;
+ return;
+ }
+ }
+
+ CLog::Log(LOGDEBUG,
+ "[WHITELIST] No match for a desktop resolution with a 3:2 pulldown refresh rate");
+ }
+
+ CLog::Log(LOGDEBUG, "[WHITELIST] No resolution matched");
+}
+
+bool CResolutionUtils::FindResolutionFromOverride(float fps, int width, bool is3D, RESOLUTION &resolution, float& weight, bool fallback)
+{
+ RESOLUTION_INFO curr = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(resolution);
+
+ //try to find a refreshrate from the override
+ for (int i = 0; i < (int)CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoAdjustRefreshOverrides.size(); i++)
+ {
+ RefreshOverride& override = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoAdjustRefreshOverrides[i];
+
+ if (override.fallback != fallback)
+ continue;
+
+ //if we're checking for overrides, check if the fps matches
+ if (!fallback && (fps < override.fpsmin || fps > override.fpsmax))
+ continue;
+
+ for (size_t j = (int)RES_DESKTOP; j < CDisplaySettings::GetInstance().ResolutionInfoSize(); j++)
+ {
+ RESOLUTION_INFO info = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo((RESOLUTION)j);
+
+ if (info.iScreenWidth == curr.iScreenWidth &&
+ info.iScreenHeight == curr.iScreenHeight &&
+ (info.dwFlags & D3DPRESENTFLAG_MODEMASK) == (curr.dwFlags & D3DPRESENTFLAG_MODEMASK))
+ {
+ if (info.fRefreshRate <= override.refreshmax &&
+ info.fRefreshRate >= override.refreshmin)
+ {
+ resolution = (RESOLUTION)j;
+
+ if (fallback)
+ {
+ CLog::Log(
+ LOGDEBUG,
+ "Found Resolution {} ({}) from fallback (refreshmin:{:.3f} refreshmax:{:.3f})",
+ info.strMode, resolution, override.refreshmin, override.refreshmax);
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG,
+ "Found Resolution {} ({}) from override of fps {:.3f} (fpsmin:{:.3f} "
+ "fpsmax:{:.3f} refreshmin:{:.3f} refreshmax:{:.3f})",
+ info.strMode, resolution, fps, override.fpsmin, override.fpsmax,
+ override.refreshmin, override.refreshmax);
+ }
+
+ weight = RefreshWeight(info.fRefreshRate, fps);
+
+ return true; //fps and refresh match with this override, use this resolution
+ }
+ }
+ }
+ }
+
+ return false; //no override found
+}
+
+//distance of refresh to the closest multiple of fps (multiple is 1 or higher), as a multiplier of fps
+float CResolutionUtils::RefreshWeight(float refresh, float fps)
+{
+ float div = refresh / fps;
+ int round = MathUtils::round_int(static_cast<double>(div));
+
+ float weight = 0.0f;
+
+ if (round < 1)
+ weight = (fps - refresh) / fps;
+ else
+ weight = fabs(div / round - 1.0f);
+
+ // punish higher refreshrates and prefer better matching
+ // e.g. 30 fps content at 60 hz is better than
+ // 30 fps at 120 hz - as we sometimes don't know if
+ // the content is interlaced at the start, only
+ // punish when refreshrate > 60 hz to not have to switch
+ // twice for 30i content
+ if (refresh > 60 && round > 1)
+ weight += round / 10000.0f;
+
+ return weight;
+}
+
+bool CResolutionUtils::HasWhitelist()
+{
+ std::vector<CVariant> indexList = CServiceBroker::GetSettingsComponent()->GetSettings()->GetList(CSettings::SETTING_VIDEOSCREEN_WHITELIST);
+ return !indexList.empty();
+}
+
+void CResolutionUtils::PrintWhitelist()
+{
+ std::string modeStr;
+ auto indexList = CServiceBroker::GetSettingsComponent()->GetSettings()->GetList(
+ CSettings::SETTING_VIDEOSCREEN_WHITELIST);
+ if (!indexList.empty())
+ {
+ for (const auto& mode : indexList)
+ {
+ auto i = CDisplaySettings::GetInstance().GetResFromString(mode.asString());
+ const RESOLUTION_INFO info = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(i);
+ modeStr.append("\n" + info.strMode);
+ }
+
+ CLog::Log(LOGDEBUG, "[WHITELIST] whitelisted modes:{}", modeStr);
+ }
+}
+
+void CResolutionUtils::GetMaxAllowedResolution(unsigned int& width, unsigned int& height)
+{
+ if (!CServiceBroker::GetWinSystem()->GetGfxContext().IsFullScreenRoot())
+ return;
+
+ std::vector<RESOLUTION_INFO> resList;
+
+ auto indexList = CServiceBroker::GetSettingsComponent()->GetSettings()->GetList(
+ CSettings::SETTING_VIDEOSCREEN_WHITELIST);
+
+ unsigned int maxWidth{0};
+ unsigned int maxHeight{0};
+
+ if (!indexList.empty())
+ {
+ for (const auto& mode : indexList)
+ {
+ RESOLUTION res = CDisplaySettings::GetInstance().GetResFromString(mode.asString());
+ RESOLUTION_INFO resInfo{CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(res)};
+ if (static_cast<unsigned int>(resInfo.iScreenWidth) > maxWidth &&
+ static_cast<unsigned int>(resInfo.iScreenHeight) > maxHeight)
+ {
+ maxWidth = static_cast<unsigned int>(resInfo.iScreenWidth);
+ maxHeight = static_cast<unsigned int>(resInfo.iScreenHeight);
+ }
+ }
+ }
+ else
+ {
+ std::vector<RESOLUTION> resList;
+ CServiceBroker::GetWinSystem()->GetGfxContext().GetAllowedResolutions(resList);
+
+ for (const auto& res : resList)
+ {
+ RESOLUTION_INFO resInfo{CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(res)};
+ if (static_cast<unsigned int>(resInfo.iScreenWidth) > maxWidth &&
+ static_cast<unsigned int>(resInfo.iScreenHeight) > maxHeight)
+ {
+ maxWidth = static_cast<unsigned int>(resInfo.iScreenWidth);
+ maxHeight = static_cast<unsigned int>(resInfo.iScreenHeight);
+ }
+ }
+ }
+
+ width = maxWidth;
+ height = maxHeight;
+}
diff --git a/xbmc/windowing/Resolution.h b/xbmc/windowing/Resolution.h
new file mode 100644
index 0000000..768d459
--- /dev/null
+++ b/xbmc/windowing/Resolution.h
@@ -0,0 +1,107 @@
+/*
+ * 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 <stdint.h>
+#include <string>
+
+typedef int DisplayMode;
+#define DM_WINDOWED -1
+#define DM_FULLSCREEN 0
+
+enum RESOLUTION
+{
+ RES_INVALID = -1,
+ RES_WINDOW = 15,
+ RES_DESKTOP = 16, // Desktop resolution
+ RES_CUSTOM = 16 + 1, // First additional resolution
+};
+
+struct OVERSCAN
+{
+ int left;
+ int top;
+ int right;
+ int bottom;
+public:
+ OVERSCAN()
+ {
+ left = top = right = bottom = 0;
+ }
+ OVERSCAN(const OVERSCAN& os)
+ {
+ left = os.left; top = os.top;
+ right = os.right; bottom = os.bottom;
+ }
+ OVERSCAN& operator=(const OVERSCAN&) = default;
+
+ bool operator==(const OVERSCAN& other)
+ {
+ return left == other.left && right == other.right && top == other.top && bottom == other.bottom;
+ }
+ bool operator!=(const OVERSCAN& other)
+ {
+ return left != other.left || right != other.right || top != other.top || bottom != other.bottom;
+ }
+};
+
+struct EdgeInsets
+{
+ float left = 0.0f;
+ float top = 0.0f;
+ float right = 0.0f;
+ float bottom = 0.0f;
+
+ EdgeInsets() = default;
+ EdgeInsets(float l, float t, float r, float b);
+};
+
+struct RESOLUTION_INFO
+{
+ OVERSCAN Overscan;
+ EdgeInsets guiInsets;
+ bool bFullScreen;
+ int iWidth;
+ int iHeight;
+ int iBlanking; /**< number of pixels of padding between stereoscopic frames */
+ int iScreenWidth;
+ int iScreenHeight;
+ int iSubtitles;
+ uint32_t dwFlags;
+ float fPixelRatio;
+ float fRefreshRate;
+ std::string strMode;
+ std::string strOutput;
+ std::string strId;
+public:
+ RESOLUTION_INFO(int width = 1280, int height = 720, float aspect = 0, const std::string &mode = "");
+ float DisplayRatio() const;
+ RESOLUTION_INFO(const RESOLUTION_INFO& res);
+ RESOLUTION_INFO& operator=(const RESOLUTION_INFO&) = default;
+};
+
+class CResolutionUtils
+{
+public:
+ static RESOLUTION ChooseBestResolution(float fps, int width, int height, bool is3D);
+ static bool HasWhitelist();
+ static void PrintWhitelist();
+
+ /*!
+ * \brief Get the max allowed resolution, if fullscreen
+ * \param width [OUT] Max width resolution
+ * \param height [OUT] Max height resolution
+ */
+ static void GetMaxAllowedResolution(unsigned int& width, unsigned int& height);
+
+protected:
+ static void FindResolutionFromWhitelist(float fps, int width, int height, bool is3D, RESOLUTION &resolution);
+ static bool FindResolutionFromOverride(float fps, int width, bool is3D, RESOLUTION &resolution, float& weight, bool fallback);
+ static float RefreshWeight(float refresh, float fps);
+};
diff --git a/xbmc/windowing/VideoSync.h b/xbmc/windowing/VideoSync.h
new file mode 100644
index 0000000..7e79339
--- /dev/null
+++ b/xbmc/windowing/VideoSync.h
@@ -0,0 +1,31 @@
+/*
+ * 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/Event.h"
+
+class CVideoReferenceClock;
+typedef void (*PUPDATECLOCK)(int NrVBlanks, uint64_t time, void *clock);
+
+class CVideoSync
+{
+public:
+ explicit CVideoSync(void* clock) { m_refClock = clock; }
+ virtual ~CVideoSync() = default;
+ virtual bool Setup(PUPDATECLOCK func) = 0;
+ virtual void Run(CEvent& stop) = 0;
+ virtual void Cleanup() = 0;
+ virtual float GetFps() = 0;
+ virtual void RefreshChanged() {}
+
+protected:
+ PUPDATECLOCK UpdateClock;
+ float m_fps;
+ void *m_refClock;
+};
diff --git a/xbmc/windowing/WinEvents.h b/xbmc/windowing/WinEvents.h
new file mode 100644
index 0000000..8f58c6c
--- /dev/null
+++ b/xbmc/windowing/WinEvents.h
@@ -0,0 +1,19 @@
+/*
+ * 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 "XBMC_events.h"
+
+class IWinEvents
+{
+public:
+ virtual ~IWinEvents() = default;
+ virtual bool MessagePump() = 0;
+};
+
diff --git a/xbmc/windowing/WinSystem.cpp b/xbmc/windowing/WinSystem.cpp
new file mode 100644
index 0000000..cef6940
--- /dev/null
+++ b/xbmc/windowing/WinSystem.cpp
@@ -0,0 +1,278 @@
+/*
+ * 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 "WinSystem.h"
+
+#include "ServiceBroker.h"
+#include "guilib/DispResource.h"
+#include "powermanagement/DPMSSupport.h"
+#include "settings/DisplaySettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "settings/lib/Setting.h"
+#include "utils/StringUtils.h"
+#include "windowing/GraphicContext.h"
+
+#include <mutex>
+#if HAS_GLES
+#include "guilib/GUIFontTTFGL.h"
+#endif
+
+const char* CWinSystemBase::SETTING_WINSYSTEM_IS_HDR_DISPLAY = "winsystem.ishdrdisplay";
+
+CWinSystemBase::CWinSystemBase()
+{
+ m_gfxContext.reset(new CGraphicContext());
+}
+
+CWinSystemBase::~CWinSystemBase() = default;
+
+bool CWinSystemBase::InitWindowSystem()
+{
+ UpdateResolutions();
+ CDisplaySettings::GetInstance().ApplyCalibrations();
+
+ CResolutionUtils::PrintWhitelist();
+
+ return true;
+}
+
+bool CWinSystemBase::DestroyWindowSystem()
+{
+ m_screenSaverManager.reset();
+ return false;
+}
+
+void CWinSystemBase::UpdateDesktopResolution(RESOLUTION_INFO& newRes, const std::string &output, int width, int height, float refreshRate, uint32_t dwFlags)
+{
+ newRes.Overscan.left = 0;
+ newRes.Overscan.top = 0;
+ newRes.Overscan.right = width;
+ newRes.Overscan.bottom = height;
+ newRes.bFullScreen = true;
+ newRes.iSubtitles = height;
+ newRes.dwFlags = dwFlags;
+ newRes.fRefreshRate = refreshRate;
+ newRes.fPixelRatio = 1.0f;
+ newRes.iWidth = width;
+ newRes.iHeight = height;
+ newRes.iScreenWidth = width;
+ newRes.iScreenHeight = height;
+ newRes.strMode = StringUtils::Format("{}: {}x{}", output, width, height);
+ if (refreshRate > 1)
+ newRes.strMode += StringUtils::Format(" @ {:.2f}Hz", refreshRate);
+ if (dwFlags & D3DPRESENTFLAG_INTERLACED)
+ newRes.strMode += "i";
+ if (dwFlags & D3DPRESENTFLAG_MODE3DTB)
+ newRes.strMode += "tab";
+ if (dwFlags & D3DPRESENTFLAG_MODE3DSBS)
+ newRes.strMode += "sbs";
+ newRes.strOutput = output;
+}
+
+void CWinSystemBase::UpdateResolutions()
+{
+ // add the window res - defaults are fine.
+ RESOLUTION_INFO& window = CDisplaySettings::GetInstance().GetResolutionInfo(RES_WINDOW);
+ window.bFullScreen = false;
+ if (window.iWidth == 0)
+ window.iWidth = 720;
+ if (window.iHeight == 0)
+ window.iHeight = 480;
+ window.iScreenWidth = window.iWidth;
+ window.iScreenHeight = window.iHeight;
+ if (window.iSubtitles == 0)
+ window.iSubtitles = window.iHeight;
+ window.fPixelRatio = 1.0f;
+ window.strMode = "Windowed";
+}
+
+void CWinSystemBase::SetWindowResolution(int width, int height)
+{
+ RESOLUTION_INFO& window = CDisplaySettings::GetInstance().GetResolutionInfo(RES_WINDOW);
+ window.iWidth = width;
+ window.iHeight = height;
+ window.iScreenWidth = width;
+ window.iScreenHeight = height;
+ window.iSubtitles = window.iHeight;
+ CServiceBroker::GetWinSystem()->GetGfxContext().ResetOverscan(window);
+}
+
+static void AddResolution(std::vector<RESOLUTION_WHR> &resolutions, unsigned int addindex, float bestRefreshrate)
+{
+ RESOLUTION_INFO resInfo = CDisplaySettings::GetInstance().GetResolutionInfo(addindex);
+ int width = resInfo.iScreenWidth;
+ int height = resInfo.iScreenHeight;
+ int flags = resInfo.dwFlags & D3DPRESENTFLAG_MODEMASK;
+ float refreshrate = resInfo.fRefreshRate;
+
+ // don't touch RES_DESKTOP
+ for (unsigned int idx = 1; idx < resolutions.size(); idx++)
+ if ( resolutions[idx].width == width
+ && resolutions[idx].height == height
+ &&(resolutions[idx].flags & D3DPRESENTFLAG_MODEMASK) == flags)
+ {
+ // check if the refresh rate of this resolution is better suited than
+ // the refresh rate of the resolution with the same width/height/interlaced
+ // property and if so replace it
+ if (bestRefreshrate > 0.0f && refreshrate == bestRefreshrate)
+ resolutions[idx].ResInfo_Index = addindex;
+
+ // no need to add the resolution again
+ return;
+ }
+
+ RESOLUTION_WHR res = {width, height, flags, (int)addindex};
+ resolutions.push_back(res);
+}
+
+static bool resSortPredicate(RESOLUTION_WHR i, RESOLUTION_WHR j)
+{
+ // note: this comparison must obey "strict weak ordering"
+ // a "!=" on the flags comparison resulted in memory corruption
+ return ( i.width < j.width
+ || (i.width == j.width && i.height < j.height)
+ || (i.width == j.width && i.height == j.height && i.flags < j.flags) );
+}
+
+std::vector<RESOLUTION_WHR> CWinSystemBase::ScreenResolutions(float refreshrate)
+{
+ std::vector<RESOLUTION_WHR> resolutions;
+
+ for (unsigned int idx = RES_CUSTOM; idx < CDisplaySettings::GetInstance().ResolutionInfoSize(); idx++)
+ {
+ RESOLUTION_INFO info = CDisplaySettings::GetInstance().GetResolutionInfo(idx);
+ AddResolution(resolutions, idx, refreshrate);
+ }
+
+ // Can't assume a sort order
+ sort(resolutions.begin(), resolutions.end(), resSortPredicate);
+
+ return resolutions;
+}
+
+static void AddRefreshRate(std::vector<REFRESHRATE> &refreshrates, unsigned int addindex)
+{
+ float RefreshRate = CDisplaySettings::GetInstance().GetResolutionInfo(addindex).fRefreshRate;
+
+ for (unsigned int idx = 0; idx < refreshrates.size(); idx++)
+ if ( refreshrates[idx].RefreshRate == RefreshRate)
+ return; // already taken care of.
+
+ REFRESHRATE rr = {RefreshRate, (int)addindex};
+ refreshrates.push_back(rr);
+}
+
+static bool rrSortPredicate(REFRESHRATE i, REFRESHRATE j)
+{
+ return (i.RefreshRate < j.RefreshRate);
+}
+
+std::vector<REFRESHRATE> CWinSystemBase::RefreshRates(int width, int height, uint32_t dwFlags)
+{
+ std::vector<REFRESHRATE> refreshrates;
+
+ for (unsigned int idx = RES_DESKTOP; idx < CDisplaySettings::GetInstance().ResolutionInfoSize(); idx++)
+ {
+ if (CDisplaySettings::GetInstance().GetResolutionInfo(idx).iScreenWidth == width &&
+ CDisplaySettings::GetInstance().GetResolutionInfo(idx).iScreenHeight == height &&
+ (CDisplaySettings::GetInstance().GetResolutionInfo(idx).dwFlags & D3DPRESENTFLAG_MODEMASK) == (dwFlags & D3DPRESENTFLAG_MODEMASK))
+ AddRefreshRate(refreshrates, idx);
+ }
+
+ // Can't assume a sort order
+ sort(refreshrates.begin(), refreshrates.end(), rrSortPredicate);
+
+ return refreshrates;
+}
+
+REFRESHRATE CWinSystemBase::DefaultRefreshRate(std::vector<REFRESHRATE> rates)
+{
+ REFRESHRATE bestmatch = rates[0];
+ float bestfitness = -1.0f;
+ float targetfps = CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP).fRefreshRate;
+
+ for (unsigned i = 0; i < rates.size(); i++)
+ {
+ float fitness = fabs(targetfps - rates[i].RefreshRate);
+
+ if (bestfitness <0 || fitness < bestfitness)
+ {
+ bestfitness = fitness;
+ bestmatch = rates[i];
+ if (bestfitness == 0.0f) // perfect match
+ break;
+ }
+ }
+ return bestmatch;
+}
+
+bool CWinSystemBase::UseLimitedColor()
+{
+ return false;
+}
+
+std::string CWinSystemBase::GetClipboardText(void)
+{
+ return "";
+}
+
+int CWinSystemBase::NoOfBuffers(void)
+{
+ int buffers = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_VIDEOSCREEN_NOOFBUFFERS);
+ return buffers;
+}
+
+KODI::WINDOWING::COSScreenSaverManager* CWinSystemBase::GetOSScreenSaver()
+{
+ if (!m_screenSaverManager)
+ {
+ auto impl = GetOSScreenSaverImpl();
+ if (impl)
+ {
+ m_screenSaverManager.reset(new KODI::WINDOWING::COSScreenSaverManager(std::move(impl)));
+ }
+ }
+
+ return m_screenSaverManager.get();
+}
+
+void CWinSystemBase::RegisterRenderLoop(IRenderLoop *client)
+{
+ std::unique_lock<CCriticalSection> lock(m_renderLoopSection);
+ m_renderLoopClients.push_back(client);
+}
+
+void CWinSystemBase::UnregisterRenderLoop(IRenderLoop *client)
+{
+ std::unique_lock<CCriticalSection> lock(m_renderLoopSection);
+ auto i = find(m_renderLoopClients.begin(), m_renderLoopClients.end(), client);
+ if (i != m_renderLoopClients.end())
+ m_renderLoopClients.erase(i);
+}
+
+void CWinSystemBase::DriveRenderLoop()
+{
+ MessagePump();
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_renderLoopSection);
+ for (auto i = m_renderLoopClients.begin(); i != m_renderLoopClients.end(); ++i)
+ (*i)->FrameMove();
+ }
+}
+
+CGraphicContext& CWinSystemBase::GetGfxContext()
+{
+ return *m_gfxContext;
+}
+
+std::shared_ptr<CDPMSSupport> CWinSystemBase::GetDPMSManager()
+{
+ return m_dpms;
+}
diff --git a/xbmc/windowing/WinSystem.h b/xbmc/windowing/WinSystem.h
new file mode 100644
index 0000000..cac94b0
--- /dev/null
+++ b/xbmc/windowing/WinSystem.h
@@ -0,0 +1,203 @@
+/*
+ * 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 "HDRStatus.h"
+#include "OSScreenSaver.h"
+#include "Resolution.h"
+#include "VideoSync.h"
+#include "WinEvents.h"
+#include "cores/VideoPlayer/VideoRenderers/DebugInfo.h"
+#include "guilib/DispResource.h"
+#include "utils/HDRCapabilities.h"
+
+#include <memory>
+#include <vector>
+
+struct RESOLUTION_WHR
+{
+ int width;
+ int height;
+ int flags; //< only D3DPRESENTFLAG_MODEMASK flags
+ int ResInfo_Index;
+};
+
+struct REFRESHRATE
+{
+ float RefreshRate;
+ int ResInfo_Index;
+};
+
+class CDPMSSupport;
+class CGraphicContext;
+class CRenderSystemBase;
+class IRenderLoop;
+
+struct VideoPicture;
+
+class CWinSystemBase
+{
+public:
+ CWinSystemBase();
+ virtual ~CWinSystemBase();
+
+ static std::unique_ptr<CWinSystemBase> CreateWinSystem();
+
+ // Access render system interface
+ virtual CRenderSystemBase *GetRenderSystem() { return nullptr; }
+
+ virtual const std::string GetName() { return "platform default"; }
+
+ // windowing interfaces
+ virtual bool InitWindowSystem();
+ virtual bool DestroyWindowSystem();
+ virtual bool CreateNewWindow(const std::string& name, bool fullScreen, RESOLUTION_INFO& res) = 0;
+ virtual bool DestroyWindow(){ return false; }
+ virtual bool ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop) = 0;
+ virtual bool SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) = 0;
+ virtual bool DisplayHardwareScalingEnabled() { return false; }
+ virtual void UpdateDisplayHardwareScaling(const RESOLUTION_INFO& resInfo) { }
+ virtual bool MoveWindow(int topLeft, int topRight){return false;}
+ virtual void FinishModeChange(RESOLUTION res){}
+ virtual void FinishWindowResize(int newWidth, int newHeight) {ResizeWindow(newWidth, newHeight, -1, -1);}
+ virtual bool CenterWindow(){return false;}
+ virtual bool IsCreated(){ return m_bWindowCreated; }
+ virtual void NotifyAppFocusChange(bool bGaining) {}
+ virtual void NotifyAppActiveChange(bool bActivated) {}
+ virtual void ShowOSMouse(bool show) {}
+ virtual bool HasCursor(){ return true; }
+ //some platforms have api for gesture inertial scrolling - default to false and use the InertialScrollingHandler
+ virtual bool HasInertialGestures(){ return false; }
+ //does the output expect limited color range (ie 16-235)
+ virtual bool UseLimitedColor();
+ //the number of presentation buffers
+ virtual int NoOfBuffers();
+ /**
+ * Get average display latency
+ *
+ * The latency should be measured as the time between finishing the rendering
+ * of a frame, i.e. calling PresentRender, and the rendered content becoming
+ * visible on the screen.
+ *
+ * \return average display latency in seconds, or negative value if unknown
+ */
+ virtual float GetDisplayLatency() { return -1.0f; }
+ /**
+ * Get time that should be subtracted from the display latency for this frame
+ * in milliseconds
+ *
+ * Contrary to \ref GetDisplayLatency, this value is calculated ad-hoc
+ * for the frame currently being rendered and not a value that is calculated/
+ * averaged from past frames and their presentation times
+ */
+ virtual float GetFrameLatencyAdjustment() { return 0.0; }
+
+ virtual bool Minimize() { return false; }
+ virtual bool Restore() { return false; }
+ virtual bool Hide() { return false; }
+ virtual bool Show(bool raise = true) { return false; }
+
+ // videosync
+ virtual std::unique_ptr<CVideoSync> GetVideoSync(void *clock) { return nullptr; }
+
+ // notifications
+ virtual void OnMove(int x, int y) {}
+
+ // OS System screensaver
+ /**
+ * Get OS screen saver inhibit implementation if available
+ *
+ * \return OS screen saver implementation that can be used with this windowing system
+ * or nullptr if unsupported.
+ * Lifetime of the returned object will usually end with \ref DestroyWindowSystem, so
+ * do not use any more after calling that.
+ */
+ KODI::WINDOWING::COSScreenSaverManager* GetOSScreenSaver();
+
+ // resolution interfaces
+ unsigned int GetWidth() { return m_nWidth; }
+ unsigned int GetHeight() { return m_nHeight; }
+ virtual bool CanDoWindowed() { return true; }
+ bool IsFullScreen() { return m_bFullScreen; }
+ virtual void UpdateResolutions();
+ void SetWindowResolution(int width, int height);
+ std::vector<RESOLUTION_WHR> ScreenResolutions(float refreshrate);
+ std::vector<REFRESHRATE> RefreshRates(int width, int height, uint32_t dwFlags);
+ REFRESHRATE DefaultRefreshRate(std::vector<REFRESHRATE> rates);
+ virtual bool HasCalibration(const RESOLUTION_INFO& resInfo) { return true; }
+
+ // text input interface
+ virtual std::string GetClipboardText(void);
+
+ // Display event callback
+ virtual void Register(IDispResource *resource) = 0;
+ virtual void Unregister(IDispResource *resource) = 0;
+
+ // render loop
+ void RegisterRenderLoop(IRenderLoop *client);
+ void UnregisterRenderLoop(IRenderLoop *client);
+ void DriveRenderLoop();
+
+ // winsystem events
+ virtual bool MessagePump() { return false; }
+
+ // Access render system interface
+ CGraphicContext& GetGfxContext();
+
+ /**
+ * Get OS specific hardware context
+ *
+ * \return OS specific context or nullptr if OS not have
+ *
+ * \note This function is currently only related to Windows with DirectX,
+ * all other OS where use GL returns nullptr.
+ * Returned Windows class pointer is ID3D11DeviceContext1.
+ */
+ virtual void* GetHWContext() { return nullptr; }
+
+ std::shared_ptr<CDPMSSupport> GetDPMSManager();
+
+ /**
+ * @brief Set the HDR metadata. Passing nullptr as the parameter should
+ * disable HDR.
+ *
+ */
+ virtual bool SetHDR(const VideoPicture* videoPicture) { return false; }
+ virtual bool IsHDRDisplay() { return false; }
+ virtual HDR_STATUS ToggleHDR() { return HDR_STATUS::HDR_UNSUPPORTED; }
+ virtual HDR_STATUS GetOSHDRStatus() { return HDR_STATUS::HDR_UNSUPPORTED; }
+ virtual CHDRCapabilities GetDisplayHDRCapabilities() const { return {}; }
+
+ static const char* SETTING_WINSYSTEM_IS_HDR_DISPLAY;
+
+ // Gets debug info from video renderer
+ virtual DEBUG_INFO_RENDER GetDebugInfo() { return {}; }
+
+ virtual std::vector<std::string> GetConnectedOutputs() { return {}; }
+
+protected:
+ void UpdateDesktopResolution(RESOLUTION_INFO& newRes, const std::string &output, int width, int height, float refreshRate, uint32_t dwFlags);
+ virtual std::unique_ptr<KODI::WINDOWING::IOSScreenSaver> GetOSScreenSaverImpl() { return nullptr; }
+
+ int m_nWidth = 0;
+ int m_nHeight = 0;
+ int m_nTop = 0;
+ int m_nLeft = 0;
+ bool m_bWindowCreated = false;
+ bool m_bFullScreen = false;
+ bool m_bBlankOtherDisplay = false;
+ float m_fRefreshRate = 0.0f;
+ std::unique_ptr<KODI::WINDOWING::COSScreenSaverManager> m_screenSaverManager;
+ CCriticalSection m_renderLoopSection;
+ std::vector<IRenderLoop*> m_renderLoopClients;
+
+ std::unique_ptr<IWinEvents> m_winEvents;
+ std::unique_ptr<CGraphicContext> m_gfxContext;
+ std::shared_ptr<CDPMSSupport> m_dpms;
+};
diff --git a/xbmc/windowing/WindowSystemFactory.cpp b/xbmc/windowing/WindowSystemFactory.cpp
new file mode 100644
index 0000000..edb6098
--- /dev/null
+++ b/xbmc/windowing/WindowSystemFactory.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2005-2020 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 "WindowSystemFactory.h"
+
+#include <algorithm>
+
+using namespace KODI::WINDOWING;
+
+std::list<std::pair<std::string, std::function<std::unique_ptr<CWinSystemBase>()>>>
+ CWindowSystemFactory::m_windowSystems;
+
+std::list<std::string> CWindowSystemFactory::GetWindowSystems()
+{
+ std::list<std::string> available;
+ for (const auto& windowSystem : m_windowSystems)
+ available.emplace_back(windowSystem.first);
+
+ return available;
+}
+
+std::unique_ptr<CWinSystemBase> CWindowSystemFactory::CreateWindowSystem(const std::string& name)
+{
+ auto windowSystem =
+ std::find_if(m_windowSystems.begin(), m_windowSystems.end(),
+ [&name](auto& windowSystem) { return windowSystem.first == name; });
+ if (windowSystem != m_windowSystems.end())
+ return windowSystem->second();
+
+ return nullptr;
+}
+
+void CWindowSystemFactory::RegisterWindowSystem(
+ const std::function<std::unique_ptr<CWinSystemBase>()>& createFunction, const std::string& name)
+{
+ m_windowSystems.emplace_back(std::make_pair(name, createFunction));
+}
diff --git a/xbmc/windowing/WindowSystemFactory.h b/xbmc/windowing/WindowSystemFactory.h
new file mode 100644
index 0000000..20e051d
--- /dev/null
+++ b/xbmc/windowing/WindowSystemFactory.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2005-2020 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 "WinSystem.h"
+
+#include <functional>
+#include <list>
+#include <map>
+#include <memory>
+
+namespace KODI
+{
+namespace WINDOWING
+{
+
+class CWindowSystemFactory
+{
+public:
+ static std::unique_ptr<CWinSystemBase> CreateWindowSystem(const std::string& name);
+ static std::list<std::string> GetWindowSystems();
+ static void RegisterWindowSystem(
+ const std::function<std::unique_ptr<CWinSystemBase>()>& createFunction,
+ const std::string& name = "default");
+
+private:
+ static std::list<std::pair<std::string, std::function<std::unique_ptr<CWinSystemBase>()>>>
+ m_windowSystems;
+};
+
+} // namespace WINDOWING
+} // namespace KODI
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 <cstdint>
+#include <string>
+
+#include <X11/Xlib.h>
+
+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 <clocale>
+#include <mutex>
+
+#include <EGL/eglext.h>
+#include <unistd.h>
+
+#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<unsigned int>(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<uint64_t>(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<CCriticalSection> 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<uint64_t>(nowTs.tv_sec) * 1000000000ULL + nowTs.tv_nsec;
+ now /= 1000;
+
+ std::unique_lock<CCriticalSection> 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 <cstdint>
+
+#include <EGL/eglext.h>
+#ifdef HAVE_EGLEXTANGLE
+#include <EGL/eglext_angle.h>
+#else
+#include <EGL/eglextchromium.h>
+#endif
+#include <X11/Xutil.h>
+
+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 <GL/glx.h>
+
+#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 <GL/glx.h>
+
+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 <cassert>
+
+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 <X11/Xlib.h>
+
+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 <va/va_x11.h>
+#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<Display*>(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 <GL/glx.h>
+#include "VideoSyncGLX.h"
+#include "GLContextGLX.h"
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace X11
+{
+
+XID GLXGetWindow(void* context)
+{
+ return static_cast<CGLContextGLX*>(context)->m_glxWindow;
+}
+
+void* GLXGetContext(void* context)
+{
+ return static_cast<CGLContextGLX*>(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 <X11/Xlib.h>
+
+//-----------------------------------------------------------------------------
+// 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 <mutex>
+#include <sstream>
+
+#include <X11/extensions/Xrandr.h>
+
+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<CCriticalSection> 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<CWinSystemX11*>(&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<CCriticalSection> 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 <GL/glx.h>
+#include <X11/X.h>
+#include <X11/Xlib.h>
+
+#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 <unistd.h>
+
+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<CWinSystemX11*>(&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 <atomic>
+
+
+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 <stdexcept>
+
+#include <X11/XF86keysym.h>
+#include <X11/Xlib.h>
+#include <X11/extensions/Xrandr.h>
+#include <X11/keysymdef.h>
+
+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<CAppInboundProtocol> 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<XRRNotifyEvent*>(&xevent);
+ if (rrEvent->subtype == RRNotify_OutputChange)
+ {
+ XRROutputChangeNotifyEvent* changeEvent = reinterpret_cast<XRROutputChangeNotifyEvent*>(&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<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ appPort->OnEvent(event);
+ return true;
+}
+
+XBMCKey CWinEventsX11::LookupXbmcKeySym(KeySym keysym)
+{
+ // try direct mapping first
+ std::map<uint32_t, uint32_t>::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 <clocale>
+#include <map>
+
+#include <X11/Xlib.h>
+
+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<uint32_t,uint32_t> 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 <mutex>
+#include <string>
+#include <vector>
+
+#include <X11/Xatom.h>
+#include <X11/extensions/Xrandr.h>
+
+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<XOutput> 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<XOutput> 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<CSettings> 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<XOutput> outputs = g_xrandr.GetModes();
+ for (size_t i=0; i<outputs.size(); i++)
+ {
+ if (StringUtils::EqualsNoCase(outputs[i].name, m_userOutput))
+ continue;
+ g_xrandr.TurnOffOutput(outputs[i].name);
+ }
+ }
+
+ XMode mode = g_xrandr.GetCurrentMode(m_userOutput);
+ if (mode.id.empty())
+ mode = g_xrandr.GetPreferredMode(m_userOutput);
+ m_bIsRotated = out->isRotated;
+ 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<std::string> CWinSystemX11::GetConnectedOutputs()
+{
+ std::vector<std::string> outputs;
+ std::vector<XOutput> outs;
+ g_xrandr.Query(true);
+ outs = g_xrandr.GetModes();
+ outputs.emplace_back("Default");
+ for(unsigned int i=0; i<outs.size(); ++i)
+ {
+ outputs.emplace_back(outs[i].name);
+ }
+
+ return outputs;
+}
+
+bool CWinSystemX11::IsCurrentOutput(const std::string& output)
+{
+ return (StringUtils::EqualsNoCase(output, "Default")) || (m_currentOutput.compare(output.c_str()) == 0);
+}
+
+void CWinSystemX11::ShowOSMouse(bool show)
+{
+ if (show)
+ XUndefineCursor(m_dpy,m_mainWindow);
+ else if (m_invisibleCursor)
+ XDefineCursor(m_dpy,m_mainWindow, m_invisibleCursor);
+}
+
+std::unique_ptr<KODI::WINDOWING::IOSScreenSaver> CWinSystemX11::GetOSScreenSaverImpl()
+{
+ std::unique_ptr<IOSScreenSaver> 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<CCriticalSection> 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<CCriticalSection> 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<CCriticalSection> lock(m_resourceSection);
+ for (std::vector<IDispResource *>::iterator i = m_resources.begin(); i != m_resources.end(); ++i)
+ (*i)->OnLostDisplay();
+ }
+
+ m_winEventsX11->SetXRRFailSafeTimer(3s);
+}
+
+void CWinSystemX11::Register(IDispResource *resource)
+{
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ m_resources.push_back(resource);
+}
+
+void CWinSystemX11::Unregister(IDispResource* resource)
+{
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ std::vector<IDispResource*>::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<char*>(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<char*>(classString.c_str());
+ class_hints->res_name = const_cast<char*>(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<CTexture> 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; i<iconTexture->GetHeight();++i)
+ {
+ for (j=0; j<iconTexture->GetWidth();++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<const char*>(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 <string>
+#include <vector>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+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<std::string> 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<KODI::WINDOWING::IOSScreenSaver> 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<IDispResource*> 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 <mutex>
+#include <vector>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+using namespace KODI;
+using namespace KODI::WINDOWING::X11;
+
+
+void CWinSystemX11GLContext::Register()
+{
+ KODI::WINDOWING::CWindowSystemFactory::RegisterWindowSystem(CreateWinSystem, "x11");
+}
+
+std::unique_ptr<CWinSystemBase> CWinSystemX11GLContext::CreateWinSystem()
+{
+ return std::make_unique<CWinSystemX11GLContext>();
+}
+
+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<CCriticalSection> lock(m_resourceSection);
+ // tell any shared resources
+ for (std::vector<IDispResource *>::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<CGLContextEGL*>(m_pGLContext)->m_eglDisplay;
+}
+
+EGLSurface CWinSystemX11GLContext::GetEGLSurface() const
+{
+ return static_cast<CGLContextEGL*>(m_pGLContext)->m_eglSurface;
+}
+
+EGLContext CWinSystemX11GLContext::GetEGLContext() const
+{
+ return static_cast<CGLContextEGL*>(m_pGLContext)->m_eglContext;
+}
+
+EGLConfig CWinSystemX11GLContext::GetEGLConfig() const
+{
+ return static_cast<CGLContextEGL*>(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<CCriticalSection> lock(m_resourceSection);
+ // tell any shared resources
+ for (std::vector<IDispResource *>::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<CApplicationSkinHandling>();
+ 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<CApplicationSkinHandling>();
+ 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<CApplicationSkinHandling>();
+ 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<CApplicationSkinHandling>();
+ 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<CX11DPMSSupport>();
+ 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<CGLContextEGL*>(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<CVideoSync> CWinSystemX11GLContext::GetVideoSync(void *clock)
+{
+ std::unique_ptr<CVideoSync> pVSync;
+
+ if (dynamic_cast<CGLContextEGL*>(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 <memory>
+
+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<CWinSystemBase> 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<CVideoSync> 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<CVaapiProxy, delete_CVaapiProxy> 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 <mutex>
+
+using namespace KODI;
+using namespace KODI::WINDOWING::X11;
+
+void CWinSystemX11GLESContext::Register()
+{
+ KODI::WINDOWING::CWindowSystemFactory::RegisterWindowSystem(CreateWinSystem, "x11");
+}
+
+std::unique_ptr<CWinSystemBase> CWinSystemX11GLESContext::CreateWinSystem()
+{
+ return std::make_unique<CWinSystemX11GLESContext>();
+}
+
+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<CCriticalSection> lock(m_resourceSection);
+ // tell any shared resources
+ for (std::vector<IDispResource*>::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<CCriticalSection> lock(m_resourceSection);
+ // tell any shared resources
+ for (std::vector<IDispResource*>::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<CApplicationSkinHandling>();
+ 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<CApplicationSkinHandling>();
+ 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<CApplicationSkinHandling>();
+ 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<PFNEGLGETPLATFORMDISPLAYEXTPROC>(eglGetProcAddress("eglGetPlatformDisplayEXT"));
+ if (eglGetPlatformDisplayEXT)
+ {
+ EGLint attribs[] =
+ {
+ EGL_PLATFORM_X11_SCREEN_EXT, m_screen,
+ EGL_NONE
+ };
+ eglDisplay = eglGetPlatformDisplayEXT(EGL_PLATFORM_X11_EXT,static_cast<EGLNativeDisplayType>(m_dpy), attribs);
+ }
+ else
+ eglDisplay = eglGetDisplay(static_cast<EGLNativeDisplayType>(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<EGLint*>(&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<CX11DPMSSupport>();
+ 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<CWinSystemBase> 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 <X11/Xlib.h>
+#include <X11/extensions/dpms.h>
+
+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<CWinSystemX11*>(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<CWinSystemX11*>(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<CWinSystemX11*>(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 <string.h>
+
+#include <sys/wait.h>
+
+#include "PlatformDefs.h"
+
+#if defined(TARGET_FREEBSD)
+#include <sys/types.h>
+#include <sys/wait.h>
+#endif
+
+using namespace std::chrono_literals;
+
+CXRandR::CXRandR(bool query)
+{
+ m_bInit = false;
+ m_numScreens = 1;
+ if (query)
+ Query();
+}
+
+bool CXRandR::Query(bool force, bool ignoreoff)
+{
+ if (!force)
+ if (m_bInit)
+ return m_outputs.size() > 0;
+
+ m_bInit = true;
+
+ if (getenv("KODI_BIN_HOME") == NULL)
+ return false;
+
+ m_outputs.clear();
+ // query all screens
+ // we are happy if at least one screen returns results
+ bool success = false;
+ for(unsigned int screennum=0; screennum<m_numScreens; ++screennum)
+ {
+ if(Query(force, screennum, ignoreoff))
+ success = true;
+ }
+ return success;
+}
+
+bool CXRandR::Query(bool force, int screennum, bool ignoreoff)
+{
+ std::string cmd;
+ std::string appname = CCompileInfo::GetAppName();
+ StringUtils::ToLower(appname);
+ if (getenv("KODI_BIN_HOME"))
+ {
+ cmd = getenv("KODI_BIN_HOME");
+ cmd += "/" + appname + "-xrandr";
+ cmd = StringUtils::Format("{} -q --screen {}", cmd, screennum);
+ }
+
+ FILE* file = popen(cmd.c_str(),"r");
+ if (!file)
+ {
+ CLog::Log(LOGERROR, "CXRandR::Query - unable to execute xrandr tool");
+ return false;
+ }
+
+
+ CXBMCTinyXML xmlDoc;
+ if (!xmlDoc.LoadFile(file, TIXML_DEFAULT_ENCODING))
+ {
+ CLog::Log(LOGERROR, "CXRandR::Query - unable to open xrandr xml");
+ pclose(file);
+ return false;
+ }
+ pclose(file);
+
+ TiXmlElement *pRootElement = xmlDoc.RootElement();
+ if (atoi(pRootElement->Attribute("id")) != screennum)
+ {
+ //! @todo ERROR
+ return false;
+ }
+
+ for (TiXmlElement* output = pRootElement->FirstChildElement("output"); output; output = output->NextSiblingElement("output"))
+ {
+ XOutput xoutput;
+ xoutput.name = output->Attribute("name");
+ StringUtils::Trim(xoutput.name);
+ xoutput.isConnected = (StringUtils::CompareNoCase(output->Attribute("connected"), "true") == 0);
+ xoutput.screen = screennum;
+ xoutput.w = (output->Attribute("w") != NULL ? atoi(output->Attribute("w")) : 0);
+ xoutput.h = (output->Attribute("h") != NULL ? atoi(output->Attribute("h")) : 0);
+ xoutput.x = (output->Attribute("x") != NULL ? atoi(output->Attribute("x")) : 0);
+ xoutput.y = (output->Attribute("y") != NULL ? atoi(output->Attribute("y")) : 0);
+ xoutput.crtc = (output->Attribute("crtc") != NULL ? atoi(output->Attribute("crtc")) : 0);
+ xoutput.wmm = (output->Attribute("wmm") != NULL ? atoi(output->Attribute("wmm")) : 0);
+ xoutput.hmm = (output->Attribute("hmm") != NULL ? atoi(output->Attribute("hmm")) : 0);
+ if (output->Attribute("rotation") != NULL &&
+ (StringUtils::CompareNoCase(output->Attribute("rotation"), "left") == 0 ||
+ StringUtils::CompareNoCase(output->Attribute("rotation"), "right") == 0))
+ {
+ xoutput.isRotated = true;
+ }
+ else
+ xoutput.isRotated = false;
+
+ if (!xoutput.isConnected)
+ continue;
+
+ bool hascurrent = false;
+ for (TiXmlElement* mode = output->FirstChildElement("mode"); mode; mode = mode->NextSiblingElement("mode"))
+ {
+ XMode xmode;
+ xmode.id = mode->Attribute("id");
+ xmode.name = mode->Attribute("name");
+ xmode.hz = atof(mode->Attribute("hz"));
+ xmode.w = atoi(mode->Attribute("w"));
+ xmode.h = atoi(mode->Attribute("h"));
+ xmode.isPreferred = (StringUtils::CompareNoCase(mode->Attribute("preferred"), "true") == 0);
+ xmode.isCurrent = (StringUtils::CompareNoCase(mode->Attribute("current"), "true") == 0);
+ xoutput.modes.push_back(xmode);
+ if (xmode.isCurrent)
+ hascurrent = true;
+ }
+ if (hascurrent || !ignoreoff)
+ m_outputs.push_back(xoutput);
+ else
+ CLog::Log(LOGWARNING, "CXRandR::Query - output {} has no current mode, assuming disconnected",
+ xoutput.name);
+ }
+ return m_outputs.size() > 0;
+}
+
+bool CXRandR::TurnOffOutput(const std::string& name)
+{
+ XOutput *output = GetOutput(name);
+ if (!output)
+ return false;
+
+ std::string cmd;
+ std::string appname = CCompileInfo::GetAppName();
+ StringUtils::ToLower(appname);
+
+ if (getenv("KODI_BIN_HOME"))
+ {
+ cmd = getenv("KODI_BIN_HOME");
+ cmd += "/" + appname + "-xrandr";
+ cmd = StringUtils::Format("{} --screen {} --output {} --off", cmd, output->screen, name);
+ }
+
+ int status = system(cmd.c_str());
+ if (status == -1)
+ return false;
+
+ if (WEXITSTATUS(status) != 0)
+ return false;
+
+ return true;
+}
+
+bool CXRandR::TurnOnOutput(const std::string& name)
+{
+ XOutput *output = GetOutput(name);
+ if (!output)
+ return false;
+
+ XMode mode = GetCurrentMode(output->name);
+ if (mode.isCurrent)
+ return true;
+
+ // get preferred mode
+ for (unsigned int j = 0; j < m_outputs.size(); j++)
+ {
+ if (m_outputs[j].name == output->name)
+ {
+ for (unsigned int i = 0; i < m_outputs[j].modes.size(); i++)
+ {
+ if (m_outputs[j].modes[i].isPreferred)
+ {
+ mode = m_outputs[j].modes[i];
+ break;
+ }
+ }
+ }
+ }
+
+ if (!mode.isPreferred)
+ return false;
+
+ if (!SetMode(*output, mode))
+ return false;
+
+ XbmcThreads::EndTime<> timeout(5s);
+ while (!timeout.IsTimePast())
+ {
+ if (!Query(true))
+ return false;
+
+ output = GetOutput(name);
+ if (output && output->h > 0)
+ return true;
+
+ KODI::TIME::Sleep(200ms);
+ }
+
+ return false;
+}
+
+std::vector<XOutput> CXRandR::GetModes(void)
+{
+ Query();
+ return m_outputs;
+}
+
+void CXRandR::SaveState()
+{
+ Query(true);
+}
+
+bool CXRandR::SetMode(const XOutput& output, const XMode& mode)
+{
+ if ((output.name == "" && mode.id == ""))
+ return true;
+
+ Query();
+
+ // Make sure the output exists, if not -- complain and exit
+ bool isOutputFound = false;
+ XOutput outputFound;
+ for (size_t i = 0; i < m_outputs.size(); i++)
+ {
+ if (m_outputs[i].name == output.name)
+ {
+ isOutputFound = true;
+ outputFound = m_outputs[i];
+ }
+ }
+
+ if (!isOutputFound)
+ {
+ CLog::Log(LOGERROR,
+ "CXRandR::SetMode: asked to change resolution for non existing output: {} mode: {}",
+ output.name, mode.id);
+ return false;
+ }
+
+ // try to find the same exact mode (same id, resolution, hz)
+ bool isModeFound = false;
+ XMode modeFound;
+ for (size_t i = 0; i < outputFound.modes.size(); i++)
+ {
+ if (outputFound.modes[i].id == mode.id)
+ {
+ if (outputFound.modes[i].w == mode.w &&
+ outputFound.modes[i].h == mode.h &&
+ outputFound.modes[i].hz == mode.hz)
+ {
+ isModeFound = true;
+ modeFound = outputFound.modes[i];
+ }
+ else
+ {
+ CLog::Log(LOGERROR,
+ "CXRandR::SetMode: asked to change resolution for mode that exists but with "
+ "different w/h/hz: {} mode: {}. Searching for similar modes...",
+ output.name, mode.id);
+ break;
+ }
+ }
+ }
+
+ if (!isModeFound)
+ {
+ for (size_t i = 0; i < outputFound.modes.size(); i++)
+ {
+ if (outputFound.modes[i].w == mode.w &&
+ outputFound.modes[i].h == mode.h &&
+ outputFound.modes[i].hz == mode.hz)
+ {
+ isModeFound = true;
+ modeFound = outputFound.modes[i];
+ CLog::Log(LOGWARNING, "CXRandR::SetMode: found alternative mode (same hz): {} mode: {}.",
+ output.name, outputFound.modes[i].id);
+ }
+ }
+ }
+
+ if (!isModeFound)
+ {
+ for (size_t i = 0; i < outputFound.modes.size(); i++)
+ {
+ if (outputFound.modes[i].w == mode.w &&
+ outputFound.modes[i].h == mode.h)
+ {
+ isModeFound = true;
+ modeFound = outputFound.modes[i];
+ CLog::Log(LOGWARNING,
+ "CXRandR::SetMode: found alternative mode (different hz): {} mode: {}.",
+ output.name, outputFound.modes[i].id);
+ }
+ }
+ }
+
+ // Let's try finding a mode that is the same
+ if (!isModeFound)
+ {
+ CLog::Log(LOGERROR,
+ "CXRandR::SetMode: asked to change resolution for non existing mode: {} mode: {}",
+ output.name, mode.id);
+ return false;
+ }
+
+ m_currentOutput = outputFound.name;
+ m_currentMode = modeFound.id;
+ std::string appname = CCompileInfo::GetAppName();
+ StringUtils::ToLower(appname);
+ char cmd[255];
+
+ if (getenv("KODI_BIN_HOME"))
+ snprintf(cmd, sizeof(cmd), "%s/%s-xrandr --screen %d --output %s --mode %s",
+ getenv("KODI_BIN_HOME"),appname.c_str(),
+ outputFound.screen, outputFound.name.c_str(), modeFound.id.c_str());
+ else
+ return false;
+ CLog::Log(LOGINFO, "XRANDR: {}", cmd);
+ int status = system(cmd);
+ if (status == -1)
+ return false;
+
+ if (WEXITSTATUS(status) != 0)
+ return false;
+
+ return true;
+}
+
+XMode CXRandR::GetCurrentMode(const std::string& outputName)
+{
+ Query();
+ XMode result;
+
+ for (unsigned int j = 0; j < m_outputs.size(); j++)
+ {
+ if (m_outputs[j].name == outputName || outputName == "")
+ {
+ for (unsigned int i = 0; i < m_outputs[j].modes.size(); i++)
+ {
+ if (m_outputs[j].modes[i].isCurrent)
+ {
+ result = m_outputs[j].modes[i];
+ break;
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+XMode CXRandR::GetPreferredMode(const std::string& outputName)
+{
+ Query();
+ XMode result;
+
+ for (unsigned int j = 0; j < m_outputs.size(); j++)
+ {
+ if (m_outputs[j].name == outputName || outputName == "")
+ {
+ for (unsigned int i = 0; i < m_outputs[j].modes.size(); i++)
+ {
+ if (m_outputs[j].modes[i].isPreferred)
+ {
+ result = m_outputs[j].modes[i];
+ break;
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+void CXRandR::LoadCustomModeLinesToAllOutputs(void)
+{
+ Query();
+ CXBMCTinyXML xmlDoc;
+
+ if (!xmlDoc.LoadFile("special://xbmc/userdata/ModeLines.xml"))
+ {
+ return;
+ }
+
+ TiXmlElement *pRootElement = xmlDoc.RootElement();
+ if (StringUtils::CompareNoCase(pRootElement->Value(), "modelines") != 0)
+ {
+ //! @todo ERROR
+ return;
+ }
+
+ char cmd[255];
+ std::string name;
+ std::string strModeLine;
+
+ for (TiXmlElement* modeline = pRootElement->FirstChildElement("modeline"); modeline; modeline = modeline->NextSiblingElement("modeline"))
+ {
+ name = modeline->Attribute("label");
+ StringUtils::Trim(name);
+ strModeLine = modeline->FirstChild()->Value();
+ StringUtils::Trim(strModeLine);
+ std::string appname = CCompileInfo::GetAppName();
+ StringUtils::ToLower(appname);
+
+ if (getenv("KODI_BIN_HOME"))
+ {
+ snprintf(cmd, sizeof(cmd), "%s/%s-xrandr --newmode \"%s\" %s > /dev/null 2>&1", getenv("KODI_BIN_HOME"),
+ appname.c_str(), name.c_str(), strModeLine.c_str());
+ if (system(cmd) != 0)
+ CLog::Log(LOGERROR, "Unable to create modeline \"{}\"", name);
+ }
+
+ for (unsigned int i = 0; i < m_outputs.size(); i++)
+ {
+ if (getenv("KODI_BIN_HOME"))
+ {
+ snprintf(cmd, sizeof(cmd), "%s/%s-xrandr --addmode %s \"%s\" > /dev/null 2>&1", getenv("KODI_BIN_HOME"),
+ appname.c_str(), m_outputs[i].name.c_str(), name.c_str());
+ if (system(cmd) != 0)
+ CLog::Log(LOGERROR, "Unable to add modeline \"{}\"", name);
+ }
+ }
+ }
+}
+
+void CXRandR::SetNumScreens(unsigned int num)
+{
+ m_numScreens = num;
+ m_bInit = false;
+}
+
+bool CXRandR::IsOutputConnected(const std::string& name)
+{
+ bool result = false;
+ Query();
+
+ for (unsigned int i = 0; i < m_outputs.size(); ++i)
+ {
+ if (m_outputs[i].name == name)
+ {
+ result = true;
+ break;
+ }
+ }
+ return result;
+}
+
+XOutput* CXRandR::GetOutput(const std::string& outputName)
+{
+ XOutput *result = 0;
+ Query();
+ for (unsigned int i = 0; i < m_outputs.size(); ++i)
+ {
+ if (m_outputs[i].name == outputName)
+ {
+ result = &m_outputs[i];
+ break;
+ }
+ }
+ return result;
+}
+
+int CXRandR::GetCrtc(int x, int y, float &hz)
+{
+ int crtc = 0;
+ for (unsigned int i = 0; i < m_outputs.size(); ++i)
+ {
+ if (!m_outputs[i].isConnected)
+ continue;
+
+ if ((m_outputs[i].x <= x && (m_outputs[i].x+m_outputs[i].w) > x) &&
+ (m_outputs[i].y <= y && (m_outputs[i].y+m_outputs[i].h) > y))
+ {
+ crtc = m_outputs[i].crtc;
+ for (const auto& mode : m_outputs[i].modes)
+ {
+ if (mode.isCurrent)
+ {
+ hz = mode.hz;
+ break;
+ }
+ }
+ break;
+ }
+ }
+ return crtc;
+}
+
+CXRandR g_xrandr;
+
+/*
+ int main()
+ {
+ CXRandR r;
+ r.LoadCustomModeLinesToAllOutputs();
+ }
+*/
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 <map>
+#include <string>
+#include <vector>
+
+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<XMode> 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<XOutput> 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<XOutput> m_outputs;
+ std::string m_currentOutput;
+ std::string m_currentMode;
+ unsigned int m_numScreens;
+};
+
+extern CXRandR g_xrandr;
diff --git a/xbmc/windowing/XBMC_events.h b/xbmc/windowing/XBMC_events.h
new file mode 100644
index 0000000..d9bf3ea
--- /dev/null
+++ b/xbmc/windowing/XBMC_events.h
@@ -0,0 +1,134 @@
+/*
+ * SDL - Simple DirectMedia Layer
+ * Copyright (C) 1997-2009 Sam Lantinga
+ * Sam Lantinga
+ * slouken@libsdl.org
+ *
+ * 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 file for SDL event handling */
+
+#include "Resolution.h"
+#include "input/XBMC_keyboard.h"
+
+/* Event enumerations */
+typedef enum
+{
+ XBMC_NOEVENT = 0, /* Unused (do not remove) */
+ XBMC_KEYDOWN, /* Keys pressed */
+ XBMC_KEYUP, /* Keys released */
+ XBMC_MOUSEMOTION, /* Mouse moved */
+ XBMC_MOUSEBUTTONDOWN, /* Mouse button pressed */
+ XBMC_MOUSEBUTTONUP, /* Mouse button released */
+ XBMC_QUIT, /* User-requested quit */
+ XBMC_VIDEORESIZE, /* User resized video mode */
+ XBMC_FULLSCREEN_UPDATE, /* Triggered by an OS event when Kodi is running in fullscreen, rescale and repositioning is required */
+ XBMC_VIDEOMOVE, /* User moved the window */
+ XBMC_MODECHANGE, /* Video mode must be changed */
+ XBMC_TOUCH,
+ XBMC_BUTTON, /* Button (remote) pressed */
+ XBMC_SETFOCUS,
+ XBMC_USEREVENT,
+
+ XBMC_MAXEVENT = 256 /* XBMC_EventType is represented as uchar */
+} XBMC_EventType;
+
+/* Keyboard event structure */
+typedef struct XBMC_KeyboardEvent {
+ XBMC_keysym keysym;
+} XBMC_KeyboardEvent;
+
+/* Mouse motion event structure */
+typedef struct XBMC_MouseMotionEvent {
+ uint16_t x, y; /* The X/Y coordinates of the mouse */
+} XBMC_MouseMotionEvent;
+
+/* Mouse button event structure */
+typedef struct XBMC_MouseButtonEvent {
+ unsigned char button; /* The mouse button index */
+ uint16_t x, y; /* The X/Y coordinates of the mouse at press time */
+} XBMC_MouseButtonEvent;
+
+/* The "window resized" event
+ When you get this event, you are responsible for setting a new video
+ mode with the new width and height.
+ */
+typedef struct XBMC_ResizeEvent {
+ int w; /* New width */
+ int h; /* New height */
+} XBMC_ResizeEvent;
+
+typedef struct XBMC_MoveEvent {
+ int x; /* New x position */
+ int y; /* New y position */
+} XBMC_MoveEvent;
+
+struct XBMC_ModeChangeEvent
+{
+ RESOLUTION res;
+};
+
+/* The "quit requested" event */
+typedef struct XBMC_QuitEvent {
+} XBMC_QuitEvent;
+
+/* A user-defined event type */
+typedef struct XBMC_UserEvent {
+ int code; /* User defined event code */
+ void *data1; /* User defined data pointer */
+ void *data2; /* User defined data pointer */
+} XBMC_UserEvent;
+
+/* Multimedia keys on keyboards / remotes are mapped to APPCOMMAND events */
+typedef struct XBMC_AppCommandEvent {
+ unsigned int action; /* One of ACTION_... */
+} XBMC_AppCommandEvent;
+
+/* Mouse motion event structure */
+typedef struct XBMC_TouchEvent {
+ int action; /* action ID */
+ float x, y; /* The X/Y coordinates of the mouse */
+ float x2, y2; /* Additional X/Y coordinates */
+ float x3, y3; /* Additional X/Y coordinates */
+ int pointers; /* number of touch pointers */
+} XBMC_TouchEvent;
+
+typedef struct XBMC_SetFocusEvent {
+ int x; /* x position */
+ int y; /* y position */
+} XBMC_SetFocusEvent;
+
+/* Button event structure */
+typedef struct XBMC_ButtonEvent
+{
+ uint32_t button;
+ uint32_t holdtime;
+} XBMC_ButtonEvent;
+
+/* General event structure */
+typedef struct XBMC_Event {
+ uint8_t type;
+ union
+ {
+ XBMC_KeyboardEvent key;
+ XBMC_MouseMotionEvent motion;
+ XBMC_MouseButtonEvent button;
+ XBMC_ResizeEvent resize;
+ XBMC_MoveEvent move;
+ XBMC_ModeChangeEvent mode;
+ XBMC_QuitEvent quit;
+ XBMC_UserEvent user;
+ XBMC_AppCommandEvent appcommand;
+ XBMC_TouchEvent touch;
+ XBMC_ButtonEvent keybutton;
+ XBMC_SetFocusEvent focus;
+ };
+} XBMC_Event;
+
diff --git a/xbmc/windowing/android/AndroidUtils.cpp b/xbmc/windowing/android/AndroidUtils.cpp
new file mode 100644
index 0000000..8c61fae
--- /dev/null
+++ b/xbmc/windowing/android/AndroidUtils.cpp
@@ -0,0 +1,440 @@
+/*
+ * Copyright (C) 2011-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 "AndroidUtils.h"
+
+#include "ServiceBroker.h"
+#include "settings/DisplaySettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "settings/lib/SettingsManager.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+
+#include "platform/android/activity/XBMCApp.h"
+
+#include <androidjni/MediaCodecInfo.h>
+#include <androidjni/MediaCodecList.h>
+#include <androidjni/System.h>
+#include <androidjni/SystemProperties.h>
+#include <androidjni/View.h>
+#include <androidjni/Window.h>
+#include <androidjni/WindowManager.h>
+
+static bool s_hasModeApi = false;
+static std::vector<RESOLUTION_INFO> s_res_displayModes;
+static RESOLUTION_INFO s_res_cur_displayMode;
+
+static float currentRefreshRate()
+{
+ if (s_hasModeApi)
+ return s_res_cur_displayMode.fRefreshRate;
+
+ CJNIWindow window = CXBMCApp::getWindow();
+ if (window)
+ {
+ float preferredRate = window.getAttributes().getpreferredRefreshRate();
+ if (preferredRate > 20.0f)
+ {
+ CLog::Log(LOGINFO, "CAndroidUtils: Preferred refresh rate: {:f}", preferredRate);
+ return preferredRate;
+ }
+ CJNIView view(window.getDecorView());
+ if (view)
+ {
+ CJNIDisplay display(view.getDisplay());
+ if (display)
+ {
+ float reportedRate = display.getRefreshRate();
+ if (reportedRate > 20.0f)
+ {
+ CLog::Log(LOGINFO, "CAndroidUtils: Current display refresh rate: {:f}", reportedRate);
+ return reportedRate;
+ }
+ }
+ }
+ }
+ CLog::Log(LOGDEBUG, "found no refresh rate");
+ return 60.0;
+}
+
+static void fetchDisplayModes()
+{
+ s_hasModeApi = false;
+ s_res_displayModes.clear();
+
+ CJNIDisplay display = CXBMCApp::getWindow().getDecorView().getDisplay();
+
+ if (display)
+ {
+ CJNIDisplayMode m = display.getMode();
+ if (m)
+ {
+ if (m.getPhysicalWidth() > m.getPhysicalHeight()) // Assume unusable if portrait is returned
+ {
+ s_hasModeApi = true;
+
+ CLog::Log(LOGDEBUG, "CAndroidUtils: current mode: {}: {}x{}@{:f}", m.getModeId(),
+ m.getPhysicalWidth(), m.getPhysicalHeight(), m.getRefreshRate());
+ s_res_cur_displayMode.strId = std::to_string(m.getModeId());
+ s_res_cur_displayMode.iWidth = s_res_cur_displayMode.iScreenWidth = m.getPhysicalWidth();
+ s_res_cur_displayMode.iHeight = s_res_cur_displayMode.iScreenHeight = m.getPhysicalHeight();
+ s_res_cur_displayMode.fRefreshRate = m.getRefreshRate();
+ s_res_cur_displayMode.dwFlags = D3DPRESENTFLAG_PROGRESSIVE;
+ s_res_cur_displayMode.bFullScreen = true;
+ s_res_cur_displayMode.iSubtitles = s_res_cur_displayMode.iHeight;
+ s_res_cur_displayMode.fPixelRatio = 1.0f;
+ s_res_cur_displayMode.strMode = StringUtils::Format(
+ "{}x{} @ {:.6f}{} - Full Screen", s_res_cur_displayMode.iScreenWidth,
+ s_res_cur_displayMode.iScreenHeight, s_res_cur_displayMode.fRefreshRate,
+ s_res_cur_displayMode.dwFlags & D3DPRESENTFLAG_INTERLACED ? "i" : "");
+
+ std::vector<CJNIDisplayMode> modes = display.getSupportedModes();
+ for (CJNIDisplayMode& m : modes)
+ {
+ CLog::Log(LOGDEBUG, "CAndroidUtils: available mode: {}: {}x{}@{:f}", m.getModeId(),
+ m.getPhysicalWidth(), m.getPhysicalHeight(), m.getRefreshRate());
+
+ RESOLUTION_INFO res;
+ res.strId = std::to_string(m.getModeId());
+ res.iWidth = res.iScreenWidth = m.getPhysicalWidth();
+ res.iHeight = res.iScreenHeight = m.getPhysicalHeight();
+ res.fRefreshRate = m.getRefreshRate();
+ res.dwFlags = D3DPRESENTFLAG_PROGRESSIVE;
+ res.bFullScreen = true;
+ res.iSubtitles = res.iHeight;
+ res.fPixelRatio = 1.0f;
+ res.strMode = StringUtils::Format("{}x{} @ {:.6f}{} - Full Screen", res.iScreenWidth,
+ res.iScreenHeight, res.fRefreshRate,
+ res.dwFlags & D3DPRESENTFLAG_INTERLACED ? "i" : "");
+
+ s_res_displayModes.push_back(res);
+ }
+ }
+ }
+ }
+}
+
+namespace
+{
+const std::map<int, std::string> hdrTypeMap = {
+ {CAndroidUtils::HDRTypes::HDR10, "HDR10"},
+ {CAndroidUtils::HDRTypes::HLG, "HLG"},
+ {CAndroidUtils::HDRTypes::HDR10_PLUS, "HDR10+"},
+ {CAndroidUtils::HDRTypes::DOLBY_VISION, "Dolby Vision"}};
+
+std::string HdrTypeString(int type)
+{
+ auto hdr = hdrTypeMap.find(type);
+ if (hdr != hdrTypeMap.end())
+ return hdr->second;
+
+ return "Unknown";
+}
+} // unnamed namespace
+
+const std::string CAndroidUtils::SETTING_LIMITGUI = "videoscreen.limitgui";
+
+CAndroidUtils::CAndroidUtils()
+{
+ std::string displaySize;
+ m_width = m_height = 0;
+
+ if (CJNIBase::GetSDKVersion() >= 23)
+ {
+ fetchDisplayModes();
+ for (const RESOLUTION_INFO& res : s_res_displayModes)
+ {
+ if (res.iWidth > m_width || res.iHeight > m_height)
+ {
+ m_width = res.iWidth;
+ m_height = res.iHeight;
+ }
+ }
+ }
+
+ if (!m_width || !m_height)
+ {
+ // Property available on some devices
+ displaySize = CJNISystemProperties::get("sys.display-size", "");
+ if (!displaySize.empty())
+ {
+ std::vector<std::string> aSize = StringUtils::Split(displaySize, "x");
+ if (aSize.size() == 2)
+ {
+ m_width = StringUtils::IsInteger(aSize[0]) ? atoi(aSize[0].c_str()) : 0;
+ m_height = StringUtils::IsInteger(aSize[1]) ? atoi(aSize[1].c_str()) : 0;
+ }
+ CLog::Log(LOGDEBUG, "CAndroidUtils: display-size: {}({}x{})", displaySize, m_width, m_height);
+ }
+ }
+
+ CLog::Log(LOGDEBUG, "CAndroidUtils: maximum/current resolution: {}x{}", m_width, m_height);
+ int limit = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
+ CAndroidUtils::SETTING_LIMITGUI);
+ switch (limit)
+ {
+ case 0: // auto
+ m_width = 0;
+ m_height = 0;
+ break;
+
+ case 9999: // unlimited
+ break;
+
+ case 720:
+ if (m_height > 720)
+ {
+ m_width = 1280;
+ m_height = 720;
+ }
+ break;
+
+ case 1080:
+ if (m_height > 1080)
+ {
+ m_width = 1920;
+ m_height = 1080;
+ }
+ break;
+ }
+ CLog::Log(LOGDEBUG, "CAndroidUtils: selected resolution: {}x{}", m_width, m_height);
+
+ CServiceBroker::GetSettingsComponent()->GetSettings()->GetSettingsManager()->RegisterCallback(
+ this, {CAndroidUtils::SETTING_LIMITGUI});
+
+ LogDisplaySupportedHdrTypes();
+}
+
+bool CAndroidUtils::GetNativeResolution(RESOLUTION_INFO* res) const
+{
+ const std::shared_ptr<CNativeWindow> nativeWindow = CXBMCApp::Get().GetNativeWindow(30000);
+ if (!nativeWindow)
+ return false;
+
+ if (!m_width || !m_height)
+ {
+ m_width = nativeWindow->GetWidth();
+ m_height = nativeWindow->GetHeight();
+ CLog::Log(LOGINFO, "CAndroidUtils: window resolution: {}x{}", m_width, m_height);
+ }
+
+ if (s_hasModeApi)
+ {
+ *res = s_res_cur_displayMode;
+ res->iWidth = m_width;
+ res->iHeight = m_height;
+ }
+ else
+ {
+ res->strId = "-1";
+ res->fRefreshRate = currentRefreshRate();
+ res->dwFlags = D3DPRESENTFLAG_PROGRESSIVE;
+ res->bFullScreen = true;
+ res->iWidth = m_width;
+ res->iHeight = m_height;
+ res->fPixelRatio = 1.0f;
+ res->iScreenWidth = res->iWidth;
+ res->iScreenHeight = res->iHeight;
+ }
+ res->iSubtitles = res->iHeight;
+ res->strMode =
+ StringUtils::Format("{}x{} @ {:.6f}{} - Full Screen", res->iScreenWidth, res->iScreenHeight,
+ res->fRefreshRate, res->dwFlags & D3DPRESENTFLAG_INTERLACED ? "i" : "");
+ CLog::Log(LOGINFO, "CAndroidUtils: Current resolution: {}x{} {}", res->iWidth, res->iHeight,
+ res->strMode);
+ return true;
+}
+
+bool CAndroidUtils::SetNativeResolution(const RESOLUTION_INFO& res)
+{
+ CLog::Log(LOGINFO, "CAndroidUtils: SetNativeResolution: {}: {}x{} {}x{}@{:f}", res.strId,
+ res.iWidth, res.iHeight, res.iScreenWidth, res.iScreenHeight, res.fRefreshRate);
+
+ if (s_hasModeApi)
+ {
+ CXBMCApp::Get().SetDisplayMode(std::atoi(res.strId.c_str()), res.fRefreshRate);
+ s_res_cur_displayMode = res;
+ }
+ else
+ CXBMCApp::Get().SetRefreshRate(res.fRefreshRate);
+
+ CXBMCApp::Get().SetBuffersGeometry(res.iWidth, res.iHeight, 0);
+
+ return true;
+}
+
+bool CAndroidUtils::ProbeResolutions(std::vector<RESOLUTION_INFO>& resolutions)
+{
+ RESOLUTION_INFO cur_res;
+ bool ret = GetNativeResolution(&cur_res);
+
+ CLog::Log(LOGDEBUG, "CAndroidUtils: ProbeResolutions: {}x{}", m_width, m_height);
+
+ if (s_hasModeApi)
+ {
+ for (RESOLUTION_INFO res : s_res_displayModes)
+ {
+ if (m_width && m_height)
+ {
+ res.iWidth = std::min(res.iWidth, m_width);
+ res.iHeight = std::min(res.iHeight, m_height);
+ res.iSubtitles = res.iHeight;
+ }
+ resolutions.push_back(res);
+ }
+ return true;
+ }
+
+ if (ret && cur_res.iWidth > 1 && cur_res.iHeight > 1)
+ {
+ std::vector<float> refreshRates;
+ CJNIWindow window = CXBMCApp::getWindow();
+ if (window)
+ {
+ CJNIView view = window.getDecorView();
+ if (view)
+ {
+ CJNIDisplay display = view.getDisplay();
+ if (display)
+ {
+ refreshRates = display.getSupportedRefreshRates();
+ }
+ }
+
+ if (!refreshRates.empty())
+ {
+ for (unsigned int i = 0; i < refreshRates.size(); i++)
+ {
+ if (refreshRates[i] < 20.0f)
+ continue;
+ cur_res.fRefreshRate = refreshRates[i];
+ cur_res.strMode = StringUtils::Format(
+ "{}x{} @ {:.6f}{} - Full Screen", cur_res.iScreenWidth, cur_res.iScreenHeight,
+ cur_res.fRefreshRate, cur_res.dwFlags & D3DPRESENTFLAG_INTERLACED ? "i" : "");
+ resolutions.push_back(cur_res);
+ }
+ }
+ }
+ if (resolutions.empty())
+ {
+ /* No valid refresh rates available, just provide the current one */
+ resolutions.push_back(cur_res);
+ }
+ return true;
+ }
+ return false;
+}
+
+bool CAndroidUtils::UpdateDisplayModes()
+{
+ if (CJNIBase::GetSDKVersion() >= 23)
+ fetchDisplayModes();
+ return true;
+}
+
+bool CAndroidUtils::IsHDRDisplay()
+{
+ CJNIWindow window = CXBMCApp::getWindow();
+ bool ret = false;
+
+ if (window)
+ {
+ CJNIView view = window.getDecorView();
+ if (view)
+ {
+ CJNIDisplay display = view.getDisplay();
+ if (display)
+ ret = display.isHdr();
+ }
+ }
+ CLog::Log(LOGDEBUG, "CAndroidUtils: IsHDRDisplay: {}", ret ? "true" : "false");
+ return ret;
+}
+
+void CAndroidUtils::OnSettingChanged(const std::shared_ptr<const CSetting>& setting)
+{
+ const std::string& settingId = setting->GetId();
+ /* Calibration (overscan / subtitles) are based on GUI size -> reset required */
+ if (settingId == CAndroidUtils::SETTING_LIMITGUI)
+ CDisplaySettings::GetInstance().ClearCalibrations();
+}
+
+std::vector<int> CAndroidUtils::GetDisplaySupportedHdrTypes()
+{
+ CJNIWindow window = CXBMCApp::getWindow();
+
+ if (window)
+ {
+ CJNIView view = window.getDecorView();
+ if (view)
+ {
+ CJNIDisplay display = view.getDisplay();
+ if (display)
+ {
+ CJNIDisplayHdrCapabilities caps = display.getHdrCapabilities();
+ if (caps)
+ return caps.getSupportedHdrTypes();
+ }
+ }
+ }
+
+ return {};
+}
+
+void CAndroidUtils::LogDisplaySupportedHdrTypes()
+{
+ const std::vector<int> hdrTypes = GetDisplaySupportedHdrTypes();
+ std::string text;
+
+ for (const int& type : hdrTypes)
+ {
+ text += " " + HdrTypeString(type);
+ }
+
+ CLog::Log(LOGDEBUG, "CAndroidUtils: Display supported HDR types:{}",
+ text.empty() ? " None" : text);
+}
+
+CHDRCapabilities CAndroidUtils::GetDisplayHDRCapabilities()
+{
+ CHDRCapabilities caps;
+ const std::vector<int> types = GetDisplaySupportedHdrTypes();
+
+ if (std::find(types.begin(), types.end(), CAndroidUtils::HDRTypes::HDR10) != types.end())
+ caps.SetHDR10();
+
+ if (std::find(types.begin(), types.end(), CAndroidUtils::HDRTypes::HLG) != types.end())
+ caps.SetHLG();
+
+ if (std::find(types.begin(), types.end(), CAndroidUtils::HDRTypes::HDR10_PLUS) != types.end())
+ caps.SetHDR10Plus();
+
+ if (std::find(types.begin(), types.end(), CAndroidUtils::HDRTypes::DOLBY_VISION) != types.end())
+ caps.SetDolbyVision();
+
+ return caps;
+}
+
+bool CAndroidUtils::SupportsMediaCodecMimeType(const std::string& mimeType)
+{
+ const std::vector<CJNIMediaCodecInfo> codecInfos =
+ CJNIMediaCodecList(CJNIMediaCodecList::REGULAR_CODECS).getCodecInfos();
+
+ for (const CJNIMediaCodecInfo& codec_info : codecInfos)
+ {
+ if (codec_info.isEncoder())
+ continue;
+
+ std::vector<std::string> types = codec_info.getSupportedTypes();
+ if (std::find(types.begin(), types.end(), mimeType) != types.end())
+ return true;
+ }
+
+ return false;
+}
diff --git a/xbmc/windowing/android/AndroidUtils.h b/xbmc/windowing/android/AndroidUtils.h
new file mode 100644
index 0000000..426baee
--- /dev/null
+++ b/xbmc/windowing/android/AndroidUtils.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2011-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 "utils/HDRCapabilities.h"
+#include "windowing/Resolution.h"
+
+#include <string>
+#include <vector>
+
+#include <androidjni/Display.h>
+
+class CAndroidUtils : public ISettingCallback
+{
+public:
+ CAndroidUtils();
+ ~CAndroidUtils() override = default;
+ bool GetNativeResolution(RESOLUTION_INFO* res) const;
+ bool SetNativeResolution(const RESOLUTION_INFO& res);
+ bool ProbeResolutions(std::vector<RESOLUTION_INFO>& resolutions);
+ bool UpdateDisplayModes();
+ bool IsHDRDisplay();
+
+ // Implementation of ISettingCallback
+ static const std::string SETTING_LIMITGUI;
+ void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override;
+
+ static bool SupportsMediaCodecMimeType(const std::string& mimeType);
+
+ // Android specific HDR type mapping
+ // https://developer.android.com/reference/android/view/Display.HdrCapabilities#constants_1
+ enum HDRTypes
+ {
+ DOLBY_VISION = 1,
+ HDR10 = 2,
+ HLG = 3,
+ HDR10_PLUS = 4
+ };
+
+ static std::vector<int> GetDisplaySupportedHdrTypes();
+ static CHDRCapabilities GetDisplayHDRCapabilities();
+
+protected:
+ mutable int m_width;
+ mutable int m_height;
+
+private:
+ static void LogDisplaySupportedHdrTypes();
+};
diff --git a/xbmc/windowing/android/CMakeLists.txt b/xbmc/windowing/android/CMakeLists.txt
new file mode 100644
index 0000000..fd489d1
--- /dev/null
+++ b/xbmc/windowing/android/CMakeLists.txt
@@ -0,0 +1,18 @@
+set(SOURCES OSScreenSaverAndroid.cpp
+ WinEventsAndroid.cpp
+ WinSystemAndroid.cpp
+ AndroidUtils.cpp
+ VideoSyncAndroid.cpp)
+
+set(HEADERS OSScreenSaverAndroid.h
+ WinEventsAndroid.h
+ WinSystemAndroid.h
+ AndroidUtils.h
+ VideoSyncAndroid.h)
+
+if(OPENGLES_FOUND)
+ list(APPEND SOURCES WinSystemAndroidGLESContext.cpp)
+ list(APPEND HEADERS WinSystemAndroidGLESContext.h)
+endif()
+
+core_add_library(windowing_android)
diff --git a/xbmc/windowing/android/OSScreenSaverAndroid.cpp b/xbmc/windowing/android/OSScreenSaverAndroid.cpp
new file mode 100644
index 0000000..aa6dfc5
--- /dev/null
+++ b/xbmc/windowing/android/OSScreenSaverAndroid.cpp
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2018 Chris Browet
+ * 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 "OSScreenSaverAndroid.h"
+
+#include "platform/android/activity/XBMCApp.h"
+
+void COSScreenSaverAndroid::Inhibit()
+{
+ CXBMCApp::Get().EnableWakeLock(true);
+}
+
+void COSScreenSaverAndroid::Uninhibit()
+{
+ CXBMCApp::Get().EnableWakeLock(false);
+}
diff --git a/xbmc/windowing/android/OSScreenSaverAndroid.h b/xbmc/windowing/android/OSScreenSaverAndroid.h
new file mode 100644
index 0000000..1b67d85
--- /dev/null
+++ b/xbmc/windowing/android/OSScreenSaverAndroid.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2018 Chris Browet
+ * 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"
+
+class COSScreenSaverAndroid : public KODI::WINDOWING::IOSScreenSaver
+{
+public:
+ explicit COSScreenSaverAndroid() = default;
+ void Inhibit() override;
+ void Uninhibit() override;
+};
diff --git a/xbmc/windowing/android/VideoSyncAndroid.cpp b/xbmc/windowing/android/VideoSyncAndroid.cpp
new file mode 100644
index 0000000..cee6f3e
--- /dev/null
+++ b/xbmc/windowing/android/VideoSyncAndroid.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2015-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 "VideoSyncAndroid.h"
+
+#include "ServiceBroker.h"
+#include "cores/VideoPlayer/VideoReferenceClock.h"
+#include "utils/MathUtils.h"
+#include "utils/TimeUtils.h"
+#include "utils/XTimeUtils.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+#include "windowing/WinSystem.h"
+
+#include "platform/android/activity/XBMCApp.h"
+
+
+bool CVideoSyncAndroid::Setup(PUPDATECLOCK func)
+{
+ CLog::Log(LOGDEBUG, "CVideoSyncAndroid::{} setting up", __FUNCTION__);
+
+ //init the vblank timestamp
+ m_LastVBlankTime = CurrentHostCounter();
+ UpdateClock = func;
+ m_abortEvent.Reset();
+
+ CXBMCApp::Get().InitFrameCallback(this);
+ CServiceBroker::GetWinSystem()->Register(this);
+
+ return true;
+}
+
+void CVideoSyncAndroid::Run(CEvent& stopEvent)
+{
+ XbmcThreads::CEventGroup waitGroup{&stopEvent, &m_abortEvent};
+ waitGroup.wait();
+}
+
+void CVideoSyncAndroid::Cleanup()
+{
+ CLog::Log(LOGDEBUG, "CVideoSyncAndroid::{} cleaning up", __FUNCTION__);
+ CXBMCApp::Get().DeinitFrameCallback();
+ CServiceBroker::GetWinSystem()->Unregister(this);
+}
+
+float CVideoSyncAndroid::GetFps()
+{
+ m_fps = CServiceBroker::GetWinSystem()->GetGfxContext().GetFPS();
+ CLog::Log(LOGDEBUG, "CVideoSyncAndroid::{} Detected refreshrate: {:f} hertz", __FUNCTION__,
+ m_fps);
+ return m_fps;
+}
+
+void CVideoSyncAndroid::OnResetDisplay()
+{
+ m_abortEvent.Set();
+}
+
+void CVideoSyncAndroid::FrameCallback(int64_t frameTimeNanos)
+{
+ int NrVBlanks;
+ double VBlankTime;
+
+ //calculate how many vblanks happened
+ VBlankTime = (double)(frameTimeNanos - m_LastVBlankTime) / (double)CurrentHostFrequency();
+ NrVBlanks = MathUtils::round_int(VBlankTime * static_cast<double>(m_fps));
+
+ //save the timestamp of this vblank so we can calculate how many happened next time
+ m_LastVBlankTime = frameTimeNanos;
+
+ //update the vblank timestamp, update the clock and send a signal that we got a vblank
+ UpdateClock(NrVBlanks, frameTimeNanos, m_refClock);
+}
diff --git a/xbmc/windowing/android/VideoSyncAndroid.h b/xbmc/windowing/android/VideoSyncAndroid.h
new file mode 100644
index 0000000..6bf76c0
--- /dev/null
+++ b/xbmc/windowing/android/VideoSyncAndroid.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2015-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"
+
+class CVideoSyncAndroid : public CVideoSync, IDispResource
+{
+public:
+ CVideoSyncAndroid(void *clock) : CVideoSync(clock), m_LastVBlankTime(0) {}
+
+ // CVideoSync interface
+ bool Setup(PUPDATECLOCK func) override;
+ void Run(CEvent& stop) override;
+ void Cleanup() override;
+ float GetFps() override;
+
+ // IDispResource interface
+ void OnResetDisplay() override;
+
+ // Choreographer callback
+ void FrameCallback(int64_t frameTimeNanos);
+
+private:
+ int64_t m_LastVBlankTime; //timestamp of the last vblank, used for calculating how many vblanks happened
+ CEvent m_abortEvent;
+};
diff --git a/xbmc/windowing/android/WinEventsAndroid.cpp b/xbmc/windowing/android/WinEventsAndroid.cpp
new file mode 100644
index 0000000..a384902
--- /dev/null
+++ b/xbmc/windowing/android/WinEventsAndroid.cpp
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2010-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 "WinEventsAndroid.h"
+
+#include "ServiceBroker.h"
+#include "application/AppInboundProtocol.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "input/InputManager.h"
+#include "input/XBMC_vkeys.h"
+#include "utils/log.h"
+
+#include <mutex>
+
+#define ALMOST_ZERO 0.125f
+enum {
+ EVENT_STATE_TEST,
+ EVENT_STATE_HOLD,
+ EVENT_STATE_REPEAT
+};
+
+/************************************************************************/
+/************************************************************************/
+static bool different_event(XBMC_Event &curEvent, XBMC_Event &newEvent)
+{
+ // different type
+ if (curEvent.type != newEvent.type)
+ return true;
+
+ return false;
+}
+
+/************************************************************************/
+/************************************************************************/
+CWinEventsAndroid::CWinEventsAndroid()
+: CThread("CWinEventsAndroid")
+{
+ CLog::Log(LOGDEBUG, "CWinEventsAndroid::CWinEventsAndroid");
+ Create();
+}
+
+CWinEventsAndroid::~CWinEventsAndroid()
+{
+ m_bStop = true;
+ StopThread(true);
+}
+
+void CWinEventsAndroid::MessagePush(XBMC_Event *newEvent)
+{
+ std::unique_lock<CCriticalSection> lock(m_eventsCond);
+
+ m_events.push_back(*newEvent);
+}
+
+void CWinEventsAndroid::MessagePushRepeat(XBMC_Event *repeatEvent)
+{
+ std::unique_lock<CCriticalSection> lock(m_eventsCond);
+
+ std::list<XBMC_Event>::iterator itt;
+ for (itt = m_events.begin(); itt != m_events.end(); ++itt)
+ {
+ // we have events pending, if we we just
+ // repush, we might push the repeat event
+ // in back of a canceling non-active event.
+ // do not repush if pending are different event.
+ if (different_event(*itt, *repeatEvent))
+ return;
+ }
+
+ // is a repeat, push it
+ m_events.push_back(*repeatEvent);
+}
+
+bool CWinEventsAndroid::MessagePump()
+{
+ bool ret = false;
+
+ // Do not always loop, only pump the initial queued count events. else if ui keep pushing
+ // events the loop won't finish then it will block xbmc main message loop.
+ for (size_t pumpEventCount = GetQueueSize(); pumpEventCount > 0; --pumpEventCount)
+ {
+ // Pop up only one event per time since in App::OnEvent it may init modal dialog which init
+ // deeper message loop and call the deeper MessagePump from there.
+ XBMC_Event pumpEvent;
+ {
+ std::unique_lock<CCriticalSection> lock(m_eventsCond);
+ if (m_events.empty())
+ return ret;
+ pumpEvent = m_events.front();
+ m_events.pop_front();
+ }
+
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ ret |= appPort->OnEvent(pumpEvent);
+
+ if (pumpEvent.type == XBMC_MOUSEBUTTONUP)
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage(GUI_MSG_UNFOCUS_ALL, 0, 0, 0, 0);
+ }
+
+ return ret;
+}
+
+size_t CWinEventsAndroid::GetQueueSize()
+{
+ std::unique_lock<CCriticalSection> lock(m_eventsCond);
+ return m_events.size();
+}
+
+void CWinEventsAndroid::Process()
+{
+ uint32_t timeout = 10;
+ uint32_t holdTimeout = 500;
+ uint32_t repeatTimeout = 100;
+ uint32_t repeatDuration = 0;
+
+ XBMC_Event cur_event;
+ int state = EVENT_STATE_TEST;
+ while (!m_bStop)
+ {
+ // run a 10ms (timeout) wait cycle
+ CThread::Sleep(std::chrono::milliseconds(timeout));
+
+ std::unique_lock<CCriticalSection> lock(m_lasteventCond);
+
+ switch(state)
+ {
+ default:
+ case EVENT_STATE_TEST:
+ // non-active event, eat it
+ if (!m_lastevent.empty())
+ m_lastevent.pop();
+ break;
+
+ case EVENT_STATE_HOLD:
+ repeatDuration += timeout;
+ if (!m_lastevent.empty())
+ {
+ if (different_event(cur_event, m_lastevent.front()))
+ {
+ // different event, cycle back to test
+ state = EVENT_STATE_TEST;
+ break;
+ }
+
+ // same event, eat it
+ m_lastevent.pop();
+ }
+
+ if (repeatDuration >= holdTimeout)
+ {
+ CLog::Log(LOGDEBUG, "hold ->repeat, size({}), repeatDuration({})", m_lastevent.size(),
+ repeatDuration);
+ state = EVENT_STATE_REPEAT;
+ }
+ break;
+
+ case EVENT_STATE_REPEAT:
+ repeatDuration += timeout;
+ if (!m_lastevent.empty())
+ {
+ if (different_event(cur_event, m_lastevent.front()))
+ {
+ // different event, cycle back to test
+ state = EVENT_STATE_TEST;
+ break;
+ }
+
+ // same event, eat it
+ m_lastevent.pop();
+ }
+
+ if (repeatDuration >= holdTimeout)
+ {
+ // this is a repeat, push it
+ MessagePushRepeat(&cur_event);
+ // assuming holdTimeout > repeatTimeout,
+ // just subtract the repeatTimeout
+ // to get the next cycle time
+ repeatDuration -= repeatTimeout;
+ }
+ break;
+ }
+ }
+}
diff --git a/xbmc/windowing/android/WinEventsAndroid.h b/xbmc/windowing/android/WinEventsAndroid.h
new file mode 100644
index 0000000..62b04b5
--- /dev/null
+++ b/xbmc/windowing/android/WinEventsAndroid.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2010-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/CriticalSection.h"
+#include "threads/Event.h"
+#include "threads/Thread.h"
+#include "windowing/WinEvents.h"
+
+#include <list>
+#include <queue>
+#include <string>
+#include <vector>
+
+class CWinEventsAndroid : public IWinEvents, public CThread
+{
+public:
+ CWinEventsAndroid();
+ ~CWinEventsAndroid() override;
+
+ void MessagePush(XBMC_Event *newEvent);
+ void MessagePushRepeat(XBMC_Event *repeatEvent);
+ bool MessagePump() override;
+
+private:
+ size_t GetQueueSize();
+
+ // for CThread
+ void Process() override;
+
+ CCriticalSection m_eventsCond;
+ std::list<XBMC_Event> m_events;
+
+ CCriticalSection m_lasteventCond;
+ std::queue<XBMC_Event> m_lastevent;
+};
+
diff --git a/xbmc/windowing/android/WinSystemAndroid.cpp b/xbmc/windowing/android/WinSystemAndroid.cpp
new file mode 100644
index 0000000..d8ddbb0
--- /dev/null
+++ b/xbmc/windowing/android/WinSystemAndroid.cpp
@@ -0,0 +1,329 @@
+/*
+ * 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 "WinSystemAndroid.h"
+
+#include "OSScreenSaverAndroid.h"
+#include "ServiceBroker.h"
+#include "WinEventsAndroid.h"
+#include "addons/interfaces/platform/android/System.h"
+#include "cores/RetroPlayer/process/android/RPProcessInfoAndroid.h"
+#include "cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGLES.h"
+#include "cores/VideoPlayer/DVDCodecs/Audio/DVDAudioCodecAndroidMediaCodec.h"
+#include "cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.h"
+#include "cores/VideoPlayer/Process/android/ProcessInfoAndroid.h"
+#include "cores/VideoPlayer/VideoRenderers/HwDecRender/RendererMediaCodec.h"
+#include "cores/VideoPlayer/VideoRenderers/HwDecRender/RendererMediaCodecSurface.h"
+#include "guilib/DispResource.h"
+#include "rendering/gles/ScreenshotSurfaceGLES.h"
+#include "settings/DisplaySettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "system_egl.h"
+#include "utils/HDRCapabilities.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+#include "windowing/Resolution.h"
+
+#include "platform/android/activity/XBMCApp.h"
+#include "platform/android/media/decoderfilter/MediaCodecDecoderFilterManager.h"
+#include "platform/android/media/drm/MediaDrmCryptoSession.h"
+
+#include <float.h>
+#include <mutex>
+#include <string.h>
+
+#include <EGL/eglplatform.h>
+
+using namespace KODI;
+using namespace std::chrono_literals;
+
+CWinSystemAndroid::CWinSystemAndroid()
+{
+ m_displayWidth = 0;
+ m_displayHeight = 0;
+
+ m_stereo_mode = RENDER_STEREO_MODE_OFF;
+
+ m_dispResetTimer = new CTimer(this);
+
+ m_android = nullptr;
+
+ m_winEvents.reset(new CWinEventsAndroid());
+}
+
+CWinSystemAndroid::~CWinSystemAndroid()
+{
+ delete m_dispResetTimer;
+}
+
+bool CWinSystemAndroid::InitWindowSystem()
+{
+ m_nativeDisplay = EGL_DEFAULT_DISPLAY;
+
+ m_android = new CAndroidUtils();
+
+ m_decoderFilterManager = new(CMediaCodecDecoderFilterManager);
+ CServiceBroker::RegisterDecoderFilterManager(m_decoderFilterManager);
+
+ CDVDVideoCodecAndroidMediaCodec::Register();
+ CDVDAudioCodecAndroidMediaCodec::Register();
+
+ CLinuxRendererGLES::Register();
+ RETRO::CRPProcessInfoAndroid::Register();
+ RETRO::CRPProcessInfoAndroid::RegisterRendererFactory(new RETRO::CRendererFactoryOpenGLES);
+ CRendererMediaCodec::Register();
+ CRendererMediaCodecSurface::Register();
+ ADDON::Interface_Android::Register();
+ DRM::CMediaDrmCryptoSession::Register();
+ VIDEOPLAYER::CProcessInfoAndroid::Register();
+
+ CScreenshotSurfaceGLES::Register();
+
+ return CWinSystemBase::InitWindowSystem();
+}
+
+bool CWinSystemAndroid::DestroyWindowSystem()
+{
+ CLog::Log(LOGINFO, "CWinSystemAndroid::{}", __FUNCTION__);
+
+ delete m_android;
+ m_android = nullptr;
+
+ CServiceBroker::RegisterDecoderFilterManager(nullptr);
+ delete m_decoderFilterManager;
+ m_decoderFilterManager = nullptr;
+
+ return true;
+}
+
+bool CWinSystemAndroid::CreateNewWindow(const std::string& name,
+ bool fullScreen,
+ RESOLUTION_INFO& res)
+{
+ RESOLUTION_INFO current_resolution;
+ current_resolution.iWidth = current_resolution.iHeight = 0;
+ RENDER_STEREO_MODE stereo_mode = CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode();
+
+ m_nWidth = res.iWidth;
+ m_nHeight = res.iHeight;
+ m_displayWidth = res.iScreenWidth;
+ m_displayHeight = res.iScreenHeight;
+ m_fRefreshRate = res.fRefreshRate;
+
+ if ((m_bWindowCreated && m_android->GetNativeResolution(&current_resolution)) &&
+ current_resolution.iWidth == res.iWidth && current_resolution.iHeight == res.iHeight &&
+ current_resolution.iScreenWidth == res.iScreenWidth && current_resolution.iScreenHeight == res.iScreenHeight &&
+ m_bFullScreen == fullScreen && current_resolution.fRefreshRate == res.fRefreshRate &&
+ (current_resolution.dwFlags & D3DPRESENTFLAG_MODEMASK) == (res.dwFlags & D3DPRESENTFLAG_MODEMASK) &&
+ m_stereo_mode == stereo_mode)
+ {
+ CLog::Log(LOGDEBUG, "CWinSystemAndroid::CreateNewWindow: No need to create a new window");
+ return true;
+ }
+
+ m_dispResetTimer->Stop();
+ m_HdmiModeTriggered = false;
+
+ m_stereo_mode = stereo_mode;
+ m_bFullScreen = fullScreen;
+
+ m_nativeWindow = CXBMCApp::Get().GetNativeWindow(2000);
+ if (!m_nativeWindow)
+ {
+ CLog::Log(LOGERROR, "CWinSystemAndroid::CreateNewWindow: failed");
+ return false;
+ }
+
+ m_android->SetNativeResolution(res);
+
+ return true;
+}
+
+bool CWinSystemAndroid::DestroyWindow()
+{
+ CLog::Log(LOGINFO, "CWinSystemAndroid::{}", __FUNCTION__);
+ m_nativeWindow.reset();
+ m_bWindowCreated = false;
+ return true;
+}
+
+void CWinSystemAndroid::UpdateResolutions()
+{
+ UpdateResolutions(true);
+}
+
+void CWinSystemAndroid::UpdateResolutions(bool bUpdateDesktopRes)
+{
+ CWinSystemBase::UpdateResolutions();
+
+ std::vector<RESOLUTION_INFO> resolutions;
+ if (!m_android->ProbeResolutions(resolutions) || resolutions.empty())
+ {
+ CLog::Log(LOGWARNING, "CWinSystemAndroid::{} failed.", __FUNCTION__);
+ }
+
+ const RESOLUTION_INFO resWindow = CDisplaySettings::GetInstance().GetResolutionInfo(RES_WINDOW);
+
+ RESOLUTION_INFO resDesktop;
+ if (bUpdateDesktopRes)
+ {
+ // ProbeResolutions includes already all resolutions.
+ // Only get desktop resolution so we can replace Kodi's desktop res.
+ RESOLUTION_INFO curDisplay;
+ if (m_android->GetNativeResolution(&curDisplay))
+ resDesktop = curDisplay;
+ }
+ else
+ {
+ // Do not replace Kodi's desktop res, just update the data.
+ resDesktop = CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP);
+ }
+
+ CDisplaySettings::GetInstance().ClearCustomResolutions();
+
+ for (auto& res : resolutions)
+ {
+ CServiceBroker::GetWinSystem()->GetGfxContext().ResetOverscan(res);
+ CDisplaySettings::GetInstance().AddResolutionInfo(res);
+
+ if (resDesktop.iScreenWidth == res.iScreenWidth &&
+ resDesktop.iScreenHeight == res.iScreenHeight &&
+ (resDesktop.dwFlags & D3DPRESENTFLAG_MODEMASK) == (res.dwFlags & D3DPRESENTFLAG_MODEMASK) &&
+ std::fabs(resDesktop.fRefreshRate - res.fRefreshRate) < FLT_EPSILON)
+ {
+ CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP) = res;
+ }
+
+ if (resWindow.iScreenWidth == res.iScreenWidth &&
+ resWindow.iScreenHeight == res.iScreenHeight &&
+ (resWindow.dwFlags & D3DPRESENTFLAG_MODEMASK) == (res.dwFlags & D3DPRESENTFLAG_MODEMASK) &&
+ std::fabs(resWindow.fRefreshRate - res.fRefreshRate) < FLT_EPSILON)
+ {
+ CDisplaySettings::GetInstance().GetResolutionInfo(RES_WINDOW) = res;
+ }
+ }
+
+ CDisplaySettings::GetInstance().ApplyCalibrations();
+}
+
+void CWinSystemAndroid::OnTimeout()
+{
+ m_HdmiModeTriggered = true;
+}
+
+void CWinSystemAndroid::InitiateModeChange()
+{
+ auto delay =
+ std::chrono::milliseconds(CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
+ "videoscreen.delayrefreshchange") *
+ 100);
+
+ if (delay < 2000ms)
+ delay = 2000ms;
+ m_dispResetTimer->Stop();
+ m_dispResetTimer->Start(delay);
+
+ SetHdmiState(false);
+}
+
+void CWinSystemAndroid::SetHdmiState(bool connected)
+{
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ CLog::Log(LOGDEBUG, "CWinSystemAndroid::SetHdmiState: state: {}", static_cast<int>(connected));
+
+ if (connected)
+ {
+ if (m_dispResetTimer->IsRunning())
+ {
+ // We stop the timer if OS supports HDMI_AUDIO_PLUG intent
+ // and configured delay is smaller than the time HDMI_PLUG took.
+ // Note that timer is always started with minimum of 2 seconds
+ // regardless if the configured delay is smaller
+ if (m_dispResetTimer->GetElapsedMilliseconds() >=
+ CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
+ "videoscreen.delayrefreshchange") *
+ 100)
+ m_dispResetTimer->Stop();
+ else
+ return;
+ }
+
+ for (auto resource : m_resources)
+ resource->OnResetDisplay();
+ m_dispResetTimer->Stop();
+ m_HdmiModeTriggered = false;
+ }
+ else
+ {
+ for (auto resource : m_resources)
+ resource->OnLostDisplay();
+ }
+}
+
+void CWinSystemAndroid::UpdateDisplayModes()
+{
+ // re-fetch display modes
+ m_android->UpdateDisplayModes();
+
+ if (m_nativeWindow)
+ {
+ // update display settings
+ UpdateResolutions(false);
+ }
+}
+
+bool CWinSystemAndroid::Hide()
+{
+ return false;
+}
+
+bool CWinSystemAndroid::Show(bool raise)
+{
+ return false;
+}
+
+void CWinSystemAndroid::Register(IDispResource *resource)
+{
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ m_resources.push_back(resource);
+}
+
+void CWinSystemAndroid::Unregister(IDispResource *resource)
+{
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ std::vector<IDispResource*>::iterator i = find(m_resources.begin(), m_resources.end(), resource);
+ if (i != m_resources.end())
+ m_resources.erase(i);
+}
+
+void CWinSystemAndroid::MessagePush(XBMC_Event *newEvent)
+{
+ dynamic_cast<CWinEventsAndroid&>(*m_winEvents).MessagePush(newEvent);
+}
+
+bool CWinSystemAndroid::MessagePump()
+{
+ return m_winEvents->MessagePump();
+}
+
+bool CWinSystemAndroid::IsHDRDisplay()
+{
+ return m_android->IsHDRDisplay();
+}
+
+std::unique_ptr<WINDOWING::IOSScreenSaver> CWinSystemAndroid::GetOSScreenSaverImpl()
+{
+ std::unique_ptr<KODI::WINDOWING::IOSScreenSaver> ret(new COSScreenSaverAndroid());
+ return ret;
+}
+
+CHDRCapabilities CWinSystemAndroid::GetDisplayHDRCapabilities() const
+{
+ return CAndroidUtils::GetDisplayHDRCapabilities();
+}
diff --git a/xbmc/windowing/android/WinSystemAndroid.h b/xbmc/windowing/android/WinSystemAndroid.h
new file mode 100644
index 0000000..0e39193
--- /dev/null
+++ b/xbmc/windowing/android/WinSystemAndroid.h
@@ -0,0 +1,85 @@
+/*
+ * 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 "AndroidUtils.h"
+#include "rendering/gles/RenderSystemGLES.h"
+#include "system_egl.h"
+#include "threads/CriticalSection.h"
+#include "threads/Timer.h"
+#include "utils/HDRCapabilities.h"
+#include "windowing/WinSystem.h"
+
+#include <memory>
+
+class CDecoderFilterManager;
+class IDispResource;
+class CNativeWindow;
+
+class CWinSystemAndroid : public CWinSystemBase, public ITimerCallback
+{
+public:
+ CWinSystemAndroid();
+ ~CWinSystemAndroid() override;
+
+ bool InitWindowSystem() override;
+ bool DestroyWindowSystem() override;
+
+ bool CreateNewWindow(const std::string& name,
+ bool fullScreen,
+ RESOLUTION_INFO& res) override;
+
+ bool DestroyWindow() override;
+ void UpdateResolutions() override;
+
+ void InitiateModeChange();
+ bool IsHdmiModeTriggered() const { return m_HdmiModeTriggered; }
+ void SetHdmiState(bool connected);
+
+ void UpdateDisplayModes();
+
+ bool HasCursor() override { return false; }
+
+ bool Hide() override;
+ bool Show(bool raise = true) override;
+ void Register(IDispResource *resource) override;
+ void Unregister(IDispResource *resource) override;
+
+ void MessagePush(XBMC_Event *newEvent);
+
+ // winevents override
+ bool MessagePump() override;
+ bool IsHDRDisplay() override;
+
+ CHDRCapabilities GetDisplayHDRCapabilities() const override;
+
+protected:
+ std::unique_ptr<KODI::WINDOWING::IOSScreenSaver> GetOSScreenSaverImpl() override;
+ void OnTimeout() override;
+
+ CAndroidUtils *m_android;
+
+ EGLDisplay m_nativeDisplay = EGL_NO_DISPLAY;
+ std::shared_ptr<CNativeWindow> m_nativeWindow;
+
+ int m_displayWidth;
+ int m_displayHeight;
+
+ RENDER_STEREO_MODE m_stereo_mode;
+
+ CTimer *m_dispResetTimer;
+
+ CCriticalSection m_resourceSection;
+ std::vector<IDispResource*> m_resources;
+ CDecoderFilterManager *m_decoderFilterManager;
+
+private:
+ bool m_HdmiModeTriggered = false;
+ void UpdateResolutions(bool bUpdateDesktopRes);
+};
diff --git a/xbmc/windowing/android/WinSystemAndroidGLESContext.cpp b/xbmc/windowing/android/WinSystemAndroidGLESContext.cpp
new file mode 100644
index 0000000..ac562ae
--- /dev/null
+++ b/xbmc/windowing/android/WinSystemAndroidGLESContext.cpp
@@ -0,0 +1,270 @@
+/*
+ * 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 "WinSystemAndroidGLESContext.h"
+
+#include "ServiceBroker.h"
+#include "VideoSyncAndroid.h"
+#include "cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "threads/SingleLock.h"
+#include "utils/log.h"
+#include "windowing/WindowSystemFactory.h"
+
+#include "platform/android/activity/XBMCApp.h"
+
+#include <EGL/eglext.h>
+#include <unistd.h>
+
+#include "PlatformDefs.h"
+
+void CWinSystemAndroidGLESContext::Register()
+{
+ KODI::WINDOWING::CWindowSystemFactory::RegisterWindowSystem(CreateWinSystem);
+}
+
+std::unique_ptr<CWinSystemBase> CWinSystemAndroidGLESContext::CreateWinSystem()
+{
+ return std::make_unique<CWinSystemAndroidGLESContext>();
+}
+
+bool CWinSystemAndroidGLESContext::InitWindowSystem()
+{
+ if (!CWinSystemAndroid::InitWindowSystem())
+ {
+ return false;
+ }
+
+ if (!m_pGLContext.CreateDisplay(m_nativeDisplay))
+ {
+ return false;
+ }
+
+ if (!m_pGLContext.InitializeDisplay(EGL_OPENGL_ES_API))
+ {
+ return false;
+ }
+
+ if (!m_pGLContext.ChooseConfig(EGL_OPENGL_ES2_BIT))
+ {
+ return false;
+ }
+
+ m_hasHDRConfig = m_pGLContext.ChooseConfig(EGL_OPENGL_ES2_BIT, 0, true);
+
+ m_hasEGL_BT2020_PQ_Colorspace_Extension =
+ CEGLUtils::HasExtension(m_pGLContext.GetEGLDisplay(), "EGL_EXT_gl_colorspace_bt2020_pq");
+ m_hasEGL_ST2086_Extension =
+ CEGLUtils::HasExtension(m_pGLContext.GetEGLDisplay(), "EGL_EXT_surface_SMPTE2086_metadata");
+
+ bool hasEGLHDRExtensions = m_hasEGL_BT2020_PQ_Colorspace_Extension && m_hasEGL_ST2086_Extension;
+
+ CLog::Log(LOGDEBUG,
+ "CWinSystemAndroidGLESContext::InitWindowSystem: HDRConfig: {}, HDRExtensions: {}",
+ static_cast<int>(m_hasHDRConfig), static_cast<int>(hasEGLHDRExtensions));
+
+ CEGLAttributesVec contextAttribs;
+ contextAttribs.Add({{EGL_CONTEXT_CLIENT_VERSION, 2}});
+
+ if (!m_pGLContext.CreateContext(contextAttribs))
+ {
+ return false;
+ }
+
+ return true;
+}
+
+bool CWinSystemAndroidGLESContext::CreateNewWindow(const std::string& name,
+ bool fullScreen,
+ RESOLUTION_INFO& res)
+{
+ m_pGLContext.DestroySurface();
+
+ if (!CWinSystemAndroid::CreateNewWindow(name, fullScreen, res))
+ {
+ return false;
+ }
+
+ if (!CreateSurface())
+ {
+ return false;
+ }
+
+ if (!m_pGLContext.BindContext())
+ {
+ return false;
+ }
+
+ return true;
+}
+
+bool CWinSystemAndroidGLESContext::ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop)
+{
+ CRenderSystemGLES::ResetRenderSystem(newWidth, newHeight);
+ return true;
+}
+
+bool CWinSystemAndroidGLESContext::SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays)
+{
+ CreateNewWindow("", fullScreen, res);
+ CRenderSystemGLES::ResetRenderSystem(res.iWidth, res.iHeight);
+ return true;
+}
+
+void CWinSystemAndroidGLESContext::SetVSyncImpl(bool enable)
+{
+ // We use Choreographer for timing
+ m_pGLContext.SetVSync(false);
+}
+
+void CWinSystemAndroidGLESContext::PresentRenderImpl(bool rendered)
+{
+ if (!m_nativeWindow)
+ {
+ usleep(10000);
+ return;
+ }
+
+ // Mode change finalization was triggered by timer
+ if (IsHdmiModeTriggered())
+ SetHdmiState(true);
+
+ // Ignore EGL_BAD_SURFACE: It seems to happen during/after mode changes, but
+ // we can't actually do anything about it
+ if (rendered && !m_pGLContext.TrySwapBuffers())
+ CEGLUtils::Log(LOGERROR, "eglSwapBuffers failed");
+
+ CXBMCApp::Get().WaitVSync(1000);
+}
+
+float CWinSystemAndroidGLESContext::GetFrameLatencyAdjustment()
+{
+ return CXBMCApp::Get().GetFrameLatencyMs();
+}
+
+EGLDisplay CWinSystemAndroidGLESContext::GetEGLDisplay() const
+{
+ return m_pGLContext.GetEGLDisplay();
+}
+
+EGLSurface CWinSystemAndroidGLESContext::GetEGLSurface() const
+{
+ return m_pGLContext.GetEGLSurface();
+}
+
+EGLContext CWinSystemAndroidGLESContext::GetEGLContext() const
+{
+ return m_pGLContext.GetEGLContext();
+}
+
+EGLConfig CWinSystemAndroidGLESContext::GetEGLConfig() const
+{
+ return m_pGLContext.GetEGLConfig();
+}
+
+std::unique_ptr<CVideoSync> CWinSystemAndroidGLESContext::GetVideoSync(void *clock)
+{
+ std::unique_ptr<CVideoSync> pVSync(new CVideoSyncAndroid(clock));
+ return pVSync;
+}
+
+bool CWinSystemAndroidGLESContext::CreateSurface()
+{
+ if (!m_pGLContext.CreateSurface(static_cast<EGLNativeWindowType>(m_nativeWindow->m_window),
+ m_HDRColorSpace))
+ {
+ if (m_HDRColorSpace != EGL_NONE)
+ {
+ m_HDRColorSpace = EGL_NONE;
+ m_displayMetadata = nullptr;
+ m_lightMetadata = nullptr;
+ if (!m_pGLContext.CreateSurface(static_cast<EGLNativeWindowType>(m_nativeWindow->m_window)))
+ return false;
+ }
+ else
+ return false;
+ }
+
+#if EGL_EXT_surface_SMPTE2086_metadata
+ if (m_displayMetadata)
+ {
+ m_pGLContext.SurfaceAttrib(EGL_SMPTE2086_DISPLAY_PRIMARY_RX_EXT, static_cast<int>(av_q2d(m_displayMetadata->display_primaries[0][0]) * EGL_METADATA_SCALING_EXT + 0.5));
+ m_pGLContext.SurfaceAttrib(EGL_SMPTE2086_DISPLAY_PRIMARY_RY_EXT, static_cast<int>(av_q2d(m_displayMetadata->display_primaries[0][1]) * EGL_METADATA_SCALING_EXT + 0.5));
+ m_pGLContext.SurfaceAttrib(EGL_SMPTE2086_DISPLAY_PRIMARY_GX_EXT, static_cast<int>(av_q2d(m_displayMetadata->display_primaries[1][0]) * EGL_METADATA_SCALING_EXT + 0.5));
+ m_pGLContext.SurfaceAttrib(EGL_SMPTE2086_DISPLAY_PRIMARY_GY_EXT, static_cast<int>(av_q2d(m_displayMetadata->display_primaries[1][1]) * EGL_METADATA_SCALING_EXT + 0.5));
+ m_pGLContext.SurfaceAttrib(EGL_SMPTE2086_DISPLAY_PRIMARY_BX_EXT, static_cast<int>(av_q2d(m_displayMetadata->display_primaries[2][0]) * EGL_METADATA_SCALING_EXT + 0.5));
+ m_pGLContext.SurfaceAttrib(EGL_SMPTE2086_DISPLAY_PRIMARY_BY_EXT, static_cast<int>(av_q2d(m_displayMetadata->display_primaries[2][1]) * EGL_METADATA_SCALING_EXT + 0.5));
+ m_pGLContext.SurfaceAttrib(EGL_SMPTE2086_WHITE_POINT_X_EXT, static_cast<int>(av_q2d(m_displayMetadata->white_point[0]) * EGL_METADATA_SCALING_EXT + 0.5));
+ m_pGLContext.SurfaceAttrib(EGL_SMPTE2086_WHITE_POINT_Y_EXT, static_cast<int>(av_q2d(m_displayMetadata->white_point[1]) * EGL_METADATA_SCALING_EXT + 0.5));
+ m_pGLContext.SurfaceAttrib(EGL_SMPTE2086_MAX_LUMINANCE_EXT, static_cast<int>(av_q2d(m_displayMetadata->max_luminance) * EGL_METADATA_SCALING_EXT + 0.5));
+ m_pGLContext.SurfaceAttrib(EGL_SMPTE2086_MIN_LUMINANCE_EXT, static_cast<int>(av_q2d(m_displayMetadata->min_luminance) * EGL_METADATA_SCALING_EXT + 0.5));
+ }
+ if (m_lightMetadata)
+ {
+ m_pGLContext.SurfaceAttrib(EGL_CTA861_3_MAX_CONTENT_LIGHT_LEVEL_EXT, static_cast<int>(m_lightMetadata->MaxCLL * EGL_METADATA_SCALING_EXT));
+ m_pGLContext.SurfaceAttrib(EGL_CTA861_3_MAX_FRAME_AVERAGE_LEVEL_EXT, static_cast<int>(m_lightMetadata->MaxFALL * EGL_METADATA_SCALING_EXT));
+ }
+#endif
+ return true;
+}
+
+bool CWinSystemAndroidGLESContext::IsHDRDisplay()
+{
+ return m_hasHDRConfig && (m_hasEGL_BT2020_PQ_Colorspace_Extension || m_hasEGL_ST2086_Extension) &&
+ CWinSystemAndroid::IsHDRDisplay();
+}
+
+bool CWinSystemAndroidGLESContext::SetHDR(const VideoPicture* videoPicture)
+{
+ if (!CWinSystemAndroid::IsHDRDisplay() || !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(SETTING_WINSYSTEM_IS_HDR_DISPLAY))
+ return false;
+
+ EGLint HDRColorSpace = 0;
+
+#if EGL_EXT_gl_colorspace_bt2020_linear
+ if (m_hasHDRConfig && m_hasEGL_BT2020_PQ_Colorspace_Extension && m_hasEGL_ST2086_Extension)
+ {
+ HDRColorSpace = EGL_NONE;
+ if (videoPicture && videoPicture->hasDisplayMetadata)
+ {
+ switch (videoPicture->color_space)
+ {
+ case AVCOL_SPC_BT2020_NCL:
+ case AVCOL_SPC_BT2020_CL:
+ case AVCOL_SPC_BT709:
+ HDRColorSpace = EGL_GL_COLORSPACE_BT2020_PQ_EXT;
+ break;
+ default:
+ m_displayMetadata = nullptr;
+ m_lightMetadata = nullptr;
+ }
+ }
+ else
+ {
+ m_displayMetadata = nullptr;
+ m_lightMetadata = nullptr;
+ }
+
+ if (HDRColorSpace != m_HDRColorSpace)
+ {
+ CLog::Log(LOGDEBUG, "CWinSystemAndroidGLESContext::SetHDR: ColorSpace: {}", HDRColorSpace);
+
+ m_HDRColorSpace = HDRColorSpace;
+ m_displayMetadata = m_HDRColorSpace == EGL_NONE ? nullptr : std::unique_ptr<AVMasteringDisplayMetadata>(new AVMasteringDisplayMetadata(videoPicture->displayMetadata));
+ // TODO: discuss with NVIDIA why this prevent turning HDR display off
+ //m_lightMetadata = !videoPicture || m_HDRColorSpace == EGL_NONE ? nullptr : std::unique_ptr<AVContentLightMetadata>(new AVContentLightMetadata(videoPicture->lightMetadata));
+ m_pGLContext.DestroySurface();
+ CreateSurface();
+ m_pGLContext.BindContext();
+ }
+ }
+#endif
+
+ return m_HDRColorSpace == HDRColorSpace;
+}
diff --git a/xbmc/windowing/android/WinSystemAndroidGLESContext.h b/xbmc/windowing/android/WinSystemAndroidGLESContext.h
new file mode 100644
index 0000000..b04ff41
--- /dev/null
+++ b/xbmc/windowing/android/WinSystemAndroidGLESContext.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 "WinSystemAndroid.h"
+#include "rendering/gles/RenderSystemGLES.h"
+#include "utils/EGLUtils.h"
+#include "utils/GlobalsHandling.h"
+
+struct AVMasteringDisplayMetadata;
+struct AVContentLightMetadata;
+
+class CWinSystemAndroidGLESContext : public CWinSystemAndroid, public CRenderSystemGLES
+{
+public:
+ CWinSystemAndroidGLESContext() = default;
+ ~CWinSystemAndroidGLESContext() override = default;
+
+ static void Register();
+ static std::unique_ptr<CWinSystemBase> CreateWinSystem();
+
+ // Implementation of CWinSystemBase via CWinSystemAndroid
+ CRenderSystemBase *GetRenderSystem() override { return this; }
+ bool InitWindowSystem() override;
+ bool CreateNewWindow(const std::string& name,
+ bool fullScreen,
+ RESOLUTION_INFO& res) override;
+
+ bool ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop) override;
+ bool SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) override;
+
+ std::unique_ptr<CVideoSync> GetVideoSync(void* clock) override;
+
+ float GetFrameLatencyAdjustment() override;
+ bool IsHDRDisplay() override;
+ bool SetHDR(const VideoPicture* videoPicture) override;
+
+ EGLDisplay GetEGLDisplay() const;
+ EGLSurface GetEGLSurface() const;
+ EGLContext GetEGLContext() const;
+ EGLConfig GetEGLConfig() const;
+protected:
+ void SetVSyncImpl(bool enable) override;
+ void PresentRenderImpl(bool rendered) override;
+
+private:
+ bool CreateSurface();
+
+ CEGLContextUtils m_pGLContext;
+ bool m_hasHDRConfig = false;
+
+ std::unique_ptr<AVMasteringDisplayMetadata> m_displayMetadata;
+ std::unique_ptr<AVContentLightMetadata> m_lightMetadata;
+ EGLint m_HDRColorSpace = EGL_NONE;
+ bool m_hasEGL_ST2086_Extension = false;
+ bool m_hasEGL_BT2020_PQ_Colorspace_Extension = false;
+};
diff --git a/xbmc/windowing/gbm/CMakeLists.txt b/xbmc/windowing/gbm/CMakeLists.txt
new file mode 100644
index 0000000..254b2db
--- /dev/null
+++ b/xbmc/windowing/gbm/CMakeLists.txt
@@ -0,0 +1,26 @@
+add_subdirectory(drm)
+
+set(SOURCES OptionalsReg.cpp
+ WinSystemGbm.cpp
+ VideoSyncGbm.cpp
+ GBMUtils.cpp
+ WinSystemGbmEGLContext.cpp
+ GBMDPMSSupport.cpp)
+
+set(HEADERS OptionalsReg.h
+ WinSystemGbm.h
+ VideoSyncGbm.h
+ GBMUtils.h
+ WinSystemGbmEGLContext.h
+ GBMDPMSSupport.h)
+
+if (OPENGL_FOUND)
+ list(APPEND SOURCES WinSystemGbmGLContext.cpp)
+ list(APPEND HEADERS WinSystemGbmGLContext.h)
+endif()
+if(OPENGLES_FOUND)
+ list(APPEND SOURCES WinSystemGbmGLESContext.cpp)
+ list(APPEND HEADERS WinSystemGbmGLESContext.h)
+endif()
+
+core_add_library(windowing_gbm)
diff --git a/xbmc/windowing/gbm/GBMDPMSSupport.cpp b/xbmc/windowing/gbm/GBMDPMSSupport.cpp
new file mode 100644
index 0000000..6544587
--- /dev/null
+++ b/xbmc/windowing/gbm/GBMDPMSSupport.cpp
@@ -0,0 +1,43 @@
+/*
+ * 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 "GBMDPMSSupport.h"
+
+#include "ServiceBroker.h"
+#include "windowing/gbm/WinSystemGbm.h"
+
+using namespace KODI::WINDOWING::GBM;
+
+CGBMDPMSSupport::CGBMDPMSSupport()
+{
+ m_supportedModes.push_back(OFF);
+}
+
+bool CGBMDPMSSupport::EnablePowerSaving(PowerSavingMode mode)
+{
+ auto winSystem = dynamic_cast<CWinSystemGbm*>(CServiceBroker::GetWinSystem());
+ if (!winSystem)
+ return false;
+
+ switch (mode)
+ {
+ case OFF:
+ return winSystem->Hide();
+ default:
+ return false;
+ }
+}
+
+bool CGBMDPMSSupport::DisablePowerSaving()
+{
+ auto winSystem = dynamic_cast<CWinSystemGbm*>(CServiceBroker::GetWinSystem());
+ if (!winSystem)
+ return false;
+
+ return winSystem->Show();
+}
diff --git a/xbmc/windowing/gbm/GBMDPMSSupport.h b/xbmc/windowing/gbm/GBMDPMSSupport.h
new file mode 100644
index 0000000..f5fabf8
--- /dev/null
+++ b/xbmc/windowing/gbm/GBMDPMSSupport.h
@@ -0,0 +1,22 @@
+/*
+ * 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 "powermanagement/DPMSSupport.h"
+
+#include <memory>
+
+class CGBMDPMSSupport : public CDPMSSupport
+{
+public:
+ CGBMDPMSSupport();
+ ~CGBMDPMSSupport() override = default;
+
+protected:
+ bool EnablePowerSaving(PowerSavingMode mode) override;
+ bool DisablePowerSaving() override;
+};
diff --git a/xbmc/windowing/gbm/GBMUtils.cpp b/xbmc/windowing/gbm/GBMUtils.cpp
new file mode 100644
index 0000000..5267c93
--- /dev/null
+++ b/xbmc/windowing/gbm/GBMUtils.cpp
@@ -0,0 +1,107 @@
+/*
+ * 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 "GBMUtils.h"
+
+#include "utils/log.h"
+
+#include <mutex>
+
+using namespace KODI::WINDOWING::GBM;
+
+namespace
+{
+std::once_flag flag;
+}
+
+bool CGBMUtils::CreateDevice(int fd)
+{
+ auto device = gbm_create_device(fd);
+ if (!device)
+ {
+ CLog::Log(LOGERROR, "CGBMUtils::{} - failed to create device: {}", __FUNCTION__,
+ strerror(errno));
+ return false;
+ }
+
+ m_device.reset(new CGBMDevice(device));
+
+ return true;
+}
+
+CGBMUtils::CGBMDevice::CGBMDevice(gbm_device* device) : m_device(device)
+{
+}
+
+bool CGBMUtils::CGBMDevice::CreateSurface(
+ int width, int height, uint32_t format, const uint64_t* modifiers, const int modifiers_count)
+{
+ gbm_surface* surface{nullptr};
+#if defined(HAS_GBM_MODIFIERS)
+ if (modifiers)
+ {
+ surface = gbm_surface_create_with_modifiers(m_device, width, height, format, modifiers,
+ modifiers_count);
+ }
+#endif
+ if (!surface)
+ {
+ surface = gbm_surface_create(m_device, width, height, format,
+ GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING);
+ }
+
+ if (!surface)
+ {
+ CLog::Log(LOGERROR, "CGBMUtils::{} - failed to create surface: {}", __FUNCTION__,
+ strerror(errno));
+ return false;
+ }
+
+ CLog::Log(LOGDEBUG, "CGBMUtils::{} - created surface with size {}x{}", __FUNCTION__, width,
+ height);
+
+ m_surface.reset(new CGBMSurface(surface));
+
+ return true;
+}
+
+CGBMUtils::CGBMDevice::CGBMSurface::CGBMSurface(gbm_surface* surface) : m_surface(surface)
+{
+}
+
+CGBMUtils::CGBMDevice::CGBMSurface::CGBMSurfaceBuffer* CGBMUtils::CGBMDevice::CGBMSurface::
+ LockFrontBuffer()
+{
+ m_buffers.emplace(std::make_unique<CGBMSurfaceBuffer>(m_surface));
+
+ if (!static_cast<bool>(gbm_surface_has_free_buffers(m_surface)))
+ {
+ /*
+ * We want to use call_once here because we want it to be logged the first time that
+ * we have to release buffers. This means that the maximum amount of buffers had been reached.
+ * For mesa this should be 4 buffers but it may vary across other implementations.
+ */
+ std::call_once(
+ flag, [this]() { CLog::Log(LOGDEBUG, "CGBMUtils - using {} buffers", m_buffers.size()); });
+
+ m_buffers.pop();
+ }
+
+ return m_buffers.back().get();
+}
+
+CGBMUtils::CGBMDevice::CGBMSurface::CGBMSurfaceBuffer::CGBMSurfaceBuffer(gbm_surface* surface)
+ : m_surface(surface), m_buffer(gbm_surface_lock_front_buffer(surface))
+{
+}
+
+CGBMUtils::CGBMDevice::CGBMSurface::CGBMSurfaceBuffer::~CGBMSurfaceBuffer()
+{
+ if (m_surface && m_buffer)
+ gbm_surface_release_buffer(m_surface, m_buffer);
+}
diff --git a/xbmc/windowing/gbm/GBMUtils.h b/xbmc/windowing/gbm/GBMUtils.h
new file mode 100644
index 0000000..291a93a
--- /dev/null
+++ b/xbmc/windowing/gbm/GBMUtils.h
@@ -0,0 +1,177 @@
+/*
+ * 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 <memory>
+#include <queue>
+
+#include <gbm.h>
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace GBM
+{
+
+/**
+ * @brief A wrapper for gbm c classes to allow OOP and RAII.
+ *
+ */
+class CGBMUtils
+{
+public:
+ CGBMUtils(const CGBMUtils&) = delete;
+ CGBMUtils& operator=(const CGBMUtils&) = delete;
+ CGBMUtils() = default;
+ ~CGBMUtils() = default;
+
+ /**
+ * @brief Create a gbm device for allocating buffers
+ *
+ * @param fd The file descriptor for a backend device
+ * @return true The device creation succeeded
+ * @return false The device creation failed
+ */
+ bool CreateDevice(int fd);
+
+ /**
+ * @brief A wrapper for gbm_device to allow OOP and RAII
+ *
+ */
+ class CGBMDevice
+ {
+ public:
+ CGBMDevice(const CGBMDevice&) = delete;
+ CGBMDevice& operator=(const CGBMDevice&) = delete;
+ explicit CGBMDevice(gbm_device* device);
+ ~CGBMDevice() = default;
+
+ /**
+ * @brief Create a gbm surface
+ *
+ * @param width The width to use for the surface
+ * @param height The height to use for the surface
+ * @param format The format to use for the surface
+ * @param modifiers The modifiers to use for the surface
+ * @param modifiers_count The amount of modifiers in the modifiers param
+ * @return true The surface creation succeeded
+ * @return false The surface creation failed
+ */
+ bool CreateSurface(int width,
+ int height,
+ uint32_t format,
+ const uint64_t* modifiers,
+ const int modifiers_count);
+
+ /**
+ * @brief Get the underlying gbm_device
+ *
+ * @return gbm_device* A pointer to the underlying gbm_device
+ */
+ gbm_device* Get() const { return m_device; }
+
+ /**
+ * @brief A wrapper for gbm_surface to allow OOP and RAII
+ *
+ */
+ class CGBMSurface
+ {
+ public:
+ CGBMSurface(const CGBMSurface&) = delete;
+ CGBMSurface& operator=(const CGBMSurface&) = delete;
+ explicit CGBMSurface(gbm_surface* surface);
+ ~CGBMSurface() = default;
+
+ /**
+ * @brief Get the underlying gbm_surface
+ *
+ * @return gbm_surface* A pointer to the underlying gbm_surface
+ */
+ gbm_surface* Get() const { return m_surface; }
+
+ /**
+ * @brief A wrapper for gbm_bo to allow OOP and RAII
+ *
+ */
+ class CGBMSurfaceBuffer
+ {
+ public:
+ CGBMSurfaceBuffer(const CGBMSurfaceBuffer&) = delete;
+ CGBMSurfaceBuffer& operator=(const CGBMSurfaceBuffer&) = delete;
+ explicit CGBMSurfaceBuffer(gbm_surface* surface);
+ ~CGBMSurfaceBuffer();
+
+ /**
+ * @brief Get the underlying gbm_bo
+ *
+ * @return gbm_bo* A pointer to the underlying gbm_bo
+ */
+ gbm_bo* Get() const { return m_buffer; }
+
+ private:
+ gbm_surface* m_surface{nullptr};
+ gbm_bo* m_buffer{nullptr};
+ };
+
+ /**
+ * @brief Lock the surface's current front buffer.
+ *
+ * @return CGBMSurfaceBuffer* A pointer to a CGBMSurfaceBuffer object
+ */
+ CGBMSurfaceBuffer* LockFrontBuffer();
+
+ private:
+ gbm_surface* m_surface{nullptr};
+ std::queue<std::unique_ptr<CGBMSurfaceBuffer>> m_buffers;
+ };
+
+ /**
+ * @brief Get the CGBMSurface object
+ *
+ * @return CGBMSurface* A pointer to the CGBMSurface object
+ */
+ CGBMDevice::CGBMSurface* GetSurface() const { return m_surface.get(); }
+
+ private:
+ gbm_device* m_device{nullptr};
+
+ struct CGBMSurfaceDeleter
+ {
+ void operator()(CGBMSurface* p) const
+ {
+ if (p)
+ gbm_surface_destroy(p->Get());
+ }
+ };
+ std::unique_ptr<CGBMSurface, CGBMSurfaceDeleter> m_surface;
+ };
+
+ /**
+ * @brief Get the CGBMDevice object
+ *
+ * @return CGBMDevice* A pointer to the CGBMDevice object
+ */
+ CGBMUtils::CGBMDevice* GetDevice() const { return m_device.get(); }
+
+private:
+ struct CGBMDeviceDeleter
+ {
+ void operator()(CGBMDevice* p) const
+ {
+ if (p)
+ gbm_device_destroy(p->Get());
+ }
+ };
+ std::unique_ptr<CGBMDevice, CGBMDeviceDeleter> m_device;
+};
+
+}
+}
+}
diff --git a/xbmc/windowing/gbm/OptionalsReg.cpp b/xbmc/windowing/gbm/OptionalsReg.cpp
new file mode 100644
index 0000000..9f5076f
--- /dev/null
+++ b/xbmc/windowing/gbm/OptionalsReg.cpp
@@ -0,0 +1,139 @@
+/*
+ * 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 <va/va_drm.h>
+#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
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace GBM
+{
+
+class CVaapiProxy : public VAAPI::IVaapiWinSystem
+{
+public:
+ CVaapiProxy(int fd) : m_fd(fd) {};
+ virtual ~CVaapiProxy() = default;
+ VADisplay GetVADisplay() override;
+ void *GetEGLDisplay() override { return eglDisplay; };
+
+ VADisplay vaDpy;
+ void *eglDisplay;
+
+private:
+ int m_fd{-1};
+};
+
+VADisplay CVaapiProxy::GetVADisplay()
+{
+ return vaGetDisplayDRM(m_fd);
+}
+
+CVaapiProxy* VaapiProxyCreate(int fd)
+{
+ return new CVaapiProxy(fd);
+}
+
+void VaapiProxyDelete(CVaapiProxy *proxy)
+{
+ delete proxy;
+}
+
+void VaapiProxyConfig(CVaapiProxy *proxy, void *eglDpy)
+{
+ proxy->vaDpy = proxy->GetVADisplay();
+ 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)
+{
+ CRendererVAAPIGL::Register(winSystem, winSystem->vaDpy, winSystem->eglDisplay, general,
+ deepColor);
+}
+#endif
+
+#if defined(HAS_GLES)
+void VAAPIRegisterRenderGLES(CVaapiProxy* winSystem, bool& general, bool& deepColor)
+{
+ CRendererVAAPIGLES::Register(winSystem, winSystem->vaDpy, winSystem->eglDisplay, general,
+ deepColor);
+}
+#endif
+}
+}
+}
+
+#else
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace GBM
+{
+
+class CVaapiProxy
+{
+};
+
+CVaapiProxy* VaapiProxyCreate(int fd)
+{
+ return nullptr;
+}
+
+void VaapiProxyDelete(CVaapiProxy *proxy)
+{
+}
+
+void VaapiProxyConfig(CVaapiProxy *proxy, 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
+}
+}
+}
+
+#endif
diff --git a/xbmc/windowing/gbm/OptionalsReg.h b/xbmc/windowing/gbm/OptionalsReg.h
new file mode 100644
index 0000000..ee459f6
--- /dev/null
+++ b/xbmc/windowing/gbm/OptionalsReg.h
@@ -0,0 +1,36 @@
+/*
+ * 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
+
+
+//-----------------------------------------------------------------------------
+// VAAPI
+//-----------------------------------------------------------------------------
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace GBM
+{
+class CVaapiProxy;
+
+CVaapiProxy* VaapiProxyCreate(int fd);
+void VaapiProxyDelete(CVaapiProxy *proxy);
+void VaapiProxyConfig(CVaapiProxy *proxy, 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
+}
+}
+}
diff --git a/xbmc/windowing/gbm/VideoLayerBridge.h b/xbmc/windowing/gbm/VideoLayerBridge.h
new file mode 100644
index 0000000..7070567
--- /dev/null
+++ b/xbmc/windowing/gbm/VideoLayerBridge.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 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
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace GBM
+{
+
+class CVideoLayerBridge
+{
+public:
+ virtual ~CVideoLayerBridge() = default;
+ virtual void Disable() {}
+};
+
+}
+}
+}
diff --git a/xbmc/windowing/gbm/VideoSyncGbm.cpp b/xbmc/windowing/gbm/VideoSyncGbm.cpp
new file mode 100644
index 0000000..d113c90
--- /dev/null
+++ b/xbmc/windowing/gbm/VideoSyncGbm.cpp
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2005-2021 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 "VideoSyncGbm.h"
+
+#include "ServiceBroker.h"
+#include "threads/Thread.h"
+#include "utils/TimeUtils.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+#include "windowing/WinSystem.h"
+#include "windowing/gbm/WinSystemGbm.h"
+#include "xf86drm.h"
+#include "xf86drmMode.h"
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <unistd.h>
+
+CVideoSyncGbm::CVideoSyncGbm(void* clock)
+ : CVideoSync(clock), m_winSystem(CServiceBroker::GetWinSystem())
+{
+ if (!m_winSystem)
+ throw std::runtime_error("window system not available");
+}
+
+bool CVideoSyncGbm::Setup(PUPDATECLOCK func)
+{
+ UpdateClock = func;
+ m_abort = false;
+ m_winSystem->Register(this);
+ CLog::Log(LOGDEBUG, "CVideoSyncGbm::{} setting up", __FUNCTION__);
+
+ auto winSystemGbm = dynamic_cast<KODI::WINDOWING::GBM::CWinSystemGbm*>(m_winSystem);
+ if (!winSystemGbm)
+ {
+ CLog::Log(LOGWARNING, "CVideoSyncGbm::{}: failed to get winSystem", __FUNCTION__);
+ return false;
+ }
+
+ auto drm = winSystemGbm->GetDrm();
+ if (!drm)
+ {
+ CLog::Log(LOGWARNING, "CVideoSyncGbm::{}: failed to get drm", __FUNCTION__);
+ return false;
+ }
+
+ auto crtc = drm->GetCrtc();
+ if (!crtc)
+ {
+ CLog::Log(LOGWARNING, "CVideoSyncGbm::{}: failed to get crtc", __FUNCTION__);
+ return false;
+ }
+
+ uint64_t ns = 0;
+ m_crtcId = crtc->GetCrtcId();
+ m_fd = drm->GetFileDescriptor();
+ int s = drmCrtcGetSequence(m_fd, m_crtcId, &m_sequence, &ns);
+ m_offset = CurrentHostCounter() - ns;
+ if (s != 0)
+ {
+ CLog::Log(LOGWARNING, "CVideoSyncGbm::{}: drmCrtcGetSequence failed ({})", __FUNCTION__, s);
+ return false;
+ }
+
+ CLog::Log(LOGINFO, "CVideoSyncGbm::{}: opened (fd:{} crtc:{} seq:{} ns:{}:{})", __FUNCTION__,
+ m_fd, m_crtcId, m_sequence, ns, m_offset + ns);
+ return true;
+}
+
+void CVideoSyncGbm::Run(CEvent& stopEvent)
+{
+ /* This shouldn't be very busy and timing is important so increase priority */
+ CThread::GetCurrentThread()->SetPriority(ThreadPriority::ABOVE_NORMAL);
+
+ if (m_fd < 0)
+ {
+ CLog::Log(LOGWARNING, "CVideoSyncGbm::{}: failed to open device ({})", __FUNCTION__, m_fd);
+ return;
+ }
+ CLog::Log(LOGDEBUG, "CVideoSyncGbm::{}: started {}", __FUNCTION__, m_fd);
+
+ while (!stopEvent.Signaled() && !m_abort)
+ {
+ uint64_t sequence = 0, ns = 0;
+ usleep(1000);
+ int s = drmCrtcGetSequence(m_fd, m_crtcId, &sequence, &ns);
+ if (s != 0)
+ {
+ CLog::Log(LOGWARNING, "CVideoSyncGbm::{}: drmCrtcGetSequence failed ({})", __FUNCTION__, s);
+ break;
+ }
+
+ if (sequence == m_sequence)
+ continue;
+
+ UpdateClock(sequence - m_sequence, m_offset + ns, m_refClock);
+ m_sequence = sequence;
+ }
+}
+
+void CVideoSyncGbm::Cleanup()
+{
+ CLog::Log(LOGDEBUG, "CVideoSyncGbm::{}: cleaning up", __FUNCTION__);
+ m_winSystem->Unregister(this);
+}
+
+float CVideoSyncGbm::GetFps()
+{
+ m_fps = m_winSystem->GetGfxContext().GetFPS();
+ CLog::Log(LOGDEBUG, "CVideoSyncGbm::{}: fps:{}", __FUNCTION__, m_fps);
+ return m_fps;
+}
+
+void CVideoSyncGbm::OnResetDisplay()
+{
+ m_abort = true;
+}
+
+void CVideoSyncGbm::RefreshChanged()
+{
+ if (m_fps != m_winSystem->GetGfxContext().GetFPS())
+ m_abort = true;
+}
diff --git a/xbmc/windowing/gbm/VideoSyncGbm.h b/xbmc/windowing/gbm/VideoSyncGbm.h
new file mode 100644
index 0000000..610d988
--- /dev/null
+++ b/xbmc/windowing/gbm/VideoSyncGbm.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2005-2021 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 <atomic>
+
+class CWinSystemBase;
+
+class CVideoSyncGbm : public CVideoSync, IDispResource
+{
+public:
+ explicit CVideoSyncGbm(void* clock);
+ CVideoSyncGbm() = delete;
+ ~CVideoSyncGbm() override = default;
+
+ // CVideoSync overrides
+ bool Setup(PUPDATECLOCK func) override;
+ void Run(CEvent& stopEvent) override;
+ void Cleanup() override;
+ float GetFps() override;
+ void RefreshChanged() override;
+
+ // IDispResource overrides
+ void OnResetDisplay() override;
+
+private:
+ int m_fd = -1;
+ uint32_t m_crtcId = 0;
+ uint64_t m_sequence = 0;
+ uint64_t m_offset = 0;
+ std::atomic<bool> m_abort{false};
+
+ CWinSystemBase* m_winSystem;
+};
diff --git a/xbmc/windowing/gbm/WinSystemGbm.cpp b/xbmc/windowing/gbm/WinSystemGbm.cpp
new file mode 100644
index 0000000..4fd2da4
--- /dev/null
+++ b/xbmc/windowing/gbm/WinSystemGbm.cpp
@@ -0,0 +1,445 @@
+/*
+ * 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 "WinSystemGbm.h"
+
+#include "GBMDPMSSupport.h"
+#include "OptionalsReg.h"
+#include "ServiceBroker.h"
+#include "VideoSyncGbm.h"
+#include "cores/VideoPlayer/Buffers/VideoBufferDRMPRIME.h"
+#include "drm/DRMAtomic.h"
+#include "drm/DRMLegacy.h"
+#include "drm/OffScreenModeSetting.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/log.h"
+#include "windowing/GraphicContext.h"
+
+#include <mutex>
+#include <string.h>
+
+#ifndef HAVE_HDR_OUTPUT_METADATA
+// HDR structs is copied from linux include/linux/hdmi.h
+struct hdr_metadata_infoframe
+{
+ uint8_t eotf;
+ uint8_t metadata_type;
+ struct
+ {
+ uint16_t x, y;
+ } display_primaries[3];
+ struct
+ {
+ uint16_t x, y;
+ } white_point;
+ uint16_t max_display_mastering_luminance;
+ uint16_t min_display_mastering_luminance;
+ uint16_t max_cll;
+ uint16_t max_fall;
+};
+struct hdr_output_metadata
+{
+ uint32_t metadata_type;
+ union
+ {
+ struct hdr_metadata_infoframe hdmi_metadata_type1;
+ };
+};
+#endif
+
+using namespace KODI::WINDOWING::GBM;
+
+using namespace std::chrono_literals;
+
+CWinSystemGbm::CWinSystemGbm() :
+ m_DRM(nullptr),
+ m_GBM(new CGBMUtils),
+ m_libinput(new CLibInputHandler)
+{
+ m_dpms = std::make_shared<CGBMDPMSSupport>();
+ m_libinput->Start();
+}
+
+bool CWinSystemGbm::InitWindowSystem()
+{
+ const char* x11 = getenv("DISPLAY");
+ const char* wayland = getenv("WAYLAND_DISPLAY");
+ if (x11 || wayland)
+ {
+ CLog::Log(LOGDEBUG, "CWinSystemGbm::{} - not allowed to run GBM under a window manager",
+ __FUNCTION__);
+ return false;
+ }
+
+ m_DRM = std::make_shared<CDRMAtomic>();
+
+ if (!m_DRM->InitDrm())
+ {
+ CLog::Log(LOGERROR, "CWinSystemGbm::{} - failed to initialize Atomic DRM", __FUNCTION__);
+ m_DRM.reset();
+
+ m_DRM = std::make_shared<CDRMLegacy>();
+
+ if (!m_DRM->InitDrm())
+ {
+ CLog::Log(LOGERROR, "CWinSystemGbm::{} - failed to initialize Legacy DRM", __FUNCTION__);
+ m_DRM.reset();
+
+ m_DRM = std::make_shared<COffScreenModeSetting>();
+ if (!m_DRM->InitDrm())
+ {
+ CLog::Log(LOGERROR, "CWinSystemGbm::{} - failed to initialize off screen DRM",
+ __FUNCTION__);
+ m_DRM.reset();
+ return false;
+ }
+ }
+ }
+
+ if (!m_GBM->CreateDevice(m_DRM->GetFileDescriptor()))
+ {
+ m_GBM.reset();
+ return false;
+ }
+
+ auto settingsComponent = CServiceBroker::GetSettingsComponent();
+ if (!settingsComponent)
+ return false;
+
+ auto settings = settingsComponent->GetSettings();
+ if (!settings)
+ return false;
+
+ auto setting = settings->GetSetting(CSettings::SETTING_VIDEOSCREEN_LIMITEDRANGE);
+ if (setting)
+ setting->SetVisible(true);
+
+ setting = settings->GetSetting("videoscreen.limitguisize");
+ if (setting)
+ setting->SetVisible(true);
+
+ CLog::Log(LOGDEBUG, "CWinSystemGbm::{} - initialized DRM", __FUNCTION__);
+ return CWinSystemBase::InitWindowSystem();
+}
+
+bool CWinSystemGbm::DestroyWindowSystem()
+{
+ CLog::Log(LOGDEBUG, "CWinSystemGbm::{} - deinitialized DRM", __FUNCTION__);
+
+ m_libinput.reset();
+
+ return true;
+}
+
+void CWinSystemGbm::UpdateResolutions()
+{
+ RESOLUTION_INFO current = m_DRM->GetCurrentMode();
+
+ auto resolutions = m_DRM->GetModes();
+ if (resolutions.empty())
+ {
+ CLog::Log(LOGWARNING, "CWinSystemGbm::{} - Failed to get resolutions", __FUNCTION__);
+ }
+ else
+ {
+ CDisplaySettings::GetInstance().ClearCustomResolutions();
+
+ for (auto &res : resolutions)
+ {
+ CServiceBroker::GetWinSystem()->GetGfxContext().ResetOverscan(res);
+ CDisplaySettings::GetInstance().AddResolutionInfo(res);
+
+ if (current.iScreenWidth == res.iScreenWidth &&
+ current.iScreenHeight == res.iScreenHeight &&
+ current.iWidth == res.iWidth &&
+ current.iHeight == res.iHeight &&
+ current.fRefreshRate == res.fRefreshRate &&
+ current.dwFlags == res.dwFlags)
+ {
+ CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP) = res;
+ }
+
+ CLog::Log(LOGINFO, "Found resolution {}x{} with {}x{}{} @ {:f} Hz", res.iWidth, res.iHeight,
+ res.iScreenWidth, res.iScreenHeight,
+ res.dwFlags & D3DPRESENTFLAG_INTERLACED ? "i" : "", res.fRefreshRate);
+ }
+ }
+
+ CDisplaySettings::GetInstance().ApplyCalibrations();
+}
+
+bool CWinSystemGbm::ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop)
+{
+ return true;
+}
+
+bool CWinSystemGbm::SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays)
+{
+ // Notify other subsystems that we will change resolution
+ OnLostDevice();
+
+ if(!m_DRM->SetMode(res))
+ {
+ CLog::Log(LOGERROR, "CWinSystemGbm::{} - failed to set DRM mode", __FUNCTION__);
+ return false;
+ }
+
+ struct gbm_bo *bo = nullptr;
+
+ if (!std::dynamic_pointer_cast<CDRMAtomic>(m_DRM))
+ {
+ bo = m_GBM->GetDevice()->GetSurface()->LockFrontBuffer()->Get();
+ }
+
+ auto result = m_DRM->SetVideoMode(res, bo);
+
+ auto delay =
+ std::chrono::milliseconds(CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
+ "videoscreen.delayrefreshchange") *
+ 100);
+ if (delay > 0ms)
+ m_dispResetTimer.Set(delay);
+
+ return result;
+}
+
+bool CWinSystemGbm::DisplayHardwareScalingEnabled()
+{
+ auto drmAtomic = std::dynamic_pointer_cast<CDRMAtomic>(m_DRM);
+ if (drmAtomic && drmAtomic->DisplayHardwareScalingEnabled())
+ return true;
+
+ return false;
+}
+
+void CWinSystemGbm::UpdateDisplayHardwareScaling(const RESOLUTION_INFO& resInfo)
+{
+ if (!DisplayHardwareScalingEnabled())
+ return;
+
+ //! @todo The PR that made the res struct constant was abandoned due to drama.
+ // It should be const-corrected and changed here.
+ RESOLUTION_INFO& resMutable = const_cast<RESOLUTION_INFO&>(resInfo);
+
+ SetFullScreen(true, resMutable, false);
+}
+
+void CWinSystemGbm::FlipPage(bool rendered, bool videoLayer)
+{
+ if (m_videoLayerBridge && !videoLayer)
+ {
+ // disable video plane when video layer no longer is active
+ m_videoLayerBridge->Disable();
+ }
+
+ struct gbm_bo *bo = nullptr;
+
+ if (rendered)
+ {
+ bo = m_GBM->GetDevice()->GetSurface()->LockFrontBuffer()->Get();
+ }
+
+ m_DRM->FlipPage(bo, rendered, videoLayer);
+
+ if (m_videoLayerBridge && !videoLayer)
+ {
+ // delete video layer bridge when video layer no longer is active
+ m_videoLayerBridge.reset();
+ }
+}
+
+bool CWinSystemGbm::UseLimitedColor()
+{
+ return CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOSCREEN_LIMITEDRANGE);
+}
+
+bool CWinSystemGbm::Hide()
+{
+ bool ret = m_DRM->SetActive(false);
+ FlipPage(false, false);
+ return ret;
+}
+
+bool CWinSystemGbm::Show(bool raise)
+{
+ bool ret = m_DRM->SetActive(true);
+ FlipPage(false, false);
+ return ret;
+}
+
+void CWinSystemGbm::Register(IDispResource *resource)
+{
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ m_resources.push_back(resource);
+}
+
+void CWinSystemGbm::Unregister(IDispResource *resource)
+{
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ std::vector<IDispResource*>::iterator i = find(m_resources.begin(), m_resources.end(), resource);
+ if (i != m_resources.end())
+ {
+ m_resources.erase(i);
+ }
+}
+
+void CWinSystemGbm::OnLostDevice()
+{
+ CLog::Log(LOGDEBUG, "{} - notify display change event", __FUNCTION__);
+ m_dispReset = true;
+
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ for (auto resource : m_resources)
+ resource->OnLostDisplay();
+}
+
+std::unique_ptr<CVideoSync> CWinSystemGbm::GetVideoSync(void* clock)
+{
+ return std::make_unique<CVideoSyncGbm>(clock);
+}
+
+std::vector<std::string> CWinSystemGbm::GetConnectedOutputs()
+{
+ return m_DRM->GetConnectedConnectorNames();
+}
+
+bool CWinSystemGbm::SetHDR(const VideoPicture* videoPicture)
+{
+ auto settingsComponent = CServiceBroker::GetSettingsComponent();
+ if (!settingsComponent)
+ return false;
+
+ auto settings = settingsComponent->GetSettings();
+ if (!settings)
+ return false;
+
+ if (!settings->GetBool(SETTING_WINSYSTEM_IS_HDR_DISPLAY))
+ return false;
+
+ auto drm = std::dynamic_pointer_cast<CDRMAtomic>(m_DRM);
+ if (!drm)
+ return false;
+
+ if (!videoPicture)
+ {
+ auto connector = drm->GetConnector();
+ if (connector->SupportsProperty("HDR_OUTPUT_METADATA"))
+ {
+ drm->AddProperty(connector, "HDR_OUTPUT_METADATA", 0);
+ drm->SetActive(true);
+
+ if (m_hdr_blob_id)
+ drmModeDestroyPropertyBlob(drm->GetFileDescriptor(), m_hdr_blob_id);
+ m_hdr_blob_id = 0;
+ }
+
+ return true;
+ }
+
+ auto connector = drm->GetConnector();
+ if (connector->SupportsProperty("HDR_OUTPUT_METADATA"))
+ {
+ hdr_output_metadata hdr_metadata = {};
+
+ hdr_metadata.metadata_type = DRMPRIME::HDMI_STATIC_METADATA_TYPE1;
+ hdr_metadata.hdmi_metadata_type1.eotf = DRMPRIME::GetEOTF(*videoPicture);
+ hdr_metadata.hdmi_metadata_type1.metadata_type = DRMPRIME::HDMI_STATIC_METADATA_TYPE1;
+
+ if (m_hdr_blob_id)
+ drmModeDestroyPropertyBlob(drm->GetFileDescriptor(), m_hdr_blob_id);
+ m_hdr_blob_id = 0;
+
+ if (hdr_metadata.hdmi_metadata_type1.eotf)
+ {
+ const AVMasteringDisplayMetadata* mdmd = DRMPRIME::GetMasteringDisplayMetadata(*videoPicture);
+ if (mdmd && mdmd->has_primaries)
+ {
+ // Convert to unsigned 16-bit values in units of 0.00002,
+ // where 0x0000 represents zero and 0xC350 represents 1.0000
+ for (int i = 0; i < 3; i++)
+ {
+ hdr_metadata.hdmi_metadata_type1.display_primaries[i].x =
+ std::round(av_q2d(mdmd->display_primaries[i][0]) * 50000.0);
+ hdr_metadata.hdmi_metadata_type1.display_primaries[i].y =
+ std::round(av_q2d(mdmd->display_primaries[i][1]) * 50000.0);
+
+ CLog::Log(LOGDEBUG, LOGVIDEO, "CWinSystemGbm::{} - display_primaries[{}].x: {}",
+ __FUNCTION__, i, hdr_metadata.hdmi_metadata_type1.display_primaries[i].x);
+ CLog::Log(LOGDEBUG, LOGVIDEO, "CWinSystemGbm::{} - display_primaries[{}].y: {}",
+ __FUNCTION__, i, hdr_metadata.hdmi_metadata_type1.display_primaries[i].y);
+ }
+ hdr_metadata.hdmi_metadata_type1.white_point.x =
+ std::round(av_q2d(mdmd->white_point[0]) * 50000.0);
+ hdr_metadata.hdmi_metadata_type1.white_point.y =
+ std::round(av_q2d(mdmd->white_point[1]) * 50000.0);
+
+ CLog::Log(LOGDEBUG, LOGVIDEO, "CWinSystemGbm::{} - white_point.x: {}", __FUNCTION__,
+ hdr_metadata.hdmi_metadata_type1.white_point.x);
+ CLog::Log(LOGDEBUG, LOGVIDEO, "CWinSystemGbm::{} - white_point.y: {}", __FUNCTION__,
+ hdr_metadata.hdmi_metadata_type1.white_point.y);
+ }
+ if (mdmd && mdmd->has_luminance)
+ {
+ // Convert to unsigned 16-bit value in units of 1 cd/m2,
+ // where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2
+ hdr_metadata.hdmi_metadata_type1.max_display_mastering_luminance =
+ std::round(av_q2d(mdmd->max_luminance));
+
+ // Convert to unsigned 16-bit value in units of 0.0001 cd/m2,
+ // where 0x0001 represents 0.0001 cd/m2 and 0xFFFF represents 6.5535 cd/m2
+ hdr_metadata.hdmi_metadata_type1.min_display_mastering_luminance =
+ std::round(av_q2d(mdmd->min_luminance) * 10000.0);
+
+ CLog::Log(LOGDEBUG, LOGVIDEO, "CWinSystemGbm::{} - max_display_mastering_luminance: {}",
+ __FUNCTION__, hdr_metadata.hdmi_metadata_type1.max_display_mastering_luminance);
+ CLog::Log(LOGDEBUG, LOGVIDEO, "CWinSystemGbm::{} - min_display_mastering_luminance: {}",
+ __FUNCTION__, hdr_metadata.hdmi_metadata_type1.min_display_mastering_luminance);
+ }
+
+ const AVContentLightMetadata* clmd = DRMPRIME::GetContentLightMetadata(*videoPicture);
+ if (clmd)
+ {
+ hdr_metadata.hdmi_metadata_type1.max_cll = clmd->MaxCLL;
+ hdr_metadata.hdmi_metadata_type1.max_fall = clmd->MaxFALL;
+
+ CLog::Log(LOGDEBUG, LOGVIDEO, "CWinSystemGbm::{} - max_cll: {}", __FUNCTION__,
+ hdr_metadata.hdmi_metadata_type1.max_cll);
+ CLog::Log(LOGDEBUG, LOGVIDEO, "CWinSystemGbm::{} - max_fall: {}", __FUNCTION__,
+ hdr_metadata.hdmi_metadata_type1.max_fall);
+ }
+
+ drmModeCreatePropertyBlob(drm->GetFileDescriptor(), &hdr_metadata, sizeof(hdr_metadata),
+ &m_hdr_blob_id);
+ }
+
+ drm->AddProperty(connector, "HDR_OUTPUT_METADATA", m_hdr_blob_id);
+ drm->SetActive(true);
+ }
+
+ return true;
+}
+
+bool CWinSystemGbm::IsHDRDisplay()
+{
+ auto drm = std::dynamic_pointer_cast<CDRMAtomic>(m_DRM);
+ if (!drm)
+ return false;
+
+ auto connector = drm->GetConnector();
+ if (!connector)
+ return false;
+
+ //! @todo: improve detection (edid?)
+ // we have no way to know if the display is actually HDR capable and we blindly set the HDR metadata
+ return connector->SupportsProperty("HDR_OUTPUT_METADATA");
+}
diff --git a/xbmc/windowing/gbm/WinSystemGbm.h b/xbmc/windowing/gbm/WinSystemGbm.h
new file mode 100644
index 0000000..b313d33
--- /dev/null
+++ b/xbmc/windowing/gbm/WinSystemGbm.h
@@ -0,0 +1,96 @@
+/*
+ * 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 "VideoLayerBridge.h"
+#include "drm/DRMUtils.h"
+#include "threads/CriticalSection.h"
+#include "threads/SystemClock.h"
+#include "windowing/WinSystem.h"
+
+#include "platform/linux/input/LibInputHandler.h"
+
+#include <utility>
+
+#include <gbm.h>
+
+class IDispResource;
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace GBM
+{
+
+class CWinSystemGbm : public CWinSystemBase
+{
+public:
+ CWinSystemGbm();
+ ~CWinSystemGbm() override = default;
+
+ const std::string GetName() override { return "gbm"; }
+
+ bool InitWindowSystem() override;
+ bool DestroyWindowSystem() override;
+
+ bool ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop) override;
+ bool SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) override;
+ bool DisplayHardwareScalingEnabled() override;
+ void UpdateDisplayHardwareScaling(const RESOLUTION_INFO& resInfo) override;
+
+ void FlipPage(bool rendered, bool videoLayer);
+
+ bool CanDoWindowed() override { return false; }
+ void UpdateResolutions() override;
+
+ bool UseLimitedColor() override;
+
+ bool Hide() override;
+ bool Show(bool raise = true) override;
+ void Register(IDispResource* resource) override;
+ void Unregister(IDispResource* resource) override;
+
+ bool SetHDR(const VideoPicture* videoPicture) override;
+ bool IsHDRDisplay() override;
+
+ std::shared_ptr<CVideoLayerBridge> GetVideoLayerBridge() const { return m_videoLayerBridge; }
+ void RegisterVideoLayerBridge(std::shared_ptr<CVideoLayerBridge> bridge)
+ {
+ m_videoLayerBridge = std::move(bridge);
+ };
+
+ CGBMUtils::CGBMDevice* GetGBMDevice() const { return m_GBM->GetDevice(); }
+ std::shared_ptr<CDRMUtils> GetDrm() const { return m_DRM; }
+
+ std::vector<std::string> GetConnectedOutputs() override;
+
+protected:
+ void OnLostDevice();
+
+ std::unique_ptr<CVideoSync> GetVideoSync(void* clock) override;
+
+ std::shared_ptr<CDRMUtils> m_DRM;
+ std::unique_ptr<CGBMUtils> m_GBM;
+ std::shared_ptr<CVideoLayerBridge> m_videoLayerBridge;
+
+ CCriticalSection m_resourceSection;
+ std::vector<IDispResource*> m_resources;
+
+ bool m_dispReset = false;
+ XbmcThreads::EndTime<> m_dispResetTimer;
+ std::unique_ptr<CLibInputHandler> m_libinput;
+
+private:
+ uint32_t m_hdr_blob_id = 0;
+};
+
+}
+}
+}
diff --git a/xbmc/windowing/gbm/WinSystemGbmEGLContext.cpp b/xbmc/windowing/gbm/WinSystemGbmEGLContext.cpp
new file mode 100644
index 0000000..83a5941
--- /dev/null
+++ b/xbmc/windowing/gbm/WinSystemGbmEGLContext.cpp
@@ -0,0 +1,141 @@
+/*
+ * 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 "WinSystemGbmEGLContext.h"
+
+#include "OptionalsReg.h"
+#include "cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.h"
+#include "cores/VideoPlayer/VideoRenderers/RenderFactory.h"
+#include "utils/log.h"
+
+using namespace KODI::WINDOWING::GBM;
+using namespace KODI::WINDOWING::LINUX;
+
+bool CWinSystemGbmEGLContext::InitWindowSystemEGL(EGLint renderableType, EGLint apiType)
+{
+ if (!CWinSystemGbm::InitWindowSystem())
+ {
+ return false;
+ }
+
+ if (!m_eglContext.CreatePlatformDisplay(m_GBM->GetDevice()->Get(), m_GBM->GetDevice()->Get()))
+ {
+ return false;
+ }
+
+ if (!m_eglContext.InitializeDisplay(apiType))
+ {
+ return false;
+ }
+
+ auto plane = m_DRM->GetGuiPlane();
+ uint32_t visualId = plane != nullptr ? plane->GetFormat() : DRM_FORMAT_XRGB2101010;
+
+ // prefer alpha visual id, fallback to non-alpha visual id
+ if (!m_eglContext.ChooseConfig(renderableType, CDRMUtils::FourCCWithAlpha(visualId)) &&
+ !m_eglContext.ChooseConfig(renderableType, CDRMUtils::FourCCWithoutAlpha(visualId)))
+ {
+ // fallback to 8bit format if no EGL config was found for 10bit
+ if (plane)
+ plane->SetFormat(DRM_FORMAT_XRGB8888);
+
+ visualId = plane != nullptr ? plane->GetFormat() : DRM_FORMAT_XRGB8888;
+
+ if (!m_eglContext.ChooseConfig(renderableType, CDRMUtils::FourCCWithAlpha(visualId)) &&
+ !m_eglContext.ChooseConfig(renderableType, CDRMUtils::FourCCWithoutAlpha(visualId)))
+ {
+ return false;
+ }
+ }
+
+ if (!CreateContext())
+ {
+ return false;
+ }
+
+ return true;
+}
+
+bool CWinSystemGbmEGLContext::CreateNewWindow(const std::string& name,
+ bool fullScreen,
+ RESOLUTION_INFO& res)
+{
+ //Notify other subsystems that we change resolution
+ OnLostDevice();
+
+ if (!DestroyWindow())
+ {
+ return false;
+ }
+
+ if (!m_DRM->SetMode(res))
+ {
+ CLog::Log(LOGERROR, "CWinSystemGbmEGLContext::{} - failed to set DRM mode", __FUNCTION__);
+ return false;
+ }
+
+ uint32_t format = m_eglContext.GetConfigAttrib(EGL_NATIVE_VISUAL_ID);
+
+ std::vector<uint64_t> modifiers;
+
+ auto plane = m_DRM->GetGuiPlane();
+ if (plane)
+ modifiers = plane->GetModifiersForFormat(format);
+
+ if (!m_GBM->GetDevice()->CreateSurface(res.iWidth, res.iHeight, format, modifiers.data(),
+ modifiers.size()))
+ {
+ CLog::Log(LOGERROR, "CWinSystemGbmEGLContext::{} - failed to initialize GBM", __FUNCTION__);
+ return false;
+ }
+
+ // This check + the reinterpret cast is for security reason, if the user has outdated platform header files which often is the case
+ static_assert(sizeof(EGLNativeWindowType) == sizeof(gbm_surface*), "Declaration specifier differs in size");
+
+ if (!m_eglContext.CreatePlatformSurface(
+ m_GBM->GetDevice()->GetSurface()->Get(),
+ reinterpret_cast<khronos_uintptr_t>(m_GBM->GetDevice()->GetSurface()->Get())))
+ {
+ return false;
+ }
+
+ if (!m_eglContext.BindContext())
+ {
+ return false;
+ }
+
+ m_bFullScreen = fullScreen;
+ m_nWidth = res.iWidth;
+ m_nHeight = res.iHeight;
+ m_fRefreshRate = res.fRefreshRate;
+
+ CLog::Log(LOGDEBUG, "CWinSystemGbmEGLContext::{} - initialized GBM", __FUNCTION__);
+ return true;
+}
+
+bool CWinSystemGbmEGLContext::DestroyWindow()
+{
+ m_eglContext.DestroySurface();
+
+ CLog::Log(LOGDEBUG, "CWinSystemGbmEGLContext::{} - deinitialized GBM", __FUNCTION__);
+ return true;
+}
+
+bool CWinSystemGbmEGLContext::DestroyWindowSystem()
+{
+ CDVDFactoryCodec::ClearHWAccels();
+ VIDEOPLAYER::CRendererFactory::ClearRenderer();
+ m_eglContext.Destroy();
+
+ return CWinSystemGbm::DestroyWindowSystem();
+}
+
+void CWinSystemGbmEGLContext::delete_CVaapiProxy::operator()(CVaapiProxy *p) const
+{
+ VaapiProxyDelete(p);
+}
diff --git a/xbmc/windowing/gbm/WinSystemGbmEGLContext.h b/xbmc/windowing/gbm/WinSystemGbmEGLContext.h
new file mode 100644
index 0000000..84f863d
--- /dev/null
+++ b/xbmc/windowing/gbm/WinSystemGbmEGLContext.h
@@ -0,0 +1,58 @@
+/*
+ * 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 "WinSystemGbm.h"
+#include "utils/EGLUtils.h"
+#include "windowing/linux/WinSystemEGL.h"
+
+#include <memory>
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace GBM
+{
+
+class CVaapiProxy;
+
+class CWinSystemGbmEGLContext : public KODI::WINDOWING::LINUX::CWinSystemEGL, public CWinSystemGbm
+{
+public:
+ ~CWinSystemGbmEGLContext() override = default;
+
+ bool DestroyWindowSystem() override;
+ bool CreateNewWindow(const std::string& name,
+ bool fullScreen,
+ RESOLUTION_INFO& res) override;
+ bool DestroyWindow() override;
+
+protected:
+ CWinSystemGbmEGLContext(EGLenum platform, std::string const& platformExtension)
+ : CWinSystemEGL{platform, platformExtension}
+ {}
+
+ /**
+ * Inheriting classes should override InitWindowSystem() without parameters
+ * and call this function there with appropriate parameters
+ */
+ bool InitWindowSystemEGL(EGLint renderableType, EGLint apiType);
+ virtual bool CreateContext() = 0;
+
+ struct delete_CVaapiProxy
+ {
+ void operator()(CVaapiProxy *p) const;
+ };
+ std::unique_ptr<CVaapiProxy, delete_CVaapiProxy> m_vaapiProxy;
+};
+
+}
+}
+}
diff --git a/xbmc/windowing/gbm/WinSystemGbmGLContext.cpp b/xbmc/windowing/gbm/WinSystemGbmGLContext.cpp
new file mode 100644
index 0000000..e4ff49c
--- /dev/null
+++ b/xbmc/windowing/gbm/WinSystemGbmGLContext.cpp
@@ -0,0 +1,174 @@
+/*
+ * 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 "WinSystemGbmGLContext.h"
+
+#include "OptionalsReg.h"
+#include "cores/RetroPlayer/process/gbm/RPProcessInfoGbm.h"
+#include "cores/RetroPlayer/rendering/VideoRenderers/RPRendererDMA.h"
+#include "cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGL.h"
+#include "cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.h"
+#include "cores/VideoPlayer/VideoRenderers/LinuxRendererGL.h"
+#include "cores/VideoPlayer/VideoRenderers/RenderFactory.h"
+#include "rendering/gl/ScreenshotSurfaceGL.h"
+#include "utils/BufferObjectFactory.h"
+#include "utils/DMAHeapBufferObject.h"
+#include "utils/DumbBufferObject.h"
+#include "utils/GBMBufferObject.h"
+#include "utils/UDMABufferObject.h"
+#include "utils/XTimeUtils.h"
+#include "utils/log.h"
+#include "windowing/WindowSystemFactory.h"
+
+#include <mutex>
+
+#include <EGL/eglext.h>
+
+using namespace KODI::WINDOWING::GBM;
+
+using namespace std::chrono_literals;
+
+CWinSystemGbmGLContext::CWinSystemGbmGLContext()
+: CWinSystemGbmEGLContext(EGL_PLATFORM_GBM_MESA, "EGL_MESA_platform_gbm")
+{}
+
+void CWinSystemGbmGLContext::Register()
+{
+ CWindowSystemFactory::RegisterWindowSystem(CreateWinSystem, "gbm");
+}
+
+std::unique_ptr<CWinSystemBase> CWinSystemGbmGLContext::CreateWinSystem()
+{
+ return std::make_unique<CWinSystemGbmGLContext>();
+}
+
+bool CWinSystemGbmGLContext::InitWindowSystem()
+{
+ VIDEOPLAYER::CRendererFactory::ClearRenderer();
+ CDVDFactoryCodec::ClearHWAccels();
+ CLinuxRendererGL::Register();
+ RETRO::CRPProcessInfoGbm::Register();
+ RETRO::CRPProcessInfoGbm::RegisterRendererFactory(new RETRO::CRendererFactoryDMA);
+ RETRO::CRPProcessInfoGbm::RegisterRendererFactory(new RETRO::CRendererFactoryOpenGL);
+
+ if (!CWinSystemGbmEGLContext::InitWindowSystemEGL(EGL_OPENGL_BIT, EGL_OPENGL_API))
+ {
+ return false;
+ }
+
+ bool general, deepColor;
+ m_vaapiProxy.reset(VaapiProxyCreate(m_DRM->GetRenderNodeFileDescriptor()));
+ VaapiProxyConfig(m_vaapiProxy.get(), m_eglContext.GetEGLDisplay());
+ VAAPIRegisterRenderGL(m_vaapiProxy.get(), general, deepColor);
+
+ if (general)
+ {
+ VAAPIRegister(m_vaapiProxy.get(), deepColor);
+ }
+
+ CScreenshotSurfaceGL::Register();
+
+ CBufferObjectFactory::ClearBufferObjects();
+ CDumbBufferObject::Register();
+#if defined(HAS_GBM_BO_MAP)
+ CGBMBufferObject::Register();
+#endif
+#if defined(HAVE_LINUX_MEMFD) && defined(HAVE_LINUX_UDMABUF)
+ CUDMABufferObject::Register();
+#endif
+#if defined(HAVE_LINUX_DMA_HEAP)
+ CDMAHeapBufferObject::Register();
+#endif
+
+ return true;
+}
+
+bool CWinSystemGbmGLContext::SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays)
+{
+ if (res.iWidth != m_nWidth ||
+ res.iHeight != m_nHeight)
+ {
+ CLog::Log(LOGDEBUG, "CWinSystemGbmGLContext::{} - resolution changed, creating a new window",
+ __FUNCTION__);
+ CreateNewWindow("", fullScreen, res);
+ }
+
+ if (!m_eglContext.TrySwapBuffers())
+ {
+ CEGLUtils::Log(LOGERROR, "eglSwapBuffers failed");
+ throw std::runtime_error("eglSwapBuffers failed");
+ }
+
+ CWinSystemGbm::SetFullScreen(fullScreen, res, blankOtherDisplays);
+ CRenderSystemGL::ResetRenderSystem(res.iWidth, res.iHeight);
+
+ return true;
+}
+
+void CWinSystemGbmGLContext::PresentRender(bool rendered, bool videoLayer)
+{
+ if (!m_bRenderCreated)
+ return;
+
+ if (rendered || videoLayer)
+ {
+ if (rendered)
+ {
+ if (!m_eglContext.TrySwapBuffers())
+ {
+ CEGLUtils::Log(LOGERROR, "eglSwapBuffers failed");
+ throw std::runtime_error("eglSwapBuffers failed");
+ }
+ }
+ CWinSystemGbm::FlipPage(rendered, videoLayer);
+
+ if (m_dispReset && m_dispResetTimer.IsTimePast())
+ {
+ CLog::Log(LOGDEBUG, "CWinSystemGbmGLContext::{} - Sending display reset to all clients",
+ __FUNCTION__);
+ m_dispReset = false;
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+
+ for (auto resource : m_resources)
+ resource->OnResetDisplay();
+ }
+ }
+ else
+ {
+ KODI::TIME::Sleep(10ms);
+ }
+}
+
+bool CWinSystemGbmGLContext::CreateContext()
+{
+ const EGLint glMajor = 3;
+ const EGLint glMinor = 2;
+
+ CEGLAttributesVec contextAttribs;
+ contextAttribs.Add({{EGL_CONTEXT_MAJOR_VERSION_KHR, glMajor},
+ {EGL_CONTEXT_MINOR_VERSION_KHR, glMinor},
+ {EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR}});
+
+ if (!m_eglContext.CreateContext(contextAttribs))
+ {
+ CEGLAttributesVec fallbackContextAttribs;
+ fallbackContextAttribs.Add({{EGL_CONTEXT_CLIENT_VERSION, 2}});
+
+ if (!m_eglContext.CreateContext(fallbackContextAttribs))
+ {
+ CLog::Log(LOGERROR, "EGL context creation failed");
+ return false;
+ }
+ else
+ {
+ CLog::Log(LOGWARNING, "Your OpenGL drivers do not support OpenGL {}.{} core profile. Kodi will run in compatibility mode, but performance may suffer.", glMajor, glMinor);
+ }
+ }
+
+ return true;
+}
diff --git a/xbmc/windowing/gbm/WinSystemGbmGLContext.h b/xbmc/windowing/gbm/WinSystemGbmGLContext.h
new file mode 100644
index 0000000..8994ce6
--- /dev/null
+++ b/xbmc/windowing/gbm/WinSystemGbmGLContext.h
@@ -0,0 +1,48 @@
+/*
+ * 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 "WinSystemGbmEGLContext.h"
+#include "rendering/gl/RenderSystemGL.h"
+#include "utils/EGLUtils.h"
+
+#include <memory>
+
+class CVaapiProxy;
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace GBM
+{
+
+class CWinSystemGbmGLContext : public CWinSystemGbmEGLContext, public CRenderSystemGL
+{
+public:
+ CWinSystemGbmGLContext();
+ ~CWinSystemGbmGLContext() override = default;
+
+ static void Register();
+ static std::unique_ptr<CWinSystemBase> CreateWinSystem();
+
+ // Implementation of CWinSystemBase via CWinSystemGbm
+ CRenderSystemBase *GetRenderSystem() override { return this; }
+ bool InitWindowSystem() override;
+ bool SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) override;
+ void PresentRender(bool rendered, bool videoLayer) override;
+protected:
+ void SetVSyncImpl(bool enable) override {}
+ void PresentRenderImpl(bool rendered) override {};
+ bool CreateContext() override;
+};
+
+}
+}
+}
diff --git a/xbmc/windowing/gbm/WinSystemGbmGLESContext.cpp b/xbmc/windowing/gbm/WinSystemGbmGLESContext.cpp
new file mode 100644
index 0000000..0d071c3
--- /dev/null
+++ b/xbmc/windowing/gbm/WinSystemGbmGLESContext.cpp
@@ -0,0 +1,167 @@
+/*
+ * 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 "WinSystemGbmGLESContext.h"
+
+#include "OptionalsReg.h"
+#include "cores/RetroPlayer/process/gbm/RPProcessInfoGbm.h"
+#include "cores/RetroPlayer/rendering/VideoRenderers/RPRendererDMA.h"
+#include "cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGLES.h"
+#include "cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.h"
+#include "cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.h"
+#include "cores/VideoPlayer/Process/gbm/ProcessInfoGBM.h"
+#include "cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIME.h"
+#include "cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIMEGLES.h"
+#include "cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.h"
+#include "cores/VideoPlayer/VideoRenderers/RenderFactory.h"
+#include "rendering/gles/ScreenshotSurfaceGLES.h"
+#include "utils/BufferObjectFactory.h"
+#include "utils/DMAHeapBufferObject.h"
+#include "utils/DumbBufferObject.h"
+#include "utils/GBMBufferObject.h"
+#include "utils/UDMABufferObject.h"
+#include "utils/XTimeUtils.h"
+#include "utils/log.h"
+#include "windowing/WindowSystemFactory.h"
+
+#include <mutex>
+
+#include <gbm.h>
+
+using namespace KODI::WINDOWING::GBM;
+
+using namespace std::chrono_literals;
+
+CWinSystemGbmGLESContext::CWinSystemGbmGLESContext()
+: CWinSystemGbmEGLContext(EGL_PLATFORM_GBM_MESA, "EGL_MESA_platform_gbm")
+{}
+
+void CWinSystemGbmGLESContext::Register()
+{
+ CWindowSystemFactory::RegisterWindowSystem(CreateWinSystem, "gbm");
+}
+
+std::unique_ptr<CWinSystemBase> CWinSystemGbmGLESContext::CreateWinSystem()
+{
+ return std::make_unique<CWinSystemGbmGLESContext>();
+}
+
+bool CWinSystemGbmGLESContext::InitWindowSystem()
+{
+ VIDEOPLAYER::CRendererFactory::ClearRenderer();
+ CDVDFactoryCodec::ClearHWAccels();
+ CLinuxRendererGLES::Register();
+ RETRO::CRPProcessInfoGbm::Register();
+ RETRO::CRPProcessInfoGbm::RegisterRendererFactory(new RETRO::CRendererFactoryDMA);
+ RETRO::CRPProcessInfoGbm::RegisterRendererFactory(new RETRO::CRendererFactoryOpenGLES);
+
+ if (!CWinSystemGbmEGLContext::InitWindowSystemEGL(EGL_OPENGL_ES2_BIT, EGL_OPENGL_ES_API))
+ {
+ return false;
+ }
+
+ bool general, deepColor;
+ m_vaapiProxy.reset(GBM::VaapiProxyCreate(m_DRM->GetRenderNodeFileDescriptor()));
+ GBM::VaapiProxyConfig(m_vaapiProxy.get(), m_eglContext.GetEGLDisplay());
+ GBM::VAAPIRegisterRenderGLES(m_vaapiProxy.get(), general, deepColor);
+
+ if (general)
+ {
+ GBM::VAAPIRegister(m_vaapiProxy.get(), deepColor);
+ }
+
+ CRendererDRMPRIMEGLES::Register();
+ CRendererDRMPRIME::Register();
+ CDVDVideoCodecDRMPRIME::Register();
+ VIDEOPLAYER::CProcessInfoGBM::Register();
+
+ CScreenshotSurfaceGLES::Register();
+
+ CBufferObjectFactory::ClearBufferObjects();
+ CDumbBufferObject::Register();
+#if defined(HAS_GBM_BO_MAP)
+ CGBMBufferObject::Register();
+#endif
+#if defined(HAVE_LINUX_MEMFD) && defined(HAVE_LINUX_UDMABUF)
+ CUDMABufferObject::Register();
+#endif
+#if defined(HAVE_LINUX_DMA_HEAP)
+ CDMAHeapBufferObject::Register();
+#endif
+
+ return true;
+}
+
+bool CWinSystemGbmGLESContext::SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays)
+{
+ if (res.iWidth != m_nWidth ||
+ res.iHeight != m_nHeight)
+ {
+ CLog::Log(LOGDEBUG, "CWinSystemGbmGLESContext::{} - resolution changed, creating a new window",
+ __FUNCTION__);
+ CreateNewWindow("", fullScreen, res);
+ }
+
+ if (!m_eglContext.TrySwapBuffers())
+ {
+ CEGLUtils::Log(LOGERROR, "eglSwapBuffers failed");
+ throw std::runtime_error("eglSwapBuffers failed");
+ }
+
+ CWinSystemGbm::SetFullScreen(fullScreen, res, blankOtherDisplays);
+ CRenderSystemGLES::ResetRenderSystem(res.iWidth, res.iHeight);
+
+ return true;
+}
+
+void CWinSystemGbmGLESContext::PresentRender(bool rendered, bool videoLayer)
+{
+ if (!m_bRenderCreated)
+ return;
+
+ if (rendered || videoLayer)
+ {
+ if (rendered)
+ {
+ if (!m_eglContext.TrySwapBuffers())
+ {
+ CEGLUtils::Log(LOGERROR, "eglSwapBuffers failed");
+ throw std::runtime_error("eglSwapBuffers failed");
+ }
+ }
+ CWinSystemGbm::FlipPage(rendered, videoLayer);
+
+ if (m_dispReset && m_dispResetTimer.IsTimePast())
+ {
+ CLog::Log(LOGDEBUG, "CWinSystemGbmGLESContext::{} - Sending display reset to all clients",
+ __FUNCTION__);
+ m_dispReset = false;
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+
+ for (auto resource : m_resources)
+ resource->OnResetDisplay();
+ }
+ }
+ else
+ {
+ KODI::TIME::Sleep(10ms);
+ }
+}
+
+bool CWinSystemGbmGLESContext::CreateContext()
+{
+ CEGLAttributesVec contextAttribs;
+ contextAttribs.Add({{EGL_CONTEXT_CLIENT_VERSION, 2}});
+
+ if (!m_eglContext.CreateContext(contextAttribs))
+ {
+ CLog::Log(LOGERROR, "EGL context creation failed");
+ return false;
+ }
+ return true;
+}
diff --git a/xbmc/windowing/gbm/WinSystemGbmGLESContext.h b/xbmc/windowing/gbm/WinSystemGbmGLESContext.h
new file mode 100644
index 0000000..8b9de77
--- /dev/null
+++ b/xbmc/windowing/gbm/WinSystemGbmGLESContext.h
@@ -0,0 +1,48 @@
+/*
+ * 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 "WinSystemGbmEGLContext.h"
+#include "rendering/gles/RenderSystemGLES.h"
+#include "utils/EGLUtils.h"
+
+#include <memory>
+
+class CVaapiProxy;
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace GBM
+{
+
+class CWinSystemGbmGLESContext : public CWinSystemGbmEGLContext, public CRenderSystemGLES
+{
+public:
+ CWinSystemGbmGLESContext();
+ ~CWinSystemGbmGLESContext() override = default;
+
+ static void Register();
+ static std::unique_ptr<CWinSystemBase> CreateWinSystem();
+
+ // Implementation of CWinSystemBase via CWinSystemGbm
+ CRenderSystemBase *GetRenderSystem() override { return this; }
+ bool InitWindowSystem() override;
+ bool SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) override;
+ void PresentRender(bool rendered, bool videoLayer) override;
+protected:
+ void SetVSyncImpl(bool enable) override {}
+ void PresentRenderImpl(bool rendered) override {};
+ bool CreateContext() override;
+};
+
+}
+}
+}
diff --git a/xbmc/windowing/gbm/drm/CMakeLists.txt b/xbmc/windowing/gbm/drm/CMakeLists.txt
new file mode 100644
index 0000000..8bd07ea
--- /dev/null
+++ b/xbmc/windowing/gbm/drm/CMakeLists.txt
@@ -0,0 +1,21 @@
+set(SOURCES DRMAtomic.cpp
+ DRMConnector.cpp
+ DRMCrtc.cpp
+ DRMEncoder.cpp
+ DRMLegacy.cpp
+ DRMObject.cpp
+ DRMPlane.cpp
+ DRMUtils.cpp
+ OffScreenModeSetting.cpp)
+
+set(HEADERS DRMAtomic.h
+ DRMConnector.h
+ DRMCrtc.h
+ DRMEncoder.h
+ DRMLegacy.h
+ DRMObject.h
+ DRMPlane.h
+ DRMUtils.h
+ OffScreenModeSetting.h)
+
+core_add_library(windowing_gbm_drm)
diff --git a/xbmc/windowing/gbm/drm/DRMAtomic.cpp b/xbmc/windowing/gbm/drm/DRMAtomic.cpp
new file mode 100644
index 0000000..5d61a69
--- /dev/null
+++ b/xbmc/windowing/gbm/drm/DRMAtomic.cpp
@@ -0,0 +1,344 @@
+/*
+ * 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 "DRMAtomic.h"
+
+#include "ServiceBroker.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "settings/lib/Setting.h"
+#include "utils/log.h"
+
+#include <errno.h>
+#include <string.h>
+
+#include <drm_fourcc.h>
+#include <drm_mode.h>
+#include <unistd.h>
+
+using namespace KODI::WINDOWING::GBM;
+
+namespace
+{
+
+const auto SETTING_VIDEOSCREEN_HW_SCALING_FILTER = "videoscreen.hwscalingfilter";
+
+uint32_t GetScalingFactor(uint32_t srcWidth,
+ uint32_t srcHeight,
+ uint32_t destWidth,
+ uint32_t destHeight)
+{
+ uint32_t factor_W = destWidth / srcWidth;
+ uint32_t factor_H = destHeight / srcHeight;
+ if (factor_W != factor_H)
+ return (factor_W < factor_H) ? factor_W : factor_H;
+ return factor_W;
+}
+
+} // namespace
+
+bool CDRMAtomic::SetScalingFilter(CDRMObject* object, const char* name, const char* type)
+{
+ bool result;
+ uint64_t value;
+ std::tie(result, value) = m_gui_plane->GetPropertyValue(name, type);
+ if (!result)
+ return false;
+
+ if (!AddProperty(object, name, value))
+ return false;
+
+ uint32_t mar_scale_factor =
+ GetScalingFactor(m_width, m_height, m_mode->hdisplay, m_mode->vdisplay);
+ AddProperty(object, "CRTC_W", (mar_scale_factor * m_width));
+ AddProperty(object, "CRTC_H", (mar_scale_factor * m_height));
+
+ return true;
+}
+
+void CDRMAtomic::DrmAtomicCommit(int fb_id, int flags, bool rendered, bool videoLayer)
+{
+ uint32_t blob_id;
+
+ if (flags & DRM_MODE_ATOMIC_ALLOW_MODESET)
+ {
+ if (!AddProperty(m_connector, "CRTC_ID", m_crtc->GetCrtcId()))
+ return;
+
+ if (drmModeCreatePropertyBlob(m_fd, m_mode, sizeof(*m_mode), &blob_id) != 0)
+ return;
+
+ if (m_active && m_orig_crtc && m_orig_crtc->GetCrtcId() != m_crtc->GetCrtcId())
+ {
+ // if using a different CRTC than the original, disable original to avoid EINVAL
+ if (!AddProperty(m_orig_crtc, "MODE_ID", 0))
+ return;
+
+ if (!AddProperty(m_orig_crtc, "ACTIVE", 0))
+ return;
+ }
+
+ if (!AddProperty(m_crtc, "MODE_ID", blob_id))
+ return;
+
+ if (!AddProperty(m_crtc, "ACTIVE", m_active ? 1 : 0))
+ return;
+ }
+
+ if (rendered)
+ {
+ AddProperty(m_gui_plane, "FB_ID", fb_id);
+ AddProperty(m_gui_plane, "CRTC_ID", m_crtc->GetCrtcId());
+ AddProperty(m_gui_plane, "SRC_X", 0);
+ AddProperty(m_gui_plane, "SRC_Y", 0);
+ AddProperty(m_gui_plane, "SRC_W", m_width << 16);
+ AddProperty(m_gui_plane, "SRC_H", m_height << 16);
+ AddProperty(m_gui_plane, "CRTC_X", 0);
+ AddProperty(m_gui_plane, "CRTC_Y", 0);
+ //! @todo: disabled until upstream kernel changes are merged
+ // if (DisplayHardwareScalingEnabled())
+ // {
+ // SetScalingFilter(m_gui_plane, "SCALING_FILTER", "Nearest Neighbor");
+ // }
+ // else
+ {
+ AddProperty(m_gui_plane, "CRTC_W", m_mode->hdisplay);
+ AddProperty(m_gui_plane, "CRTC_H", m_mode->vdisplay);
+ }
+
+ }
+ else if (videoLayer && !CServiceBroker::GetGUI()->GetWindowManager().HasVisibleControls())
+ {
+ // disable gui plane when video layer is active and gui has no visible controls
+ AddProperty(m_gui_plane, "FB_ID", 0);
+ AddProperty(m_gui_plane, "CRTC_ID", 0);
+ }
+
+ if (CServiceBroker::GetLogging().CanLogComponent(LOGWINDOWING))
+ m_req->LogAtomicRequest();
+
+ auto ret = drmModeAtomicCommit(m_fd, m_req->Get(), flags | DRM_MODE_ATOMIC_TEST_ONLY, nullptr);
+ if (ret < 0)
+ {
+ CLog::Log(LOGERROR,
+ "CDRMAtomic::{} - test commit failed: ({}) - falling back to last successful atomic "
+ "request",
+ __FUNCTION__, strerror(errno));
+
+ auto oldRequest = m_atomicRequestQueue.front().get();
+ CDRMAtomicRequest::LogAtomicDiff(m_req, oldRequest);
+ m_req = oldRequest;
+
+ // update the old atomic request with the new fb id to avoid tearing
+ if (rendered)
+ AddProperty(m_gui_plane, "FB_ID", fb_id);
+ }
+
+ ret = drmModeAtomicCommit(m_fd, m_req->Get(), flags, nullptr);
+ if (ret < 0)
+ {
+ CLog::Log(LOGERROR, "CDRMAtomic::{} - atomic commit failed: {}", __FUNCTION__,
+ strerror(errno));
+ }
+
+ if (flags & DRM_MODE_ATOMIC_ALLOW_MODESET)
+ {
+ if (drmModeDestroyPropertyBlob(m_fd, blob_id) != 0)
+ CLog::Log(LOGERROR, "CDRMAtomic::{} - failed to destroy property blob: {}", __FUNCTION__,
+ strerror(errno));
+ }
+
+ if (m_atomicRequestQueue.size() > 1)
+ m_atomicRequestQueue.pop_back();
+
+ m_atomicRequestQueue.emplace_back(std::make_unique<CDRMAtomicRequest>());
+ m_req = m_atomicRequestQueue.back().get();
+}
+
+void CDRMAtomic::FlipPage(struct gbm_bo *bo, bool rendered, bool videoLayer)
+{
+ struct drm_fb *drm_fb = nullptr;
+
+ if (rendered)
+ {
+ if (videoLayer)
+ m_gui_plane->SetFormat(CDRMUtils::FourCCWithAlpha(m_gui_plane->GetFormat()));
+ else
+ m_gui_plane->SetFormat(CDRMUtils::FourCCWithoutAlpha(m_gui_plane->GetFormat()));
+
+ drm_fb = CDRMUtils::DrmFbGetFromBo(bo);
+ if (!drm_fb)
+ {
+ CLog::Log(LOGERROR, "CDRMAtomic::{} - Failed to get a new FBO", __FUNCTION__);
+ return;
+ }
+ }
+
+ uint32_t flags = 0;
+
+ if (m_need_modeset)
+ {
+ flags |= DRM_MODE_ATOMIC_ALLOW_MODESET;
+ m_need_modeset = false;
+ CLog::Log(LOGDEBUG, "CDRMAtomic::{} - Execute modeset at next commit", __FUNCTION__);
+ }
+
+ DrmAtomicCommit(!drm_fb ? 0 : drm_fb->fb_id, flags, rendered, videoLayer);
+}
+
+bool CDRMAtomic::InitDrm()
+{
+ if (!CDRMUtils::OpenDrm(true))
+ return false;
+
+ auto ret = drmSetClientCap(m_fd, DRM_CLIENT_CAP_ATOMIC, 1);
+ if (ret)
+ {
+ CLog::Log(LOGERROR, "CDRMAtomic::{} - no atomic modesetting support: {}", __FUNCTION__,
+ strerror(errno));
+ return false;
+ }
+
+ m_atomicRequestQueue.emplace_back(std::make_unique<CDRMAtomicRequest>());
+ m_req = m_atomicRequestQueue.back().get();
+
+ if (!CDRMUtils::InitDrm())
+ return false;
+
+ for (auto& plane : m_planes)
+ {
+ AddProperty(plane.get(), "FB_ID", 0);
+ AddProperty(plane.get(), "CRTC_ID", 0);
+ }
+
+ CLog::Log(LOGDEBUG, "CDRMAtomic::{} - initialized atomic DRM", __FUNCTION__);
+
+ //! @todo: disabled until upstream kernel changes are merged
+ // if (m_gui_plane->SupportsProperty("SCALING_FILTER"))
+ // {
+ // const std::shared_ptr<CSettings> settings =
+ // CServiceBroker::GetSettingsComponent()->GetSettings();
+ // settings->GetSetting(SETTING_VIDEOSCREEN_HW_SCALING_FILTER)->SetVisible(true);
+ // }
+
+ return true;
+}
+
+void CDRMAtomic::DestroyDrm()
+{
+ CDRMUtils::DestroyDrm();
+}
+
+bool CDRMAtomic::SetVideoMode(const RESOLUTION_INFO& res, struct gbm_bo *bo)
+{
+ m_need_modeset = true;
+
+ return true;
+}
+
+bool CDRMAtomic::SetActive(bool active)
+{
+ m_need_modeset = true;
+ m_active = active;
+
+ return true;
+}
+
+bool CDRMAtomic::AddProperty(CDRMObject* object, const char* name, uint64_t value)
+{
+ return m_req->AddProperty(object, name, value);
+}
+
+bool CDRMAtomic::DisplayHardwareScalingEnabled()
+{
+ auto settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+
+ if (settings && settings->GetBool(SETTING_VIDEOSCREEN_HW_SCALING_FILTER))
+ return true;
+
+ return false;
+}
+
+CDRMAtomic::CDRMAtomicRequest::CDRMAtomicRequest() : m_atomicRequest(drmModeAtomicAlloc())
+{
+}
+
+bool CDRMAtomic::CDRMAtomicRequest::AddProperty(CDRMObject* object, const char* name, uint64_t value)
+{
+ uint32_t propertyId = object->GetPropertyId(name);
+ if (propertyId == 0)
+ return false;
+
+ int ret = drmModeAtomicAddProperty(m_atomicRequest.get(), object->GetId(), propertyId, value);
+ if (ret < 0)
+ return false;
+
+ m_atomicRequestItems[object][propertyId] = value;
+ return true;
+}
+
+void CDRMAtomic::CDRMAtomicRequest::LogAtomicDiff(CDRMAtomicRequest* current,
+ CDRMAtomicRequest* old)
+{
+ std::map<CDRMObject*, std::map<uint32_t, uint64_t>> atomicDiff;
+
+ for (const auto& object : current->m_atomicRequestItems)
+ {
+ auto sameObject = old->m_atomicRequestItems.find(object.first);
+ if (sameObject != old->m_atomicRequestItems.end())
+ {
+ std::map<uint32_t, uint64_t> propertyDiff;
+
+ std::set_difference(current->m_atomicRequestItems[object.first].begin(),
+ current->m_atomicRequestItems[object.first].end(),
+ old->m_atomicRequestItems[object.first].begin(),
+ old->m_atomicRequestItems[object.first].end(),
+ std::inserter(propertyDiff, propertyDiff.begin()));
+
+ atomicDiff[object.first] = propertyDiff;
+ }
+ else
+ {
+ atomicDiff[object.first] = current->m_atomicRequestItems[object.first];
+ }
+ }
+
+ CLog::Log(LOGDEBUG, "CDRMAtomicRequest::{} - DRM Atomic Request Diff:", __FUNCTION__);
+
+ LogAtomicRequest(LOGERROR, atomicDiff);
+}
+
+void CDRMAtomic::CDRMAtomicRequest::LogAtomicRequest()
+{
+ CLog::Log(LOGDEBUG, "CDRMAtomicRequest::{} - DRM Atomic Request:", __FUNCTION__);
+ LogAtomicRequest(LOGDEBUG, m_atomicRequestItems);
+}
+
+void CDRMAtomic::CDRMAtomicRequest::LogAtomicRequest(
+ uint8_t logLevel, std::map<CDRMObject*, std::map<uint32_t, uint64_t>>& atomicRequestItems)
+{
+ std::string message;
+ for (const auto& object : atomicRequestItems)
+ {
+ message.append("\nObject: " + object.first->GetTypeName() +
+ "\tID: " + std::to_string(object.first->GetId()));
+ for (const auto& property : object.second)
+ message.append("\n Property: " + object.first->GetPropertyName(property.first) +
+ "\tID: " + std::to_string(property.first) +
+ "\tValue: " + std::to_string(property.second));
+ }
+
+ CLog::Log(logLevel, "{}", message);
+}
+
+void CDRMAtomic::CDRMAtomicRequest::DrmModeAtomicReqDeleter::operator()(drmModeAtomicReqPtr p) const
+{
+ drmModeAtomicFree(p);
+};
diff --git a/xbmc/windowing/gbm/drm/DRMAtomic.h b/xbmc/windowing/gbm/drm/DRMAtomic.h
new file mode 100644
index 0000000..ca2cd9a
--- /dev/null
+++ b/xbmc/windowing/gbm/drm/DRMAtomic.h
@@ -0,0 +1,81 @@
+/*
+ * 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 "DRMUtils.h"
+
+#include <cstdint>
+#include <deque>
+#include <map>
+#include <memory>
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace GBM
+{
+
+class CDRMAtomic : public CDRMUtils
+{
+public:
+ CDRMAtomic() = default;
+ ~CDRMAtomic() override = default;
+ void FlipPage(struct gbm_bo* bo, bool rendered, bool videoLayer) override;
+ bool SetVideoMode(const RESOLUTION_INFO& res, struct gbm_bo* bo) override;
+ bool SetActive(bool active) override;
+ bool InitDrm() override;
+ void DestroyDrm() override;
+ bool AddProperty(CDRMObject* object, const char* name, uint64_t value);
+
+ bool DisplayHardwareScalingEnabled();
+
+private:
+ void DrmAtomicCommit(int fb_id, int flags, bool rendered, bool videoLayer);
+
+ bool SetScalingFilter(CDRMObject* object, const char* name, const char* type);
+
+ bool m_need_modeset;
+ bool m_active = true;
+
+ class CDRMAtomicRequest
+ {
+ public:
+ CDRMAtomicRequest();
+ ~CDRMAtomicRequest() = default;
+ CDRMAtomicRequest(const CDRMAtomicRequest& right) = delete;
+
+ drmModeAtomicReqPtr Get() const { return m_atomicRequest.get(); }
+
+ bool AddProperty(CDRMObject* object, const char* name, uint64_t value);
+ void LogAtomicRequest();
+
+ static void LogAtomicDiff(CDRMAtomicRequest* current, CDRMAtomicRequest* old);
+
+ private:
+ static void LogAtomicRequest(
+ uint8_t logLevel, std::map<CDRMObject*, std::map<uint32_t, uint64_t>>& atomicRequestItems);
+
+ std::map<CDRMObject*, std::map<uint32_t, uint64_t>> m_atomicRequestItems;
+
+ struct DrmModeAtomicReqDeleter
+ {
+ void operator()(drmModeAtomicReqPtr p) const;
+ };
+
+ std::unique_ptr<drmModeAtomicReq, DrmModeAtomicReqDeleter> m_atomicRequest;
+ };
+
+ CDRMAtomicRequest* m_req = nullptr;
+ std::deque<std::unique_ptr<CDRMAtomicRequest>> m_atomicRequestQueue;
+};
+
+}
+}
+}
diff --git a/xbmc/windowing/gbm/drm/DRMConnector.cpp b/xbmc/windowing/gbm/drm/DRMConnector.cpp
new file mode 100644
index 0000000..fc76b90
--- /dev/null
+++ b/xbmc/windowing/gbm/drm/DRMConnector.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2005-2020 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 "DRMConnector.h"
+
+#include "utils/XTimeUtils.h"
+#include "utils/log.h"
+
+#include <map>
+
+using namespace KODI::WINDOWING::GBM;
+
+using namespace std::chrono_literals;
+
+namespace
+{
+
+std::map<int, std::string> connectorTypeNames = {
+ {DRM_MODE_CONNECTOR_Unknown, "unknown"},
+ {DRM_MODE_CONNECTOR_VGA, "VGA"},
+ {DRM_MODE_CONNECTOR_DVII, "DVI-I"},
+ {DRM_MODE_CONNECTOR_DVID, "DVI-D"},
+ {DRM_MODE_CONNECTOR_DVIA, "DVI-A"},
+ {DRM_MODE_CONNECTOR_Composite, "composite"},
+ {DRM_MODE_CONNECTOR_SVIDEO, "s-video"},
+ {DRM_MODE_CONNECTOR_LVDS, "LVDS"},
+ {DRM_MODE_CONNECTOR_Component, "component"},
+ {DRM_MODE_CONNECTOR_9PinDIN, "9-pin DIN"},
+ {DRM_MODE_CONNECTOR_DisplayPort, "DP"},
+ {DRM_MODE_CONNECTOR_HDMIA, "HDMI-A"},
+ {DRM_MODE_CONNECTOR_HDMIB, "HDMI-B"},
+ {DRM_MODE_CONNECTOR_TV, "TV"},
+ {DRM_MODE_CONNECTOR_eDP, "eDP"},
+ {DRM_MODE_CONNECTOR_VIRTUAL, "Virtual"},
+ {DRM_MODE_CONNECTOR_DSI, "DSI"},
+ {DRM_MODE_CONNECTOR_DPI, "DPI"},
+};
+
+std::map<drmModeConnection, std::string> connectorStatusNames = {
+ {DRM_MODE_CONNECTED, "connected"},
+ {DRM_MODE_DISCONNECTED, "disconnected"},
+ {DRM_MODE_UNKNOWNCONNECTION, "unknown"},
+};
+
+} // namespace
+
+CDRMConnector::CDRMConnector(int fd, uint32_t connector)
+ : CDRMObject(fd), m_connector(drmModeGetConnector(m_fd, connector))
+{
+ if (!m_connector)
+ throw std::runtime_error("drmModeGetConnector failed: " + std::string{strerror(errno)});
+
+ if (!GetProperties(m_connector->connector_id, DRM_MODE_OBJECT_CONNECTOR))
+ throw std::runtime_error("failed to get properties for connector: " +
+ std::to_string(m_connector->connector_id));
+}
+
+bool CDRMConnector::CheckConnector()
+{
+ unsigned retryCnt = 7;
+ while (!IsConnected() && retryCnt > 0)
+ {
+ CLog::Log(LOGDEBUG, "CDRMConnector::{} - connector is disconnected", __FUNCTION__);
+ retryCnt--;
+ KODI::TIME::Sleep(1s);
+
+ m_connector.reset(drmModeGetConnector(m_fd, m_connector->connector_id));
+ }
+
+ return m_connector->connection == DRM_MODE_CONNECTED;
+}
+
+std::string CDRMConnector::GetType()
+{
+ auto typeName = connectorTypeNames.find(m_connector->connector_type);
+ if (typeName == connectorTypeNames.end())
+ return connectorTypeNames[DRM_MODE_CONNECTOR_Unknown];
+
+ return typeName->second;
+}
+
+std::string CDRMConnector::GetStatus()
+{
+ auto statusName = connectorStatusNames.find(m_connector->connection);
+ if (statusName == connectorStatusNames.end())
+ return connectorStatusNames[DRM_MODE_UNKNOWNCONNECTION];
+
+ return statusName->second;
+}
+
+std::string CDRMConnector::GetName()
+{
+ return GetType() + "-" + std::to_string(m_connector->connector_type_id);
+}
diff --git a/xbmc/windowing/gbm/drm/DRMConnector.h b/xbmc/windowing/gbm/drm/DRMConnector.h
new file mode 100644
index 0000000..5a29222
--- /dev/null
+++ b/xbmc/windowing/gbm/drm/DRMConnector.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2005-2020 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 "DRMObject.h"
+
+#include <string>
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace GBM
+{
+
+class CDRMConnector : public CDRMObject
+{
+public:
+ explicit CDRMConnector(int fd, uint32_t connector);
+ CDRMConnector(const CDRMConnector&) = delete;
+ CDRMConnector& operator=(const CDRMConnector&) = delete;
+ ~CDRMConnector() = default;
+
+ std::string GetType();
+ std::string GetStatus();
+ std::string GetName();
+
+ uint32_t GetEncoderId() const { return m_connector->encoder_id; }
+ uint32_t* GetConnectorId() const { return &m_connector->connector_id; }
+ int GetModesCount() const { return m_connector->count_modes; }
+ drmModeModeInfoPtr GetModeForIndex(int index) const { return &m_connector->modes[index]; }
+
+ bool IsConnected() { return m_connector->connection == DRM_MODE_CONNECTED; }
+ bool CheckConnector();
+
+private:
+ struct DrmModeConnectorDeleter
+ {
+ void operator()(drmModeConnector* p) { drmModeFreeConnector(p); }
+ };
+
+ std::unique_ptr<drmModeConnector, DrmModeConnectorDeleter> m_connector;
+};
+
+} // namespace GBM
+} // namespace WINDOWING
+} // namespace KODI
diff --git a/xbmc/windowing/gbm/drm/DRMCrtc.cpp b/xbmc/windowing/gbm/drm/DRMCrtc.cpp
new file mode 100644
index 0000000..426fd95
--- /dev/null
+++ b/xbmc/windowing/gbm/drm/DRMCrtc.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2005-2020 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 "DRMCrtc.h"
+
+#include <cstring>
+#include <errno.h>
+#include <stdexcept>
+#include <string>
+
+using namespace KODI::WINDOWING::GBM;
+
+CDRMCrtc::CDRMCrtc(int fd, uint32_t crtc) : CDRMObject(fd), m_crtc(drmModeGetCrtc(m_fd, crtc))
+{
+ if (!m_crtc)
+ throw std::runtime_error("drmModeGetCrtc failed: " + std::string{strerror(errno)});
+
+ if (!GetProperties(m_crtc->crtc_id, DRM_MODE_OBJECT_CRTC))
+ throw std::runtime_error("failed to get properties for crtc: " +
+ std::to_string(m_crtc->crtc_id));
+}
diff --git a/xbmc/windowing/gbm/drm/DRMCrtc.h b/xbmc/windowing/gbm/drm/DRMCrtc.h
new file mode 100644
index 0000000..a1aadc2
--- /dev/null
+++ b/xbmc/windowing/gbm/drm/DRMCrtc.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2005-2020 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 "DRMObject.h"
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace GBM
+{
+
+class CDRMCrtc : public CDRMObject
+{
+public:
+ explicit CDRMCrtc(int fd, uint32_t crtc);
+ CDRMCrtc(const CDRMCrtc&) = delete;
+ CDRMCrtc& operator=(const CDRMCrtc&) = delete;
+ ~CDRMCrtc() = default;
+
+ uint32_t GetCrtcId() const { return m_crtc->crtc_id; }
+ uint32_t GetBufferId() const { return m_crtc->buffer_id; }
+ uint32_t GetX() const { return m_crtc->x; }
+ uint32_t GetY() const { return m_crtc->y; }
+ drmModeModeInfoPtr GetMode() const { return &m_crtc->mode; }
+ bool GetModeValid() const { return m_crtc->mode_valid != 0; }
+
+private:
+ struct DrmModeCrtcDeleter
+ {
+ void operator()(drmModeCrtc* p) { drmModeFreeCrtc(p); }
+ };
+
+ std::unique_ptr<drmModeCrtc, DrmModeCrtcDeleter> m_crtc;
+};
+
+} // namespace GBM
+} // namespace WINDOWING
+} // namespace KODI
diff --git a/xbmc/windowing/gbm/drm/DRMEncoder.cpp b/xbmc/windowing/gbm/drm/DRMEncoder.cpp
new file mode 100644
index 0000000..e4289c6
--- /dev/null
+++ b/xbmc/windowing/gbm/drm/DRMEncoder.cpp
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2005-2020 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 "DRMEncoder.h"
+
+#include <cstring>
+#include <errno.h>
+#include <stdexcept>
+#include <string>
+
+using namespace KODI::WINDOWING::GBM;
+
+CDRMEncoder::CDRMEncoder(int fd, uint32_t encoder)
+ : CDRMObject(fd), m_encoder(drmModeGetEncoder(m_fd, encoder))
+{
+ if (!m_encoder)
+ throw std::runtime_error("drmModeGetEncoder failed: " + std::string{strerror(errno)});
+}
diff --git a/xbmc/windowing/gbm/drm/DRMEncoder.h b/xbmc/windowing/gbm/drm/DRMEncoder.h
new file mode 100644
index 0000000..aceb276
--- /dev/null
+++ b/xbmc/windowing/gbm/drm/DRMEncoder.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2005-2020 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 "DRMObject.h"
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace GBM
+{
+
+class CDRMEncoder : public CDRMObject
+{
+public:
+ explicit CDRMEncoder(int fd, uint32_t encoder);
+ CDRMEncoder(const CDRMEncoder&) = delete;
+ CDRMEncoder& operator=(const CDRMEncoder&) = delete;
+ ~CDRMEncoder() = default;
+
+ uint32_t GetEncoderId() const { return m_encoder->encoder_id; }
+ uint32_t GetCrtcId() const { return m_encoder->crtc_id; }
+ uint32_t GetPossibleCrtcs() const { return m_encoder->possible_crtcs; }
+
+private:
+ struct DrmModeEncoderDeleter
+ {
+ void operator()(drmModeEncoder* p) { drmModeFreeEncoder(p); }
+ };
+
+ std::unique_ptr<drmModeEncoder, DrmModeEncoderDeleter> m_encoder;
+};
+
+} // namespace GBM
+} // namespace WINDOWING
+} // namespace KODI
diff --git a/xbmc/windowing/gbm/drm/DRMLegacy.cpp b/xbmc/windowing/gbm/drm/DRMLegacy.cpp
new file mode 100644
index 0000000..418d067
--- /dev/null
+++ b/xbmc/windowing/gbm/drm/DRMLegacy.cpp
@@ -0,0 +1,141 @@
+/*
+ * 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 "DRMLegacy.h"
+
+#include "guilib/gui3d.h"
+#include "settings/Settings.h"
+#include "utils/log.h"
+
+#include <errno.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <drm_mode.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <unistd.h>
+
+using namespace KODI::WINDOWING::GBM;
+
+static int flip_happening = 0;
+
+bool CDRMLegacy::SetVideoMode(const RESOLUTION_INFO& res, struct gbm_bo *bo)
+{
+ struct drm_fb *drm_fb = DrmFbGetFromBo(bo);
+
+ auto ret = drmModeSetCrtc(m_fd, m_crtc->GetCrtcId(), drm_fb->fb_id, 0, 0,
+ m_connector->GetConnectorId(), 1, m_mode);
+
+ if(ret < 0)
+ {
+ CLog::Log(LOGERROR, "CDRMLegacy::{} - failed to set crtc mode: {}x{}{} @ {} Hz", __FUNCTION__,
+ m_mode->hdisplay, m_mode->vdisplay,
+ m_mode->flags & DRM_MODE_FLAG_INTERLACE ? "i" : "", m_mode->vrefresh);
+
+ return false;
+ }
+
+ CLog::Log(LOGDEBUG, "CDRMLegacy::{} - set crtc mode: {}x{}{} @ {} Hz", __FUNCTION__,
+ m_mode->hdisplay, m_mode->vdisplay, m_mode->flags & DRM_MODE_FLAG_INTERLACE ? "i" : "",
+ m_mode->vrefresh);
+
+ return true;
+}
+
+void CDRMLegacy::PageFlipHandler(int fd, unsigned int frame, unsigned int sec,
+ unsigned int usec, void *data)
+{
+ (void) fd, (void) frame, (void) sec, (void) usec;
+
+ int *flip_happening = static_cast<int *>(data);
+ *flip_happening = 0;
+}
+
+bool CDRMLegacy::WaitingForFlip()
+{
+ if (!flip_happening)
+ return false;
+
+ struct pollfd drm_fds =
+ {
+ m_fd,
+ POLLIN,
+ 0,
+ };
+
+ drmEventContext drm_evctx{};
+ drm_evctx.version = DRM_EVENT_CONTEXT_VERSION;
+ drm_evctx.page_flip_handler = PageFlipHandler;
+
+ while(flip_happening)
+ {
+ auto ret = poll(&drm_fds, 1, -1);
+
+ if(ret < 0)
+ return true;
+
+ if(drm_fds.revents & (POLLHUP | POLLERR))
+ return true;
+
+ if(drm_fds.revents & POLLIN)
+ drmHandleEvent(m_fd, &drm_evctx);
+ }
+
+ return false;
+}
+
+bool CDRMLegacy::QueueFlip(struct gbm_bo *bo)
+{
+ struct drm_fb *drm_fb = DrmFbGetFromBo(bo);
+
+ auto ret = drmModePageFlip(m_fd, m_crtc->GetCrtcId(), drm_fb->fb_id, DRM_MODE_PAGE_FLIP_EVENT,
+ &flip_happening);
+
+ if(ret)
+ {
+ CLog::Log(LOGDEBUG, "CDRMLegacy::{} - failed to queue DRM page flip", __FUNCTION__);
+ return false;
+ }
+
+ return true;
+}
+
+void CDRMLegacy::FlipPage(struct gbm_bo *bo, bool rendered, bool videoLayer)
+{
+ if (rendered || videoLayer)
+ {
+ flip_happening = QueueFlip(bo);
+ WaitingForFlip();
+ }
+}
+
+bool CDRMLegacy::InitDrm()
+{
+ if (!CDRMUtils::OpenDrm(true))
+ return false;
+
+ if (!CDRMUtils::InitDrm())
+ return false;
+
+ CLog::Log(LOGDEBUG, "CDRMLegacy::{} - initialized legacy DRM", __FUNCTION__);
+ return true;
+}
+
+bool CDRMLegacy::SetActive(bool active)
+{
+ if (!m_connector->SetProperty("DPMS", active ? DRM_MODE_DPMS_ON : DRM_MODE_DPMS_OFF))
+ {
+ CLog::Log(LOGDEBUG, "CDRMLegacy::{} - failed to set DPMS property", __FUNCTION__);
+ return false;
+ }
+
+ return true;
+}
diff --git a/xbmc/windowing/gbm/drm/DRMLegacy.h b/xbmc/windowing/gbm/drm/DRMLegacy.h
new file mode 100644
index 0000000..2b7ff45
--- /dev/null
+++ b/xbmc/windowing/gbm/drm/DRMLegacy.h
@@ -0,0 +1,39 @@
+/*
+ * 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 "DRMUtils.h"
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace GBM
+{
+
+class CDRMLegacy : public CDRMUtils
+{
+public:
+ CDRMLegacy() = default;
+ ~CDRMLegacy() override = default;
+ void FlipPage(struct gbm_bo* bo, bool rendered, bool videoLayer) override;
+ bool SetVideoMode(const RESOLUTION_INFO& res, struct gbm_bo* bo) override;
+ bool SetActive(bool active) override;
+ bool InitDrm() override;
+
+private:
+ bool WaitingForFlip();
+ bool QueueFlip(struct gbm_bo *bo);
+ static void PageFlipHandler(int fd, unsigned int frame, unsigned int sec,
+ unsigned int usec, void *data);
+};
+
+}
+}
+}
diff --git a/xbmc/windowing/gbm/drm/DRMObject.cpp b/xbmc/windowing/gbm/drm/DRMObject.cpp
new file mode 100644
index 0000000..599bb61
--- /dev/null
+++ b/xbmc/windowing/gbm/drm/DRMObject.cpp
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2005-2020 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 "DRMObject.h"
+
+#include "utils/log.h"
+
+#include <algorithm>
+#include <array>
+
+using namespace KODI::WINDOWING::GBM;
+
+namespace
+{
+
+constexpr std::array<std::pair<uint32_t, const char*>, 8> DrmModeObjectTypes = {
+ {{DRM_MODE_OBJECT_CRTC, "crtc"},
+ {DRM_MODE_OBJECT_CONNECTOR, "connector"},
+ {DRM_MODE_OBJECT_ENCODER, "encoder"},
+ {DRM_MODE_OBJECT_MODE, "mode"},
+ {DRM_MODE_OBJECT_PROPERTY, "property"},
+ {DRM_MODE_OBJECT_FB, "framebuffer"},
+ {DRM_MODE_OBJECT_BLOB, "blob"},
+ {DRM_MODE_OBJECT_PLANE, "plane"}}};
+}
+
+CDRMObject::CDRMObject(int fd) : m_fd(fd)
+{
+}
+
+std::string CDRMObject::GetTypeName() const
+{
+ auto name = std::find_if(DrmModeObjectTypes.begin(), DrmModeObjectTypes.end(),
+ [this](const auto& p) { return p.first == m_type; });
+ if (name != DrmModeObjectTypes.end())
+ return name->second;
+
+ return "invalid type";
+}
+
+std::string CDRMObject::GetPropertyName(uint32_t propertyId) const
+{
+ auto prop = std::find_if(m_propsInfo.begin(), m_propsInfo.end(),
+ [&propertyId](const auto& p) { return p->prop_id == propertyId; });
+ if (prop != m_propsInfo.end())
+ return prop->get()->name;
+
+ return "invalid property";
+}
+
+uint32_t CDRMObject::GetPropertyId(const std::string& name) const
+{
+ auto property = std::find_if(m_propsInfo.begin(), m_propsInfo.end(),
+ [&name](const auto& prop) { return prop->name == name; });
+
+ if (property != m_propsInfo.end())
+ return property->get()->prop_id;
+
+ return 0;
+}
+
+bool CDRMObject::GetProperties(uint32_t id, uint32_t type)
+{
+ m_props.reset(drmModeObjectGetProperties(m_fd, id, type));
+ if (!m_props)
+ return false;
+
+ m_id = id;
+ m_type = type;
+
+ for (uint32_t i = 0; i < m_props->count_props; i++)
+ m_propsInfo.emplace_back(std::unique_ptr<drmModePropertyRes, DrmModePropertyResDeleter>(
+ drmModeGetProperty(m_fd, m_props->props[i])));
+
+ return true;
+}
+
+//! @todo: improve with c++17
+std::tuple<bool, uint64_t> CDRMObject::GetPropertyValue(const std::string& name,
+ const std::string& valueName) const
+{
+ auto property = std::find_if(m_propsInfo.begin(), m_propsInfo.end(),
+ [&name](const auto& prop) { return prop->name == name; });
+
+ if (property == m_propsInfo.end())
+ return std::make_tuple(false, 0);
+
+ auto prop = property->get();
+
+ if (!static_cast<bool>(drm_property_type_is(prop, DRM_MODE_PROP_ENUM)))
+ return std::make_tuple(false, 0);
+
+ for (int j = 0; j < prop->count_enums; j++)
+ {
+ if (prop->enums[j].name != valueName)
+ continue;
+
+ return std::make_tuple(true, prop->enums[j].value);
+ }
+
+ return std::make_tuple(false, 0);
+}
+
+bool CDRMObject::SetProperty(const std::string& name, uint64_t value)
+{
+ auto property = std::find_if(m_propsInfo.begin(), m_propsInfo.end(),
+ [&name](const auto& prop) { return prop->name == name; });
+
+ if (property != m_propsInfo.end())
+ {
+ int ret = drmModeObjectSetProperty(m_fd, m_id, m_type, property->get()->prop_id, value);
+ if (ret == 0)
+ return true;
+ }
+
+ return false;
+}
+
+bool CDRMObject::SupportsProperty(const std::string& name)
+{
+ auto property = std::find_if(m_propsInfo.begin(), m_propsInfo.end(),
+ [&name](const auto& prop) { return prop->name == name; });
+
+ if (property != m_propsInfo.end())
+ return true;
+
+ return false;
+}
diff --git a/xbmc/windowing/gbm/drm/DRMObject.h b/xbmc/windowing/gbm/drm/DRMObject.h
new file mode 100644
index 0000000..e2ae326
--- /dev/null
+++ b/xbmc/windowing/gbm/drm/DRMObject.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2005-2020 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 <cstddef>
+#include <cstdint>
+#include <memory>
+#include <vector>
+
+#include <xf86drmMode.h>
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace GBM
+{
+
+class CDRMObject
+{
+public:
+ CDRMObject(const CDRMObject&) = delete;
+ CDRMObject& operator=(const CDRMObject&) = delete;
+ virtual ~CDRMObject() = default;
+
+ std::string GetTypeName() const;
+ std::string GetPropertyName(uint32_t propertyId) const;
+
+ uint32_t GetId() const { return m_id; }
+ uint32_t GetPropertyId(const std::string& name) const;
+ std::tuple<bool, uint64_t> GetPropertyValue(const std::string& name,
+ const std::string& valueName) const;
+
+ bool SetProperty(const std::string& name, uint64_t value);
+ bool SupportsProperty(const std::string& name);
+
+protected:
+ explicit CDRMObject(int fd);
+
+ bool GetProperties(uint32_t id, uint32_t type);
+
+ struct DrmModeObjectPropertiesDeleter
+ {
+ void operator()(drmModeObjectProperties* p) { drmModeFreeObjectProperties(p); }
+ };
+
+ std::unique_ptr<drmModeObjectProperties, DrmModeObjectPropertiesDeleter> m_props;
+
+ struct DrmModePropertyResDeleter
+ {
+ void operator()(drmModePropertyRes* p) { drmModeFreeProperty(p); }
+ };
+
+ std::vector<std::unique_ptr<drmModePropertyRes, DrmModePropertyResDeleter>> m_propsInfo;
+
+ int m_fd{-1};
+
+private:
+ uint32_t m_id{0};
+ uint32_t m_type{0};
+};
+
+} // namespace GBM
+} // namespace WINDOWING
+} // namespace KODI
diff --git a/xbmc/windowing/gbm/drm/DRMPlane.cpp b/xbmc/windowing/gbm/drm/DRMPlane.cpp
new file mode 100644
index 0000000..90b5660
--- /dev/null
+++ b/xbmc/windowing/gbm/drm/DRMPlane.cpp
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2005-2020 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 "DRMPlane.h"
+
+#include "DRMUtils.h"
+#include "utils/DRMHelpers.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+using namespace KODI::WINDOWING::GBM;
+
+CDRMPlane::CDRMPlane(int fd, uint32_t plane) : CDRMObject(fd), m_plane(drmModeGetPlane(m_fd, plane))
+{
+ if (!m_plane)
+ throw std::runtime_error("drmModeGetPlane failed: " + std::string{strerror(errno)});
+
+ if (!GetProperties(m_plane->plane_id, DRM_MODE_OBJECT_PLANE))
+ throw std::runtime_error("failed to get properties for plane: " +
+ std::to_string(m_plane->plane_id));
+}
+
+bool CDRMPlane::SupportsFormat(uint32_t format)
+{
+ for (uint32_t i = 0; i < m_plane->count_formats; i++)
+ if (m_plane->formats[i] == format)
+ return true;
+
+ return false;
+}
+
+bool CDRMPlane::SupportsFormatAndModifier(uint32_t format, uint64_t modifier)
+{
+ /*
+ * Some broadcom modifiers have parameters encoded which need to be
+ * masked out before comparing with reported modifiers.
+ */
+ if (modifier >> 56 == DRM_FORMAT_MOD_VENDOR_BROADCOM)
+ modifier = fourcc_mod_broadcom_mod(modifier);
+
+ if (modifier == DRM_FORMAT_MOD_LINEAR)
+ {
+ if (!SupportsFormat(format))
+ {
+ CLog::Log(LOGDEBUG, "CDRMPlane::{} - format not supported: {}", __FUNCTION__,
+ DRMHELPERS::FourCCToString(format));
+ return false;
+ }
+ }
+ else
+ {
+ auto formatModifiers = &m_modifiers_map[format];
+ if (formatModifiers->empty())
+ {
+ CLog::Log(LOGDEBUG, "CDRMPlane::{} - format not supported: {}", __FUNCTION__,
+ DRMHELPERS::FourCCToString(format));
+ return false;
+ }
+
+ auto formatModifier = std::find(formatModifiers->begin(), formatModifiers->end(), modifier);
+ if (formatModifier == formatModifiers->end())
+ {
+ CLog::Log(LOGDEBUG, "CDRMPlane::{} - modifier ({}) not supported for format ({})",
+ __FUNCTION__, DRMHELPERS::ModifierToString(modifier),
+ DRMHELPERS::FourCCToString(format));
+ return false;
+ }
+ }
+
+ CLog::Log(LOGDEBUG, "CDRMPlane::{} - found plane format ({}) and modifier ({})", __FUNCTION__,
+ DRMHELPERS::FourCCToString(format), DRMHELPERS::ModifierToString(modifier));
+
+ return true;
+}
+
+void CDRMPlane::FindModifiers()
+{
+ auto property = std::find_if(m_propsInfo.begin(), m_propsInfo.end(), [](auto& prop) {
+ return StringUtils::EqualsNoCase(prop->name, "IN_FORMATS");
+ });
+
+ uint64_t blob_id = 0;
+ if (property != m_propsInfo.end())
+ blob_id = m_props->prop_values[std::distance(m_propsInfo.begin(), property)];
+
+ if (blob_id == 0)
+ return;
+
+ drmModePropertyBlobPtr blob = drmModeGetPropertyBlob(m_fd, blob_id);
+ if (!blob)
+ return;
+
+ drm_format_modifier_blob* header = static_cast<drm_format_modifier_blob*>(blob->data);
+ uint32_t* formats =
+ reinterpret_cast<uint32_t*>(reinterpret_cast<char*>(header) + header->formats_offset);
+ drm_format_modifier* mod = reinterpret_cast<drm_format_modifier*>(
+ reinterpret_cast<char*>(header) + header->modifiers_offset);
+
+ for (uint32_t i = 0; i < header->count_formats; i++)
+ {
+ std::vector<uint64_t> modifiers;
+ for (uint32_t j = 0; j < header->count_modifiers; j++)
+ {
+ if (mod[j].formats & 1ULL << i)
+ modifiers.emplace_back(mod[j].modifier);
+ }
+
+ m_modifiers_map.emplace(formats[i], modifiers);
+ }
+
+ if (blob)
+ drmModeFreePropertyBlob(blob);
+}
diff --git a/xbmc/windowing/gbm/drm/DRMPlane.h b/xbmc/windowing/gbm/drm/DRMPlane.h
new file mode 100644
index 0000000..e077fc3
--- /dev/null
+++ b/xbmc/windowing/gbm/drm/DRMPlane.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2005-2020 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 "DRMObject.h"
+
+#include <map>
+
+#include <drm_fourcc.h>
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace GBM
+{
+
+class CDRMPlane : public CDRMObject
+{
+public:
+ explicit CDRMPlane(int fd, uint32_t plane);
+ CDRMPlane(const CDRMPlane&) = delete;
+ CDRMPlane& operator=(const CDRMPlane&) = delete;
+ ~CDRMPlane() = default;
+
+ uint32_t GetPlaneId() const { return m_plane->plane_id; }
+ uint32_t GetPossibleCrtcs() const { return m_plane->possible_crtcs; }
+
+ void FindModifiers();
+
+ void SetFormat(const uint32_t newFormat) { m_format = newFormat; }
+ uint32_t GetFormat() const { return m_format; }
+ std::vector<uint64_t>& GetModifiersForFormat(uint32_t format) { return m_modifiers_map[format]; }
+
+ bool SupportsFormat(uint32_t format);
+ bool SupportsFormatAndModifier(uint32_t format, uint64_t modifier);
+
+private:
+ struct DrmModePlaneDeleter
+ {
+ void operator()(drmModePlane* p) { drmModeFreePlane(p); }
+ };
+
+ std::unique_ptr<drmModePlane, DrmModePlaneDeleter> m_plane;
+
+ std::map<uint32_t, std::vector<uint64_t>> m_modifiers_map;
+ uint32_t m_format{DRM_FORMAT_XRGB8888};
+};
+
+} // namespace GBM
+} // namespace WINDOWING
+} // namespace KODI
diff --git a/xbmc/windowing/gbm/drm/DRMUtils.cpp b/xbmc/windowing/gbm/drm/DRMUtils.cpp
new file mode 100644
index 0000000..6b61403
--- /dev/null
+++ b/xbmc/windowing/gbm/drm/DRMUtils.cpp
@@ -0,0 +1,740 @@
+/*
+ * 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 "DRMUtils.h"
+
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/DRMHelpers.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+
+#include "PlatformDefs.h"
+
+using namespace KODI::WINDOWING::GBM;
+
+namespace
+{
+const std::string SETTING_VIDEOSCREEN_LIMITGUISIZE = "videoscreen.limitguisize";
+
+void DrmFbDestroyCallback(gbm_bo* bo, void* data)
+{
+ drm_fb* fb = static_cast<drm_fb*>(data);
+
+ if (fb->fb_id > 0)
+ {
+ CLog::Log(LOGDEBUG, "CDRMUtils::{} - removing framebuffer: {}", __FUNCTION__, fb->fb_id);
+ int drm_fd = gbm_device_get_fd(gbm_bo_get_device(bo));
+ drmModeRmFB(drm_fd, fb->fb_id);
+ }
+
+ delete fb;
+}
+}
+
+CDRMUtils::~CDRMUtils()
+{
+ DestroyDrm();
+}
+
+bool CDRMUtils::SetMode(const RESOLUTION_INFO& res)
+{
+ if (!m_connector->CheckConnector())
+ return false;
+
+ m_mode = m_connector->GetModeForIndex(std::atoi(res.strId.c_str()));
+ m_width = res.iWidth;
+ m_height = res.iHeight;
+
+ CLog::Log(LOGDEBUG, "CDRMUtils::{} - found crtc mode: {}x{}{} @ {} Hz", __FUNCTION__,
+ m_mode->hdisplay, m_mode->vdisplay, m_mode->flags & DRM_MODE_FLAG_INTERLACE ? "i" : "",
+ m_mode->vrefresh);
+
+ return true;
+}
+
+drm_fb * CDRMUtils::DrmFbGetFromBo(struct gbm_bo *bo)
+{
+ {
+ struct drm_fb *fb = static_cast<drm_fb *>(gbm_bo_get_user_data(bo));
+ if(fb)
+ {
+ if (m_gui_plane->GetFormat() == fb->format)
+ return fb;
+ else
+ DrmFbDestroyCallback(bo, gbm_bo_get_user_data(bo));
+ }
+ }
+
+ struct drm_fb *fb = new drm_fb;
+ fb->bo = bo;
+ fb->format = m_gui_plane->GetFormat();
+
+ uint32_t width,
+ height,
+ handles[4] = {0},
+ strides[4] = {0},
+ offsets[4] = {0};
+
+ uint64_t modifiers[4] = {0};
+
+ width = gbm_bo_get_width(bo);
+ height = gbm_bo_get_height(bo);
+
+#if defined(HAS_GBM_MODIFIERS)
+ for (int i = 0; i < gbm_bo_get_plane_count(bo); i++)
+ {
+ handles[i] = gbm_bo_get_handle_for_plane(bo, i).u32;
+ strides[i] = gbm_bo_get_stride_for_plane(bo, i);
+ offsets[i] = gbm_bo_get_offset(bo, i);
+ modifiers[i] = gbm_bo_get_modifier(bo);
+ }
+#else
+ handles[0] = gbm_bo_get_handle(bo).u32;
+ strides[0] = gbm_bo_get_stride(bo);
+ memset(offsets, 0, 16);
+#endif
+
+ uint32_t flags = 0;
+
+ if (modifiers[0] && modifiers[0] != DRM_FORMAT_MOD_INVALID)
+ {
+ flags |= DRM_MODE_FB_MODIFIERS;
+ CLog::Log(LOGDEBUG, "CDRMUtils::{} - using modifier: {}", __FUNCTION__,
+ DRMHELPERS::ModifierToString(modifiers[0]));
+ }
+
+ int ret = drmModeAddFB2WithModifiers(m_fd,
+ width,
+ height,
+ fb->format,
+ handles,
+ strides,
+ offsets,
+ modifiers,
+ &fb->fb_id,
+ flags);
+
+ if(ret < 0)
+ {
+ ret = drmModeAddFB2(m_fd,
+ width,
+ height,
+ fb->format,
+ handles,
+ strides,
+ offsets,
+ &fb->fb_id,
+ flags);
+
+ if (ret < 0)
+ {
+ delete (fb);
+ CLog::Log(LOGDEBUG, "CDRMUtils::{} - failed to add framebuffer: {} ({})", __FUNCTION__, strerror(errno), errno);
+ return nullptr;
+ }
+ }
+
+ gbm_bo_set_user_data(bo, fb, DrmFbDestroyCallback);
+
+ return fb;
+}
+
+bool CDRMUtils::FindPreferredMode()
+{
+ if (m_mode)
+ return true;
+
+ for (int i = 0, area = 0; i < m_connector->GetModesCount(); i++)
+ {
+ drmModeModeInfo* current_mode = m_connector->GetModeForIndex(i);
+
+ if(current_mode->type & DRM_MODE_TYPE_PREFERRED)
+ {
+ m_mode = current_mode;
+ CLog::Log(LOGDEBUG, "CDRMUtils::{} - found preferred mode: {}x{}{} @ {} Hz", __FUNCTION__,
+ m_mode->hdisplay, m_mode->vdisplay,
+ m_mode->flags & DRM_MODE_FLAG_INTERLACE ? "i" : "", m_mode->vrefresh);
+ break;
+ }
+
+ auto current_area = current_mode->hdisplay * current_mode->vdisplay;
+ if (current_area > area)
+ {
+ m_mode = current_mode;
+ area = current_area;
+ }
+ }
+
+ if(!m_mode)
+ {
+ CLog::Log(LOGDEBUG, "CDRMUtils::{} - failed to find preferred mode", __FUNCTION__);
+ return false;
+ }
+
+ return true;
+}
+
+bool CDRMUtils::FindPlanes()
+{
+ for (size_t i = 0; i < m_crtcs.size(); i++)
+ {
+ if (!(m_encoder->GetPossibleCrtcs() & (1 << i)))
+ continue;
+
+ auto videoPlane = std::find_if(m_planes.begin(), m_planes.end(), [&i](auto& plane) {
+ if (plane->GetPossibleCrtcs() & (1 << i))
+ {
+ return plane->SupportsFormat(DRM_FORMAT_NV12);
+ }
+ return false;
+ });
+
+ uint32_t videoPlaneId{0};
+
+ if (videoPlane != m_planes.end())
+ videoPlaneId = videoPlane->get()->GetPlaneId();
+
+ auto guiPlane =
+ std::find_if(m_planes.begin(), m_planes.end(), [&i, &videoPlaneId](auto& plane) {
+ if (plane->GetPossibleCrtcs() & (1 << i))
+ {
+ return (plane->GetPlaneId() != videoPlaneId &&
+ (videoPlaneId == 0 || plane->SupportsFormat(DRM_FORMAT_ARGB8888)) &&
+ (plane->SupportsFormat(DRM_FORMAT_XRGB2101010) ||
+ plane->SupportsFormat(DRM_FORMAT_XRGB8888)));
+ }
+ return false;
+ });
+
+ if (videoPlane != m_planes.end() && guiPlane != m_planes.end())
+ {
+ m_crtc = m_crtcs[i].get();
+ m_video_plane = videoPlane->get();
+ m_gui_plane = guiPlane->get();
+ break;
+ }
+
+ if (guiPlane != m_planes.end())
+ {
+ if (!m_crtc && m_encoder->GetCrtcId() == m_crtcs[i]->GetCrtcId())
+ {
+ m_crtc = m_crtcs[i].get();
+ m_gui_plane = guiPlane->get();
+ m_video_plane = nullptr;
+ }
+ }
+ }
+
+ CLog::Log(LOGINFO, "CDRMUtils::{} - using crtc: {}", __FUNCTION__, m_crtc->GetCrtcId());
+
+ // video plane may not be available
+ if (m_video_plane)
+ CLog::Log(LOGDEBUG, "CDRMUtils::{} - using video plane {}", __FUNCTION__,
+ m_video_plane->GetPlaneId());
+
+ if (m_gui_plane->SupportsFormat(DRM_FORMAT_XRGB2101010))
+ {
+ m_gui_plane->SetFormat(DRM_FORMAT_XRGB2101010);
+ CLog::Log(LOGDEBUG, "CDRMUtils::{} - using 10bit gui plane {}", __FUNCTION__,
+ m_gui_plane->GetPlaneId());
+ }
+ else
+ {
+ m_gui_plane->SetFormat(DRM_FORMAT_XRGB8888);
+ CLog::Log(LOGDEBUG, "CDRMUtils::{} - using gui plane {}", __FUNCTION__,
+ m_gui_plane->GetPlaneId());
+ }
+
+ return true;
+}
+
+void CDRMUtils::PrintDrmDeviceInfo(drmDevicePtr device)
+{
+ std::string message;
+
+ // clang-format off
+ message.append(fmt::format("CDRMUtils::{} - DRM Device Info:", __FUNCTION__));
+ message.append(fmt::format("\n available_nodes: {:#04x}", device->available_nodes));
+ message.append("\n nodes:");
+
+ for (int i = 0; i < DRM_NODE_MAX; i++)
+ {
+ if (device->available_nodes & 1 << i)
+ message.append(fmt::format("\n nodes[{}]: {}", i, device->nodes[i]));
+ }
+
+ message.append(fmt::format("\n bustype: {:#04x}", device->bustype));
+
+ if (device->bustype == DRM_BUS_PCI)
+ {
+ message.append("\n pci:");
+ message.append(fmt::format("\n domain: {:#04x}", device->businfo.pci->domain));
+ message.append(fmt::format("\n bus: {:#02x}", device->businfo.pci->bus));
+ message.append(fmt::format("\n dev: {:#02x}", device->businfo.pci->dev));
+ message.append(fmt::format("\n func: {:#1}", device->businfo.pci->func));
+
+ message.append("\n deviceinfo:");
+ message.append("\n pci:");
+ message.append(fmt::format("\n vendor_id: {:#04x}", device->deviceinfo.pci->vendor_id));
+ message.append(fmt::format("\n device_id: {:#04x}", device->deviceinfo.pci->device_id));
+ message.append(fmt::format("\n subvendor_id: {:#04x}", device->deviceinfo.pci->subvendor_id));
+ message.append(fmt::format("\n subdevice_id: {:#04x}", device->deviceinfo.pci->subdevice_id));
+ }
+ else if (device->bustype == DRM_BUS_USB)
+ {
+ message.append("\n usb:");
+ message.append(fmt::format("\n bus: {:#03x}", device->businfo.usb->bus));
+ message.append(fmt::format("\n dev: {:#03x}", device->businfo.usb->dev));
+
+ message.append("\n deviceinfo:");
+ message.append("\n usb:");
+ message.append(fmt::format("\n vendor: {:#04x}", device->deviceinfo.usb->vendor));
+ message.append(fmt::format("\n product: {:#04x}", device->deviceinfo.usb->product));
+ }
+ else if (device->bustype == DRM_BUS_PLATFORM)
+ {
+ message.append("\n platform:");
+ message.append(fmt::format("\n fullname: {}", device->businfo.platform->fullname));
+ }
+ else if (device->bustype == DRM_BUS_HOST1X)
+ {
+ message.append("\n host1x:");
+ message.append(fmt::format("\n fullname: {}", device->businfo.host1x->fullname));
+ }
+ else
+ message.append("\n unhandled bus type");
+ // clang-format on
+
+ CLog::Log(LOGDEBUG, "{}", message);
+}
+
+bool CDRMUtils::OpenDrm(bool needConnector)
+{
+ int numDevices = drmGetDevices2(0, nullptr, 0);
+ if (numDevices <= 0)
+ {
+ CLog::Log(LOGERROR, "CDRMUtils::{} - no drm devices found: ({})", __FUNCTION__,
+ strerror(errno));
+ return false;
+ }
+
+ CLog::Log(LOGDEBUG, "CDRMUtils::{} - drm devices found: {}", __FUNCTION__, numDevices);
+
+ std::vector<drmDevicePtr> devices(numDevices);
+
+ int ret = drmGetDevices2(0, devices.data(), devices.size());
+ if (ret < 0)
+ {
+ CLog::Log(LOGERROR, "CDRMUtils::{} - drmGetDevices2 return an error: ({})", __FUNCTION__,
+ strerror(errno));
+ return false;
+ }
+
+ for (const auto device : devices)
+ {
+ if (!(device->available_nodes & 1 << DRM_NODE_PRIMARY))
+ continue;
+
+ close(m_fd);
+ m_fd = open(device->nodes[DRM_NODE_PRIMARY], O_RDWR | O_CLOEXEC);
+ if (m_fd < 0)
+ continue;
+
+ if (needConnector)
+ {
+ auto resources = drmModeGetResources(m_fd);
+ if (!resources)
+ continue;
+
+ m_connectors.clear();
+ for (int i = 0; i < resources->count_connectors; i++)
+ m_connectors.emplace_back(std::make_unique<CDRMConnector>(m_fd, resources->connectors[i]));
+
+ drmModeFreeResources(resources);
+
+ if (!FindConnector())
+ continue;
+ }
+
+ CLog::Log(LOGDEBUG, "CDRMUtils::{} - opened device: {}", __FUNCTION__,
+ device->nodes[DRM_NODE_PRIMARY]);
+
+ PrintDrmDeviceInfo(device);
+
+ const char* renderPath = drmGetRenderDeviceNameFromFd(m_fd);
+
+ if (!renderPath)
+ renderPath = drmGetDeviceNameFromFd2(m_fd);
+
+ if (!renderPath)
+ renderPath = drmGetDeviceNameFromFd(m_fd);
+
+ if (renderPath)
+ {
+ m_renderDevicePath = renderPath;
+ m_renderFd = open(renderPath, O_RDWR | O_CLOEXEC);
+ if (m_renderFd != 0)
+ CLog::Log(LOGDEBUG, "CDRMUtils::{} - opened render node: {}", __FUNCTION__, renderPath);
+ }
+
+ drmFreeDevices(devices.data(), devices.size());
+ return true;
+ }
+
+ drmFreeDevices(devices.data(), devices.size());
+ return false;
+}
+
+bool CDRMUtils::InitDrm()
+{
+ if (m_fd < 0)
+ return false;
+
+ /* caps need to be set before allocating connectors, encoders, crtcs, and planes */
+ int ret = drmSetClientCap(m_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
+ if (ret)
+ {
+ CLog::Log(LOGERROR, "CDRMUtils::{} - failed to set universal planes capability: {}",
+ __FUNCTION__, strerror(errno));
+ return false;
+ }
+
+ ret = drmSetClientCap(m_fd, DRM_CLIENT_CAP_STEREO_3D, 1);
+ if (ret)
+ {
+ CLog::Log(LOGERROR, "CDRMUtils::{} - failed to set stereo 3d capability: {}", __FUNCTION__,
+ strerror(errno));
+ return false;
+ }
+
+#if defined(DRM_CLIENT_CAP_ASPECT_RATIO)
+ ret = drmSetClientCap(m_fd, DRM_CLIENT_CAP_ASPECT_RATIO, 0);
+ if (ret != 0)
+ CLog::Log(LOGERROR, "CDRMUtils::{} - aspect ratio capability is not supported: {}",
+ __FUNCTION__, strerror(errno));
+#endif
+
+ auto resources = drmModeGetResources(m_fd);
+ if (!resources)
+ {
+ CLog::Log(LOGERROR, "CDRMUtils::{} - failed to get drm resources: {}", __FUNCTION__, strerror(errno));
+ return false;
+ }
+
+ m_connectors.clear();
+ for (int i = 0; i < resources->count_connectors; i++)
+ m_connectors.emplace_back(std::make_unique<CDRMConnector>(m_fd, resources->connectors[i]));
+
+ m_encoders.clear();
+ for (int i = 0; i < resources->count_encoders; i++)
+ m_encoders.emplace_back(std::make_unique<CDRMEncoder>(m_fd, resources->encoders[i]));
+
+ m_crtcs.clear();
+ for (int i = 0; i < resources->count_crtcs; i++)
+ m_crtcs.emplace_back(std::make_unique<CDRMCrtc>(m_fd, resources->crtcs[i]));
+
+ drmModeFreeResources(resources);
+
+ auto planeResources = drmModeGetPlaneResources(m_fd);
+ if (!planeResources)
+ {
+ CLog::Log(LOGERROR, "CDRMUtils::{} - failed to get drm plane resources: {}", __FUNCTION__, strerror(errno));
+ return false;
+ }
+
+ m_planes.clear();
+ for (uint32_t i = 0; i < planeResources->count_planes; i++)
+ {
+ m_planes.emplace_back(std::make_unique<CDRMPlane>(m_fd, planeResources->planes[i]));
+ m_planes[i]->FindModifiers();
+ }
+
+ drmModeFreePlaneResources(planeResources);
+
+ if (!FindConnector())
+ return false;
+
+ if (!FindEncoder())
+ return false;
+
+ if (!FindCrtc())
+ return false;
+
+ if (!FindPlanes())
+ return false;
+
+ if (!FindPreferredMode())
+ return false;
+
+ ret = drmSetMaster(m_fd);
+ if (ret < 0)
+ {
+ CLog::Log(LOGDEBUG,
+ "CDRMUtils::{} - failed to set drm master, will try to authorize instead: {}",
+ __FUNCTION__, strerror(errno));
+
+ drm_magic_t magic;
+
+ ret = drmGetMagic(m_fd, &magic);
+ if (ret < 0)
+ {
+ CLog::Log(LOGERROR, "CDRMUtils::{} - failed to get drm magic: {}", __FUNCTION__,
+ strerror(errno));
+ return false;
+ }
+
+ ret = drmAuthMagic(m_fd, magic);
+ if (ret < 0)
+ {
+ CLog::Log(LOGERROR, "CDRMUtils::{} - failed to authorize drm magic: {}", __FUNCTION__,
+ strerror(errno));
+ return false;
+ }
+
+ CLog::Log(LOGINFO, "CDRMUtils::{} - successfully authorized drm magic", __FUNCTION__);
+ }
+
+ return true;
+}
+
+bool CDRMUtils::FindConnector()
+{
+ auto settingsComponent = CServiceBroker::GetSettingsComponent();
+ if (!settingsComponent)
+ return false;
+
+ auto settings = settingsComponent->GetSettings();
+ if (!settings)
+ return false;
+
+ std::vector<std::unique_ptr<CDRMConnector>>::iterator connector;
+
+ std::string connectorName = settings->GetString(CSettings::SETTING_VIDEOSCREEN_MONITOR);
+ if (connectorName != "Default")
+ {
+ connector = std::find_if(m_connectors.begin(), m_connectors.end(),
+ [&connectorName](auto& connector)
+ {
+ return connector->GetEncoderId() > 0 && connector->IsConnected() &&
+ connector->GetName() == connectorName;
+ });
+ }
+
+ if (connector == m_connectors.end())
+ {
+ CLog::Log(LOGDEBUG, "CDRMUtils::{} - failed to find specified connector: {}, trying default",
+ __FUNCTION__, connectorName);
+ connectorName = "Default";
+ }
+
+ if (connectorName == "Default")
+ {
+ connector = std::find_if(m_connectors.begin(), m_connectors.end(),
+ [](auto& connector)
+ { return connector->GetEncoderId() > 0 && connector->IsConnected(); });
+ }
+
+ if (connector == m_connectors.end())
+ {
+ CLog::Log(LOGDEBUG, "CDRMUtils::{} - failed to find connected connector", __FUNCTION__);
+ return false;
+ }
+
+ CLog::Log(LOGINFO, "CDRMUtils::{} - using connector: {}", __FUNCTION__,
+ connector->get()->GetName());
+
+ m_connector = connector->get();
+ return true;
+}
+
+bool CDRMUtils::FindEncoder()
+{
+ auto encoder = std::find_if(m_encoders.begin(), m_encoders.end(), [this](auto& encoder) {
+ return encoder->GetEncoderId() == m_connector->GetEncoderId();
+ });
+
+ if (encoder == m_encoders.end())
+ {
+ CLog::Log(LOGDEBUG, "CDRMUtils::{} - failed to find encoder for connector id: {}", __FUNCTION__,
+ *m_connector->GetConnectorId());
+ return false;
+ }
+
+ CLog::Log(LOGINFO, "CDRMUtils::{} - using encoder: {}", __FUNCTION__,
+ encoder->get()->GetEncoderId());
+
+ m_encoder = encoder->get();
+ return true;
+}
+
+bool CDRMUtils::FindCrtc()
+{
+ for (size_t i = 0; i < m_crtcs.size(); i++)
+ {
+ if (m_encoder->GetPossibleCrtcs() & (1 << i))
+ {
+ if (m_crtcs[i]->GetCrtcId() == m_encoder->GetCrtcId())
+ {
+ m_orig_crtc = m_crtcs[i].get();
+ if (m_orig_crtc->GetModeValid())
+ {
+ m_mode = m_orig_crtc->GetMode();
+ CLog::Log(LOGDEBUG, "CDRMUtils::{} - original crtc mode: {}x{}{} @ {} Hz", __FUNCTION__,
+ m_mode->hdisplay, m_mode->vdisplay,
+ m_mode->flags & DRM_MODE_FLAG_INTERLACE ? "i" : "", m_mode->vrefresh);
+ }
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+bool CDRMUtils::RestoreOriginalMode()
+{
+ if(!m_orig_crtc)
+ {
+ return false;
+ }
+
+ auto ret = drmModeSetCrtc(m_fd, m_orig_crtc->GetCrtcId(), m_orig_crtc->GetBufferId(),
+ m_orig_crtc->GetX(), m_orig_crtc->GetY(), m_connector->GetConnectorId(),
+ 1, m_orig_crtc->GetMode());
+
+ if(ret)
+ {
+ CLog::Log(LOGERROR, "CDRMUtils::{} - failed to set original crtc mode", __FUNCTION__);
+ return false;
+ }
+
+ CLog::Log(LOGDEBUG, "CDRMUtils::{} - set original crtc mode", __FUNCTION__);
+
+ return true;
+}
+
+void CDRMUtils::DestroyDrm()
+{
+ RestoreOriginalMode();
+
+ if (drmAuthMagic(m_fd, 0) == EINVAL)
+ drmDropMaster(m_fd);
+
+ close(m_renderFd);
+ close(m_fd);
+
+ m_connector = nullptr;
+ m_encoder = nullptr;
+ m_crtc = nullptr;
+ m_orig_crtc = nullptr;
+ m_video_plane = nullptr;
+ m_gui_plane = nullptr;
+}
+
+RESOLUTION_INFO CDRMUtils::GetResolutionInfo(drmModeModeInfoPtr mode)
+{
+ RESOLUTION_INFO res;
+ res.iScreenWidth = mode->hdisplay;
+ res.iScreenHeight = mode->vdisplay;
+ res.iWidth = res.iScreenWidth;
+ res.iHeight = res.iScreenHeight;
+
+ int limit = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
+ SETTING_VIDEOSCREEN_LIMITGUISIZE);
+ if (limit > 0 && res.iScreenWidth > 1920 && res.iScreenHeight > 1080)
+ {
+ switch (limit)
+ {
+ case 1: // 720p
+ res.iWidth = 1280;
+ res.iHeight = 720;
+ break;
+ case 2: // 1080p / 720p (>30hz)
+ res.iWidth = mode->vrefresh > 30 ? 1280 : 1920;
+ res.iHeight = mode->vrefresh > 30 ? 720 : 1080;
+ break;
+ case 3: // 1080p
+ res.iWidth = 1920;
+ res.iHeight = 1080;
+ break;
+ case 4: // Unlimited / 1080p (>30hz)
+ res.iWidth = mode->vrefresh > 30 ? 1920 : res.iScreenWidth;
+ res.iHeight = mode->vrefresh > 30 ? 1080 : res.iScreenHeight;
+ break;
+ }
+ }
+
+ if (mode->clock % 5 != 0)
+ res.fRefreshRate = static_cast<float>(mode->vrefresh) * (1000.0f/1001.0f);
+ else
+ res.fRefreshRate = mode->vrefresh;
+ res.iSubtitles = res.iHeight;
+ res.fPixelRatio = 1.0f;
+ res.bFullScreen = true;
+
+ if (mode->flags & DRM_MODE_FLAG_3D_MASK)
+ {
+ if (mode->flags & DRM_MODE_FLAG_3D_TOP_AND_BOTTOM)
+ res.dwFlags = D3DPRESENTFLAG_MODE3DTB;
+ else if (mode->flags & DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF)
+ res.dwFlags = D3DPRESENTFLAG_MODE3DSBS;
+ }
+ else if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+ res.dwFlags = D3DPRESENTFLAG_INTERLACED;
+ else
+ res.dwFlags = D3DPRESENTFLAG_PROGRESSIVE;
+
+ res.strMode =
+ StringUtils::Format("{}x{}{} @ {:.6f} Hz", res.iScreenWidth, res.iScreenHeight,
+ res.dwFlags & D3DPRESENTFLAG_INTERLACED ? "i" : "", res.fRefreshRate);
+ return res;
+}
+
+RESOLUTION_INFO CDRMUtils::GetCurrentMode()
+{
+ return GetResolutionInfo(m_mode);
+}
+
+std::vector<RESOLUTION_INFO> CDRMUtils::GetModes()
+{
+ std::vector<RESOLUTION_INFO> resolutions;
+ resolutions.reserve(m_connector->GetModesCount());
+
+ for (auto i = 0; i < m_connector->GetModesCount(); i++)
+ {
+ RESOLUTION_INFO res = GetResolutionInfo(m_connector->GetModeForIndex(i));
+ res.strId = std::to_string(i);
+ resolutions.push_back(res);
+ }
+
+ return resolutions;
+}
+
+std::vector<std::string> CDRMUtils::GetConnectedConnectorNames()
+{
+ std::vector<std::string> connectorNames;
+ for (const auto& connector : m_connectors)
+ {
+ if (connector->IsConnected())
+ connectorNames.emplace_back(connector->GetName());
+ }
+
+ return connectorNames;
+}
+
+uint32_t CDRMUtils::FourCCWithAlpha(uint32_t fourcc)
+{
+ return (fourcc & 0xFFFFFF00) | static_cast<uint32_t>('A');
+}
+
+uint32_t CDRMUtils::FourCCWithoutAlpha(uint32_t fourcc)
+{
+ return (fourcc & 0xFFFFFF00) | static_cast<uint32_t>('X');
+}
diff --git a/xbmc/windowing/gbm/drm/DRMUtils.h b/xbmc/windowing/gbm/drm/DRMUtils.h
new file mode 100644
index 0000000..5327e35
--- /dev/null
+++ b/xbmc/windowing/gbm/drm/DRMUtils.h
@@ -0,0 +1,103 @@
+/*
+ * 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 "DRMConnector.h"
+#include "DRMCrtc.h"
+#include "DRMEncoder.h"
+#include "DRMPlane.h"
+#include "windowing/Resolution.h"
+#include "windowing/gbm/GBMUtils.h"
+
+#include <vector>
+
+#include <gbm.h>
+#include <xf86drm.h>
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace GBM
+{
+
+struct drm_fb
+{
+ struct gbm_bo *bo = nullptr;
+ uint32_t fb_id;
+ uint32_t format;
+};
+
+class CDRMUtils
+{
+public:
+ CDRMUtils() = default;
+ virtual ~CDRMUtils();
+ virtual void FlipPage(struct gbm_bo* bo, bool rendered, bool videoLayer) {}
+ virtual bool SetVideoMode(const RESOLUTION_INFO& res, struct gbm_bo* bo) { return false; }
+ virtual bool SetActive(bool active) { return false; }
+ virtual bool InitDrm();
+ virtual void DestroyDrm();
+
+ int GetFileDescriptor() const { return m_fd; }
+ int GetRenderNodeFileDescriptor() const { return m_renderFd; }
+ const char* GetRenderDevicePath() const { return m_renderDevicePath; }
+ CDRMPlane* GetVideoPlane() const { return m_video_plane; }
+ CDRMPlane* GetGuiPlane() const { return m_gui_plane; }
+ CDRMCrtc* GetCrtc() const { return m_crtc; }
+ CDRMConnector* GetConnector() const { return m_connector; }
+
+ std::vector<std::string> GetConnectedConnectorNames();
+
+ virtual RESOLUTION_INFO GetCurrentMode();
+ virtual std::vector<RESOLUTION_INFO> GetModes();
+ virtual bool SetMode(const RESOLUTION_INFO& res);
+
+ static uint32_t FourCCWithAlpha(uint32_t fourcc);
+ static uint32_t FourCCWithoutAlpha(uint32_t fourcc);
+
+protected:
+ bool OpenDrm(bool needConnector);
+ drm_fb* DrmFbGetFromBo(struct gbm_bo *bo);
+
+ int m_fd;
+ CDRMConnector* m_connector{nullptr};
+ CDRMEncoder* m_encoder{nullptr};
+ CDRMCrtc* m_crtc{nullptr};
+ CDRMCrtc* m_orig_crtc{nullptr};
+ CDRMPlane* m_video_plane{nullptr};
+ CDRMPlane* m_gui_plane{nullptr};
+ drmModeModeInfo *m_mode = nullptr;
+
+ int m_width = 0;
+ int m_height = 0;
+
+ std::vector<std::unique_ptr<CDRMPlane>> m_planes;
+
+private:
+ bool FindConnector();
+ bool FindEncoder();
+ bool FindCrtc();
+ bool FindPlanes();
+ bool FindPreferredMode();
+ bool RestoreOriginalMode();
+ RESOLUTION_INFO GetResolutionInfo(drmModeModeInfoPtr mode);
+ void PrintDrmDeviceInfo(drmDevicePtr device);
+
+ int m_renderFd;
+ const char* m_renderDevicePath{nullptr};
+
+ std::vector<std::unique_ptr<CDRMConnector>> m_connectors;
+ std::vector<std::unique_ptr<CDRMEncoder>> m_encoders;
+ std::vector<std::unique_ptr<CDRMCrtc>> m_crtcs;
+};
+
+}
+}
+}
diff --git a/xbmc/windowing/gbm/drm/OffScreenModeSetting.cpp b/xbmc/windowing/gbm/drm/OffScreenModeSetting.cpp
new file mode 100644
index 0000000..dc69fdc
--- /dev/null
+++ b/xbmc/windowing/gbm/drm/OffScreenModeSetting.cpp
@@ -0,0 +1,52 @@
+/*
+ * 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 "OffScreenModeSetting.h"
+
+#include "utils/log.h"
+
+using namespace KODI::WINDOWING::GBM;
+
+namespace
+{
+constexpr int DISPLAY_WIDTH = 1280;
+constexpr int DISPLAY_HEIGHT = 720;
+constexpr float DISPLAY_REFRESH = 50.0f;
+} // namespace
+
+bool COffScreenModeSetting::InitDrm()
+{
+ if (!CDRMUtils::OpenDrm(false))
+ return false;
+
+ CLog::Log(LOGDEBUG, "COffScreenModeSetting::{} - initialized offscreen DRM", __FUNCTION__);
+ return true;
+}
+
+std::vector<RESOLUTION_INFO> COffScreenModeSetting::GetModes()
+{
+ std::vector<RESOLUTION_INFO> resolutions;
+ resolutions.push_back(GetCurrentMode());
+ return resolutions;
+}
+
+RESOLUTION_INFO COffScreenModeSetting::GetCurrentMode()
+{
+ RESOLUTION_INFO res;
+ res.iScreenWidth = DISPLAY_WIDTH;
+ res.iWidth = DISPLAY_WIDTH;
+ res.iScreenHeight = DISPLAY_HEIGHT;
+ res.iHeight = DISPLAY_HEIGHT;
+ res.fRefreshRate = DISPLAY_REFRESH;
+ res.iSubtitles = res.iHeight;
+ res.fPixelRatio = 1.0f;
+ res.bFullScreen = true;
+ res.strId = "0";
+
+ return res;
+}
diff --git a/xbmc/windowing/gbm/drm/OffScreenModeSetting.h b/xbmc/windowing/gbm/drm/OffScreenModeSetting.h
new file mode 100644
index 0000000..4270d4e
--- /dev/null
+++ b/xbmc/windowing/gbm/drm/OffScreenModeSetting.h
@@ -0,0 +1,38 @@
+/*
+ * 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 "DRMUtils.h"
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace GBM
+{
+
+class COffScreenModeSetting : public CDRMUtils
+{
+public:
+ COffScreenModeSetting() = default;
+ ~COffScreenModeSetting() override = default;
+ void FlipPage(struct gbm_bo *bo, bool rendered, bool videoLayer) override {}
+ bool SetVideoMode(const RESOLUTION_INFO& res, struct gbm_bo *bo) override { return false; }
+ bool SetActive(bool active) override { return false; }
+ bool InitDrm() override;
+ void DestroyDrm() override {}
+
+ RESOLUTION_INFO GetCurrentMode() override;
+ std::vector<RESOLUTION_INFO> GetModes() override;
+ bool SetMode(const RESOLUTION_INFO& res) override { return true; }
+};
+
+}
+}
+}
diff --git a/xbmc/windowing/ios/CMakeLists.txt b/xbmc/windowing/ios/CMakeLists.txt
new file mode 100644
index 0000000..0cdd0d6
--- /dev/null
+++ b/xbmc/windowing/ios/CMakeLists.txt
@@ -0,0 +1,8 @@
+set(SOURCES WinEventsIOS.mm
+ WinSystemIOS.mm
+ VideoSyncIos.cpp)
+set(HEADERS WinEventsIOS.h
+ WinSystemIOS.h
+ VideoSyncIos.h)
+
+core_add_library(windowing_ios)
diff --git a/xbmc/windowing/ios/VideoSyncIos.cpp b/xbmc/windowing/ios/VideoSyncIos.cpp
new file mode 100644
index 0000000..a3cac13
--- /dev/null
+++ b/xbmc/windowing/ios/VideoSyncIos.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2015-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 "VideoSyncIos.h"
+
+#include "cores/VideoPlayer/VideoReferenceClock.h"
+#include "utils/MathUtils.h"
+#include "utils/TimeUtils.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+#include "windowing/ios/WinSystemIOS.h"
+
+bool CVideoSyncIos::Setup(PUPDATECLOCK func)
+{
+ CLog::Log(LOGDEBUG, "CVideoSyncIos::{} setting up OSX", __FUNCTION__);
+
+ //init the vblank timestamp
+ m_LastVBlankTime = CurrentHostCounter();
+ UpdateClock = func;
+ m_abortEvent.Reset();
+
+ bool setupOk = InitDisplayLink();
+ if (setupOk)
+ {
+ m_winSystem.Register(this);
+ }
+
+ return setupOk;
+}
+
+void CVideoSyncIos::Run(CEvent& stopEvent)
+{
+ //because cocoa has a vblank callback, we just keep sleeping until we're asked to stop the thread
+ XbmcThreads::CEventGroup waitGroup{&stopEvent, &m_abortEvent};
+ waitGroup.wait();
+}
+
+void CVideoSyncIos::Cleanup()
+{
+ CLog::Log(LOGDEBUG, "CVideoSyncIos::{} cleaning up OSX", __FUNCTION__);
+ DeinitDisplayLink();
+ m_winSystem.Unregister(this);
+}
+
+float CVideoSyncIos::GetFps()
+{
+ m_fps = CServiceBroker::GetWinSystem()->GetGfxContext().GetFPS();
+ CLog::Log(LOGDEBUG, "CVideoSyncIos::{} Detected refreshrate: {:f} hertz", __FUNCTION__, m_fps);
+ return m_fps;
+}
+
+void CVideoSyncIos::OnResetDisplay()
+{
+ m_abortEvent.Set();
+}
+
+void CVideoSyncIos::IosVblankHandler()
+{
+ int NrVBlanks;
+ double VBlankTime;
+ int64_t nowtime = CurrentHostCounter();
+
+ //calculate how many vblanks happened
+ VBlankTime = (double)(nowtime - m_LastVBlankTime) / (double)CurrentHostFrequency();
+ NrVBlanks = MathUtils::round_int(VBlankTime * static_cast<double>(m_fps));
+
+ //save the timestamp of this vblank so we can calculate how many happened next time
+ m_LastVBlankTime = nowtime;
+
+ //update the vblank timestamp, update the clock and send a signal that we got a vblank
+ UpdateClock(NrVBlanks, nowtime, m_refClock);
+}
+
+bool CVideoSyncIos::InitDisplayLink()
+{
+ bool ret = true;
+ CLog::Log(LOGDEBUG, "CVideoSyncIos: setting up displaylink");
+ if (!m_winSystem.InitDisplayLink(this))
+ {
+ CLog::Log(LOGDEBUG, "CVideoSyncIos: InitDisplayLink failed");
+ ret = false;
+ }
+ return ret;
+}
+
+void CVideoSyncIos::DeinitDisplayLink()
+{
+ m_winSystem.DeinitDisplayLink();
+}
+
diff --git a/xbmc/windowing/ios/VideoSyncIos.h b/xbmc/windowing/ios/VideoSyncIos.h
new file mode 100644
index 0000000..0828e34
--- /dev/null
+++ b/xbmc/windowing/ios/VideoSyncIos.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2015-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"
+
+class CWinSystemIOS;
+
+class CVideoSyncIos : public CVideoSync, IDispResource
+{
+public:
+ CVideoSyncIos(void *clock, CWinSystemIOS &winSystem) :
+ CVideoSync(clock), m_winSystem(winSystem) {}
+
+ // CVideoSync interface
+ bool Setup(PUPDATECLOCK func) override;
+ void Run(CEvent& stopEvent) override;
+ void Cleanup() override;
+ float GetFps() override;
+
+ // IDispResource interface
+ void OnResetDisplay() override;
+
+ // used in the displaylink callback
+ void IosVblankHandler();
+
+private:
+ // CVideoSyncDarwin interface
+ virtual bool InitDisplayLink();
+ virtual void DeinitDisplayLink();
+
+ int64_t m_LastVBlankTime = 0; //timestamp of the last vblank, used for calculating how many vblanks happened
+ CEvent m_abortEvent;
+ CWinSystemIOS &m_winSystem;
+};
+
diff --git a/xbmc/windowing/ios/WinEventsIOS.h b/xbmc/windowing/ios/WinEventsIOS.h
new file mode 100644
index 0000000..98ec4dc
--- /dev/null
+++ b/xbmc/windowing/ios/WinEventsIOS.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2012-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 "windowing/WinEvents.h"
+
+class CWinEventsIOS : public IWinEvents
+{
+public:
+ bool MessagePump() override;
+private:
+ size_t GetQueueSize();
+};
+
diff --git a/xbmc/windowing/ios/WinEventsIOS.mm b/xbmc/windowing/ios/WinEventsIOS.mm
new file mode 100644
index 0000000..ec416a6
--- /dev/null
+++ b/xbmc/windowing/ios/WinEventsIOS.mm
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2012-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 "WinEventsIOS.h"
+
+#include "application/AppInboundProtocol.h"
+#include "guilib/GUIWindowManager.h"
+#include "input/InputManager.h"
+#include "input/XBMC_vkeys.h"
+#include "threads/CriticalSection.h"
+#include "utils/log.h"
+
+#include <list>
+#include <mutex>
+
+static CCriticalSection g_inputCond;
+
+static std::list<XBMC_Event> events;
+
+bool CWinEventsIOS::MessagePump()
+{
+ bool ret = false;
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+
+ // Do not always loop, only pump the initial queued count events. else if ui keep pushing
+ // events the loop won't finish then it will block xbmc main message loop.
+ for (size_t pumpEventCount = GetQueueSize(); pumpEventCount > 0; --pumpEventCount)
+ {
+ // Pop up only one event per time since in App::OnEvent it may init modal dialog which init
+ // deeper message loop and call the deeper MessagePump from there.
+ XBMC_Event pumpEvent;
+ {
+ std::unique_lock<CCriticalSection> lock(g_inputCond);
+ if (events.empty())
+ return ret;
+ pumpEvent = events.front();
+ events.pop_front();
+ }
+
+ if (appPort)
+ ret = appPort->OnEvent(pumpEvent);
+ }
+ return ret;
+}
+
+size_t CWinEventsIOS::GetQueueSize()
+{
+ std::unique_lock<CCriticalSection> lock(g_inputCond);
+ return events.size();
+}
diff --git a/xbmc/windowing/ios/WinSystemIOS.h b/xbmc/windowing/ios/WinSystemIOS.h
new file mode 100644
index 0000000..8320b60
--- /dev/null
+++ b/xbmc/windowing/ios/WinSystemIOS.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2010-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 "rendering/gles/RenderSystemGLES.h"
+#include "threads/CriticalSection.h"
+#include "windowing/WinSystem.h"
+
+#include <string>
+#include <vector>
+
+#include <CoreVideo/CVOpenGLESTextureCache.h>
+
+class IDispResource;
+class CVideoSyncIos;
+struct CADisplayLinkWrapper;
+
+class CWinSystemIOS : public CWinSystemBase, public CRenderSystemGLES
+{
+public:
+ CWinSystemIOS();
+ ~CWinSystemIOS() override;
+
+ static void Register();
+ static std::unique_ptr<CWinSystemBase> CreateWinSystem();
+
+ int GetDisplayIndexFromSettings();
+ // Implementation of CWinSystemBase
+ CRenderSystemBase *GetRenderSystem() override { return this; }
+ 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;
+ bool SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) override;
+ void UpdateResolutions() override;
+ bool CanDoWindowed() override { return false; }
+
+ void ShowOSMouse(bool show) override {}
+ bool HasCursor() override;
+
+ void NotifyAppActiveChange(bool bActivated) override;
+
+ bool Minimize() override;
+ bool Restore() override;
+ bool Hide() override;
+ bool Show(bool raise = true) override;
+
+ bool IsExtSupported(const char* extension) const override;
+
+ bool BeginRender() override;
+ bool EndRender() override;
+
+ void Register(IDispResource *resource) override;
+ void Unregister(IDispResource *resource) override;
+
+ std::unique_ptr<CVideoSync> GetVideoSync(void* clock) override;
+
+ std::vector<std::string> GetConnectedOutputs() override;
+
+ bool InitDisplayLink(CVideoSyncIos *syncImpl);
+ void DeinitDisplayLink(void);
+ void OnAppFocusChange(bool focus);
+ bool IsBackgrounded() const { return m_bIsBackgrounded; }
+ CVEAGLContext GetEAGLContextObj();
+ void MoveToTouchscreen();
+
+ // winevents override
+ bool MessagePump() override;
+
+protected:
+ void PresentRenderImpl(bool rendered) override;
+ void SetVSyncImpl(bool enable) override {}
+
+ void *m_glView; // EAGLView opaque
+ void *m_WorkingContext; // shared EAGLContext opaque
+ bool m_bWasFullScreenBeforeMinimize;
+ std::string m_eglext;
+ CCriticalSection m_resourceSection;
+ std::vector<IDispResource*> m_resources;
+ bool m_bIsBackgrounded;
+
+private:
+ bool GetScreenResolution(int* w, int* h, double* fps, int screenIdx);
+ void FillInVideoModes(int screenIdx);
+ bool SwitchToVideoMode(int width, int height, double refreshrate);
+ CADisplayLinkWrapper *m_pDisplayLink;
+ int m_internalTouchscreenResolutionWidth = -1;
+ int m_internalTouchscreenResolutionHeight = -1;
+};
+
diff --git a/xbmc/windowing/ios/WinSystemIOS.mm b/xbmc/windowing/ios/WinSystemIOS.mm
new file mode 100644
index 0000000..805c573
--- /dev/null
+++ b/xbmc/windowing/ios/WinSystemIOS.mm
@@ -0,0 +1,501 @@
+/*
+ * Copyright (C) 2010-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 "WinSystemIOS.h"
+
+#include "ServiceBroker.h"
+#include "VideoSyncIos.h"
+#include "WinEventsIOS.h"
+#include "cores/AudioEngine/Sinks/AESinkDARWINIOS.h"
+#include "cores/RetroPlayer/process/ios/RPProcessInfoIOS.h"
+#include "cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGLES.h"
+#include "cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.h"
+#include "cores/VideoPlayer/DVDCodecs/Video/VTB.h"
+#include "cores/VideoPlayer/Process/ios/ProcessInfoIOS.h"
+#include "cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVTBGLES.h"
+#include "cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.h"
+#include "cores/VideoPlayer/VideoRenderers/RenderFactory.h"
+#include "filesystem/SpecialProtocol.h"
+#include "guilib/DispResource.h"
+#include "guilib/Texture.h"
+#include "messaging/ApplicationMessenger.h"
+#include "rendering/gles/ScreenshotSurfaceGLES.h"
+#include "settings/DisplaySettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+#include "windowing/WindowSystemFactory.h"
+
+#import "platform/darwin/ios/IOSScreenManager.h"
+#import "platform/darwin/ios/XBMCController.h"
+
+#include <mutex>
+#include <vector>
+
+#import <Foundation/Foundation.h>
+#import <OpenGLES/ES2/gl.h>
+#import <OpenGLES/ES2/glext.h>
+#import <QuartzCore/CADisplayLink.h>
+#import <dlfcn.h>
+
+#define CONST_TOUCHSCREEN "Touchscreen"
+#define CONST_EXTERNAL "External"
+
+// IOSDisplayLinkCallback is declared in the lower part of the file
+@interface IOSDisplayLinkCallback : NSObject
+{
+@private CVideoSyncIos *_videoSyncImpl;
+}
+@property (nonatomic, setter=SetVideoSyncImpl:) CVideoSyncIos *_videoSyncImpl;
+- (void) runDisplayLink;
+@end
+
+using namespace KODI;
+using namespace MESSAGING;
+
+struct CADisplayLinkWrapper
+{
+ CADisplayLink* impl;
+ IOSDisplayLinkCallback *callbackClass;
+};
+
+void CWinSystemIOS::Register()
+{
+ KODI::WINDOWING::CWindowSystemFactory::RegisterWindowSystem(CreateWinSystem);
+}
+
+std::unique_ptr<CWinSystemBase> CWinSystemIOS::CreateWinSystem()
+{
+ return std::make_unique<CWinSystemIOS>();
+}
+
+int CWinSystemIOS::GetDisplayIndexFromSettings()
+{
+ std::string currentScreen = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_VIDEOSCREEN_MONITOR);
+
+ int screenIdx = 0;
+ if (currentScreen == CONST_EXTERNAL)
+ {
+ if ([[UIScreen screens] count] > 1)
+ {
+ screenIdx = 1;
+ }
+ else// screen 1 is setup but not connected
+ {
+ // force internal screen
+ MoveToTouchscreen();
+ }
+ }
+
+ return screenIdx;
+}
+
+CWinSystemIOS::CWinSystemIOS() : CWinSystemBase()
+{
+ m_bIsBackgrounded = false;
+ m_pDisplayLink = new CADisplayLinkWrapper;
+ m_pDisplayLink->callbackClass = [[IOSDisplayLinkCallback alloc] init];
+ m_winEvents.reset(new CWinEventsIOS());
+
+ CAESinkDARWINIOS::Register();
+}
+
+CWinSystemIOS::~CWinSystemIOS()
+{
+ delete m_pDisplayLink;
+}
+
+bool CWinSystemIOS::InitWindowSystem()
+{
+ return CWinSystemBase::InitWindowSystem();
+}
+
+bool CWinSystemIOS::DestroyWindowSystem()
+{
+ return true;
+}
+
+bool CWinSystemIOS::CreateNewWindow(const std::string& name, bool fullScreen, RESOLUTION_INFO& res)
+{
+ //NSLog(@"%s", __PRETTY_FUNCTION__);
+
+ if(!SetFullScreen(fullScreen, res, false))
+ return false;
+
+ [g_xbmcController setFramebuffer];
+
+ m_bWindowCreated = true;
+
+ m_eglext = " ";
+
+ const char *tmpExtensions = (const char*) glGetString(GL_EXTENSIONS);
+ if (tmpExtensions != NULL)
+ {
+ m_eglext += tmpExtensions;
+ }
+
+ m_eglext += " ";
+
+ CLog::Log(LOGDEBUG, "EGL_EXTENSIONS:{}", m_eglext);
+
+ // register platform dependent objects
+ CDVDFactoryCodec::ClearHWAccels();
+ VTB::CDecoder::Register();
+ VIDEOPLAYER::CRendererFactory::ClearRenderer();
+ CLinuxRendererGLES::Register();
+ CRendererVTB::Register();
+ VIDEOPLAYER::CProcessInfoIOS::Register();
+ RETRO::CRPProcessInfoIOS::Register();
+ RETRO::CRPProcessInfoIOS::RegisterRendererFactory(new RETRO::CRendererFactoryOpenGLES);
+ CScreenshotSurfaceGLES::Register();
+
+ return true;
+}
+
+bool CWinSystemIOS::DestroyWindow()
+{
+ return true;
+}
+
+bool CWinSystemIOS::ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop)
+{
+ //NSLog(@"%s", __PRETTY_FUNCTION__);
+
+ if (m_nWidth != newWidth || m_nHeight != newHeight)
+ {
+ m_nWidth = newWidth;
+ m_nHeight = newHeight;
+ }
+
+ CRenderSystemGLES::ResetRenderSystem(newWidth, newHeight);
+
+ return true;
+}
+
+bool CWinSystemIOS::SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays)
+{
+ //NSLog(@"%s", __PRETTY_FUNCTION__);
+
+ m_nWidth = res.iWidth;
+ m_nHeight = res.iHeight;
+ m_bFullScreen = fullScreen;
+
+ CLog::Log(LOGDEBUG, "About to switch to {} x {}", m_nWidth, m_nHeight);
+ SwitchToVideoMode(res.iWidth, res.iHeight, static_cast<double>(res.fRefreshRate));
+ CRenderSystemGLES::ResetRenderSystem(res.iWidth, res.iHeight);
+
+ return true;
+}
+
+UIScreenMode *getModeForResolution(int width, int height, unsigned int screenIdx)
+{
+ auto screen = UIScreen.screens[screenIdx];
+ for (UIScreenMode* mode in screen.availableModes)
+ {
+ //for main screen also find modes where width and height are
+ //exchanged (because of the 90°degree rotated buildinscreens)
+ auto modeSize = mode.size;
+ if ((modeSize.width == width && modeSize.height == height) ||
+ (screenIdx == 0 && modeSize.width == height && modeSize.height == width))
+ {
+ CLog::Log(LOGDEBUG, "Found matching mode: {} x {}", modeSize.width, modeSize.height);
+ return mode;
+ }
+ }
+ CLog::Log(LOGERROR,"No matching mode found!");
+ return nil;
+}
+
+bool CWinSystemIOS::SwitchToVideoMode(int width, int height, double refreshrate)
+{
+ bool ret = false;
+ int screenIdx = GetDisplayIndexFromSettings();
+
+ //get the mode to pass to the controller
+ UIScreenMode *newMode = getModeForResolution(width, height, screenIdx);
+
+ if(newMode)
+ {
+ ret = [g_xbmcController changeScreen:screenIdx withMode:newMode];
+ }
+ return ret;
+}
+
+bool CWinSystemIOS::GetScreenResolution(int* w, int* h, double* fps, int screenIdx)
+{
+ UIScreen *screen = [[UIScreen screens] objectAtIndex:screenIdx];
+ CGSize screenSize = [screen currentMode].size;
+ *w = screenSize.width;
+ *h = screenSize.height;
+ *fps = 0.0;
+
+ //if current mode is 0x0 (happens with external screens which aren't active)
+ //then use the preferred mode
+ if(*h == 0 || *w ==0)
+ {
+ UIScreenMode *firstMode = [screen preferredMode];
+ *w = firstMode.size.width;
+ *h = firstMode.size.height;
+ }
+
+ // for mainscreen use the eagl bounds from xbmcController
+ // because mainscreen is might be 90° rotate dependend on
+ // the device and eagl gives the correct values in all cases.
+ if(screenIdx == 0)
+ {
+ // at very first start up we cache the internal screen resolution
+ // because when using external screens and need to go back
+ // to internal we are not able to determine the eagl bounds
+ // before we really switched back to internal
+ // but display settings ask for the internal resolution before
+ // switching. So we give the cached values back in that case.
+ if (m_internalTouchscreenResolutionWidth == -1 &&
+ m_internalTouchscreenResolutionHeight == -1)
+ {
+ m_internalTouchscreenResolutionWidth = [g_xbmcController getScreenSize].width;
+ m_internalTouchscreenResolutionHeight = [g_xbmcController getScreenSize].height;
+ }
+
+ *w = m_internalTouchscreenResolutionWidth;
+ *h = m_internalTouchscreenResolutionHeight;
+ }
+ CLog::Log(LOGDEBUG, "Current resolution Screen: {} with {} x {}", screenIdx, *w, *h);
+ return true;
+}
+
+void CWinSystemIOS::UpdateResolutions()
+{
+ // Add display resolution
+ int w, h;
+ double fps;
+ CWinSystemBase::UpdateResolutions();
+
+ int screenIdx = GetDisplayIndexFromSettings();
+
+ //first screen goes into the current desktop mode
+ if(GetScreenResolution(&w, &h, &fps, screenIdx))
+ UpdateDesktopResolution(CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP), screenIdx == 0 ? CONST_TOUCHSCREEN : CONST_EXTERNAL, w, h, fps, 0);
+
+ CDisplaySettings::GetInstance().ClearCustomResolutions();
+
+ //now just fill in the possible resolutions for the attached screens
+ //and push to the resolution info vector
+ FillInVideoModes(screenIdx);
+}
+
+void CWinSystemIOS::FillInVideoModes(int screenIdx)
+{
+ // Add full screen settings for additional monitors
+ RESOLUTION_INFO res;
+ int w, h;
+ // atm we don't get refreshrate info from iOS
+ // but this may change in the future. In that case
+ // we will adapt this code for filling some
+ // useful info into this local var :)
+ double refreshrate = 0.0;
+ //screen 0 is mainscreen - 1 has to be the external one...
+ UIScreen *aScreen = [[UIScreen screens]objectAtIndex:screenIdx];
+ //found external screen
+ for ( UIScreenMode *mode in [aScreen availableModes] )
+ {
+ w = mode.size.width;
+ h = mode.size.height;
+
+ UpdateDesktopResolution(res, screenIdx == 0 ? CONST_TOUCHSCREEN : CONST_EXTERNAL, w, h, refreshrate, 0);
+ CLog::Log(LOGINFO, "Found possible resolution for display {} with {} x {}", screenIdx, w, h);
+
+ CServiceBroker::GetWinSystem()->GetGfxContext().ResetOverscan(res);
+ CDisplaySettings::GetInstance().AddResolutionInfo(res);
+ }
+}
+
+bool CWinSystemIOS::IsExtSupported(const char* extension) const
+{
+ if(strncmp(extension, "EGL_", 4) != 0)
+ return CRenderSystemGLES::IsExtSupported(extension);
+
+ std::string name;
+
+ name = " ";
+ name += extension;
+ name += " ";
+
+ return m_eglext.find(name) != std::string::npos;
+}
+
+bool CWinSystemIOS::BeginRender()
+{
+ bool rtn;
+
+ [g_xbmcController setFramebuffer];
+
+ rtn = CRenderSystemGLES::BeginRender();
+ return rtn;
+}
+
+bool CWinSystemIOS::EndRender()
+{
+ bool rtn;
+
+ rtn = CRenderSystemGLES::EndRender();
+ return rtn;
+}
+
+void CWinSystemIOS::Register(IDispResource *resource)
+{
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ m_resources.push_back(resource);
+}
+
+void CWinSystemIOS::Unregister(IDispResource* resource)
+{
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ std::vector<IDispResource*>::iterator i = find(m_resources.begin(), m_resources.end(), resource);
+ if (i != m_resources.end())
+ m_resources.erase(i);
+}
+
+void CWinSystemIOS::OnAppFocusChange(bool focus)
+{
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ m_bIsBackgrounded = !focus;
+ CLog::Log(LOGDEBUG, "CWinSystemIOS::OnAppFocusChange: {}", focus ? 1 : 0);
+ for (std::vector<IDispResource *>::iterator i = m_resources.begin(); i != m_resources.end(); i++)
+ (*i)->OnAppFocusChange(focus);
+}
+
+//--------------------------------------------------------------
+//-------------------DisplayLink stuff
+@implementation IOSDisplayLinkCallback
+@synthesize _videoSyncImpl;
+//--------------------------------------------------------------
+- (void) runDisplayLink
+{
+ @autoreleasepool
+ {
+ if (_videoSyncImpl != nil)
+ {
+ _videoSyncImpl->IosVblankHandler();
+ }
+ }
+}
+@end
+
+bool CWinSystemIOS::InitDisplayLink(CVideoSyncIos *syncImpl)
+{
+ //init with the appropriate display link for the
+ //used screen
+ if([[IOSScreenManager sharedInstance] isExternalScreen])
+ {
+ fprintf(stderr,"InitDisplayLink on external");
+ }
+ else
+ {
+ fprintf(stderr,"InitDisplayLink on internal");
+ }
+
+ unsigned int currentScreenIdx = [[IOSScreenManager sharedInstance] GetScreenIdx];
+ UIScreen * currentScreen = [[UIScreen screens] objectAtIndex:currentScreenIdx];
+ [m_pDisplayLink->callbackClass SetVideoSyncImpl:syncImpl];
+ m_pDisplayLink->impl = [currentScreen displayLinkWithTarget:m_pDisplayLink->callbackClass selector:@selector(runDisplayLink)];
+
+ [m_pDisplayLink->impl setPreferredFramesPerSecond:0];
+ [m_pDisplayLink->impl addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
+ return m_pDisplayLink->impl != nil;
+}
+
+void CWinSystemIOS::DeinitDisplayLink(void)
+{
+ if (m_pDisplayLink->impl)
+ {
+ [m_pDisplayLink->impl invalidate];
+ m_pDisplayLink->impl = nil;
+ [m_pDisplayLink->callbackClass SetVideoSyncImpl:nil];
+ }
+}
+//------------DisplayLink stuff end
+//--------------------------------------------------------------
+
+void CWinSystemIOS::PresentRenderImpl(bool rendered)
+{
+ //glFlush;
+ if (rendered)
+ [g_xbmcController presentFramebuffer];
+}
+
+bool CWinSystemIOS::HasCursor()
+{
+ // apple touch devices
+ return false;
+}
+
+void CWinSystemIOS::NotifyAppActiveChange(bool bActivated)
+{
+ if (bActivated && m_bWasFullScreenBeforeMinimize && !CServiceBroker::GetWinSystem()->GetGfxContext().IsFullScreenRoot())
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_TOGGLEFULLSCREEN);
+}
+
+bool CWinSystemIOS::Minimize()
+{
+ m_bWasFullScreenBeforeMinimize = CServiceBroker::GetWinSystem()->GetGfxContext().IsFullScreenRoot();
+ if (m_bWasFullScreenBeforeMinimize)
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_TOGGLEFULLSCREEN);
+
+ return true;
+}
+
+bool CWinSystemIOS::Restore()
+{
+ return false;
+}
+
+bool CWinSystemIOS::Hide()
+{
+ return true;
+}
+
+bool CWinSystemIOS::Show(bool raise)
+{
+ return true;
+}
+
+CVEAGLContext CWinSystemIOS::GetEAGLContextObj()
+{
+ return [g_xbmcController getEAGLContextObj];
+}
+
+std::vector<std::string> CWinSystemIOS::GetConnectedOutputs()
+{
+ std::vector<std::string> outputs;
+ outputs.emplace_back("Default");
+ outputs.emplace_back(CONST_TOUCHSCREEN);
+ if ([[UIScreen screens] count] > 1)
+ {
+ outputs.emplace_back(CONST_EXTERNAL);
+ }
+
+ return outputs;
+}
+
+void CWinSystemIOS::MoveToTouchscreen()
+{
+ CDisplaySettings::GetInstance().SetMonitor(CONST_TOUCHSCREEN);
+}
+
+std::unique_ptr<CVideoSync> CWinSystemIOS::GetVideoSync(void *clock)
+{
+ std::unique_ptr<CVideoSync> pVSync(new CVideoSyncIos(clock, *this));
+ return pVSync;
+}
+
+bool CWinSystemIOS::MessagePump()
+{
+ return m_winEvents->MessagePump();
+}
diff --git a/xbmc/windowing/linux/CMakeLists.txt b/xbmc/windowing/linux/CMakeLists.txt
new file mode 100644
index 0000000..2a36d63
--- /dev/null
+++ b/xbmc/windowing/linux/CMakeLists.txt
@@ -0,0 +1,16 @@
+set(SOURCES "")
+set(HEADERS "")
+
+if(DBUS_FOUND)
+ list(APPEND SOURCES OSScreenSaverFreedesktop.cpp)
+ list(APPEND HEADERS OSScreenSaverFreedesktop.h)
+endif()
+
+if(EGL_FOUND)
+ list(APPEND SOURCES WinSystemEGL.cpp)
+ list(APPEND HEADERS WinSystemEGL.h)
+endif()
+
+if(SOURCES)
+ core_add_library(windowing_linux)
+endif()
diff --git a/xbmc/windowing/linux/OSScreenSaverFreedesktop.cpp b/xbmc/windowing/linux/OSScreenSaverFreedesktop.cpp
new file mode 100644
index 0000000..167930d
--- /dev/null
+++ b/xbmc/windowing/linux/OSScreenSaverFreedesktop.cpp
@@ -0,0 +1,78 @@
+/*
+ * 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 "OSScreenSaverFreedesktop.h"
+
+#include "CompileInfo.h"
+#include "guilib/LocalizeStrings.h"
+#include "utils/log.h"
+
+#include "platform/linux/DBusMessage.h"
+#include "platform/linux/DBusUtil.h"
+
+using namespace KODI::WINDOWING::LINUX;
+
+namespace
+{
+const std::string SCREENSAVER_OBJECT = "/org/freedesktop/ScreenSaver";
+const std::string SCREENSAVER_INTERFACE = "org.freedesktop.ScreenSaver";
+}
+
+bool COSScreenSaverFreedesktop::IsAvailable()
+{
+ // Test by making a call to a function without side-effects
+ CDBusMessage dummyMessage(SCREENSAVER_INTERFACE, SCREENSAVER_OBJECT, SCREENSAVER_INTERFACE, "GetActive");
+ CDBusError error;
+ dummyMessage.SendSession(error);
+ // We do not care whether GetActive() is actually supported, we're just checking for the name to be there
+ // (GNOME for example does not support GetActive)
+ return !error || error.Name() == DBUS_ERROR_NOT_SUPPORTED;
+}
+
+void COSScreenSaverFreedesktop::Inhibit()
+{
+ if (m_inhibited)
+ {
+ return;
+ }
+
+ CDBusMessage inhibitMessage(SCREENSAVER_INTERFACE, SCREENSAVER_OBJECT, SCREENSAVER_INTERFACE, "Inhibit");
+ inhibitMessage.AppendArguments(std::string(CCompileInfo::GetAppName()),
+ g_localizeStrings.Get(14086));
+ if (!inhibitMessage.SendSession())
+ {
+ // DBus call failed
+ CLog::Log(LOGERROR, "Inhibiting freedesktop screen saver failed");
+ return;
+ }
+
+ if (!inhibitMessage.GetReplyArguments(&m_cookie))
+ {
+ CLog::Log(LOGERROR, "Could not retrieve cookie from org.freedesktop.ScreenSaver Inhibit response");
+ return;
+ }
+
+ m_inhibited = true;
+}
+
+void COSScreenSaverFreedesktop::Uninhibit()
+{
+ if (!m_inhibited)
+ {
+ return;
+ }
+
+ CDBusMessage uninhibitMessage(SCREENSAVER_INTERFACE, SCREENSAVER_OBJECT, SCREENSAVER_INTERFACE, "UnInhibit");
+ uninhibitMessage.AppendArgument(m_cookie);
+ if (!uninhibitMessage.SendSession())
+ {
+ CLog::Log(LOGERROR, "Uninhibiting freedesktop screen saver failed");
+ }
+
+ m_inhibited = false;
+}
diff --git a/xbmc/windowing/linux/OSScreenSaverFreedesktop.h b/xbmc/windowing/linux/OSScreenSaverFreedesktop.h
new file mode 100644
index 0000000..041e8ab
--- /dev/null
+++ b/xbmc/windowing/linux/OSScreenSaverFreedesktop.h
@@ -0,0 +1,40 @@
+/*
+ * 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 <cstdint>
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace LINUX
+{
+
+// FIXME This is not really linux-specific, BSD could also have this. Better directory name?
+
+class COSScreenSaverFreedesktop : public IOSScreenSaver
+{
+public:
+ COSScreenSaverFreedesktop() = default;
+
+ static bool IsAvailable();
+ void Inhibit() override;
+ void Uninhibit() override;
+
+private:
+ bool m_inhibited{false};
+ std::uint32_t m_cookie;
+};
+
+}
+}
+}
diff --git a/xbmc/windowing/linux/WinSystemEGL.cpp b/xbmc/windowing/linux/WinSystemEGL.cpp
new file mode 100644
index 0000000..f791b91
--- /dev/null
+++ b/xbmc/windowing/linux/WinSystemEGL.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2005-2020 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 "WinSystemEGL.h"
+
+using namespace KODI::WINDOWING::LINUX;
+
+CWinSystemEGL::CWinSystemEGL(EGLenum platform, std::string const& platformExtension)
+ : m_eglContext{platform, platformExtension}
+{
+}
+
+EGLDisplay CWinSystemEGL::GetEGLDisplay() const
+{
+ return m_eglContext.GetEGLDisplay();
+}
+
+EGLSurface CWinSystemEGL::GetEGLSurface() const
+{
+ return m_eglContext.GetEGLSurface();
+}
+
+EGLContext CWinSystemEGL::GetEGLContext() const
+{
+ return m_eglContext.GetEGLContext();
+}
+
+EGLConfig CWinSystemEGL::GetEGLConfig() const
+{
+ return m_eglContext.GetEGLConfig();
+}
diff --git a/xbmc/windowing/linux/WinSystemEGL.h b/xbmc/windowing/linux/WinSystemEGL.h
new file mode 100644
index 0000000..44bb5e4
--- /dev/null
+++ b/xbmc/windowing/linux/WinSystemEGL.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2005-2020 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 "utils/EGLUtils.h"
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace LINUX
+{
+
+class CWinSystemEGL
+{
+public:
+ CWinSystemEGL(EGLenum platform, std::string const& platformExtension);
+ ~CWinSystemEGL() = default;
+
+ EGLDisplay GetEGLDisplay() const;
+ EGLSurface GetEGLSurface() const;
+ EGLContext GetEGLContext() const;
+ EGLConfig GetEGLConfig() const;
+
+protected:
+ CEGLContextUtils m_eglContext;
+};
+
+} // namespace LINUX
+} // namespace WINDOWING
+} // namespace KODI
diff --git a/xbmc/windowing/osx/CMakeLists.txt b/xbmc/windowing/osx/CMakeLists.txt
new file mode 100644
index 0000000..433f5cf
--- /dev/null
+++ b/xbmc/windowing/osx/CMakeLists.txt
@@ -0,0 +1,17 @@
+set(SOURCES CocoaDPMSSupport.cpp
+ OSScreenSaverOSX.cpp
+ VideoSyncOsx.mm)
+set(HEADERS CocoaDPMSSupport.h
+ OSScreenSaverOSX.h
+ VideoSyncOsx.h)
+
+if(NOT SDL_FOUND)
+ list(APPEND SOURCES WinEventsOSX.mm
+ WinEventsOSXImpl.mm
+ WinSystemOSX.mm)
+ list(APPEND HEADERS WinEventsOSX.h
+ WinEventsOSXImpl.h
+ WinSystemOSX.h)
+endif()
+
+core_add_library(windowing_osx)
diff --git a/xbmc/windowing/osx/CocoaDPMSSupport.cpp b/xbmc/windowing/osx/CocoaDPMSSupport.cpp
new file mode 100644
index 0000000..bc86220
--- /dev/null
+++ b/xbmc/windowing/osx/CocoaDPMSSupport.cpp
@@ -0,0 +1,55 @@
+/*
+ * 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 "CocoaDPMSSupport.h"
+
+#include "ServiceBroker.h"
+#include "utils/log.h"
+
+#include <CoreFoundation/CFNumber.h>
+#include <IOKit/IOKitLib.h>
+
+CCocoaDPMSSupport::CCocoaDPMSSupport()
+{
+ m_supportedModes.push_back(OFF);
+ m_supportedModes.push_back(STANDBY);
+}
+
+bool CCocoaDPMSSupport::EnablePowerSaving(PowerSavingMode mode)
+{
+ // http://lists.apple.com/archives/Cocoa-dev/2007/Nov/msg00267.html
+ // This is an unsupported system call that might kernel panic on PPC boxes
+ // The reported OSX-PPC panic is unverified so we are going to enable this until
+ // we find out which OSX-PPC boxes have problems, then add detect/disable for those boxes.
+ io_registry_entry_t r = IORegistryEntryFromPath(kIOMasterPortDefault, "IOService:/IOResources/IODisplayWrangler");
+ if (!r)
+ return false;
+
+ switch (mode)
+ {
+ case OFF:
+ case STANDBY:
+ return (IORegistryEntrySetCFProperty(r, CFSTR("IORequestIdle"), kCFBooleanTrue) == 0);
+ default:
+ return false;
+ }
+}
+
+bool CCocoaDPMSSupport::DisablePowerSaving()
+{
+ // http://lists.apple.com/archives/Cocoa-dev/2007/Nov/msg00267.html
+ // This is an unsupported system call that might kernel panic on PPC boxes
+ // The reported OSX-PPC panic is unverified so we are going to enable this until
+ // we find out which OSX-PPC boxes have problems, then add detect/disable for those boxes.
+ io_registry_entry_t r = IORegistryEntryFromPath(kIOMasterPortDefault, "IOService:/IOResources/IODisplayWrangler");
+ if (!r)
+ return false;
+
+ // Turn display on
+ return (IORegistryEntrySetCFProperty(r, CFSTR("IORequestIdle"), kCFBooleanFalse) == 0);
+}
diff --git a/xbmc/windowing/osx/CocoaDPMSSupport.h b/xbmc/windowing/osx/CocoaDPMSSupport.h
new file mode 100644
index 0000000..6a54f42
--- /dev/null
+++ b/xbmc/windowing/osx/CocoaDPMSSupport.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 CCocoaDPMSSupport : public CDPMSSupport
+{
+public:
+ CCocoaDPMSSupport();
+ ~CCocoaDPMSSupport() override = default;
+
+protected:
+ bool EnablePowerSaving(PowerSavingMode mode) override;
+ bool DisablePowerSaving() override;
+};
diff --git a/xbmc/windowing/osx/OSScreenSaverOSX.cpp b/xbmc/windowing/osx/OSScreenSaverOSX.cpp
new file mode 100644
index 0000000..019f86c
--- /dev/null
+++ b/xbmc/windowing/osx/OSScreenSaverOSX.cpp
@@ -0,0 +1,31 @@
+/*
+ * 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 "OSScreenSaverOSX.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+
+void COSScreenSaverOSX::Inhibit()
+{
+ // see Technical Q&A QA1340
+ if (m_assertionID == 0)
+ {
+ CFStringRef reasonForActivity= CFSTR("XBMC requested disable system screen saver");
+ IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep,
+ kIOPMAssertionLevelOn, reasonForActivity, &m_assertionID);
+ }
+}
+
+void COSScreenSaverOSX::Uninhibit()
+{
+ if (m_assertionID != 0)
+ {
+ IOPMAssertionRelease(m_assertionID);
+ m_assertionID = 0;
+ }
+} \ No newline at end of file
diff --git a/xbmc/windowing/osx/OSScreenSaverOSX.h b/xbmc/windowing/osx/OSScreenSaverOSX.h
new file mode 100644
index 0000000..c79367a
--- /dev/null
+++ b/xbmc/windowing/osx/OSScreenSaverOSX.h
@@ -0,0 +1,24 @@
+/*
+ * 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"
+
+#import <IOKit/pwr_mgt/IOPMLib.h>
+
+class COSScreenSaverOSX : public KODI::WINDOWING::IOSScreenSaver
+{
+public:
+ COSScreenSaverOSX() = default;
+ void Inhibit() override;
+ void Uninhibit() override;
+
+private:
+ IOPMAssertionID m_assertionID = 0;
+};
diff --git a/xbmc/windowing/osx/OpenGL/CMakeLists.txt b/xbmc/windowing/osx/OpenGL/CMakeLists.txt
new file mode 100644
index 0000000..fd250d7
--- /dev/null
+++ b/xbmc/windowing/osx/OpenGL/CMakeLists.txt
@@ -0,0 +1,14 @@
+if(OPENGL_FOUND)
+ set(SOURCES WinSystemOSXGL.mm)
+ set(HEADERS WinSystemOSXGL.h)
+
+ if(NOT SDL_FOUND)
+ list(APPEND SOURCES OSXGLView.mm
+ OSXGLWindow.mm)
+ list(APPEND HEADERS OSXGLView.h
+ OSXGLWindow.h)
+ endif()
+
+ core_add_library(windowing_osx_opengl)
+
+endif()
diff --git a/xbmc/windowing/osx/OpenGL/OSXGLView.h b/xbmc/windowing/osx/OpenGL/OSXGLView.h
new file mode 100644
index 0000000..577642d
--- /dev/null
+++ b/xbmc/windowing/osx/OpenGL/OSXGLView.h
@@ -0,0 +1,18 @@
+#pragma once
+
+/*
+ * Copyright (C) 2021- 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.
+ */
+
+#import <Cocoa/Cocoa.h>
+
+@interface OSXGLView : NSOpenGLView
+
+- (id)initWithFrame:(NSRect)frameRect;
+- (NSOpenGLContext*)getGLContext;
+
+@end
diff --git a/xbmc/windowing/osx/OpenGL/OSXGLView.mm b/xbmc/windowing/osx/OpenGL/OSXGLView.mm
new file mode 100644
index 0000000..6c0a8b5
--- /dev/null
+++ b/xbmc/windowing/osx/OpenGL/OSXGLView.mm
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2021- 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.
+ */
+
+#import "OSXGLView.h"
+
+#include "ServiceBroker.h"
+#include "application/AppInboundProtocol.h"
+#include "application/AppParamParser.h"
+#include "application/Application.h"
+#include "messaging/ApplicationMessenger.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/log.h"
+
+#include "system_gl.h"
+
+@implementation OSXGLView
+{
+ NSOpenGLContext* m_glcontext;
+ NSOpenGLPixelFormat* m_pixFmt;
+ NSTrackingArea* m_trackingArea;
+ BOOL pause;
+}
+
+- (id)initWithFrame:(NSRect)frameRect
+{
+ NSOpenGLPixelFormatAttribute wattrs[] = {
+ NSOpenGLPFANoRecovery, NSOpenGLPFAAccelerated,
+ NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,
+ NSOpenGLPFAColorSize, (NSOpenGLPixelFormatAttribute)32,
+ NSOpenGLPFAAlphaSize, (NSOpenGLPixelFormatAttribute)8,
+ NSOpenGLPFADepthSize, (NSOpenGLPixelFormatAttribute)24,
+ NSOpenGLPFADoubleBuffer, (NSOpenGLPixelFormatAttribute)0};
+
+ self = [super initWithFrame:frameRect];
+ if (self)
+ {
+ m_pixFmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:wattrs];
+ m_glcontext = [[NSOpenGLContext alloc] initWithFormat:m_pixFmt shareContext:nil];
+ }
+
+ [self updateTrackingAreas];
+
+ GLint swapInterval = 1;
+ [m_glcontext setValues:&swapInterval forParameter:NSOpenGLCPSwapInterval];
+ [m_glcontext makeCurrentContext];
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [NSOpenGLContext clearCurrentContext];
+ [m_glcontext clearDrawable];
+}
+
+- (void)drawRect:(NSRect)rect
+{
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ [m_glcontext setView:self];
+
+ // clear screen on first render
+ glClearColor(0, 0, 0, 0);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glClearColor(0, 0, 0, 0);
+
+ [m_glcontext update];
+ });
+}
+
+- (void)updateTrackingAreas
+{
+ if (m_trackingArea != nil)
+ {
+ [self removeTrackingArea:m_trackingArea];
+ }
+
+ const int opts =
+ (NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways);
+ m_trackingArea = [[NSTrackingArea alloc] initWithRect:self.bounds
+ options:opts
+ owner:self
+ userInfo:nil];
+ [self addTrackingArea:m_trackingArea];
+}
+
+- (void)mouseEntered:(NSEvent*)theEvent
+{
+ [NSCursor hide];
+}
+
+- (void)mouseMoved:(NSEvent*)theEvent
+{
+}
+
+- (void)mouseExited:(NSEvent*)theEvent
+{
+ [NSCursor unhide];
+}
+
+- (NSOpenGLContext*)getGLContext
+{
+ return m_glcontext;
+}
+@end
diff --git a/xbmc/windowing/osx/OpenGL/OSXGLWindow.h b/xbmc/windowing/osx/OpenGL/OSXGLWindow.h
new file mode 100644
index 0000000..a722804
--- /dev/null
+++ b/xbmc/windowing/osx/OpenGL/OSXGLWindow.h
@@ -0,0 +1,19 @@
+#pragma once
+
+/*
+ * Copyright (C) 2021- 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.
+ */
+
+#import <Cocoa/Cocoa.h>
+
+@interface OSXGLWindow : NSWindow <NSWindowDelegate>
+
+@property(atomic) bool resizeState;
+
+- (id)initWithContentRect:(NSRect)box styleMask:(uint)style;
+
+@end
diff --git a/xbmc/windowing/osx/OpenGL/OSXGLWindow.mm b/xbmc/windowing/osx/OpenGL/OSXGLWindow.mm
new file mode 100644
index 0000000..8ee26fc
--- /dev/null
+++ b/xbmc/windowing/osx/OpenGL/OSXGLWindow.mm
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2021- 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.
+ */
+
+#import "OSXGLWindow.h"
+
+#include "ServiceBroker.h"
+#include "application/AppInboundProtocol.h"
+#include "application/AppParamParser.h"
+#include "application/Application.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "messaging/ApplicationMessenger.h"
+#include "settings/DisplaySettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#import "windowing/osx/OpenGL/OSXGLView.h"
+#include "windowing/osx/WinEventsOSX.h"
+#import "windowing/osx/WinSystemOSX.h"
+
+#include "platform/darwin/osx/CocoaInterface.h"
+
+//------------------------------------------------------------------------------------------
+@implementation OSXGLWindow
+
+@synthesize resizeState = m_resizeState;
+
+- (id)initWithContentRect:(NSRect)box styleMask:(uint)style
+{
+ self = [super initWithContentRect:box styleMask:style backing:NSBackingStoreBuffered defer:YES];
+ [self setDelegate:self];
+ [self setAcceptsMouseMovedEvents:YES];
+ // autosave the window position/size
+ // Tell the controller to not cascade its windows.
+ [self.windowController setShouldCascadeWindows:NO];
+ [self setFrameAutosaveName:@"OSXGLWindowPositionHeightWidth"];
+
+ g_application.m_AppFocused = true;
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [self setDelegate:nil];
+}
+
+- (BOOL)windowShouldClose:(id)sender
+{
+ if (!g_application.m_bStop)
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_QUIT);
+
+ return NO;
+}
+
+- (void)windowDidExpose:(NSNotification*)aNotification
+{
+ g_application.m_AppFocused = true;
+}
+
+- (void)windowDidMove:(NSNotification*)aNotification
+{
+ NSOpenGLContext* context = NSOpenGLContext.currentContext;
+ if (context)
+ {
+ if (context.view)
+ {
+ NSPoint window_origin = [[[context view] window] frame].origin;
+ XBMC_Event newEvent = {};
+ newEvent.type = XBMC_VIDEOMOVE;
+ newEvent.move.x = window_origin.x;
+ newEvent.move.y = window_origin.y;
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ appPort->OnEvent(newEvent);
+ }
+ }
+}
+
+- (void)windowWillStartLiveResize:(NSNotification*)notification
+{
+ m_resizeState = true;
+}
+
+- (void)windowDidEndLiveResize:(NSNotification*)notification
+{
+ m_resizeState = false;
+ [self windowDidResize:notification];
+}
+
+- (void)windowDidResize:(NSNotification*)aNotification
+{
+ if (!m_resizeState)
+ {
+ NSRect rect = [self contentRectForFrameRect:self.frame];
+ int width = static_cast<int>(rect.size.width);
+ int height = static_cast<int>(rect.size.height);
+
+ XBMC_Event newEvent = {};
+
+ if (!CServiceBroker::GetWinSystem()->IsFullScreen())
+ {
+ RESOLUTION res_index = RES_DESKTOP;
+ if ((width == CDisplaySettings::GetInstance().GetResolutionInfo(res_index).iWidth) &&
+ (height == CDisplaySettings::GetInstance().GetResolutionInfo(res_index).iHeight))
+ return;
+
+ newEvent.type = XBMC_VIDEORESIZE;
+ }
+ else
+ {
+ // macos may trigger a resize/rescale event just after Kodi has entered fullscreen
+ // (from windowDidEndLiveResize). Kodi needs to rescale the UI - use a different event
+ // type since XBMC_VIDEORESIZE is supposed to only be used in windowed mode
+ newEvent.type = XBMC_FULLSCREEN_UPDATE;
+ newEvent.move.x = -1;
+ newEvent.move.y = -1;
+ }
+
+ newEvent.resize.w = width;
+ newEvent.resize.h = height;
+
+ // check for valid sizes cause in some cases
+ // we are hit during fullscreen transition from macos
+ // and might be technically "zero" sized
+ if (newEvent.resize.w != 0 && newEvent.resize.h != 0)
+ {
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ appPort->OnEvent(newEvent);
+ }
+ }
+}
+
+- (void)windowDidChangeScreen:(NSNotification*)notification
+{
+ // user has moved the window to a
+ // different screen
+ // if (CServiceBroker::GetWinSystem()->IsFullScreen())
+ // CServiceBroker::GetWinSystem()->SetMovedToOtherScreen(true);
+}
+
+- (NSSize)windowWillResize:(NSWindow*)sender toSize:(NSSize)frameSize
+{
+ return frameSize;
+}
+
+- (void)windowWillEnterFullScreen:(NSNotification*)pNotification
+{
+ CWinSystemOSX* winSystem = dynamic_cast<CWinSystemOSX*>(CServiceBroker::GetWinSystem());
+ if (!winSystem)
+ return;
+
+ // if osx is the issuer of the toggle
+ // call Kodi's toggle function
+ if (!winSystem->GetFullscreenWillToggle())
+ {
+ // indicate that we are toggling
+ // flag will be reset in SetFullscreen once its
+ // called from Kodi's gui thread
+ winSystem->SetFullscreenWillToggle(true);
+
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_TOGGLEFULLSCREEN);
+ }
+ else
+ {
+ // in this case we are just called because
+ // of Kodi did a toggle - just reset the flag
+ // we don't need to do anything else
+ winSystem->SetFullscreenWillToggle(false);
+ }
+}
+
+- (void)windowDidExitFullScreen:(NSNotification*)pNotification
+{
+ auto winSystem = dynamic_cast<CWinSystemOSX*>(CServiceBroker::GetWinSystem());
+ if (!winSystem)
+ return;
+
+ // if osx is the issuer of the toggle
+ // call Kodi's toggle function
+ if (!winSystem->GetFullscreenWillToggle())
+ {
+ // indicate that we are toggling
+ // flag will be reset in SetFullscreen once its
+ // called from Kodi's gui thread
+ winSystem->SetFullscreenWillToggle(true);
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_TOGGLEFULLSCREEN);
+ }
+ else
+ {
+ // in this case we are just called because
+ // of Kodi did a toggle - just reset the flag
+ // we don't need to do anything else
+ winSystem->SetFullscreenWillToggle(false);
+ }
+}
+
+- (NSApplicationPresentationOptions)window:(NSWindow*)window
+ willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions
+{
+ // customize our appearance when entering full screen:
+ // we don't want the dock to appear but we want the menubar to hide/show automatically
+ //
+ return (NSApplicationPresentationFullScreen | // support full screen for this window (required)
+ NSApplicationPresentationHideDock | // completely hide the dock
+ NSApplicationPresentationAutoHideMenuBar); // yes we want the menu bar to show/hide
+}
+
+- (void)windowDidMiniaturize:(NSNotification*)aNotification
+{
+ g_application.m_AppFocused = false;
+}
+
+- (void)windowDidDeminiaturize:(NSNotification*)aNotification
+{
+ g_application.m_AppFocused = true;
+}
+
+- (void)windowDidBecomeKey:(NSNotification*)aNotification
+{
+ g_application.m_AppFocused = true;
+
+ auto winSystem = dynamic_cast<CWinSystemOSX*>(CServiceBroker::GetWinSystem());
+ if (winSystem)
+ {
+ winSystem->enableInputEvents();
+ }
+}
+
+- (void)windowDidResignKey:(NSNotification*)aNotification
+{
+ g_application.m_AppFocused = false;
+
+ auto winSystem = dynamic_cast<CWinSystemOSX*>(CServiceBroker::GetWinSystem());
+ if (winSystem)
+ {
+ winSystem->disableInputEvents();
+ }
+}
+@end
diff --git a/xbmc/windowing/osx/OpenGL/WinSystemOSXGL.h b/xbmc/windowing/osx/OpenGL/WinSystemOSXGL.h
new file mode 100644
index 0000000..c38e5e3
--- /dev/null
+++ b/xbmc/windowing/osx/OpenGL/WinSystemOSXGL.h
@@ -0,0 +1,35 @@
+/*
+ * 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
+
+#if defined(HAS_SDL)
+#include "windowing/osx/SDL/WinSystemOSXSDL.h"
+#else
+#include "windowing/osx/WinSystemOSX.h"
+#endif
+#include "rendering/gl/RenderSystemGL.h"
+
+class CWinSystemOSXGL : public CWinSystemOSX, public CRenderSystemGL
+{
+public:
+ CWinSystemOSXGL() = default;
+ ~CWinSystemOSXGL() override = default;
+
+ static void Register();
+ static std::unique_ptr<CWinSystemBase> CreateWinSystem();
+
+ // Implementation of CWinSystemBase via CWinSystemOSX
+ CRenderSystemBase *GetRenderSystem() override { return this; }
+ bool ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop) override;
+ bool SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) override;
+
+protected:
+ void PresentRenderImpl(bool rendered) override;
+ void SetVSyncImpl(bool enable) override;
+};
diff --git a/xbmc/windowing/osx/OpenGL/WinSystemOSXGL.mm b/xbmc/windowing/osx/OpenGL/WinSystemOSXGL.mm
new file mode 100644
index 0000000..30e44f2
--- /dev/null
+++ b/xbmc/windowing/osx/OpenGL/WinSystemOSXGL.mm
@@ -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.
+ */
+
+#include "WinSystemOSXGL.h"
+
+#include "guilib/Texture.h"
+#include "rendering/gl/RenderSystemGL.h"
+#include "windowing/WindowSystemFactory.h"
+
+#include <unistd.h>
+
+void CWinSystemOSXGL::Register()
+{
+ KODI::WINDOWING::CWindowSystemFactory::RegisterWindowSystem(CreateWinSystem);
+}
+
+std::unique_ptr<CWinSystemBase> CWinSystemOSXGL::CreateWinSystem()
+{
+ return std::make_unique<CWinSystemOSXGL>();
+}
+
+void CWinSystemOSXGL::PresentRenderImpl(bool rendered)
+{
+ if (rendered)
+ FlushBuffer();
+
+ // FlushBuffer does not block if window is obscured
+ // in this case we need to throttle the render loop
+ if (IsObscured())
+ usleep(10000);
+
+ if (m_delayDispReset && m_dispResetTimer.IsTimePast())
+ {
+ m_delayDispReset = false;
+ AnnounceOnResetDevice();
+ }
+}
+
+void CWinSystemOSXGL::SetVSyncImpl(bool enable)
+{
+ EnableVSync(false);
+
+ if (enable)
+ {
+ EnableVSync(true);
+ }
+}
+
+bool CWinSystemOSXGL::ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop)
+{
+ CWinSystemOSX::ResizeWindow(newWidth, newHeight, newLeft, newTop);
+ CRenderSystemGL::ResetRenderSystem(newWidth, newHeight);
+
+ if (m_bVSync)
+ {
+ EnableVSync(m_bVSync);
+ }
+
+ return true;
+}
+
+bool CWinSystemOSXGL::SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays)
+{
+ CWinSystemOSX::SetFullScreen(fullScreen, res, blankOtherDisplays);
+ CRenderSystemGL::ResetRenderSystem(res.iWidth, res.iHeight);
+
+ if (m_bVSync)
+ {
+ EnableVSync(m_bVSync);
+ }
+
+ return true;
+}
+
diff --git a/xbmc/windowing/osx/SDL/CMakeLists.txt b/xbmc/windowing/osx/SDL/CMakeLists.txt
new file mode 100644
index 0000000..33e25d8
--- /dev/null
+++ b/xbmc/windowing/osx/SDL/CMakeLists.txt
@@ -0,0 +1,10 @@
+if(SDL_FOUND)
+ set(SOURCES WinEventsSDL.cpp
+ WinSystemOSXSDL.mm)
+ set(HEADERS WinEventsSDL.h
+ WinSystemOSXSDL.h)
+endif()
+
+if(SOURCES)
+ core_add_library(windowing_osx_SDL)
+endif()
diff --git a/xbmc/windowing/osx/SDL/WinEventsSDL.cpp b/xbmc/windowing/osx/SDL/WinEventsSDL.cpp
new file mode 100644
index 0000000..31b2506
--- /dev/null
+++ b/xbmc/windowing/osx/SDL/WinEventsSDL.cpp
@@ -0,0 +1,242 @@
+/*
+ * 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 "WinEventsSDL.h"
+
+#include "GUIUserMessages.h"
+#include "ServiceBroker.h"
+#include "application/AppInboundProtocol.h"
+#include "application/Application.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "input/InputManager.h"
+#include "input/Key.h"
+#include "input/mouse/MouseStat.h"
+#include "messaging/ApplicationMessenger.h"
+#include "settings/DisplaySettings.h"
+#include "windowing/WinSystem.h"
+
+#include "platform/darwin/osx/CocoaInterface.h"
+
+bool CWinEventsOSX::MessagePump()
+{
+ SDL_Event event;
+ bool ret = false;
+
+ while (SDL_PollEvent(&event))
+ {
+ switch(event.type)
+ {
+ case SDL_QUIT:
+ if (!g_application.m_bStop)
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_QUIT);
+ break;
+
+ case SDL_ACTIVEEVENT:
+ //If the window was inconified or restored
+ if( event.active.state & SDL_APPACTIVE )
+ {
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ appPort->SetRenderGUI(event.active.gain != 0);
+ CServiceBroker::GetWinSystem()->NotifyAppActiveChange(g_application.GetRenderGUI());
+ }
+ else if (event.active.state & SDL_APPINPUTFOCUS)
+ {
+ g_application.m_AppFocused = event.active.gain != 0;
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort && g_application.m_AppFocused)
+ appPort->SetRenderGUI(g_application.m_AppFocused);
+ CServiceBroker::GetWinSystem()->NotifyAppFocusChange(g_application.m_AppFocused);
+ }
+ break;
+
+ case SDL_KEYDOWN:
+ {
+ // process any platform specific shortcuts before handing off to XBMC
+ if (ProcessOSXShortcuts(event))
+ {
+ ret = true;
+ break;
+ }
+
+ XBMC_Event newEvent = {};
+ newEvent.type = XBMC_KEYDOWN;
+ newEvent.key.keysym.scancode = event.key.keysym.scancode;
+ newEvent.key.keysym.sym = (XBMCKey) event.key.keysym.sym;
+ newEvent.key.keysym.unicode = event.key.keysym.unicode;
+
+ // Check if the Windows keys are down because SDL doesn't flag this.
+ uint16_t mod = event.key.keysym.mod;
+ uint8_t* keystate = SDL_GetKeyState(NULL);
+ if (keystate[SDLK_LSUPER] || keystate[SDLK_RSUPER])
+ mod |= XBMCKMOD_LSUPER;
+ newEvent.key.keysym.mod = (XBMCMod) mod;
+
+ // don't handle any more messages in the queue until we've handled keydown,
+ // if a keyup is in the queue it will reset the keypress before it is handled.
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ ret |= appPort->OnEvent(newEvent);
+ break;
+ }
+
+ case SDL_KEYUP:
+ {
+ XBMC_Event newEvent = {};
+ newEvent.type = XBMC_KEYUP;
+ newEvent.key.keysym.scancode = event.key.keysym.scancode;
+ newEvent.key.keysym.sym = (XBMCKey) event.key.keysym.sym;
+ newEvent.key.keysym.mod =(XBMCMod) event.key.keysym.mod;
+ newEvent.key.keysym.unicode = event.key.keysym.unicode;
+
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ ret |= appPort->OnEvent(newEvent);
+ break;
+ }
+
+ case SDL_MOUSEBUTTONDOWN:
+ {
+ XBMC_Event newEvent = {};
+ newEvent.type = XBMC_MOUSEBUTTONDOWN;
+ newEvent.button.button = event.button.button;
+ newEvent.button.x = event.button.x;
+ newEvent.button.y = event.button.y;
+
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ ret |= appPort->OnEvent(newEvent);
+ break;
+ }
+
+ case SDL_MOUSEBUTTONUP:
+ {
+ XBMC_Event newEvent = {};
+ newEvent.type = XBMC_MOUSEBUTTONUP;
+ newEvent.button.button = event.button.button;
+ newEvent.button.x = event.button.x;
+ newEvent.button.y = event.button.y;
+
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ ret |= appPort->OnEvent(newEvent);
+ break;
+ }
+
+ case SDL_MOUSEMOTION:
+ {
+ if (0 == (SDL_GetAppState() & SDL_APPMOUSEFOCUS))
+ {
+ CServiceBroker::GetInputManager().SetMouseActive(false);
+ // See CApplication::ProcessSlow() for a description as to why we call Cocoa_HideMouse.
+ // this is here to restore the pointer when toggling back to window mode from fullscreen.
+ Cocoa_ShowMouse();
+ break;
+ }
+ XBMC_Event newEvent = {};
+ newEvent.type = XBMC_MOUSEMOTION;
+ newEvent.motion.x = event.motion.x;
+ newEvent.motion.y = event.motion.y;
+
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ ret |= appPort->OnEvent(newEvent);
+ break;
+ }
+ case SDL_VIDEORESIZE:
+ {
+ // Under newer osx versions sdl is so fucked up that it even fires resize events
+ // that exceed the screen size (maybe some HiDP incompatibility in old SDL?)
+ // ensure to ignore those events because it will mess with windowed size
+ if((event.resize.w > CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP).iWidth) ||
+ (event.resize.h > CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP).iHeight))
+ {
+ break;
+ }
+ XBMC_Event newEvent = {};
+ newEvent.type = XBMC_VIDEORESIZE;
+ newEvent.resize.w = event.resize.w;
+ newEvent.resize.h = event.resize.h;
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ ret |= appPort->OnEvent(newEvent);
+ CServiceBroker::GetGUI()->GetWindowManager().MarkDirty();
+ break;
+ }
+ case SDL_USEREVENT:
+ {
+ XBMC_Event newEvent = {};
+ newEvent.type = XBMC_USEREVENT;
+ newEvent.user.code = event.user.code;
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ ret |= appPort->OnEvent(newEvent);
+ break;
+ }
+ case SDL_VIDEOEXPOSE:
+ CServiceBroker::GetGUI()->GetWindowManager().MarkDirty();
+ break;
+ }
+ memset(&event, 0, sizeof(SDL_Event));
+ }
+
+ return ret;
+}
+
+bool CWinEventsOSX::ProcessOSXShortcuts(SDL_Event& event)
+{
+ static bool shift = false, cmd = false;
+
+ cmd = !!(SDL_GetModState() & (KMOD_LMETA | KMOD_RMETA ));
+ shift = !!(SDL_GetModState() & (KMOD_LSHIFT | KMOD_RSHIFT));
+
+ if (cmd && event.key.type == SDL_KEYDOWN)
+ {
+ char keysymbol = event.key.keysym.sym;
+
+ // if the unicode is in the ascii range
+ // use this instead for getting the real
+ // character based on the used keyboard layout
+ // see http://lists.libsdl.org/pipermail/sdl-libsdl.org/2004-May/043716.html
+ bool isControl = (event.key.keysym.mod & KMOD_CTRL) != 0;
+ if (!isControl && !(event.key.keysym.unicode & 0xff80))
+ keysymbol = event.key.keysym.unicode;
+
+ switch(keysymbol)
+ {
+ case SDLK_q: // CMD-q to quit
+ if (!g_application.m_bStop)
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_QUIT);
+ return true;
+
+ case SDLK_f: // CMD-Ctrl-f to toggle fullscreen
+ if (!isControl)
+ return false;
+ g_application.OnAction(CAction(ACTION_TOGGLE_FULLSCREEN));
+ return true;
+
+ case SDLK_s: // CMD-3 to take a screenshot
+ g_application.OnAction(CAction(ACTION_TAKE_SCREENSHOT));
+ return true;
+
+ case SDLK_h: // CMD-h to hide
+ CServiceBroker::GetWinSystem()->Hide();
+ return true;
+
+ case SDLK_m: // CMD-m to minimize
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MINIMIZE);
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+ return false;
+}
diff --git a/xbmc/windowing/osx/SDL/WinEventsSDL.h b/xbmc/windowing/osx/SDL/WinEventsSDL.h
new file mode 100644
index 0000000..06e6325
--- /dev/null
+++ b/xbmc/windowing/osx/SDL/WinEventsSDL.h
@@ -0,0 +1,24 @@
+/*
+ * 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 "windowing/WinEvents.h"
+
+#include <SDL/SDL_events.h>
+
+class CWinEventsOSX : public IWinEvents
+{
+public:
+ CWinEventsOSX() = default;
+ ~CWinEventsOSX() override = default;
+ bool MessagePump() override;
+
+private:
+ static bool ProcessOSXShortcuts(SDL_Event& event);
+};
diff --git a/xbmc/windowing/osx/SDL/WinSystemOSXSDL.h b/xbmc/windowing/osx/SDL/WinSystemOSXSDL.h
new file mode 100644
index 0000000..ab19301
--- /dev/null
+++ b/xbmc/windowing/osx/SDL/WinSystemOSXSDL.h
@@ -0,0 +1,111 @@
+/*
+ * 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/CriticalSection.h"
+#include "threads/SystemClock.h"
+#include "threads/Timer.h"
+#include "windowing/WinSystem.h"
+
+#include <chrono>
+#include <string>
+#include <vector>
+
+typedef struct SDL_Surface SDL_Surface;
+
+class IDispResource;
+class CWinEventsOSX;
+class CWinSystemOSXImpl;
+#ifdef __OBJC__
+@class NSOpenGLContext;
+#endif
+
+class CWinSystemOSX : public CWinSystemBase, public ITimerCallback
+{
+public:
+
+ CWinSystemOSX();
+ ~CWinSystemOSX() override;
+
+ // ITimerCallback interface
+ void OnTimeout() override;
+
+ // 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;
+ bool SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) override;
+ void UpdateResolutions() override;
+ void NotifyAppFocusChange(bool bGaining) override;
+ void ShowOSMouse(bool show) override;
+ bool Minimize() override;
+ bool Restore() override;
+ bool Hide() override;
+ bool Show(bool raise = true) override;
+ void OnMove(int x, int y) override;
+
+ std::string GetClipboardText() override;
+
+ void Register(IDispResource *resource) override;
+ void Unregister(IDispResource *resource) override;
+
+ std::unique_ptr<CVideoSync> GetVideoSync(void* clock) override;
+
+ std::vector<std::string> GetConnectedOutputs() override;
+
+ void WindowChangedScreen();
+
+ void AnnounceOnLostDevice();
+ void AnnounceOnResetDevice();
+ void HandleOnResetDevice();
+ void StartLostDeviceTimer();
+ void StopLostDeviceTimer();
+
+ void* GetCGLContextObj();
+#ifdef __OBJC__
+ NSOpenGLContext* GetNSOpenGLContext();
+#else
+ void* GetNSOpenGLContext();
+#endif
+
+ // winevents override
+ bool MessagePump() override;
+
+protected:
+ std::unique_ptr<KODI::WINDOWING::IOSScreenSaver> GetOSScreenSaverImpl() override;
+
+ void HandlePossibleRefreshrateChange();
+ void GetScreenResolution(int* w, int* h, double* fps, int screenIdx);
+ void EnableVSync(bool enable);
+ bool SwitchToVideoMode(int width, int height, double refreshrate);
+ void FillInVideoModes();
+ bool FlushBuffer(void);
+ bool IsObscured(void);
+ void StartTextInput();
+ void StopTextInput();
+
+ std::unique_ptr<CWinSystemOSXImpl> m_impl;
+ SDL_Surface* m_SDLSurface;
+ CWinEventsOSX *m_osx_events;
+ bool m_obscured;
+ std::chrono::time_point<std::chrono::steady_clock> m_obscured_timecheck;
+
+ bool m_movedToOtherScreen;
+ int m_lastDisplayNr;
+ double m_refreshRate;
+
+ CCriticalSection m_resourceSection;
+ std::vector<IDispResource*> m_resources;
+ CTimer m_lostDeviceTimer;
+ bool m_delayDispReset;
+ XbmcThreads::EndTime<> m_dispResetTimer;
+ int m_updateGLContext = 0;
+};
diff --git a/xbmc/windowing/osx/SDL/WinSystemOSXSDL.mm b/xbmc/windowing/osx/SDL/WinSystemOSXSDL.mm
new file mode 100644
index 0000000..7a1b46b
--- /dev/null
+++ b/xbmc/windowing/osx/SDL/WinSystemOSXSDL.mm
@@ -0,0 +1,1852 @@
+/*
+ * 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 "WinSystemOSXSDL.h"
+
+#include "CompileInfo.h"
+#include "ServiceBroker.h"
+#include "application/AppInboundProtocol.h"
+#include "cores/AudioEngine/AESinkFactory.h"
+#include "cores/AudioEngine/Sinks/AESinkDARWINOSX.h"
+#include "cores/RetroPlayer/process/osx/RPProcessInfoOSX.h"
+#include "cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGL.h"
+#include "cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.h"
+#include "cores/VideoPlayer/DVDCodecs/Video/VTB.h"
+#include "cores/VideoPlayer/Process/osx/ProcessInfoOSX.h"
+#include "cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVTBGL.h"
+#include "cores/VideoPlayer/VideoRenderers/LinuxRendererGL.h"
+#include "cores/VideoPlayer/VideoRenderers/RenderFactory.h"
+#include "guilib/DispResource.h"
+#include "guilib/GUIWindowManager.h"
+#include "input/KeyboardStat.h"
+#include "messaging/ApplicationMessenger.h"
+#include "rendering/gl/ScreenshotSurfaceGL.h"
+#include "settings/DisplaySettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/StringUtils.h"
+#include "utils/SystemInfo.h"
+#include "utils/log.h"
+#include "windowing/osx/CocoaDPMSSupport.h"
+#include "windowing/osx/OSScreenSaverOSX.h"
+#include "windowing/osx/SDL/WinEventsSDL.h"
+#include "windowing/osx/VideoSyncOsx.h"
+
+#include "platform/darwin/DarwinUtils.h"
+#include "platform/darwin/DictionaryUtils.h"
+#include "platform/darwin/osx/CocoaInterface.h"
+#import "platform/darwin/osx/SDL/OSXTextInputResponder.h"
+#include "platform/darwin/osx/XBMCHelper.h"
+
+#include <cstdlib>
+#include <mutex>
+#include <signal.h>
+
+#import <Cocoa/Cocoa.h>
+#import <IOKit/graphics/IOGraphicsLib.h>
+#import <IOKit/pwr_mgt/IOPMLib.h>
+#import <QuartzCore/QuartzCore.h>
+#import <SDL/SDL.h>
+
+// turn off deprecated warning spew.
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
+using namespace KODI;
+using namespace WINDOWING;
+using namespace std::chrono_literals;
+
+//------------------------------------------------------------------------------------------
+// special object-c class for handling the NSWindowDidMoveNotification callback.
+@interface windowDidMoveNoteClass : NSObject
+{
+ void *m_userdata;
+}
++ (windowDidMoveNoteClass*) initWith: (void*) userdata;
+- (void) windowDidMoveNotification:(NSNotification*) note;
+@end
+
+@implementation windowDidMoveNoteClass
++ (windowDidMoveNoteClass*) initWith: (void*) userdata
+{
+ windowDidMoveNoteClass *windowDidMove = [windowDidMoveNoteClass new];
+ windowDidMove->m_userdata = userdata;
+ return windowDidMove;
+}
+- (void) windowDidMoveNotification:(NSNotification*) note
+{
+ CWinSystemOSX *winsys = (CWinSystemOSX*)m_userdata;
+ if (!winsys)
+ return;
+
+ NSOpenGLContext* context = [NSOpenGLContext currentContext];
+ if (context)
+ {
+ if ([context view])
+ {
+ NSPoint window_origin = [[[context view] window] frame].origin;
+ XBMC_Event newEvent = {};
+ newEvent.type = XBMC_VIDEOMOVE;
+ newEvent.move.x = window_origin.x;
+ newEvent.move.y = window_origin.y;
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ appPort->OnEvent(newEvent);
+ }
+ }
+}
+@end
+//------------------------------------------------------------------------------------------
+// special object-c class for handling the NSWindowDidReSizeNotification callback.
+@interface windowDidReSizeNoteClass : NSObject
+{
+ void *m_userdata;
+}
++ (windowDidReSizeNoteClass*) initWith: (void*) userdata;
+- (void) windowDidReSizeNotification:(NSNotification*) note;
+@end
+@implementation windowDidReSizeNoteClass
++ (windowDidReSizeNoteClass*) initWith: (void*) userdata
+{
+ windowDidReSizeNoteClass *windowDidReSize = [windowDidReSizeNoteClass new];
+ windowDidReSize->m_userdata = userdata;
+ return windowDidReSize;
+}
+- (void) windowDidReSizeNotification:(NSNotification*) note
+{
+ CWinSystemOSX *winsys = (CWinSystemOSX*)m_userdata;
+ if (!winsys)
+ return;
+
+}
+@end
+
+//------------------------------------------------------------------------------------------
+// special object-c class for handling the NSWindowDidChangeScreenNotification callback.
+@interface windowDidChangeScreenNoteClass : NSObject
+{
+ void *m_userdata;
+}
++ (windowDidChangeScreenNoteClass*) initWith: (void*) userdata;
+- (void) windowDidChangeScreenNotification:(NSNotification*) note;
+@end
+@implementation windowDidChangeScreenNoteClass
++ (windowDidChangeScreenNoteClass*) initWith: (void*) userdata
+{
+ windowDidChangeScreenNoteClass *windowDidChangeScreen = [windowDidChangeScreenNoteClass new];
+ windowDidChangeScreen->m_userdata = userdata;
+ return windowDidChangeScreen;
+}
+- (void) windowDidChangeScreenNotification:(NSNotification*) note
+{
+ CWinSystemOSX *winsys = (CWinSystemOSX*)m_userdata;
+ if (!winsys)
+ return;
+ winsys->WindowChangedScreen();
+}
+@end
+//------------------------------------------------------------------------------------------
+
+class CWinSystemOSXImpl
+{
+public:
+ NSOpenGLContext* m_glContext;
+ static NSOpenGLContext* m_lastOwnedContext;
+
+ windowDidMoveNoteClass* m_windowDidMove;
+ windowDidReSizeNoteClass* m_windowDidReSize;
+ windowDidChangeScreenNoteClass* m_windowChangedScreen;
+};
+
+NSOpenGLContext* CWinSystemOSXImpl::m_lastOwnedContext = nil;
+
+
+#define MAX_DISPLAYS 32
+// if there was a devicelost callback
+// but no device reset for 3 secs
+// a timeout fires the reset callback
+// (for ensuring that e.x. AE isn't stuck)
+constexpr auto LOST_DEVICE_TIMEOUT_MS = 3000ms;
+static NSWindow* blankingWindows[MAX_DISPLAYS];
+
+//------------------------------------------------------------------------------------------
+CRect CGRectToCRect(CGRect cgrect)
+{
+ CRect crect = CRect(
+ cgrect.origin.x,
+ cgrect.origin.y,
+ cgrect.origin.x + cgrect.size.width,
+ cgrect.origin.y + cgrect.size.height);
+ return crect;
+}
+//---------------------------------------------------------------------------------
+void SetMenuBarVisible(bool visible)
+{
+ if(visible)
+ {
+ [[NSApplication sharedApplication]
+ setPresentationOptions: NSApplicationPresentationDefault];
+ }
+ else
+ {
+ [[NSApplication sharedApplication]
+ setPresentationOptions: NSApplicationPresentationHideMenuBar |
+ NSApplicationPresentationHideDock];
+ }
+}
+//---------------------------------------------------------------------------------
+CGDirectDisplayID GetDisplayID(int screen_index)
+{
+ CGDirectDisplayID displayArray[MAX_DISPLAYS];
+ CGDisplayCount numDisplays;
+
+ // Get the list of displays.
+ CGGetActiveDisplayList(MAX_DISPLAYS, displayArray, &numDisplays);
+ if (screen_index >= 0 && screen_index < static_cast<int>(numDisplays))
+ return(displayArray[screen_index]);
+ else
+ return(displayArray[0]);
+}
+
+size_t DisplayBitsPerPixelForMode(CGDisplayModeRef mode)
+{
+ size_t bitsPerPixel = 0;
+
+ CFStringRef pixEnc = CGDisplayModeCopyPixelEncoding(mode);
+ if(CFStringCompare(pixEnc, CFSTR(IO32BitDirectPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo)
+ {
+ bitsPerPixel = 32;
+ }
+ else if(CFStringCompare(pixEnc, CFSTR(IO16BitDirectPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo)
+ {
+ bitsPerPixel = 16;
+ }
+ else if(CFStringCompare(pixEnc, CFSTR(IO8BitIndexedPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo)
+ {
+ bitsPerPixel = 8;
+ }
+
+ CFRelease(pixEnc);
+
+ return bitsPerPixel;
+}
+
+CFArrayRef GetAllDisplayModes(CGDirectDisplayID display)
+{
+ int value = 1;
+
+ CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &value);
+ if (!number)
+ {
+ CLog::Log(LOGERROR, "GetAllDisplayModes - could not create Number!");
+ return NULL;
+ }
+
+ CFStringRef key = kCGDisplayShowDuplicateLowResolutionModes;
+ CFDictionaryRef options = CFDictionaryCreate(kCFAllocatorDefault, (const void **)&key, (const void **)&number, 1, NULL, NULL);
+ CFRelease(number);
+
+ if (!options)
+ {
+ CLog::Log(LOGERROR, "GetAllDisplayModes - could not create Dictionary!");
+ return NULL;
+ }
+
+ CFArrayRef displayModes = CGDisplayCopyAllDisplayModes(display, options);
+ CFRelease(options);
+
+ if (!displayModes)
+ {
+ CLog::Log(LOGERROR, "GetAllDisplayModes - no displaymodes found!");
+ return NULL;
+ }
+
+ return displayModes;
+}
+
+// mimic former behavior of deprecated CGDisplayBestModeForParameters
+CGDisplayModeRef BestMatchForMode(CGDirectDisplayID display, size_t bitsPerPixel, size_t width, size_t height, boolean_t &match)
+{
+
+ // Get a copy of the current display mode
+ CGDisplayModeRef displayMode = CGDisplayCopyDisplayMode(display);
+
+ // Loop through all display modes to determine the closest match.
+ // CGDisplayBestModeForParameters is deprecated on 10.6 so we will emulate it's behavior
+ // Try to find a mode with the requested depth and equal or greater dimensions first.
+ // If no match is found, try to find a mode with greater depth and same or greater dimensions.
+ // If still no match is found, just use the current mode.
+ CFArrayRef allModes = GetAllDisplayModes(display);
+
+ for(int i = 0; i < CFArrayGetCount(allModes); i++) {
+ CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i);
+
+ if(DisplayBitsPerPixelForMode(mode) != bitsPerPixel)
+ continue;
+
+ if((CGDisplayModeGetWidth(mode) == width) && (CGDisplayModeGetHeight(mode) == height))
+ {
+ CGDisplayModeRelease(displayMode); // release the copy we got before ...
+ displayMode = mode;
+ match = true;
+ break;
+ }
+ }
+
+ // No depth match was found
+ if(!match)
+ {
+ for(int i = 0; i < CFArrayGetCount(allModes); i++)
+ {
+ CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i);
+ if(DisplayBitsPerPixelForMode(mode) >= bitsPerPixel)
+ continue;
+
+ if((CGDisplayModeGetWidth(mode) == width) && (CGDisplayModeGetHeight(mode) == height))
+ {
+ displayMode = mode;
+ match = true;
+ break;
+ }
+ }
+ }
+
+ CFRelease(allModes);
+
+ return displayMode;
+}
+
+CGDirectDisplayID GetDisplayIDFromScreen(NSScreen *screen)
+{
+ NSDictionary* screenInfo = [screen deviceDescription];
+ NSNumber* screenID = [screenInfo objectForKey:@"NSScreenNumber"];
+
+ return (CGDirectDisplayID)[screenID longValue];
+}
+
+int GetDisplayIndex(CGDirectDisplayID display)
+{
+ CGDirectDisplayID displayArray[MAX_DISPLAYS];
+ CGDisplayCount numDisplays;
+
+ // Get the list of displays.
+ CGGetActiveDisplayList(MAX_DISPLAYS, displayArray, &numDisplays);
+ while (numDisplays > 0)
+ {
+ if (display == displayArray[--numDisplays])
+ return numDisplays;
+ }
+ return -1;
+}
+
+void BlankOtherDisplays(int screen_index)
+{
+ int i;
+ int numDisplays = [[NSScreen screens] count];
+
+ // zero out blankingWindows for debugging
+ for (i=0; i<MAX_DISPLAYS; i++)
+ {
+ blankingWindows[i] = 0;
+ }
+
+ // Blank.
+ for (i=0; i<numDisplays; i++)
+ {
+ if (i != screen_index)
+ {
+ // Get the size.
+ NSScreen* pScreen = [[NSScreen screens] objectAtIndex:i];
+ NSRect screenRect = [pScreen frame];
+
+ // Build a blanking window.
+ screenRect.origin = NSZeroPoint;
+ blankingWindows[i] = [[NSWindow alloc] initWithContentRect:screenRect
+ styleMask:NSBorderlessWindowMask
+ backing:NSBackingStoreBuffered
+ defer:NO
+ screen:pScreen];
+
+ [blankingWindows[i] setBackgroundColor:[NSColor blackColor]];
+ [blankingWindows[i] setLevel:CGShieldingWindowLevel()];
+ [blankingWindows[i] makeKeyAndOrderFront:nil];
+ }
+ }
+}
+
+void UnblankDisplays(void)
+{
+ int numDisplays = [[NSScreen screens] count];
+ int i = 0;
+
+ for (i=0; i<numDisplays; i++)
+ {
+ if (blankingWindows[i] != 0)
+ {
+ // Get rid of the blanking windows we created.
+ [blankingWindows[i] close];
+ blankingWindows[i] = 0;
+ }
+ }
+}
+
+CGDisplayFadeReservationToken DisplayFadeToBlack(bool fade)
+{
+ // Fade to black to hide resolution-switching flicker and garbage.
+ CGDisplayFadeReservationToken fade_token = kCGDisplayFadeReservationInvalidToken;
+ if (CGAcquireDisplayFadeReservation (5, &fade_token) == kCGErrorSuccess && fade)
+ CGDisplayFade(fade_token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, TRUE);
+
+ return(fade_token);
+}
+
+void DisplayFadeFromBlack(CGDisplayFadeReservationToken fade_token, bool fade)
+{
+ if (fade_token != kCGDisplayFadeReservationInvalidToken)
+ {
+ if (fade)
+ CGDisplayFade(fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
+ CGReleaseDisplayFadeReservation(fade_token);
+ }
+}
+
+NSString* screenNameForDisplay(CGDirectDisplayID displayID)
+{
+ NSString* screenName;
+ @autoreleasepool
+ {
+ NSDictionary* deviceInfo = (__bridge_transfer NSDictionary*)IODisplayCreateInfoDictionary(
+ CGDisplayIOServicePort(displayID), kIODisplayOnlyPreferredName);
+ NSDictionary* localizedNames =
+ [deviceInfo objectForKey:[NSString stringWithUTF8String:kDisplayProductName]];
+
+ if ([localizedNames count] > 0)
+ {
+ screenName = [localizedNames objectForKey:[[localizedNames allKeys] objectAtIndex:0]];
+ }
+ }
+
+ if (screenName == nil)
+ {
+ screenName = [[NSString alloc] initWithFormat:@"%i", displayID];
+ }
+ else
+ {
+ // ensure screen name is unique by appending displayid
+ screenName = [screenName stringByAppendingFormat:@" (%@)", [@(displayID) stringValue]];
+ }
+
+ return screenName;
+}
+
+int GetDisplayIndex(const std::string& dispName)
+{
+ int ret = 0;
+
+ // Add full screen settings for additional monitors
+ int numDisplays = [[NSScreen screens] count];
+
+ for (int disp = 0; disp < numDisplays; disp++)
+ {
+ NSString *name = screenNameForDisplay(GetDisplayID(disp));
+ if ([name UTF8String] == dispName)
+ {
+ ret = disp;
+ break;
+ }
+ }
+
+ return ret;
+}
+
+void ShowHideNSWindow(NSWindow *wind, bool show)
+{
+ if (show)
+ [wind orderFront:nil];
+ else
+ [wind orderOut:nil];
+}
+
+static NSWindow *curtainWindow;
+void fadeInDisplay(NSScreen *theScreen, double fadeTime)
+{
+ int fadeSteps = 100;
+ double fadeInterval = (fadeTime / (double) fadeSteps);
+
+ if (curtainWindow != nil)
+ {
+ for (int step = 0; step < fadeSteps; step++)
+ {
+ double fade = 1.0 - (step * fadeInterval);
+ [curtainWindow setAlphaValue:fade];
+
+ NSDate *nextDate = [NSDate dateWithTimeIntervalSinceNow:fadeInterval];
+ [[NSRunLoop currentRunLoop] runUntilDate:nextDate];
+ }
+ }
+ [curtainWindow close];
+ curtainWindow = nil;
+
+ [NSCursor unhide];
+}
+
+void fadeOutDisplay(NSScreen *theScreen, double fadeTime)
+{
+ int fadeSteps = 100;
+ double fadeInterval = (fadeTime / (double) fadeSteps);
+
+ [NSCursor hide];
+
+ curtainWindow = [[NSWindow alloc]
+ initWithContentRect:[theScreen frame]
+ styleMask:NSBorderlessWindowMask
+ backing:NSBackingStoreBuffered
+ defer:YES
+ screen:theScreen];
+
+ [curtainWindow setAlphaValue:0.0];
+ [curtainWindow setBackgroundColor:[NSColor blackColor]];
+ [curtainWindow setLevel:NSScreenSaverWindowLevel];
+
+ [curtainWindow makeKeyAndOrderFront:nil];
+ [curtainWindow setFrame:[curtainWindow
+ frameRectForContentRect:[theScreen frame]]
+ display:YES
+ animate:NO];
+
+ for (int step = 0; step < fadeSteps; step++)
+ {
+ double fade = step * fadeInterval;
+ [curtainWindow setAlphaValue:fade];
+
+ NSDate *nextDate = [NSDate dateWithTimeIntervalSinceNow:fadeInterval];
+ [[NSRunLoop currentRunLoop] runUntilDate:nextDate];
+ }
+}
+
+// try to find mode that matches the desired size, refreshrate
+// non interlaced, nonstretched, safe for hardware
+CGDisplayModeRef GetMode(int width, int height, double refreshrate, int screenIdx)
+{
+ if ( screenIdx >= (signed)[[NSScreen screens] count])
+ return NULL;
+
+ Boolean stretched;
+ Boolean interlaced;
+ Boolean safeForHardware;
+ Boolean televisionoutput;
+ int w, h, bitsperpixel;
+ double rate;
+ RESOLUTION_INFO res;
+
+ CLog::Log(LOGDEBUG, "GetMode looking for suitable mode with {} x {} @ {:f} Hz on display {}",
+ width, height, refreshrate, screenIdx);
+
+ CFArrayRef displayModes = GetAllDisplayModes(GetDisplayID(screenIdx));
+
+ if (!displayModes)
+ return NULL;
+
+ for (int i=0; i < CFArrayGetCount(displayModes); ++i)
+ {
+ CGDisplayModeRef displayMode = (CGDisplayModeRef)CFArrayGetValueAtIndex(displayModes, i);
+ uint32_t flags = CGDisplayModeGetIOFlags(displayMode);
+ stretched = flags & kDisplayModeStretchedFlag ? true : false;
+ interlaced = flags & kDisplayModeInterlacedFlag ? true : false;
+ bitsperpixel = DisplayBitsPerPixelForMode(displayMode);
+ safeForHardware = flags & kDisplayModeSafetyFlags ? true : false;
+ televisionoutput = flags & kDisplayModeTelevisionFlag ? true : false;
+ w = CGDisplayModeGetWidth(displayMode);
+ h = CGDisplayModeGetHeight(displayMode);
+ rate = CGDisplayModeGetRefreshRate(displayMode);
+
+
+ if ((bitsperpixel == 32) &&
+ (safeForHardware == YES) &&
+ (stretched == NO) &&
+ (interlaced == NO) &&
+ (w == width) &&
+ (h == height) &&
+ (rate == refreshrate || rate == 0))
+ {
+ CLog::Log(LOGDEBUG, "GetMode found a match!");
+ return displayMode;
+ }
+ }
+
+ CFRelease(displayModes);
+ CLog::Log(LOGERROR, "GetMode - no match found!");
+ return NULL;
+}
+
+//---------------------------------------------------------------------------------
+static void DisplayReconfigured(CGDirectDisplayID display,
+ CGDisplayChangeSummaryFlags flags, void* userData)
+{
+ CWinSystemOSX *winsys = (CWinSystemOSX*)userData;
+ if (!winsys)
+ return;
+
+ CLog::Log(LOGDEBUG, "CWinSystemOSX::DisplayReconfigured with flags {}", flags);
+
+ // we fire the callbacks on start of configuration
+ // or when the mode set was finished
+ // or when we are called with flags == 0 (which is undocumented but seems to happen
+ // on some macs - we treat it as device reset)
+
+ // first check if we need to call OnLostDevice
+ if (flags & kCGDisplayBeginConfigurationFlag)
+ {
+ // pre/post-reconfiguration changes
+ RESOLUTION res = CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution();
+ if (res == RES_INVALID)
+ return;
+
+ NSScreen* pScreen = nil;
+ unsigned int screenIdx = 0;
+
+ if ( screenIdx < [[NSScreen screens] count] )
+ {
+ pScreen = [[NSScreen screens] objectAtIndex:screenIdx];
+ }
+
+ // kCGDisplayBeginConfigurationFlag is only fired while the screen is still
+ // valid
+ if (pScreen)
+ {
+ CGDirectDisplayID xbmc_display = GetDisplayIDFromScreen(pScreen);
+ if (xbmc_display == display)
+ {
+ // we only respond to changes on the display we are running on.
+ winsys->AnnounceOnLostDevice();
+ winsys->StartLostDeviceTimer();
+ }
+ }
+ }
+ else // the else case checks if we need to call OnResetDevice
+ {
+ // we fire if kCGDisplaySetModeFlag is set or if flags == 0
+ // (which is undocumented but seems to happen
+ // on some macs - we treat it as device reset)
+ // we also don't check the screen here as we might not even have
+ // one anymore (e.x. when tv is turned off)
+ if (flags & kCGDisplaySetModeFlag || flags == 0)
+ {
+ winsys->StopLostDeviceTimer(); // no need to timeout - we've got the callback
+ winsys->HandleOnResetDevice();
+ }
+ }
+
+ if ((flags & kCGDisplayAddFlag) || (flags & kCGDisplayRemoveFlag))
+ winsys->UpdateResolutions();
+}
+
+//------------------------------------------------------------------------------
+NSOpenGLContext* CreateWindowedContext(NSOpenGLContext* shareCtx);
+void ResizeWindowInternal(int newWidth, int newHeight, int newLeft, int newTop, NSView* last_view);
+
+//------------------------------------------------------------------------------
+CWinSystemOSX::CWinSystemOSX()
+ : CWinSystemBase()
+ , m_impl{new CWinSystemOSXImpl}
+ , m_lostDeviceTimer(this)
+{
+ m_SDLSurface = NULL;
+ m_osx_events = NULL;
+ m_obscured = false;
+ m_obscured_timecheck = std::chrono::steady_clock::now() + std::chrono::milliseconds(1000);
+ m_lastDisplayNr = -1;
+ m_movedToOtherScreen = false;
+ m_refreshRate = 0.0;
+ m_delayDispReset = false;
+
+ m_winEvents.reset(new CWinEventsOSX());
+
+ AE::CAESinkFactory::ClearSinks();
+ CAESinkDARWINOSX::Register();
+ m_dpms = std::make_shared<CCocoaDPMSSupport>();
+}
+
+CWinSystemOSX::~CWinSystemOSX() = default;
+
+void CWinSystemOSX::StartLostDeviceTimer()
+{
+ if (m_lostDeviceTimer.IsRunning())
+ m_lostDeviceTimer.Restart();
+ else
+ m_lostDeviceTimer.Start(LOST_DEVICE_TIMEOUT_MS, false);
+}
+
+void CWinSystemOSX::StopLostDeviceTimer()
+{
+ m_lostDeviceTimer.Stop();
+}
+
+void CWinSystemOSX::OnTimeout()
+{
+ HandleOnResetDevice();
+}
+
+bool CWinSystemOSX::InitWindowSystem()
+{
+ CLog::LogF(LOGINFO, "Setup SDL");
+
+ /* Clean up on exit, exit on window close and interrupt */
+ std::atexit(SDL_Quit);
+
+ if (SDL_Init(SDL_INIT_VIDEO) != 0)
+ {
+ CLog::LogF(LOGFATAL, "Unable to initialize SDL: {}", SDL_GetError());
+ return false;
+ }
+ // SDL_Init will install a handler for segfaults, restore the default handler.
+ signal(SIGSEGV, SIG_DFL);
+
+ SDL_EnableUNICODE(1);
+
+ // set repeat to 10ms to ensure repeat time < frame time
+ // so that hold times can be reliably detected
+ SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, 10);
+
+ if (!CWinSystemBase::InitWindowSystem())
+ return false;
+
+ m_osx_events = new CWinEventsOSX();
+
+ CGDisplayRegisterReconfigurationCallback(DisplayReconfigured, (void*)this);
+
+ NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
+ auto windowDidMove = [windowDidMoveNoteClass initWith:this];
+ [center addObserver:windowDidMove
+ selector:@selector(windowDidMoveNotification:)
+ name:NSWindowDidMoveNotification object:nil];
+ m_impl->m_windowDidMove = windowDidMove;
+
+ auto windowDidReSize = [windowDidReSizeNoteClass initWith:this];
+ [center addObserver:windowDidReSize
+ selector:@selector(windowDidReSizeNotification:)
+ name:NSWindowDidResizeNotification object:nil];
+ m_impl->m_windowDidReSize = windowDidReSize;
+
+ auto windowDidChangeScreen = [windowDidChangeScreenNoteClass initWith:this];
+ [center addObserver:windowDidChangeScreen
+ selector:@selector(windowDidChangeScreenNotification:)
+ name:NSWindowDidChangeScreenNotification object:nil];
+ m_impl->m_windowChangedScreen = windowDidChangeScreen;
+
+ return true;
+}
+
+bool CWinSystemOSX::DestroyWindowSystem()
+{
+ NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
+ [center removeObserver:m_impl->m_windowDidMove name:NSWindowDidMoveNotification object:nil];
+ [center removeObserver:m_impl->m_windowDidReSize name:NSWindowDidResizeNotification object:nil];
+ [center removeObserver:m_impl->m_windowChangedScreen
+ name:NSWindowDidChangeScreenNotification
+ object:nil];
+
+ CGDisplayRemoveReconfigurationCallback(DisplayReconfigured, (void*)this);
+
+ delete m_osx_events;
+ m_osx_events = NULL;
+
+ UnblankDisplays();
+ m_impl->m_glContext = nil;
+ return true;
+}
+
+bool CWinSystemOSX::CreateNewWindow(const std::string& name, bool fullScreen, RESOLUTION_INFO& res)
+{
+ // force initial window creation to be windowed, if fullscreen, it will switch to it below
+ // fixes the white screen of death if starting fullscreen and switching to windowed.
+ RESOLUTION_INFO resInfo = CDisplaySettings::GetInstance().GetResolutionInfo(RES_WINDOW);
+ m_nWidth = resInfo.iWidth;
+ m_nHeight = resInfo.iHeight;
+ m_bFullScreen = false;
+
+ SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
+ SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
+ SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
+ SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
+ SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
+
+ // Enable vertical sync to avoid any tearing.
+ SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 1);
+
+ m_SDLSurface = SDL_SetVideoMode(m_nWidth, m_nHeight, 0, SDL_OPENGL | SDL_RESIZABLE);
+ if (!m_SDLSurface)
+ return false;
+
+ // the context SDL creates isn't full screen compatible, so we create new one
+ // first, find the current contect and make sure a view is attached
+ NSOpenGLContext* cur_context = [NSOpenGLContext currentContext];
+ NSView* view = [cur_context view];
+ if (!view)
+ return false;
+
+ if (CDisplaySettings::GetInstance().GetCurrentResolution() != RES_WINDOW)
+ {
+ // If we are not starting up windowed, then hide the initial SDL window
+ // so we do not see it flash before the fade-out and switch to fullscreen.
+ ShowHideNSWindow([view window], false);
+ }
+
+ // disassociate view from context
+ [cur_context clearDrawable];
+
+ // release the context
+ if (CWinSystemOSXImpl::m_lastOwnedContext == cur_context)
+ {
+ [ NSOpenGLContext clearCurrentContext ];
+ [ cur_context clearDrawable ];
+ cur_context = nil;
+ }
+
+ // create a new context
+ auto new_context = CreateWindowedContext(nil);
+ if (!new_context)
+ return false;
+
+ // associate with current view
+ [new_context setView:view];
+ [new_context makeCurrentContext];
+
+ // set the window title
+ [[[new_context view] window]
+ setTitle:[NSString stringWithFormat:@"%s Media Center", CCompileInfo::GetAppName()]];
+
+ m_impl->m_glContext = new_context;
+ CWinSystemOSXImpl::m_lastOwnedContext = new_context;
+ m_bWindowCreated = true;
+
+ // get screen refreshrate - this is needed
+ // when we startup in windowed mode and don't run through SetFullScreen
+ int dummy;
+ GetScreenResolution(&dummy, &dummy, &m_refreshRate, m_lastDisplayNr);
+
+ // register platform dependent objects
+ CDVDFactoryCodec::ClearHWAccels();
+ VTB::CDecoder::Register();
+ VIDEOPLAYER::CRendererFactory::ClearRenderer();
+ CLinuxRendererGL::Register();
+ CRendererVTB::Register();
+ VIDEOPLAYER::CProcessInfoOSX::Register();
+ RETRO::CRPProcessInfoOSX::Register();
+ RETRO::CRPProcessInfoOSX::RegisterRendererFactory(new RETRO::CRendererFactoryOpenGL);
+ CScreenshotSurfaceGL::Register();
+
+ return true;
+}
+
+bool CWinSystemOSX::DestroyWindow()
+{
+ return true;
+}
+
+void ResizeWindowInternal(int newWidth, int newHeight, int newLeft, int newTop, NSView* last_view)
+{
+ if (last_view && [last_view window])
+ {
+ auto size = NSMakeSize(newWidth, newHeight);
+ NSWindow* lastWindow = [last_view window];
+ [lastWindow setContentSize:size];
+ [lastWindow update];
+ [last_view setFrameSize:size];
+ }
+}
+bool CWinSystemOSX::ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop)
+{
+ if (!m_impl->m_glContext)
+ return false;
+
+ NSOpenGLContext* context = [NSOpenGLContext currentContext];
+ NSView* view;
+ NSWindow* window;
+
+ view = [context view];
+
+ if (view)
+ {
+ // It seems, that in macOS 10.15 this defaults to YES, but we currently do not support
+ // Retina resolutions properly. Ensure that the view uses a 1 pixel per point framebuffer.
+ view.wantsBestResolutionOpenGLSurface = NO;
+ }
+
+ if (view && (newWidth > 0) && (newHeight > 0))
+ {
+ window = [view window];
+ if (window)
+ {
+ [window setContentSize:NSMakeSize(newWidth, newHeight)];
+ [window update];
+ [view setFrameSize:NSMakeSize(newWidth, newHeight)];
+ [context update];
+ // this is needed in case we traverse from fullscreen screen 2
+ // to windowed on screen 1 directly where in ScreenChangedNotification
+ // we don't have a window to get the current screen on
+ // in that case ResizeWindow is called at a later stage from SetFullScreen(false)
+ // and we can grab the correct display number here then
+ m_lastDisplayNr = GetDisplayIndex(GetDisplayIDFromScreen( [window screen] ));
+ }
+ }
+
+ // HACK: resize SDL's view manually so that mouse bounds are correctly updated.
+ // there are two parts to this, the internal SDL (current_video->screen) and
+ // the cocoa view ( handled in SetFullScreen).
+ SDL_SetWidthHeight(newWidth, newHeight);
+
+ [context makeCurrentContext];
+
+ m_nWidth = newWidth;
+ m_nHeight = newHeight;
+ m_impl->m_glContext = context;
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetFPS(m_refreshRate);
+
+ return true;
+}
+
+static bool needtoshowme = true;
+
+bool CWinSystemOSX::SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays)
+{
+ static NSWindow* windowedFullScreenwindow = nil;
+ static NSPoint last_window_origin;
+ static NSView* last_view = nil;
+ static NSSize last_view_size;
+ static NSPoint last_view_origin;
+ static NSInteger last_window_level = NSNormalWindowLevel;
+ bool was_fullscreen = m_bFullScreen;
+ NSOpenGLContext* cur_context;
+
+ // Fade to black to hide resolution-switching flicker and garbage.
+ CGDisplayFadeReservationToken fade_token = DisplayFadeToBlack(needtoshowme);
+
+ // If we're already fullscreen then we must be moving to a different display.
+ // or if we are still on the same display - it might be only a refreshrate/resolution
+ // change request.
+ // Recurse to reset fullscreen mode and then continue.
+ if (was_fullscreen && fullScreen)
+ {
+ needtoshowme = false;
+ ShowHideNSWindow([last_view window], needtoshowme);
+ RESOLUTION_INFO& window = CDisplaySettings::GetInstance().GetResolutionInfo(RES_WINDOW);
+ CWinSystemOSX::SetFullScreen(false, window, blankOtherDisplays);
+ needtoshowme = true;
+ }
+
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ m_lastDisplayNr = GetDisplayIndex(settings->GetString(CSettings::SETTING_VIDEOSCREEN_MONITOR));
+ m_nWidth = res.iWidth;
+ m_nHeight = res.iHeight;
+ m_bFullScreen = fullScreen;
+
+ cur_context = [NSOpenGLContext currentContext];
+
+ //handle resolution/refreshrate switching early here
+ if (m_bFullScreen)
+ {
+ // switch videomode
+ SwitchToVideoMode(res.iWidth, res.iHeight, static_cast<double>(res.fRefreshRate));
+ }
+
+ //no context? done.
+ if (!cur_context)
+ {
+ DisplayFadeFromBlack(fade_token, needtoshowme);
+ return false;
+ }
+
+ if (windowedFullScreenwindow != nil)
+ {
+ [windowedFullScreenwindow close];
+ windowedFullScreenwindow = nil;
+ }
+
+ if (m_bFullScreen)
+ {
+ // FullScreen Mode
+ NSOpenGLContext* newContext = nil;
+
+ // Save info about the windowed context so we can restore it when returning to windowed.
+ last_view = [cur_context view];
+ last_view_size = [last_view frame].size;
+ last_view_origin = [last_view frame].origin;
+ last_window_origin = [[last_view window] frame].origin;
+ last_window_level = [[last_view window] level];
+
+ // This is Cocoa Windowed FullScreen Mode
+ // Get the screen rect of our current display
+ NSScreen* pScreen = [[NSScreen screens] objectAtIndex:m_lastDisplayNr];
+ NSRect screenRect = [pScreen frame];
+
+ // remove frame origin offset of original display
+ screenRect.origin = NSZeroPoint;
+
+ // make a new window to act as the windowedFullScreen
+ windowedFullScreenwindow = [[NSWindow alloc] initWithContentRect:screenRect
+ styleMask:NSBorderlessWindowMask
+ backing:NSBackingStoreBuffered
+ defer:NO
+ screen:pScreen];
+ windowedFullScreenwindow.releasedWhenClosed = NO;
+
+ [windowedFullScreenwindow setBackgroundColor:[NSColor blackColor]];
+ [windowedFullScreenwindow makeKeyAndOrderFront:nil];
+
+ // make our window the same level as the rest to enable cmd+tab switching
+ [windowedFullScreenwindow setLevel:NSNormalWindowLevel];
+ // this will make our window topmost and hide all system messages
+ //[windowedFullScreenwindow setLevel:CGShieldingWindowLevel()];
+
+ // ...and the original one beneath it and on the same screen.
+ [[last_view window] setLevel:NSNormalWindowLevel-1];
+ [[last_view window] setFrameOrigin:[pScreen frame].origin];
+ // expand the mouse bounds in SDL view to fullscreen
+ [ last_view setFrameOrigin:NSMakePoint(0.0, 0.0)];
+ [ last_view setFrameSize:NSMakeSize(m_nWidth, m_nHeight) ];
+
+ NSView* blankView = [[NSView alloc] init];
+ [windowedFullScreenwindow setContentView:blankView];
+ [windowedFullScreenwindow setContentSize:NSMakeSize(m_nWidth, m_nHeight)];
+ [windowedFullScreenwindow update];
+ [blankView setFrameSize:NSMakeSize(m_nWidth, m_nHeight)];
+
+ // force SDL's window origin to be at zero:
+ // on Monterey, X coordinate becomes non-zero somewhere after this method returns
+ const auto twoSeconds = dispatch_time(DISPATCH_TIME_NOW, 2LL * NSEC_PER_SEC);
+ dispatch_after(twoSeconds, dispatch_get_main_queue(), ^{
+ for (NSWindow* w in NSApp.windows)
+ {
+ if (w == windowedFullScreenwindow)
+ continue;
+ [w setFrameOrigin:w.screen.frame.origin];
+ break;
+ }
+ });
+
+ // Obtain windowed pixel format and create a new context.
+ newContext = CreateWindowedContext(cur_context);
+ [newContext setView:blankView];
+
+ // Hide the menu bar.
+ SetMenuBarVisible(false);
+
+ // Blank other displays if requested.
+ if (blankOtherDisplays)
+ BlankOtherDisplays(m_lastDisplayNr);
+
+ // Hide the mouse.
+ [NSCursor hide];
+
+ // Release old context if we created it.
+ if (CWinSystemOSXImpl::m_lastOwnedContext == cur_context)
+ {
+ [NSOpenGLContext clearCurrentContext];
+ [cur_context clearDrawable];
+ }
+
+ // activate context
+ [newContext makeCurrentContext];
+ CWinSystemOSXImpl::m_lastOwnedContext = newContext;
+ }
+ else
+ {
+ // Windowed Mode
+ // exit fullscreen
+ [cur_context clearDrawable];
+
+ [NSCursor unhide];
+
+ // Show menubar.
+ SetMenuBarVisible(true);
+
+ // restore the windowed window level
+ [[last_view window] setLevel:last_window_level];
+
+ // Unblank.
+ // Force the unblank when returning from fullscreen, we get called with blankOtherDisplays set false.
+ //if (blankOtherDisplays)
+ UnblankDisplays();
+
+ // create our new context (sharing with the current one)
+ auto newContext = CreateWindowedContext(cur_context);
+ if (!newContext)
+ return false;
+
+ // Assign view from old context, move back to original screen.
+ [newContext setView:last_view];
+ [[last_view window] setFrameOrigin:last_window_origin];
+ // return the mouse bounds in SDL view to previous size
+ [last_view setFrameSize:last_view_size];
+ [last_view setFrameOrigin:last_view_origin];
+
+ // Release the fullscreen context.
+ if (CWinSystemOSXImpl::m_lastOwnedContext == cur_context)
+ {
+ [NSOpenGLContext clearCurrentContext];
+ [cur_context clearDrawable];
+ }
+
+ // Activate context.
+ [newContext makeCurrentContext];
+ CWinSystemOSXImpl::m_lastOwnedContext = newContext;
+ }
+
+ DisplayFadeFromBlack(fade_token, needtoshowme);
+
+ ShowHideNSWindow([last_view window], needtoshowme);
+ // need to make sure SDL tracks any window size changes
+ ResizeWindow(m_nWidth, m_nHeight, -1, -1);
+ ResizeWindowInternal(m_nWidth, m_nHeight, -1, -1, last_view);
+ // restore origin once again when going to windowed mode
+ if (!fullScreen)
+ {
+ [[last_view window] setFrameOrigin:last_window_origin];
+ }
+ HandlePossibleRefreshrateChange();
+
+ m_updateGLContext = 0;
+ return true;
+}
+
+void CWinSystemOSX::UpdateResolutions()
+{
+ CWinSystemBase::UpdateResolutions();
+
+ // Add desktop resolution
+ int w, h;
+ double fps;
+
+ int dispIdx = GetDisplayIndex(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_VIDEOSCREEN_MONITOR));
+ GetScreenResolution(&w, &h, &fps, dispIdx);
+ NSString* dispName = screenNameForDisplay(GetDisplayID(dispIdx));
+ UpdateDesktopResolution(CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP), [dispName UTF8String], w, h, fps, 0);
+
+ CDisplaySettings::GetInstance().ClearCustomResolutions();
+
+ // now just fill in the possible resolutions for the attached screens
+ // and push to the resolution info vector
+ FillInVideoModes();
+ CDisplaySettings::GetInstance().ApplyCalibrations();
+}
+
+/*
+void* Cocoa_GL_CreateContext(void* pixFmt, void* shareCtx)
+{
+ if (!pixFmt)
+ return nil;
+
+ NSOpenGLContext* newContext = [[NSOpenGLContext alloc] initWithFormat:(NSOpenGLPixelFormat*)pixFmt
+ shareContext:(NSOpenGLContext*)shareCtx];
+
+ // snipit from SDL_cocoaopengl.m
+ //
+ // Wisdom from Apple engineer in reference to UT2003's OpenGL performance:
+ // "You are blowing a couple of the internal OpenGL function caches. This
+ // appears to be happening in the VAO case. You can tell OpenGL to up
+ // the cache size by issuing the following calls right after you create
+ // the OpenGL context. The default cache size is 16." --ryan.
+ //
+
+ #ifndef GLI_ARRAY_FUNC_CACHE_MAX
+ #define GLI_ARRAY_FUNC_CACHE_MAX 284
+ #endif
+
+ #ifndef GLI_SUBMIT_FUNC_CACHE_MAX
+ #define GLI_SUBMIT_FUNC_CACHE_MAX 280
+ #endif
+
+ {
+ long cache_max = 64;
+ CGLContextObj ctx = (CGLContextObj)[newContext CGLContextObj];
+ CGLSetParameter(ctx, (CGLContextParameter)GLI_SUBMIT_FUNC_CACHE_MAX, &cache_max);
+ CGLSetParameter(ctx, (CGLContextParameter)GLI_ARRAY_FUNC_CACHE_MAX, &cache_max);
+ }
+
+ // End Wisdom from Apple Engineer section. --ryan.
+ return newContext;
+}
+*/
+
+NSOpenGLContext* CreateWindowedContext(NSOpenGLContext* shareCtx)
+{
+ NSOpenGLPixelFormat* pixFmt;
+ if (getenv("KODI_GL_PROFILE_LEGACY"))
+ {
+ NSOpenGLPixelFormatAttribute wattrs[] = {
+ NSOpenGLPFADoubleBuffer,
+ NSOpenGLPFANoRecovery,
+ NSOpenGLPFAAccelerated,
+ NSOpenGLPFADepthSize,
+ static_cast<NSOpenGLPixelFormatAttribute>(8),
+ static_cast<NSOpenGLPixelFormatAttribute>(0)};
+ pixFmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:wattrs];
+ }
+ else
+ {
+ NSOpenGLPixelFormatAttribute wattrs_gl3[] = {
+ NSOpenGLPFADoubleBuffer,
+ NSOpenGLPFAOpenGLProfile,
+ NSOpenGLProfileVersion3_2Core,
+ NSOpenGLPFANoRecovery,
+ NSOpenGLPFAAccelerated,
+ NSOpenGLPFADepthSize,
+ static_cast<NSOpenGLPixelFormatAttribute>(24),
+ static_cast<NSOpenGLPixelFormatAttribute>(0)};
+ pixFmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:wattrs_gl3];
+ }
+
+ auto newContext = [[NSOpenGLContext alloc] initWithFormat:pixFmt shareContext:shareCtx];
+
+ if (!newContext)
+ {
+ // bah, try again for non-accelerated renderer
+ NSOpenGLPixelFormatAttribute wattrs2[] =
+ {
+ NSOpenGLPFADoubleBuffer,
+ NSOpenGLPFANoRecovery,
+ NSOpenGLPFADepthSize, (NSOpenGLPixelFormatAttribute)8,
+ (NSOpenGLPixelFormatAttribute)0
+ };
+ newContext = [[NSOpenGLContext alloc]
+ initWithFormat:[[NSOpenGLPixelFormat alloc] initWithAttributes:wattrs2]
+ shareContext:shareCtx];
+ }
+
+ return newContext;
+}
+
+NSOpenGLContext* CreateFullScreenContext(int screen_index, NSOpenGLContext* shareCtx)
+{
+ CGDirectDisplayID displayArray[MAX_DISPLAYS];
+ CGDisplayCount numDisplays;
+ CGDirectDisplayID displayID;
+
+ // Get the list of displays.
+ CGGetActiveDisplayList(MAX_DISPLAYS, displayArray, &numDisplays);
+ displayID = displayArray[screen_index];
+
+ NSOpenGLPixelFormat* pixFmt;
+ if (getenv("KODI_GL_PROFILE_LEGACY"))
+ {
+ NSOpenGLPixelFormatAttribute fsattrs[] = {
+ NSOpenGLPFADoubleBuffer,
+ NSOpenGLPFANoRecovery,
+ NSOpenGLPFAAccelerated,
+ NSOpenGLPFADepthSize,
+ static_cast<NSOpenGLPixelFormatAttribute>(8),
+ NSOpenGLPFAScreenMask,
+ static_cast<NSOpenGLPixelFormatAttribute>(CGDisplayIDToOpenGLDisplayMask(displayID)),
+ static_cast<NSOpenGLPixelFormatAttribute>(0)};
+ pixFmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:fsattrs];
+ }
+ else
+ {
+ NSOpenGLPixelFormatAttribute fsattrs_gl3[] = {
+ NSOpenGLPFADoubleBuffer,
+ NSOpenGLPFANoRecovery,
+ NSOpenGLPFAAccelerated,
+ NSOpenGLPFADepthSize,
+ static_cast<NSOpenGLPixelFormatAttribute>(24),
+ NSOpenGLPFAOpenGLProfile,
+ NSOpenGLProfileVersion3_2Core,
+ NSOpenGLPFAScreenMask,
+ static_cast<NSOpenGLPixelFormatAttribute>(CGDisplayIDToOpenGLDisplayMask(displayID)),
+ static_cast<NSOpenGLPixelFormatAttribute>(0)};
+ pixFmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:fsattrs_gl3];
+ }
+
+ if (!pixFmt)
+ return nil;
+
+ auto newContext = [[NSOpenGLContext alloc] initWithFormat:pixFmt shareContext:shareCtx];
+
+ return newContext;
+}
+
+void CWinSystemOSX::GetScreenResolution(int* w, int* h, double* fps, int screenIdx)
+{
+ CGDirectDisplayID display_id = (CGDirectDisplayID)GetDisplayID(screenIdx);
+ CGDisplayModeRef mode = CGDisplayCopyDisplayMode(display_id);
+ *w = CGDisplayModeGetWidth(mode);
+ *h = CGDisplayModeGetHeight(mode);
+ *fps = CGDisplayModeGetRefreshRate(mode);
+ CGDisplayModeRelease(mode);
+ if ((int)*fps == 0)
+ {
+ // NOTE: The refresh rate will be REPORTED AS 0 for many DVI and notebook displays.
+ *fps = 60.0;
+ }
+}
+
+void CWinSystemOSX::EnableVSync(bool enable)
+{
+ // OpenGL Flush synchronised with vertical retrace
+ GLint swapInterval = enable ? 1 : 0;
+ [[NSOpenGLContext currentContext] setValues:&swapInterval forParameter:NSOpenGLCPSwapInterval];
+}
+
+bool CWinSystemOSX::SwitchToVideoMode(int width, int height, double refreshrate)
+{
+ boolean_t match = false;
+ CGDisplayModeRef dispMode = NULL;
+
+ int screenIdx = GetDisplayIndex(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_VIDEOSCREEN_MONITOR));
+
+ // Figure out the screen size. (default to main screen)
+ CGDirectDisplayID display_id = GetDisplayID(screenIdx);
+
+ // find mode that matches the desired size, refreshrate
+ // non interlaced, nonstretched, safe for hardware
+ dispMode = GetMode(width, height, refreshrate, screenIdx);
+
+ //not found - fallback to bestemdeforparameters
+ if (!dispMode)
+ {
+ dispMode = BestMatchForMode(display_id, 32, width, height, match);
+
+ if (!match)
+ dispMode = BestMatchForMode(display_id, 16, width, height, match);
+
+ // still no match? fallback to current resolution of the display which HAS to work [tm]
+ if (!match)
+ {
+ int tmpWidth;
+ int tmpHeight;
+ double tmpRefresh;
+
+ GetScreenResolution(&tmpWidth, &tmpHeight, &tmpRefresh, screenIdx);
+ dispMode = GetMode(tmpWidth, tmpHeight, tmpRefresh, screenIdx);
+
+ // no way to get a resolution set
+ if (!dispMode)
+ return false;
+ }
+
+ if (!match)
+ return false;
+ }
+
+ // switch mode and return success
+ CGDisplayCapture(display_id);
+ CGDisplayConfigRef cfg;
+ CGBeginDisplayConfiguration(&cfg);
+ CGConfigureDisplayWithDisplayMode(cfg, display_id, dispMode, nullptr);
+ CGError err = CGCompleteDisplayConfiguration(cfg, kCGConfigureForAppOnly);
+ CGDisplayRelease(display_id);
+
+ m_refreshRate = CGDisplayModeGetRefreshRate(dispMode);
+
+ Cocoa_CVDisplayLinkUpdate();
+
+ return (err == kCGErrorSuccess);
+}
+
+void CWinSystemOSX::FillInVideoModes()
+{
+ int dispIdx = GetDisplayIndex(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_VIDEOSCREEN_MONITOR));
+
+ // Add full screen settings for additional monitors
+ int numDisplays = [[NSScreen screens] count];
+
+ for (int disp = 0; disp < numDisplays; disp++)
+ {
+ Boolean stretched;
+ Boolean interlaced;
+ Boolean safeForHardware;
+ Boolean televisionoutput;
+ int w, h, bitsperpixel;
+ double refreshrate;
+ RESOLUTION_INFO res;
+
+ CFArrayRef displayModes = GetAllDisplayModes(GetDisplayID(disp));
+ NSString *dispName = screenNameForDisplay(GetDisplayID(disp));
+
+ CLog::Log(LOGINFO, "Display {} has name {}", disp, [dispName UTF8String]);
+
+ if (NULL == displayModes)
+ continue;
+
+ for (int i=0; i < CFArrayGetCount(displayModes); ++i)
+ {
+ CGDisplayModeRef displayMode = (CGDisplayModeRef)CFArrayGetValueAtIndex(displayModes, i);
+
+ uint32_t flags = CGDisplayModeGetIOFlags(displayMode);
+ stretched = flags & kDisplayModeStretchedFlag ? true : false;
+ interlaced = flags & kDisplayModeInterlacedFlag ? true : false;
+ bitsperpixel = DisplayBitsPerPixelForMode(displayMode);
+ safeForHardware = flags & kDisplayModeSafetyFlags ? true : false;
+ televisionoutput = flags & kDisplayModeTelevisionFlag ? true : false;
+
+ if ((bitsperpixel == 32) &&
+ (safeForHardware == YES) &&
+ (stretched == NO) &&
+ (interlaced == NO))
+ {
+ w = CGDisplayModeGetWidth(displayMode);
+ h = CGDisplayModeGetHeight(displayMode);
+ refreshrate = CGDisplayModeGetRefreshRate(displayMode);
+ if ((int)refreshrate == 0) // LCD display?
+ {
+ // NOTE: The refresh rate will be REPORTED AS 0 for many DVI and notebook displays.
+ refreshrate = 60.0;
+ }
+ CLog::Log(LOGINFO, "Found possible resolution for display {} with {} x {} @ {:f} Hz", disp,
+ w, h, refreshrate);
+
+ // only add the resolution if it belongs to "our" screen
+ // all others are only logged above...
+ if (disp == dispIdx)
+ {
+ UpdateDesktopResolution(res, (dispName != nil) ? [dispName UTF8String] : "Unknown", w, h, refreshrate, 0);
+ CServiceBroker::GetWinSystem()->GetGfxContext().ResetOverscan(res);
+ CDisplaySettings::GetInstance().AddResolutionInfo(res);
+ }
+ }
+ }
+ CFRelease(displayModes);
+ }
+}
+
+bool CWinSystemOSX::FlushBuffer(void)
+{
+ if (m_updateGLContext < 5)
+ {
+ [m_impl->m_glContext update];
+ m_updateGLContext++;
+ }
+
+ [m_impl->m_glContext flushBuffer];
+
+ return true;
+}
+
+bool CWinSystemOSX::IsObscured(void)
+{
+ // check once a second if we are obscured.
+ auto now_time = std::chrono::steady_clock::now();
+ if (m_obscured_timecheck > now_time)
+ return m_obscured;
+ else
+ m_obscured_timecheck = now_time + std::chrono::milliseconds(1000);
+
+ NSOpenGLContext* cur_context = [NSOpenGLContext currentContext];
+ NSView* view = [cur_context view];
+ if (!view)
+ {
+ // sanity check, we should always have a view
+ m_obscured = true;
+ return m_obscured;
+ }
+
+ NSWindow *window = [view window];
+ if (!window)
+ {
+ // sanity check, we should always have a window
+ m_obscured = true;
+ return m_obscured;
+ }
+
+ if ([window isVisible] == NO)
+ {
+ // not visible means the window is not showing.
+ // this should never really happen as we are always visible
+ // even when minimized in dock.
+ m_obscured = true;
+ return m_obscured;
+ }
+
+ // check if we are minimized (to an icon in the Dock).
+ if ([window isMiniaturized] == YES)
+ {
+ m_obscured = true;
+ return m_obscured;
+ }
+
+ // check if we are showing on the active workspace.
+ if ([window isOnActiveSpace] == NO)
+ {
+ m_obscured = true;
+ return m_obscured;
+ }
+
+ // default to false before we start parsing though the windows.
+ // if we are are obscured by any windows, then set true.
+ m_obscured = false;
+ static bool obscureLogged = false;
+
+ CGWindowListOption opts;
+ opts = kCGWindowListOptionOnScreenAboveWindow | kCGWindowListExcludeDesktopElements;
+ CFArrayRef windowIDs =CGWindowListCreate(opts, (CGWindowID)[window windowNumber]);
+
+ if (!windowIDs)
+ return m_obscured;
+
+ CFArrayRef windowDescs = CGWindowListCreateDescriptionFromArray(windowIDs);
+ if (!windowDescs)
+ {
+ CFRelease(windowIDs);
+ return m_obscured;
+ }
+
+ CGRect bounds = NSRectToCGRect([window frame]);
+ // kCGWindowBounds measures the origin as the top-left corner of the rectangle
+ // relative to the top-left corner of the screen.
+ // NSWindow’s frame property measures the origin as the bottom-left corner
+ // of the rectangle relative to the bottom-left corner of the screen.
+ // convert bounds from NSWindow to CGWindowBounds here.
+ bounds.origin.y = [[window screen] frame].size.height - bounds.origin.y - bounds.size.height;
+
+ std::vector<CRect> partialOverlaps;
+ CRect ourBounds = CGRectToCRect(bounds);
+
+ for (CFIndex idx=0; idx < CFArrayGetCount(windowDescs); idx++)
+ {
+ // walk the window list of windows that are above us and are not desktop elements
+ CFDictionaryRef windowDictionary = (CFDictionaryRef)CFArrayGetValueAtIndex(windowDescs, idx);
+
+ // skip the Dock window, it actually covers the entire screen.
+ CFStringRef ownerName = (CFStringRef)CFDictionaryGetValue(windowDictionary, kCGWindowOwnerName);
+ if (CFStringCompare(ownerName, CFSTR("Dock"), 0) == kCFCompareEqualTo)
+ continue;
+
+ // Ignore known brightness tools for dimming the screen. They claim to cover
+ // the whole XBMC window and therefore would make the framerate limiter
+ // kicking in. Unfortunately even the alpha of these windows is 1.0 so
+ // we have to check the ownerName.
+ if (CFStringCompare(ownerName, CFSTR("Shades"), 0) == kCFCompareEqualTo ||
+ CFStringCompare(ownerName, CFSTR("SmartSaver"), 0) == kCFCompareEqualTo ||
+ CFStringCompare(ownerName, CFSTR("Brightness Slider"), 0) == kCFCompareEqualTo ||
+ CFStringCompare(ownerName, CFSTR("Displaperture"), 0) == kCFCompareEqualTo ||
+ CFStringCompare(ownerName, CFSTR("Dreamweaver"), 0) == kCFCompareEqualTo ||
+ CFStringCompare(ownerName, CFSTR("Window Server"), 0) == kCFCompareEqualTo)
+ continue;
+
+ CFDictionaryRef rectDictionary = (CFDictionaryRef)CFDictionaryGetValue(windowDictionary, kCGWindowBounds);
+ if (!rectDictionary)
+ continue;
+
+ CGRect windowBounds;
+ if (CGRectMakeWithDictionaryRepresentation(rectDictionary, &windowBounds))
+ {
+ if (CGRectContainsRect(windowBounds, bounds))
+ {
+ // if the windowBounds completely encloses our bounds, we are obscured.
+ if (!obscureLogged)
+ {
+ std::string appName;
+ if (CDarwinUtils::CFStringRefToUTF8String(ownerName, appName))
+ CLog::Log(LOGDEBUG, "WinSystemOSX: Fullscreen window {} obscures Kodi!", appName);
+ obscureLogged = true;
+ }
+ m_obscured = true;
+ break;
+ }
+
+ // handle overlapping windows above us that combine
+ // to obscure by collecting any partial overlaps,
+ // then subtract them from our bounds and check
+ // for any remaining area.
+ CRect intersection = CGRectToCRect(windowBounds);
+ intersection.Intersect(ourBounds);
+ if (!intersection.IsEmpty())
+ partialOverlaps.push_back(intersection);
+ }
+ }
+
+ if (!m_obscured)
+ {
+ // if we are here we are not obscured by any fullscreen window - reset flag
+ // for allowing the logmessage above to show again if this changes.
+ if (obscureLogged)
+ obscureLogged = false;
+ std::vector<CRect> rects = ourBounds.SubtractRects(partialOverlaps);
+ // they got us covered
+ if (rects.empty())
+ m_obscured = true;
+ }
+
+ CFRelease(windowDescs);
+ CFRelease(windowIDs);
+
+ return m_obscured;
+}
+
+void CWinSystemOSX::NotifyAppFocusChange(bool bGaining)
+{
+ if (!(m_bFullScreen && bGaining))
+ return;
+ @autoreleasepool
+ {
+ // find the window
+ NSOpenGLContext* context = [NSOpenGLContext currentContext];
+ if (context)
+ {
+ NSView* view;
+
+ view = [context view];
+ if (view)
+ {
+ NSWindow* window;
+ window = [view window];
+ if (window)
+ {
+ SetMenuBarVisible(false);
+ [window orderFront:nil];
+ }
+ }
+ }
+ }
+}
+
+void CWinSystemOSX::ShowOSMouse(bool show)
+{
+ SDL_ShowCursor(show ? 1 : 0);
+}
+
+bool CWinSystemOSX::Minimize()
+{
+ @autoreleasepool
+ {
+ [[NSApplication sharedApplication] miniaturizeAll:nil];
+ }
+ return true;
+}
+
+bool CWinSystemOSX::Restore()
+{
+ @autoreleasepool
+ {
+ [[NSApplication sharedApplication] unhide:nil];
+ }
+ return true;
+}
+
+bool CWinSystemOSX::Hide()
+{
+ @autoreleasepool
+ {
+ [[NSApplication sharedApplication] hide:nil];
+ }
+ return true;
+}
+
+void CWinSystemOSX::HandlePossibleRefreshrateChange()
+{
+ static double oldRefreshRate = m_refreshRate;
+ Cocoa_CVDisplayLinkUpdate();
+ int dummy = 0;
+
+ GetScreenResolution(&dummy, &dummy, &m_refreshRate, m_lastDisplayNr);
+
+ if (oldRefreshRate != m_refreshRate)
+ {
+ oldRefreshRate = m_refreshRate;
+ // send a message so that videoresolution (and refreshrate)
+ // is changed
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_VIDEORESIZE, m_SDLSurface->w, m_SDLSurface->h);
+ }
+}
+
+void CWinSystemOSX::OnMove(int x, int y)
+{
+ HandlePossibleRefreshrateChange();
+}
+
+std::unique_ptr<IOSScreenSaver> CWinSystemOSX::GetOSScreenSaverImpl()
+{
+ return std::unique_ptr<IOSScreenSaver> (new COSScreenSaverOSX);
+}
+
+OSXTextInputResponder *g_textInputResponder = nil;
+
+void CWinSystemOSX::StartTextInput()
+{
+ NSView *parentView = [[NSApp keyWindow] contentView];
+
+ /* We only keep one field editor per process, since only the front most
+ * window can receive text input events, so it make no sense to keep more
+ * than one copy. When we switched to another window and requesting for
+ * text input, simply remove the field editor from its superview then add
+ * it to the front most window's content view */
+ if (!g_textInputResponder) {
+ g_textInputResponder =
+ [[OSXTextInputResponder alloc] initWithFrame: NSMakeRect(0.0, 0.0, 0.0, 0.0)];
+ }
+
+ if (![[g_textInputResponder superview] isEqual: parentView])
+ {
+// DLOG(@"add fieldEdit to window contentView");
+ [g_textInputResponder removeFromSuperview];
+ [parentView addSubview: g_textInputResponder];
+ [[NSApp keyWindow] makeFirstResponder: g_textInputResponder];
+ }
+}
+void CWinSystemOSX::StopTextInput()
+{
+ if (g_textInputResponder)
+ {
+ [g_textInputResponder removeFromSuperview];
+ g_textInputResponder = nil;
+ }
+}
+
+void CWinSystemOSX::Register(IDispResource *resource)
+{
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ m_resources.push_back(resource);
+}
+
+void CWinSystemOSX::Unregister(IDispResource* resource)
+{
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ std::vector<IDispResource*>::iterator i = find(m_resources.begin(), m_resources.end(), resource);
+ if (i != m_resources.end())
+ m_resources.erase(i);
+}
+
+bool CWinSystemOSX::Show(bool raise)
+{
+ @autoreleasepool
+ {
+ auto app = [NSApplication sharedApplication];
+ if (raise)
+ {
+ [app unhide:nil];
+ [app activateIgnoringOtherApps:YES];
+ [app arrangeInFront:nil];
+ }
+ else
+ {
+ [app unhideWithoutActivation];
+ }
+ }
+ return true;
+}
+
+void CWinSystemOSX::WindowChangedScreen()
+{
+ // user has moved the window to a
+ // different screen
+ NSOpenGLContext* context = [NSOpenGLContext currentContext];
+ m_lastDisplayNr = -1;
+
+ // if we are here the user dragged the window to a different
+ // screen and we return the screen of the window
+ if (context)
+ {
+ NSView* view;
+
+ view = [context view];
+ if (view)
+ {
+ NSWindow* window;
+ window = [view window];
+ if (window)
+ {
+ m_lastDisplayNr = GetDisplayIndex(GetDisplayIDFromScreen([window screen]));
+ }
+ }
+ }
+ if (m_lastDisplayNr == -1)
+ m_lastDisplayNr = 0;// default to main screen
+}
+
+void CWinSystemOSX::AnnounceOnLostDevice()
+{
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ // tell any shared resources
+ CLog::Log(LOGDEBUG, "CWinSystemOSX::AnnounceOnLostDevice");
+ for (std::vector<IDispResource *>::iterator i = m_resources.begin(); i != m_resources.end(); ++i)
+ (*i)->OnLostDisplay();
+}
+
+void CWinSystemOSX::HandleOnResetDevice()
+{
+
+ auto delay =
+ std::chrono::milliseconds(CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
+ "videoscreen.delayrefreshchange") *
+ 100);
+ if (delay > 0ms)
+ {
+ m_delayDispReset = true;
+ m_dispResetTimer.Set(delay);
+ }
+ else
+ {
+ AnnounceOnResetDevice();
+ }
+}
+
+void CWinSystemOSX::AnnounceOnResetDevice()
+{
+ double currentFps = m_refreshRate;
+ int w = 0;
+ int h = 0;
+ int currentScreenIdx = m_lastDisplayNr;
+ // ensure that graphics context knows about the current refreshrate before
+ // doing the callbacks
+ GetScreenResolution(&w, &h, &currentFps, currentScreenIdx);
+
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetFPS(currentFps);
+
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ // tell any shared resources
+ CLog::Log(LOGDEBUG, "CWinSystemOSX::AnnounceOnResetDevice");
+ for (std::vector<IDispResource *>::iterator i = m_resources.begin(); i != m_resources.end(); ++i)
+ (*i)->OnResetDisplay();
+}
+
+void* CWinSystemOSX::GetCGLContextObj()
+{
+ return [m_impl->m_glContext CGLContextObj];
+}
+
+NSOpenGLContext* CWinSystemOSX::GetNSOpenGLContext()
+{
+ return m_impl->m_glContext;
+}
+
+std::string CWinSystemOSX::GetClipboardText(void)
+{
+ std::string utf8_text;
+
+ const char *szStr = Cocoa_Paste();
+ if (szStr)
+ utf8_text = szStr;
+
+ return utf8_text;
+}
+
+std::unique_ptr<CVideoSync> CWinSystemOSX::GetVideoSync(void *clock)
+{
+ std::unique_ptr<CVideoSync> pVSync(new CVideoSyncOsx(clock));
+ return pVSync;
+}
+
+bool CWinSystemOSX::MessagePump()
+{
+ return m_winEvents->MessagePump();
+}
+
+std::vector<std::string> CWinSystemOSX::GetConnectedOutputs()
+{
+ std::vector<std::string> outputs;
+ outputs.emplace_back("Default");
+
+ int numDisplays = [[NSScreen screens] count];
+
+ for (int disp = 0; disp < numDisplays; disp++)
+ {
+ NSString *dispName = screenNameForDisplay(GetDisplayID(disp));
+ outputs.emplace_back([dispName UTF8String]);
+ }
+
+ return outputs;
+}
diff --git a/xbmc/windowing/osx/VideoSyncOsx.h b/xbmc/windowing/osx/VideoSyncOsx.h
new file mode 100644
index 0000000..b3ba145
--- /dev/null
+++ b/xbmc/windowing/osx/VideoSyncOsx.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 "threads/Event.h"
+#include "windowing/VideoSync.h"
+
+class CVideoSyncOsx : public CVideoSync, IDispResource
+{
+public:
+ CVideoSyncOsx(void* clock)
+ : CVideoSync(clock), m_LastVBlankTime(0), m_displayLost(false), m_displayReset(false)
+ {
+ }
+
+ // CVideoSync interface
+ bool Setup(PUPDATECLOCK func) override;
+ void Run(CEvent& stopEvent) override;
+ void Cleanup() override;
+ float GetFps() override;
+ void RefreshChanged() override;
+
+ // IDispResource interface
+ void OnLostDisplay() override;
+ void OnResetDisplay() override;
+
+ // used in the displaylink callback
+ void VblankHandler(int64_t nowtime, uint32_t timebase);
+
+private:
+ virtual bool InitDisplayLink();
+ virtual void DeinitDisplayLink();
+
+ int64_t m_LastVBlankTime; //timestamp of the last vblank, used for calculating how many vblanks happened
+ volatile bool m_displayLost;
+ volatile bool m_displayReset;
+ CEvent m_lostEvent;
+};
+
diff --git a/xbmc/windowing/osx/VideoSyncOsx.mm b/xbmc/windowing/osx/VideoSyncOsx.mm
new file mode 100644
index 0000000..d19e724
--- /dev/null
+++ b/xbmc/windowing/osx/VideoSyncOsx.mm
@@ -0,0 +1,148 @@
+/*
+ * 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 "VideoSyncOsx.h"
+
+#include "ServiceBroker.h"
+#include "utils/MathUtils.h"
+#include "utils/TimeUtils.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+#include "windowing/WinSystem.h"
+
+#include "platform/darwin/osx/CocoaInterface.h"
+
+#include <CoreVideo/CVHostTime.h>
+#include <QuartzCore/CVDisplayLink.h>
+#include <unistd.h>
+
+using namespace std::chrono_literals;
+
+bool CVideoSyncOsx::Setup(PUPDATECLOCK func)
+{
+ CLog::Log(LOGDEBUG, "CVideoSyncOsx::{} setting up OSX", __FUNCTION__);
+
+ //init the vblank timestamp
+ m_LastVBlankTime = 0;
+ UpdateClock = func;
+ m_displayLost = false;
+ m_displayReset = false;
+ m_lostEvent.Reset();
+
+ CServiceBroker::GetWinSystem()->Register(this);
+
+ return true;
+}
+
+void CVideoSyncOsx::Run(CEvent& stopEvent)
+{
+ InitDisplayLink();
+
+ //because cocoa has a vblank callback, we just keep sleeping until we're asked to stop the thread
+ while(!stopEvent.Signaled() && !m_displayLost && !m_displayReset)
+ {
+ usleep(100000);
+ }
+
+ m_lostEvent.Set();
+
+ while(!stopEvent.Signaled() && m_displayLost && !m_displayReset)
+ {
+ usleep(10000);
+ }
+
+ DeinitDisplayLink();
+}
+
+void CVideoSyncOsx::Cleanup()
+{
+ CLog::Log(LOGDEBUG, "CVideoSyncOsx::{} cleaning up OSX", __FUNCTION__);
+ m_lostEvent.Set();
+ m_LastVBlankTime = 0;
+ CServiceBroker::GetWinSystem()->Unregister(this);
+}
+
+float CVideoSyncOsx::GetFps()
+{
+ m_fps = CServiceBroker::GetWinSystem()->GetGfxContext().GetFPS();
+ CLog::Log(LOGDEBUG, "CVideoSyncOsx::{} Detected refreshrate: {:f} hertz", __FUNCTION__, m_fps);
+ return m_fps;
+}
+
+void CVideoSyncOsx::RefreshChanged()
+{
+ m_displayReset = true;
+}
+
+void CVideoSyncOsx::OnLostDisplay()
+{
+ if (!m_displayLost)
+ {
+ m_displayLost = true;
+ m_lostEvent.Wait(1000ms);
+ }
+}
+
+void CVideoSyncOsx::OnResetDisplay()
+{
+ m_displayReset = true;
+}
+
+void CVideoSyncOsx::VblankHandler(int64_t nowtime, uint32_t timebase)
+{
+ int NrVBlanks;
+ double VBlankTime;
+ int64_t Now = CurrentHostCounter();
+
+ if (m_LastVBlankTime != 0)
+ {
+ VBlankTime = (double)(nowtime - m_LastVBlankTime) / (double)timebase;
+ NrVBlanks = MathUtils::round_int(VBlankTime * static_cast<double>(m_fps));
+
+ //update the vblank timestamp, update the clock and send a signal that we got a vblank
+ UpdateClock(NrVBlanks, Now, m_refClock);
+ }
+
+ //save the timestamp of this vblank so we can calculate how many happened next time
+ m_LastVBlankTime = nowtime;
+}
+
+// Called by the Core Video Display Link whenever it's appropriate to render a frame.
+static CVReturn DisplayLinkCallBack(CVDisplayLinkRef displayLink, const CVTimeStamp* inNow, const CVTimeStamp* inOutputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext)
+{
+ @autoreleasepool
+ {
+ CVideoSyncOsx* VideoSyncOsx = reinterpret_cast<CVideoSyncOsx*>(displayLinkContext);
+
+ if (inOutputTime->flags & kCVTimeStampHostTimeValid)
+ VideoSyncOsx->VblankHandler(inOutputTime->hostTime, CVGetHostClockFrequency());
+ else
+ VideoSyncOsx->VblankHandler(CVGetCurrentHostTime(), CVGetHostClockFrequency());
+ }
+
+ return kCVReturnSuccess;
+}
+
+bool CVideoSyncOsx::InitDisplayLink()
+{
+ bool ret = true;
+ CLog::Log(LOGDEBUG, "CVideoSyncOsx::{} setting up displaylink", __FUNCTION__);
+
+ if (!Cocoa_CVDisplayLinkCreate((void*)DisplayLinkCallBack, reinterpret_cast<void*>(this)))
+ {
+ CLog::Log(LOGDEBUG, "CVideoSyncOsx::{} Cocoa_CVDisplayLinkCreate failed", __FUNCTION__);
+ ret = false;
+ }
+ return ret;
+}
+
+void CVideoSyncOsx::DeinitDisplayLink()
+{
+ Cocoa_CVDisplayLinkRelease();
+}
+
diff --git a/xbmc/windowing/osx/WinEventsOSX.h b/xbmc/windowing/osx/WinEventsOSX.h
new file mode 100644
index 0000000..7c4644f
--- /dev/null
+++ b/xbmc/windowing/osx/WinEventsOSX.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2011-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/Thread.h"
+#include "windowing/WinEvents.h"
+#include "windowing/XBMC_events.h"
+
+#include <memory>
+
+struct CWinEventsOSXImplWrapper;
+
+class CWinEventsOSX : public IWinEvents, public CThread
+{
+public:
+ CWinEventsOSX();
+ ~CWinEventsOSX();
+
+ void MessagePush(XBMC_Event* newEvent);
+ bool MessagePump();
+ size_t GetQueueSize();
+
+ void enableInputEvents();
+ void disableInputEvents();
+
+private:
+ std::unique_ptr<CWinEventsOSXImplWrapper> m_eventsImplWrapper;
+};
diff --git a/xbmc/windowing/osx/WinEventsOSX.mm b/xbmc/windowing/osx/WinEventsOSX.mm
new file mode 100644
index 0000000..b2d07db
--- /dev/null
+++ b/xbmc/windowing/osx/WinEventsOSX.mm
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2011-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 "WinEventsOSX.h"
+
+#import "WinEventsOSXImpl.h"
+
+struct CWinEventsOSXImplWrapper
+{
+ CWinEventsOSXImpl* callbackClass;
+};
+
+CWinEventsOSX::CWinEventsOSX() : CThread("CWinEventsOSX")
+{
+ m_eventsImplWrapper = std::make_unique<CWinEventsOSXImplWrapper>();
+ m_eventsImplWrapper->callbackClass = [CWinEventsOSXImpl new];
+ Create();
+}
+
+CWinEventsOSX::~CWinEventsOSX()
+{
+ m_bStop = true;
+ StopThread(true);
+}
+
+void CWinEventsOSX::MessagePush(XBMC_Event* newEvent)
+{
+ [m_eventsImplWrapper->callbackClass MessagePush:newEvent];
+}
+
+size_t CWinEventsOSX::GetQueueSize()
+{
+ return [m_eventsImplWrapper->callbackClass GetQueueSize];
+}
+
+bool CWinEventsOSX::MessagePump()
+{
+ return [m_eventsImplWrapper->callbackClass MessagePump];
+}
+
+void CWinEventsOSX::enableInputEvents()
+{
+ return [m_eventsImplWrapper->callbackClass enableInputEvents];
+}
+
+void CWinEventsOSX::disableInputEvents()
+{
+ return [m_eventsImplWrapper->callbackClass disableInputEvents];
+}
diff --git a/xbmc/windowing/osx/WinEventsOSXImpl.h b/xbmc/windowing/osx/WinEventsOSXImpl.h
new file mode 100644
index 0000000..2dcacd4
--- /dev/null
+++ b/xbmc/windowing/osx/WinEventsOSXImpl.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2011-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 "windowing/osx/WinEventsOSX.h"
+
+#import <CoreGraphics/CoreGraphics.h>
+#import <Foundation/Foundation.h>
+
+@interface CWinEventsOSXImpl : NSObject
+
+- (void)MessagePush:(XBMC_Event*)newEvent;
+- (size_t)GetQueueSize;
+- (bool)MessagePump;
+- (void)enableInputEvents;
+- (void)disableInputEvents;
+- (XBMC_Event)keyPressEvent:(CGEventRef*)event;
+
+@end
diff --git a/xbmc/windowing/osx/WinEventsOSXImpl.mm b/xbmc/windowing/osx/WinEventsOSXImpl.mm
new file mode 100644
index 0000000..48c4e37
--- /dev/null
+++ b/xbmc/windowing/osx/WinEventsOSXImpl.mm
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2012-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 "WinEventsOSXImpl.h"
+
+#include "ServiceBroker.h"
+#include "application/AppInboundProtocol.h"
+#include "application/Application.h"
+#include "input/XBMC_keysym.h"
+#include "input/actions/Action.h"
+#include "input/actions/ActionIDs.h"
+#include "input/mouse/MouseStat.h"
+#include "messaging/ApplicationMessenger.h"
+#include "threads/CriticalSection.h"
+#include "utils/log.h"
+#include "windowing/osx/WinSystemOSX.h"
+
+#include <memory>
+#include <mutex>
+#include <queue>
+
+#import <AppKit/AppKit.h>
+#import <Carbon/Carbon.h> // kvk_ANSI_ keycodes
+#import <CoreGraphics/CoreGraphics.h>
+#import <Foundation/Foundation.h>
+
+#pragma mark - objc implementation
+
+@implementation CWinEventsOSXImpl
+{
+ std::queue<XBMC_Event> events;
+ CCriticalSection m_inputlock;
+ id mLocalMonitorId;
+}
+
+#pragma mark - init
+
+- (instancetype)init
+{
+ self = [super init];
+
+ [self enableInputEvents];
+
+ return self;
+}
+
+- (void)MessagePush:(XBMC_Event*)newEvent
+{
+ std::unique_lock<CCriticalSection> lock(m_inputlock);
+ events.emplace(*newEvent);
+}
+
+- (bool)MessagePump
+{
+
+ bool ret = false;
+
+ // Do not always loop, only pump the initial queued count events. else if ui keep pushing
+ // events the loop won't finish then it will block xbmc main message loop.
+ for (size_t pumpEventCount = [self GetQueueSize]; pumpEventCount > 0; --pumpEventCount)
+ {
+ // Pop up only one event per time since in App::OnEvent it may init modal dialog which init
+ // deeper message loop and call the deeper MessagePump from there.
+ XBMC_Event pumpEvent = {};
+ {
+ std::unique_lock<CCriticalSection> lock(m_inputlock);
+ if (events.size() == 0)
+ return ret;
+ pumpEvent = events.front();
+ events.pop();
+ }
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ ret |= appPort->OnEvent(pumpEvent);
+ }
+ return ret;
+}
+
+- (size_t)GetQueueSize
+{
+ std::unique_lock<CCriticalSection> lock(m_inputlock);
+ return events.size();
+}
+
+- (unichar)OsxKey2XbmcKey:(unichar)character
+{
+ switch (character)
+ {
+ case kVK_ANSI_8:
+ case NSLeftArrowFunctionKey:
+ return XBMCK_LEFT;
+ case kVK_ANSI_0:
+ case NSRightArrowFunctionKey:
+ return XBMCK_RIGHT;
+ case kVK_ANSI_RightBracket:
+ case NSUpArrowFunctionKey:
+ return XBMCK_UP;
+ case kVK_ANSI_O:
+ case NSDownArrowFunctionKey:
+ return XBMCK_DOWN;
+ case NSDeleteCharacter:
+ return XBMCK_BACKSPACE;
+ default:
+ return character;
+ }
+}
+
+- (XBMCMod)OsxMod2XbmcMod:(CGEventFlags)appleModifier
+{
+ unsigned int xbmcModifier = XBMCKMOD_NONE;
+ // shift left
+ if (appleModifier & kCGEventFlagMaskAlphaShift)
+ xbmcModifier |= XBMCKMOD_LSHIFT;
+ // shift right
+ if (appleModifier & kCGEventFlagMaskShift)
+ xbmcModifier |= XBMCKMOD_RSHIFT;
+ // left ctrl
+ if (appleModifier & kCGEventFlagMaskControl)
+ xbmcModifier |= XBMCKMOD_LCTRL;
+ // left alt/option
+ if (appleModifier & kCGEventFlagMaskAlternate)
+ xbmcModifier |= XBMCKMOD_LALT;
+ // left command
+ if (appleModifier & kCGEventFlagMaskCommand)
+ xbmcModifier |= XBMCKMOD_LMETA;
+
+ return static_cast<XBMCMod>(xbmcModifier);
+}
+
+- (bool)ProcessOSXShortcuts:(XBMC_Event&)event
+{
+ const auto cmd = (event.key.keysym.mod & (XBMCKMOD_LMETA | XBMCKMOD_RMETA)) != 0;
+ if (cmd && event.type == XBMC_KEYDOWN)
+ {
+ switch (event.key.keysym.sym)
+ {
+ case XBMCK_q: // CMD-q to quit
+ if (!g_application.m_bStop)
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_QUIT);
+ return true;
+
+ case XBMCK_CTRLF: // CMD-CTRL-f to toggle fullscreen
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_TOGGLEFULLSCREEN);
+ return true;
+
+ case XBMCK_s: // CMD-s to take a screenshot
+ {
+ CAction* action = new CAction(ACTION_TAKE_SCREENSHOT);
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1,
+ static_cast<void*>(action));
+ return true;
+ }
+ case XBMCK_h: // CMD-h to hide (but we minimize for now)
+ case XBMCK_m: // CMD-m to minimize
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MINIMIZE);
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+ return false;
+}
+
+- (void)enableInputEvents
+{
+ [self disableInputEvents]; // allow only one registration at a time
+
+ // clang-format off
+ // Create an event tap. We are interested in mouse and keyboard events.
+ NSEventMask eventMask =
+ NSEventMaskKeyDown | NSEventMaskKeyUp |
+ NSEventMaskLeftMouseDown | NSEventMaskLeftMouseUp |
+ NSEventMaskRightMouseDown | NSEventMaskRightMouseUp |
+ NSEventMaskOtherMouseDown | NSEventMaskOtherMouseUp |
+ NSEventMaskScrollWheel |
+ NSEventMaskLeftMouseDragged |
+ NSEventMaskRightMouseDragged |
+ NSEventMaskOtherMouseDragged |
+ NSEventMaskMouseMoved;
+ // clang-format on
+
+ mLocalMonitorId = [NSEvent addLocalMonitorForEventsMatchingMask:eventMask
+ handler:^(NSEvent* event) {
+ return [self InputEventHandler:event];
+ }];
+}
+
+- (void)disableInputEvents
+{
+ // Disable the local Monitor
+ if (mLocalMonitorId != nil)
+ [NSEvent removeMonitor:mLocalMonitorId];
+ mLocalMonitorId = nil;
+}
+
+- (NSEvent*)InputEventHandler:(NSEvent*)nsevent
+{
+ bool passEvent = true;
+ CGEventRef event = nsevent.CGEvent;
+ CGEventType type = CGEventGetType(event);
+
+ // The incoming mouse position.
+ NSPoint location = nsevent.locationInWindow;
+ if (location.x < 0 || location.y < 0)
+ return nsevent;
+
+ // cocoa world is upside down ...
+ auto winSystem = dynamic_cast<CWinSystemOSX*>(CServiceBroker::GetWinSystem());
+ if (!winSystem)
+ return nsevent;
+
+ NSRect frame = winSystem->GetWindowDimensions();
+ location.y = frame.size.height - location.y;
+
+ XBMC_Event newEvent = {};
+
+ switch (type)
+ {
+ // handle mouse events and transform them into the xbmc event world
+ case kCGEventLeftMouseUp:
+ newEvent.type = XBMC_MOUSEBUTTONUP;
+ newEvent.button.button = XBMC_BUTTON_LEFT;
+ newEvent.button.x = location.x;
+ newEvent.button.y = location.y;
+ [self MessagePush:&newEvent];
+ break;
+ case kCGEventLeftMouseDown:
+ newEvent.type = XBMC_MOUSEBUTTONDOWN;
+ newEvent.button.button = XBMC_BUTTON_LEFT;
+ newEvent.button.x = location.x;
+ newEvent.button.y = location.y;
+ [self MessagePush:&newEvent];
+ break;
+ case kCGEventRightMouseUp:
+ newEvent.type = XBMC_MOUSEBUTTONUP;
+ newEvent.button.button = XBMC_BUTTON_RIGHT;
+ newEvent.button.x = location.x;
+ newEvent.button.y = location.y;
+ [self MessagePush:&newEvent];
+ break;
+ case kCGEventRightMouseDown:
+ newEvent.type = XBMC_MOUSEBUTTONDOWN;
+ newEvent.button.button = XBMC_BUTTON_RIGHT;
+ newEvent.button.x = location.x;
+ newEvent.button.y = location.y;
+ [self MessagePush:&newEvent];
+ break;
+ case kCGEventOtherMouseUp:
+ newEvent.type = XBMC_MOUSEBUTTONUP;
+ newEvent.button.button = XBMC_BUTTON_MIDDLE;
+ newEvent.button.x = location.x;
+ newEvent.button.y = location.y;
+ [self MessagePush:&newEvent];
+ break;
+ case kCGEventOtherMouseDown:
+ newEvent.type = XBMC_MOUSEBUTTONDOWN;
+ newEvent.button.button = XBMC_BUTTON_MIDDLE;
+ newEvent.button.x = location.x;
+ newEvent.button.y = location.y;
+ [self MessagePush:&newEvent];
+ break;
+ case kCGEventMouseMoved:
+ case kCGEventLeftMouseDragged:
+ case kCGEventRightMouseDragged:
+ case kCGEventOtherMouseDragged:
+ newEvent.type = XBMC_MOUSEMOTION;
+ newEvent.motion.x = location.x;
+ newEvent.motion.y = location.y;
+ [self MessagePush:&newEvent];
+ break;
+ case kCGEventScrollWheel:
+ // very strange, real scrolls have non-zero deltaY followed by same number of events
+ // with a zero deltaY. This reverses our scroll which is WTF? anoying. Trap them out here.
+ if (nsevent.deltaY != 0.0)
+ {
+ newEvent.type = XBMC_MOUSEBUTTONDOWN;
+ newEvent.button.x = location.x;
+ newEvent.button.y = location.y;
+ newEvent.button.button =
+ CGEventGetIntegerValueField(event, kCGScrollWheelEventDeltaAxis1) > 0
+ ? XBMC_BUTTON_WHEELUP
+ : XBMC_BUTTON_WHEELDOWN;
+ [self MessagePush:&newEvent];
+
+ newEvent.type = XBMC_MOUSEBUTTONUP;
+ [self MessagePush:&newEvent];
+ }
+ break;
+
+ // handle keyboard events and transform them into the xbmc event world
+ case kCGEventKeyUp:
+ newEvent = [self keyPressEvent:&event];
+ newEvent.type = XBMC_KEYUP;
+
+ [self MessagePush:&newEvent];
+ passEvent = false;
+ break;
+ case kCGEventKeyDown:
+ newEvent = [self keyPressEvent:&event];
+ newEvent.type = XBMC_KEYDOWN;
+
+ if (![self ProcessOSXShortcuts:newEvent])
+ [self MessagePush:&newEvent];
+ passEvent = false;
+
+ break;
+ default:
+ return nsevent;
+ }
+ // We must return the event for it to be useful if not already handled
+ if (passEvent)
+ return nsevent;
+ else
+ return nullptr;
+}
+
+- (XBMC_Event)keyPressEvent:(CGEventRef*)event
+{
+ UniCharCount actualStringLength = 0;
+ // Get stringlength of event
+ CGEventKeyboardGetUnicodeString(*event, 0, &actualStringLength, nullptr);
+
+ // Create array with size of event string
+ UniChar unicodeString[actualStringLength];
+ memset(unicodeString, 0, sizeof(unicodeString));
+
+ auto keycode =
+ static_cast<CGKeyCode>(CGEventGetIntegerValueField(*event, kCGKeyboardEventKeycode));
+ CGEventKeyboardGetUnicodeString(*event, sizeof(unicodeString) / sizeof(*unicodeString),
+ &actualStringLength, unicodeString);
+
+ XBMC_Event newEvent = {};
+
+ // May be possible for actualStringLength > 1. Havent been able to replicate anything
+ // larger than 1, but keep in mind for any regressions
+ if (actualStringLength == 0)
+ {
+ return newEvent;
+ }
+ else if (actualStringLength > 1)
+ {
+ CLog::Log(LOGERROR, "CWinEventsOSXImpl::keyPressEvent - event string > 1 - size: {}",
+ static_cast<int>(actualStringLength));
+ return newEvent;
+ }
+
+ unicodeString[0] = [self OsxKey2XbmcKey:unicodeString[0]];
+
+ newEvent.key.keysym.scancode = keycode;
+ newEvent.key.keysym.sym = static_cast<XBMCKey>(unicodeString[0]);
+ newEvent.key.keysym.unicode = unicodeString[0];
+ newEvent.key.keysym.mod = [self OsxMod2XbmcMod:CGEventGetFlags(*event)];
+
+ return newEvent;
+}
+
+@end
diff --git a/xbmc/windowing/osx/WinSystemOSX.h b/xbmc/windowing/osx/WinSystemOSX.h
new file mode 100644
index 0000000..0ac08d4
--- /dev/null
+++ b/xbmc/windowing/osx/WinSystemOSX.h
@@ -0,0 +1,120 @@
+/*
+ * 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/CriticalSection.h"
+#include "threads/SystemClock.h"
+#include "threads/Timer.h"
+#include "windowing/WinSystem.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+typedef struct _CGLContextObject* CGLContextObj;
+typedef struct CGRect NSRect;
+
+class IDispResource;
+class CWinEventsOSX;
+#ifdef __OBJC__
+@class NSWindow;
+@class OSXGLView;
+#else
+struct NSWindow;
+struct OSXGLView;
+#endif
+
+class CWinSystemOSX : public CWinSystemBase, public ITimerCallback
+{
+public:
+ CWinSystemOSX();
+ ~CWinSystemOSX() override;
+
+ // ITimerCallback interface
+ void OnTimeout() override;
+
+ // 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;
+ bool SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) override;
+ void UpdateResolutions() override;
+ void NotifyAppFocusChange(bool bGaining) override;
+ void ShowOSMouse(bool show) override;
+ bool Minimize() override;
+ bool Restore() override;
+ bool Hide() override;
+ bool Show(bool raise = true) override;
+ void OnMove(int x, int y) override;
+
+ void SetOcclusionState(bool occluded);
+
+ std::string GetClipboardText() override;
+
+ void Register(IDispResource* resource) override;
+ void Unregister(IDispResource* resource) override;
+
+ std::unique_ptr<CVideoSync> GetVideoSync(void* clock) override;
+
+ void WindowChangedScreen();
+
+ void AnnounceOnLostDevice();
+ void AnnounceOnResetDevice();
+ void HandleOnResetDevice();
+ void StartLostDeviceTimer();
+ void StopLostDeviceTimer();
+
+ void SetMovedToOtherScreen(bool moved) { m_movedToOtherScreen = moved; }
+ int CheckDisplayChanging(uint32_t flags);
+ void SetFullscreenWillToggle(bool toggle) { m_fullscreenWillToggle = toggle; }
+ bool GetFullscreenWillToggle() { return m_fullscreenWillToggle; }
+
+ CGLContextObj GetCGLContextObj();
+
+ std::vector<std::string> GetConnectedOutputs() override;
+
+ // winevents override
+ bool MessagePump() override;
+
+ NSRect GetWindowDimensions();
+ void enableInputEvents();
+ void disableInputEvents();
+
+protected:
+ std::unique_ptr<KODI::WINDOWING::IOSScreenSaver> GetOSScreenSaverImpl() override;
+
+ void GetScreenResolution(int* w, int* h, double* fps, int screenIdx);
+ void EnableVSync(bool enable);
+ bool SwitchToVideoMode(int width, int height, double refreshrate);
+ void FillInVideoModes();
+ bool FlushBuffer();
+ bool IsObscured();
+
+ bool DestroyWindowInternal();
+
+ std::unique_ptr<CWinEventsOSX> m_winEvents;
+
+ std::string m_name;
+ bool m_obscured;
+ NSWindow* m_appWindow;
+ OSXGLView* m_glView;
+ bool m_movedToOtherScreen;
+ int m_lastDisplayNr;
+ double m_refreshRate;
+
+ CCriticalSection m_resourceSection;
+ std::vector<IDispResource*> m_resources;
+ CTimer m_lostDeviceTimer;
+ bool m_delayDispReset;
+ XbmcThreads::EndTime<> m_dispResetTimer;
+ bool m_fullscreenWillToggle;
+ CCriticalSection m_critSection;
+};
diff --git a/xbmc/windowing/osx/WinSystemOSX.mm b/xbmc/windowing/osx/WinSystemOSX.mm
new file mode 100644
index 0000000..85dd4b7
--- /dev/null
+++ b/xbmc/windowing/osx/WinSystemOSX.mm
@@ -0,0 +1,1305 @@
+/*
+ * 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 "WinSystemOSX.h"
+
+#include "ServiceBroker.h"
+#include "application/AppInboundProtocol.h"
+#include "cores/AudioEngine/AESinkFactory.h"
+#include "cores/AudioEngine/Sinks/AESinkDARWINOSX.h"
+#include "cores/RetroPlayer/process/osx/RPProcessInfoOSX.h"
+#include "cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGL.h"
+#include "cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.h"
+#include "cores/VideoPlayer/DVDCodecs/Video/VTB.h"
+#include "cores/VideoPlayer/Process/osx/ProcessInfoOSX.h"
+#include "cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVTBGL.h"
+#include "cores/VideoPlayer/VideoRenderers/LinuxRendererGL.h"
+#include "cores/VideoPlayer/VideoRenderers/RenderFactory.h"
+#include "guilib/DispResource.h"
+#include "guilib/GUIWindowManager.h"
+#include "messaging/ApplicationMessenger.h"
+#include "rendering/gl/ScreenshotSurfaceGL.h"
+#include "settings/DisplaySettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "threads/CriticalSection.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+#include "windowing/osx/CocoaDPMSSupport.h"
+#include "windowing/osx/OSScreenSaverOSX.h"
+#import "windowing/osx/OpenGL/OSXGLView.h"
+#import "windowing/osx/OpenGL/OSXGLWindow.h"
+#include "windowing/osx/VideoSyncOsx.h"
+#include "windowing/osx/WinEventsOSX.h"
+
+#include "platform/darwin/DarwinUtils.h"
+#include "platform/darwin/DictionaryUtils.h"
+#include "platform/darwin/osx/CocoaInterface.h"
+#include "platform/darwin/osx/powermanagement/CocoaPowerSyscall.h"
+
+#include <chrono>
+#include <cstdlib>
+#include <mutex>
+#include <signal.h>
+
+#import <Cocoa/Cocoa.h>
+#import <Foundation/Foundation.h>
+#import <IOKit/graphics/IOGraphicsLib.h>
+#import <IOKit/pwr_mgt/IOPMLib.h>
+#import <QuartzCore/QuartzCore.h>
+
+using namespace KODI;
+using namespace MESSAGING;
+using namespace WINDOWING;
+using namespace std::chrono_literals;
+
+#define MAX_DISPLAYS 32
+static NSWindow* blankingWindows[MAX_DISPLAYS];
+
+size_t DisplayBitsPerPixelForMode(CGDisplayModeRef mode)
+{
+ size_t bitsPerPixel = 0;
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+ // No replacement for CGDisplayModeCopyPixelEncoding
+ // Disable warning for now
+ CFStringRef pixEnc = CGDisplayModeCopyPixelEncoding(mode);
+#pragma GCC diagnostic pop
+
+ if (CFStringCompare(pixEnc, CFSTR(IO32BitDirectPixels), kCFCompareCaseInsensitive) ==
+ kCFCompareEqualTo)
+ {
+ bitsPerPixel = 32;
+ }
+ else if (CFStringCompare(pixEnc, CFSTR(IO16BitDirectPixels), kCFCompareCaseInsensitive) ==
+ kCFCompareEqualTo)
+ {
+ bitsPerPixel = 16;
+ }
+ else if (CFStringCompare(pixEnc, CFSTR(IO8BitIndexedPixels), kCFCompareCaseInsensitive) ==
+ kCFCompareEqualTo)
+ {
+ bitsPerPixel = 8;
+ }
+
+ CFRelease(pixEnc);
+
+ return bitsPerPixel;
+}
+
+#pragma mark - GetScreenName
+
+NSString* screenNameForDisplay(CGDirectDisplayID displayID)
+{
+ NSString* screenName;
+ @autoreleasepool
+ {
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+ // No real replacement of CGDisplayIOServicePort
+ // Stackoverflow links to https://github.com/glfw/glfw/pull/192 as a possible replacement
+ // disable warning for now
+ NSDictionary* deviceInfo = (__bridge_transfer NSDictionary*)IODisplayCreateInfoDictionary(
+ CGDisplayIOServicePort(displayID), kIODisplayOnlyPreferredName);
+
+#pragma GCC diagnostic pop
+
+ NSDictionary* localizedNames =
+ [deviceInfo objectForKey:[NSString stringWithUTF8String:kDisplayProductName]];
+
+ if ([localizedNames count] > 0)
+ {
+ screenName = [localizedNames objectForKey:[[localizedNames allKeys] objectAtIndex:0]];
+ }
+ }
+
+ if (screenName == nil)
+ {
+ screenName = [[NSString alloc] initWithFormat:@"%i", displayID];
+ }
+ else
+ {
+ // ensure screen name is unique by appending displayid
+ screenName = [screenName stringByAppendingFormat:@" (%@)", [@(displayID) stringValue]];
+ }
+
+ return screenName;
+}
+
+#pragma mark - GetDisplay
+
+CGDirectDisplayID GetDisplayID(int screen_index)
+{
+ CGDirectDisplayID displayArray[MAX_DISPLAYS];
+ uint32_t numDisplays;
+
+ // Get the list of displays.
+ CGGetActiveDisplayList(MAX_DISPLAYS, displayArray, &numDisplays);
+ if (screen_index >= 0 && screen_index < static_cast<int>(numDisplays))
+ return (displayArray[screen_index]);
+ else
+ return (displayArray[0]);
+}
+
+CGDirectDisplayID GetDisplayIDFromScreen(NSScreen* screen)
+{
+ NSDictionary* screenInfo = screen.deviceDescription;
+ NSNumber* screenID = [screenInfo objectForKey:@"NSScreenNumber"];
+
+ return (CGDirectDisplayID)[screenID longValue];
+}
+
+int GetDisplayIndex(CGDirectDisplayID display)
+{
+ CGDirectDisplayID displayArray[MAX_DISPLAYS];
+ CGDisplayCount numDisplays;
+
+ // Get the list of displays.
+ CGGetActiveDisplayList(MAX_DISPLAYS, displayArray, &numDisplays);
+ while (numDisplays > 0)
+ {
+ if (display == displayArray[--numDisplays])
+ return numDisplays;
+ }
+ return -1;
+}
+
+int GetDisplayIndex(const std::string& dispName)
+{
+ int ret = 0;
+
+ // Add full screen settings for additional monitors
+ int numDisplays = NSScreen.screens.count;
+
+ for (int disp = 0; disp < numDisplays; disp++)
+ {
+ NSString* name = screenNameForDisplay(GetDisplayID(disp));
+ if (name.UTF8String == dispName)
+ {
+ ret = disp;
+ break;
+ }
+ }
+
+ return ret;
+}
+
+#pragma mark - Display Modes
+
+CFArrayRef GetAllDisplayModes(CGDirectDisplayID display)
+{
+ int value = 1;
+
+ CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &value);
+ if (!number)
+ {
+ CLog::Log(LOGERROR, "GetAllDisplayModes - could not create Number!");
+ return nullptr;
+ }
+
+ CFStringRef key = kCGDisplayShowDuplicateLowResolutionModes;
+ CFDictionaryRef options = CFDictionaryCreate(kCFAllocatorDefault, (const void**)&key,
+ (const void**)&number, 1, nullptr, nullptr);
+ CFRelease(number);
+
+ if (!options)
+ {
+ CLog::Log(LOGERROR, "GetAllDisplayModes - could not create Dictionary!");
+ return nullptr;
+ }
+
+ CFArrayRef displayModes = CGDisplayCopyAllDisplayModes(display, options);
+ CFRelease(options);
+
+ if (!displayModes)
+ {
+ CLog::Log(LOGERROR, "GetAllDisplayModes - no displaymodes found!");
+ return nullptr;
+ }
+
+ return displayModes;
+}
+
+// try to find mode that matches the desired size, refreshrate
+// non interlaced, nonstretched, safe for hardware
+CGDisplayModeRef GetMode(int width, int height, double refreshrate, int screenIdx)
+{
+ if (screenIdx >= (signed)[[NSScreen screens] count])
+ return nullptr;
+
+ bool stretched;
+ bool interlaced;
+ bool safeForHardware;
+ int w, h, bitsperpixel;
+ double rate;
+ RESOLUTION_INFO res;
+
+ CLog::Log(LOGDEBUG, "GetMode looking for suitable mode with {} x {} @ {} Hz on display {}", width,
+ height, refreshrate, screenIdx);
+
+ CFArrayRef allModes = GetAllDisplayModes(GetDisplayID(screenIdx));
+
+ if (!allModes)
+ return nullptr;
+
+ for (int i = 0; i < CFArrayGetCount(allModes); ++i)
+ {
+ CGDisplayModeRef displayMode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i);
+ uint32_t flags = CGDisplayModeGetIOFlags(displayMode);
+ stretched = (flags & kDisplayModeStretchedFlag) != 0;
+ interlaced = (flags & kDisplayModeInterlacedFlag) != 0;
+ bitsperpixel = DisplayBitsPerPixelForMode(displayMode);
+ safeForHardware = (flags & kDisplayModeSafetyFlags) != 0;
+ w = CGDisplayModeGetWidth(displayMode);
+ h = CGDisplayModeGetHeight(displayMode);
+ rate = CGDisplayModeGetRefreshRate(displayMode);
+
+ if ((bitsperpixel == 32) && (safeForHardware == true) && (stretched == false) &&
+ (interlaced == false) && (w == width) && (h == height) &&
+ (rate == refreshrate || rate == 0))
+ {
+ CFRelease(allModes);
+ CLog::Log(LOGDEBUG, "GetMode found a match!");
+ return CGDisplayModeRetain(displayMode);
+ }
+ }
+
+ CFRelease(allModes);
+ CLog::Log(LOGERROR, "GetMode - no match found!");
+ return nullptr;
+}
+
+// mimic former behavior of deprecated CGDisplayBestModeForParameters
+CGDisplayModeRef BestMatchForMode(CGDirectDisplayID display,
+ size_t bitsPerPixel,
+ size_t width,
+ size_t height)
+{
+ // Loop through all display modes to determine the closest match.
+ // CGDisplayBestModeForParameters is deprecated on 10.6 so we will emulate it's behavior
+ // Try to find a mode with the requested depth and equal or greater dimensions first.
+ // If no match is found, try to find a mode with greater depth and same or greater dimensions.
+ // If still no match is found, just use the current mode.
+ CFArrayRef allModes = GetAllDisplayModes(display);
+
+ if (!allModes)
+ return nullptr;
+
+ CGDisplayModeRef displayMode = nullptr;
+
+ for (int i = 0; i < CFArrayGetCount(allModes); i++)
+ {
+ CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i);
+
+ if (!mode)
+ continue;
+
+ if (DisplayBitsPerPixelForMode(mode) != bitsPerPixel)
+ continue;
+
+ if ((CGDisplayModeGetWidth(mode) == width) && (CGDisplayModeGetHeight(mode) == height))
+ {
+ displayMode = mode;
+ break;
+ }
+ }
+
+ // No depth match was found
+ if (!displayMode)
+ {
+ for (int i = 0; i < CFArrayGetCount(allModes); i++)
+ {
+ CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i);
+
+ if (!mode)
+ continue;
+
+ if (DisplayBitsPerPixelForMode(mode) >= bitsPerPixel)
+ continue;
+
+ if ((CGDisplayModeGetWidth(mode) == width) && (CGDisplayModeGetHeight(mode) == height))
+ {
+ displayMode = mode;
+ break;
+ }
+ }
+ }
+
+ CFRelease(allModes);
+
+ return displayMode;
+}
+
+#pragma mark - Blank Displays
+
+void BlankOtherDisplays(int screen_index)
+{
+ int i;
+ int numDisplays = [[NSScreen screens] count];
+
+ // zero out blankingWindows for debugging
+ for (i = 0; i < MAX_DISPLAYS; i++)
+ {
+ blankingWindows[i] = 0;
+ }
+
+ // Blank.
+ for (i = 0; i < numDisplays; i++)
+ {
+ if (i != screen_index)
+ {
+ // Get the size.
+ NSScreen* pScreen = [NSScreen.screens objectAtIndex:i];
+ NSRect screenRect = pScreen.frame;
+
+ // Build a blanking window.
+ screenRect.origin = NSZeroPoint;
+ blankingWindows[i] = [[NSWindow alloc] initWithContentRect:screenRect
+ styleMask:NSWindowStyleMaskBorderless
+ backing:NSBackingStoreBuffered
+ defer:NO
+ screen:pScreen];
+
+ [blankingWindows[i] setBackgroundColor:NSColor.blackColor];
+ [blankingWindows[i] setLevel:CGShieldingWindowLevel()];
+ [blankingWindows[i] makeKeyAndOrderFront:nil];
+ }
+ }
+}
+
+void UnblankDisplays(void)
+{
+ for (auto i = 0; i < static_cast<int>(NSScreen.screens.count); i++)
+ {
+ if (blankingWindows[i] != 0)
+ {
+ // Get rid of the blanking windows we created.
+ [blankingWindows[i] close];
+ blankingWindows[i] = 0;
+ }
+ }
+}
+
+#pragma mark - Fade Display
+//! @Todo Look to replace Fade with CABasicAnimation
+static NSWindow* curtainWindow;
+void fadeInDisplay(NSScreen* theScreen, double fadeTime)
+{
+ int fadeSteps = 100;
+ double fadeInterval = (fadeTime / (double)fadeSteps);
+
+ if (curtainWindow != nil)
+ {
+ for (int step = 0; step < fadeSteps; step++)
+ {
+ double fade = 1.0 - (step * fadeInterval);
+ [curtainWindow setAlphaValue:fade];
+
+ NSDate* nextDate = [NSDate dateWithTimeIntervalSinceNow:fadeInterval];
+ [NSRunLoop.currentRunLoop runUntilDate:nextDate];
+ }
+ }
+ [curtainWindow close];
+ curtainWindow = nil;
+}
+
+void fadeOutDisplay(NSScreen* theScreen, double fadeTime)
+{
+ int fadeSteps = 100;
+ double fadeInterval = (fadeTime / (double)fadeSteps);
+
+ [NSCursor hide];
+
+ curtainWindow = [[NSWindow alloc] initWithContentRect:[theScreen frame]
+ styleMask:NSWindowStyleMaskBorderless
+ backing:NSBackingStoreBuffered
+ defer:YES
+ screen:theScreen];
+
+ [curtainWindow setAlphaValue:0.0];
+ [curtainWindow setBackgroundColor:NSColor.blackColor];
+ [curtainWindow setLevel:NSScreenSaverWindowLevel];
+
+ [curtainWindow makeKeyAndOrderFront:nil];
+ [curtainWindow setFrame:[curtainWindow frameRectForContentRect:[theScreen frame]]
+ display:YES
+ animate:NO];
+
+ for (int step = 0; step < fadeSteps; step++)
+ {
+ double fade = step * fadeInterval;
+ [curtainWindow setAlphaValue:fade];
+
+ NSDate* nextDate = [NSDate dateWithTimeIntervalSinceNow:fadeInterval];
+ [NSRunLoop.currentRunLoop runUntilDate:nextDate];
+ }
+}
+
+//---------------------------------------------------------------------------------
+static void DisplayReconfigured(CGDirectDisplayID display,
+ CGDisplayChangeSummaryFlags flags,
+ void* userData)
+{
+ CWinSystemOSX* winsys = (CWinSystemOSX*)userData;
+ if (!winsys)
+ return;
+
+ CLog::Log(LOGDEBUG, "CWinSystemOSX::DisplayReconfigured with flags {}", flags);
+
+ // we fire the callbacks on start of configuration
+ // or when the mode set was finished
+ // or when we are called with flags == 0 (which is undocumented but seems to happen
+ // on some macs - we treat it as device reset)
+
+ // first check if we need to call OnLostDevice
+ if (flags & kCGDisplayBeginConfigurationFlag)
+ {
+ // pre/post-reconfiguration changes
+ RESOLUTION res = CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution();
+ if (res == RES_INVALID)
+ return;
+
+ NSScreen* pScreen = nil;
+ unsigned int screenIdx = 0;
+
+ if (screenIdx < NSScreen.screens.count)
+ {
+ pScreen = [NSScreen.screens objectAtIndex:screenIdx];
+ }
+
+ // kCGDisplayBeginConfigurationFlag is only fired while the screen is still
+ // valid
+ if (pScreen)
+ {
+ CGDirectDisplayID xbmc_display = GetDisplayIDFromScreen(pScreen);
+ if (xbmc_display == display)
+ {
+ // we only respond to changes on the display we are running on.
+ winsys->AnnounceOnLostDevice();
+ winsys->StartLostDeviceTimer();
+ }
+ }
+ }
+ else // the else case checks if we need to call OnResetDevice
+ {
+ // we fire if kCGDisplaySetModeFlag is set or if flags == 0
+ // (which is undocumented but seems to happen
+ // on some macs - we treat it as device reset)
+ // we also don't check the screen here as we might not even have
+ // one anymore (e.x. when tv is turned off)
+ if (flags & kCGDisplaySetModeFlag || flags == 0)
+ {
+ winsys->StopLostDeviceTimer(); // no need to timeout - we've got the callback
+ winsys->HandleOnResetDevice();
+ }
+ }
+}
+
+#pragma mark - CWinSystemOSX
+//------------------------------------------------------------------------------
+CWinSystemOSX::CWinSystemOSX() : CWinSystemBase(), m_lostDeviceTimer(this)
+{
+ m_appWindow = nullptr;
+ m_glView = nullptr;
+ m_obscured = false;
+ m_lastDisplayNr = -1;
+ m_movedToOtherScreen = false;
+ m_refreshRate = 0.0;
+ m_delayDispReset = false;
+
+ m_winEvents.reset(new CWinEventsOSX());
+
+ AE::CAESinkFactory::ClearSinks();
+ CAESinkDARWINOSX::Register();
+ CCocoaPowerSyscall::Register();
+ m_dpms = std::make_shared<CCocoaDPMSSupport>();
+}
+
+CWinSystemOSX::~CWinSystemOSX() = default;
+
+void CWinSystemOSX::Register(IDispResource* resource)
+{
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ m_resources.push_back(resource);
+}
+
+void CWinSystemOSX::Unregister(IDispResource* resource)
+{
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ std::vector<IDispResource*>::iterator i = find(m_resources.begin(), m_resources.end(), resource);
+ if (i != m_resources.end())
+ m_resources.erase(i);
+}
+
+void CWinSystemOSX::AnnounceOnLostDevice()
+{
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ // tell any shared resources
+ CLog::LogF(LOGDEBUG, "Lost Device Announce");
+ for (std::vector<IDispResource*>::iterator i = m_resources.begin(); i != m_resources.end(); ++i)
+ (*i)->OnLostDisplay();
+}
+
+void CWinSystemOSX::HandleOnResetDevice()
+{
+ auto delay =
+ std::chrono::milliseconds(CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
+ "videoscreen.delayrefreshchange") *
+ 100);
+ if (delay > 0ms)
+ {
+ m_delayDispReset = true;
+ m_dispResetTimer.Set(delay);
+ }
+ else
+ {
+ AnnounceOnResetDevice();
+ }
+}
+
+void CWinSystemOSX::AnnounceOnResetDevice()
+{
+ double currentFps = m_refreshRate;
+ int w = 0;
+ int h = 0;
+ int currentScreenIdx = m_lastDisplayNr;
+ // ensure that graphics context knows about the current refreshrate before
+ // doing the callbacks
+ GetScreenResolution(&w, &h, &currentFps, currentScreenIdx);
+
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetFPS(currentFps);
+
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ // tell any shared resources
+ CLog::LogF(LOGDEBUG, "Reset Device Announce");
+ for (std::vector<IDispResource*>::iterator i = m_resources.begin(); i != m_resources.end(); ++i)
+ (*i)->OnResetDisplay();
+}
+
+#pragma mark - Timers
+
+void CWinSystemOSX::StartLostDeviceTimer()
+{
+ if (m_lostDeviceTimer.IsRunning())
+ m_lostDeviceTimer.Restart();
+ else
+ m_lostDeviceTimer.Start(3000ms, false);
+}
+
+void CWinSystemOSX::StopLostDeviceTimer()
+{
+ m_lostDeviceTimer.Stop();
+}
+
+void CWinSystemOSX::OnTimeout()
+{
+ HandleOnResetDevice();
+}
+
+#pragma mark - WindowSystem
+
+bool CWinSystemOSX::InitWindowSystem()
+{
+ if (!CWinSystemBase::InitWindowSystem())
+ return false;
+
+ CGDisplayRegisterReconfigurationCallback(DisplayReconfigured, (void*)this);
+
+ return true;
+}
+
+bool CWinSystemOSX::DestroyWindowSystem()
+{
+ CGDisplayRemoveReconfigurationCallback(DisplayReconfigured, (void*)this);
+
+ DestroyWindowInternal();
+
+ if (m_glView)
+ {
+ m_glView = nullptr;
+ }
+
+ UnblankDisplays();
+ return true;
+}
+
+bool CWinSystemOSX::CreateNewWindow(const std::string& name, bool fullScreen, RESOLUTION_INFO& res)
+{
+ // force initial window creation to be windowed, if fullscreen, it will switch to it below
+ // fixes the white screen of death if starting fullscreen and switching to windowed.
+ RESOLUTION_INFO resInfo = CDisplaySettings::GetInstance().GetResolutionInfo(RES_WINDOW);
+ m_nWidth = resInfo.iWidth;
+ m_nHeight = resInfo.iHeight;
+ m_bFullScreen = false;
+ m_name = name;
+
+ __block NSWindow* appWindow;
+ // because we are not main thread, delay any updates
+ // and only become keyWindow after it finishes.
+ [NSAnimationContext beginGrouping];
+ [NSAnimationContext.currentContext setCompletionHandler:^{
+ [appWindow makeKeyWindow];
+ }];
+
+ const NSUInteger windowStyleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskResizable |
+ NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable;
+
+ if (m_appWindow == nullptr)
+ {
+ // create new content view
+ NSRect rect = [appWindow contentRectForFrameRect:appWindow.frame];
+
+ // create new view if we don't have one
+ if (!m_glView)
+ m_glView = [[OSXGLView alloc] initWithFrame:rect];
+
+ OSXGLView* view = (OSXGLView*)m_glView;
+
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ appWindow = [[OSXGLWindow alloc] initWithContentRect:NSMakeRect(0, 0, m_nWidth, m_nHeight)
+ styleMask:windowStyleMask];
+ NSString* title = [NSString stringWithUTF8String:m_name.c_str()];
+ appWindow.backgroundColor = NSColor.blackColor;
+ appWindow.title = title;
+
+ NSWindowCollectionBehavior behavior = appWindow.collectionBehavior;
+ behavior |= NSWindowCollectionBehaviorFullScreenPrimary;
+ [appWindow setCollectionBehavior:behavior];
+
+ // associate with current window
+ [appWindow setContentView:view];
+ });
+
+ [view.getGLContext makeCurrentContext];
+ [view.getGLContext update];
+
+ m_appWindow = appWindow;
+ m_bWindowCreated = true;
+ }
+
+ // warning, we can order front but not become
+ // key window or risk starting up with bad flicker
+ // becoming key window must happen in completion block.
+ [(NSWindow*)m_appWindow performSelectorOnMainThread:@selector(orderFront:)
+ withObject:nil
+ waitUntilDone:YES];
+
+ [NSAnimationContext endGrouping];
+
+ // get screen refreshrate - this is needed
+ // when we startup in windowed mode and don't run through SetFullScreen
+ int dummy;
+ GetScreenResolution(&dummy, &dummy, &m_refreshRate, m_lastDisplayNr);
+
+ // register platform dependent objects
+ CDVDFactoryCodec::ClearHWAccels();
+ VTB::CDecoder::Register();
+ VIDEOPLAYER::CRendererFactory::ClearRenderer();
+ CLinuxRendererGL::Register();
+ CRendererVTB::Register();
+ VIDEOPLAYER::CProcessInfoOSX::Register();
+ RETRO::CRPProcessInfoOSX::Register();
+ RETRO::CRPProcessInfoOSX::RegisterRendererFactory(new RETRO::CRendererFactoryOpenGL);
+ CScreenshotSurfaceGL::Register();
+
+ return true;
+}
+
+bool CWinSystemOSX::DestroyWindowInternal()
+{
+ // set this 1st, we should really mutex protext m_appWindow in this class
+ m_bWindowCreated = false;
+ if (m_appWindow)
+ {
+ NSWindow* oldAppWindow = m_appWindow;
+ m_appWindow = nullptr;
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ [oldAppWindow setContentView:nil];
+ });
+ }
+
+ return true;
+}
+
+bool CWinSystemOSX::DestroyWindow()
+{
+ return true;
+}
+
+bool CWinSystemOSX::Minimize()
+{
+ @autoreleasepool
+ {
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ [NSApplication.sharedApplication miniaturizeAll:nil];
+ });
+ }
+ return true;
+}
+
+bool CWinSystemOSX::Restore()
+{
+ @autoreleasepool
+ {
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ [NSApplication.sharedApplication unhide:nil];
+ });
+ }
+ return true;
+}
+
+bool CWinSystemOSX::Show(bool raise)
+{
+ @autoreleasepool
+ {
+ auto app = NSApplication.sharedApplication;
+ if (raise)
+ {
+ [app unhide:nil];
+ [app activateIgnoringOtherApps:YES];
+ [app arrangeInFront:nil];
+ }
+ else
+ {
+ [app unhideWithoutActivation];
+ }
+ }
+ return true;
+}
+
+bool CWinSystemOSX::Hide()
+{
+ @autoreleasepool
+ {
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ [NSApplication.sharedApplication hide:nil];
+ });
+ }
+ return true;
+}
+
+NSRect CWinSystemOSX::GetWindowDimensions()
+{
+ if (m_appWindow)
+ {
+ NSWindow* win = (NSWindow*)m_appWindow;
+ NSRect frame = win.contentView.frame;
+ return frame;
+ }
+}
+
+#pragma mark - Resize Window
+
+bool CWinSystemOSX::ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop)
+{
+ if (!m_appWindow)
+ return false;
+
+ [(OSXGLWindow*)m_appWindow setResizeState:true];
+
+ __block OSXGLView* view;
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ view = m_appWindow.contentView;
+ });
+
+ if (view)
+ {
+ // It seems, that in macOS 10.15 this defaults to YES, but we currently do not support
+ // Retina resolutions properly. Ensure that the view uses a 1 pixel per point framebuffer.
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ view.wantsBestResolutionOpenGLSurface = NO;
+ });
+ }
+
+ if (newWidth < 0)
+ {
+ newWidth = [(NSWindow*)m_appWindow minSize].width;
+ }
+
+ if (newHeight < 0)
+ {
+ newHeight = [(NSWindow*)m_appWindow minSize].height;
+ }
+
+ if (view)
+ {
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ NSOpenGLContext* context = [view getGLContext];
+ NSWindow* window = m_appWindow;
+
+ NSRect pos = window.frame;
+
+ NSRect myNewContentFrame = NSMakeRect(pos.origin.x, pos.origin.y, newWidth, newHeight);
+ NSRect myNewWindowRect = [window frameRectForContentRect:myNewContentFrame];
+ [window setFrame:myNewWindowRect display:TRUE];
+
+ [context update];
+ });
+ }
+
+ m_nWidth = newWidth;
+ m_nHeight = newHeight;
+
+ [(OSXGLWindow*)m_appWindow setResizeState:false];
+
+ return true;
+}
+
+bool CWinSystemOSX::SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ // if (m_lastDisplayNr == -1)
+ // m_lastDisplayNr = res.iScreen;
+
+ __block NSWindow* window = m_appWindow;
+
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ m_lastDisplayNr = GetDisplayIndex(settings->GetString(CSettings::SETTING_VIDEOSCREEN_MONITOR));
+ m_nWidth = res.iWidth;
+ m_nHeight = res.iHeight;
+ m_bFullScreen = fullScreen;
+
+ //handle resolution/refreshrate switching early here
+ if (m_bFullScreen)
+ {
+ // switch videomode
+ SwitchToVideoMode(res.iWidth, res.iHeight, static_cast<double>(res.fRefreshRate));
+ // hide the OS mouse
+ [NSCursor hide];
+ }
+
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ [window setAllowsConcurrentViewDrawing:NO];
+ });
+
+ if (m_fullscreenWillToggle)
+ {
+ ResizeWindow(m_nWidth, m_nHeight, -1, -1);
+ m_fullscreenWillToggle = false;
+ return true;
+ }
+
+ if (m_bFullScreen)
+ {
+ // This is Cocoa Windowed FullScreen Mode
+ // Get the screen rect of our current display
+ NSScreen* pScreen = [NSScreen.screens objectAtIndex:m_lastDisplayNr];
+
+ // remove frame origin offset of original display
+ pScreen.frame.origin = NSZeroPoint;
+
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ [window.contentView setFrameSize:NSMakeSize(m_nWidth, m_nHeight)];
+ window.title = @"";
+ [window setAllowsConcurrentViewDrawing:YES];
+ });
+
+ // Blank other displays if requested.
+ if (blankOtherDisplays)
+ BlankOtherDisplays(m_lastDisplayNr);
+ }
+ else
+ {
+ // Show menubar.
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ [NSApplication.sharedApplication setPresentationOptions:NSApplicationPresentationDefault];
+ });
+
+ // Unblank.
+ // Force the unblank when returning from fullscreen, we get called with blankOtherDisplays set false.
+ //if (blankOtherDisplays)
+ UnblankDisplays();
+ }
+
+ //DisplayFadeFromBlack(fade_token, needtoshowme);
+
+ m_fullscreenWillToggle = true;
+ // toggle cocoa fullscreen mode
+ if ([m_appWindow respondsToSelector:@selector(toggleFullScreen:)])
+ [m_appWindow performSelectorOnMainThread:@selector(toggleFullScreen:)
+ withObject:nil
+ waitUntilDone:YES];
+
+ ResizeWindow(m_nWidth, m_nHeight, -1, -1);
+
+ return true;
+}
+
+#pragma mark - Resolution
+
+void CWinSystemOSX::UpdateResolutions()
+{
+ CWinSystemBase::UpdateResolutions();
+
+ // Add desktop resolution
+ int w;
+ int h;
+ double fps;
+
+ int dispIdx = GetDisplayIndex(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(
+ CSettings::SETTING_VIDEOSCREEN_MONITOR));
+ GetScreenResolution(&w, &h, &fps, dispIdx);
+ NSString* dispName = screenNameForDisplay(GetDisplayID(dispIdx));
+ UpdateDesktopResolution(CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP),
+ dispName.UTF8String, w, h, fps, 0);
+
+ CDisplaySettings::GetInstance().ClearCustomResolutions();
+
+ // now just fill in the possible resolutions for the attached screens
+ // and push to the resolution info vector
+ FillInVideoModes();
+ CDisplaySettings::GetInstance().ApplyCalibrations();
+}
+
+void CWinSystemOSX::GetScreenResolution(int* w, int* h, double* fps, int screenIdx)
+{
+ CGDirectDisplayID display_id = (CGDirectDisplayID)GetDisplayID(screenIdx);
+ CGDisplayModeRef mode = CGDisplayCopyDisplayMode(display_id);
+ *w = CGDisplayModeGetWidth(mode);
+ *h = CGDisplayModeGetHeight(mode);
+ *fps = CGDisplayModeGetRefreshRate(mode);
+ CGDisplayModeRelease(mode);
+ if (static_cast<int>(*fps) == 0)
+ {
+ // NOTE: The refresh rate will be REPORTED AS 0 for many DVI and notebook displays.
+ *fps = 60.0;
+ }
+}
+
+#pragma mark - Video Modes
+
+bool CWinSystemOSX::SwitchToVideoMode(int width, int height, double refreshrate)
+{
+ CGDisplayModeRef dispMode = nullptr;
+
+ int screenIdx = GetDisplayIndex(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(
+ CSettings::SETTING_VIDEOSCREEN_MONITOR));
+
+ // Figure out the screen size. (default to main screen)
+ CGDirectDisplayID display_id = GetDisplayID(screenIdx);
+
+ // find mode that matches the desired size, refreshrate
+ // non interlaced, nonstretched, safe for hardware
+ dispMode = GetMode(width, height, refreshrate, screenIdx);
+
+ //not found - fallback to bestemdeforparameters
+ if (!dispMode)
+ {
+ dispMode = BestMatchForMode(display_id, 32, width, height);
+
+ if (!dispMode)
+ {
+ dispMode = BestMatchForMode(display_id, 16, width, height);
+
+ // still no match? fallback to current resolution of the display which HAS to work [tm]
+ if (!dispMode)
+ {
+ int currentWidth;
+ int currentHeight;
+ double currentRefresh;
+
+ GetScreenResolution(&currentWidth, &currentHeight, &currentRefresh, screenIdx);
+ dispMode = GetMode(currentWidth, currentHeight, currentRefresh, screenIdx);
+
+ // no way to get a resolution set
+ if (!dispMode)
+ return false;
+ }
+ }
+ }
+ // switch mode and return success
+ CGDisplayCapture(display_id);
+ CGDisplayConfigRef cfg;
+ CGBeginDisplayConfiguration(&cfg);
+ CGConfigureDisplayWithDisplayMode(cfg, display_id, dispMode, nullptr);
+ CGError err = CGCompleteDisplayConfiguration(cfg, kCGConfigureForAppOnly);
+ CGDisplayRelease(display_id);
+
+ m_refreshRate = CGDisplayModeGetRefreshRate(dispMode);
+
+ Cocoa_CVDisplayLinkUpdate();
+
+ return (err == kCGErrorSuccess);
+}
+
+void CWinSystemOSX::FillInVideoModes()
+{
+ int dispIdx = GetDisplayIndex(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(
+ CSettings::SETTING_VIDEOSCREEN_MONITOR));
+
+ for (int disp = 0; disp < static_cast<int>(NSScreen.screens.count); disp++)
+ {
+ bool stretched;
+ bool interlaced;
+ bool safeForHardware;
+ int w, h, bitsperpixel;
+ double refreshrate;
+ RESOLUTION_INFO res;
+
+ CFArrayRef displayModes = GetAllDisplayModes(GetDisplayID(disp));
+ NSString* dispName = screenNameForDisplay(GetDisplayID(disp));
+
+ CLog::LogF(LOGINFO, "Display {} has name {}", disp, [dispName UTF8String]);
+
+ if (!displayModes)
+ continue;
+
+ for (int i = 0; i < CFArrayGetCount(displayModes); ++i)
+ {
+ CGDisplayModeRef displayMode = (CGDisplayModeRef)CFArrayGetValueAtIndex(displayModes, i);
+
+ uint32_t flags = CGDisplayModeGetIOFlags(displayMode);
+ stretched = (flags & kDisplayModeStretchedFlag) != 0;
+ interlaced = (flags & kDisplayModeInterlacedFlag) != 0;
+ bitsperpixel = DisplayBitsPerPixelForMode(displayMode);
+ safeForHardware = (flags & kDisplayModeSafetyFlags) != 0;
+
+ if ((bitsperpixel == 32) && (safeForHardware == true) && (stretched == false) &&
+ (interlaced == false))
+ {
+ w = CGDisplayModeGetWidth(displayMode);
+ h = CGDisplayModeGetHeight(displayMode);
+ refreshrate = CGDisplayModeGetRefreshRate(displayMode);
+ if (static_cast<int>(refreshrate) == 0) // LCD display?
+ {
+ // NOTE: The refresh rate will be REPORTED AS 0 for many DVI and notebook displays.
+ refreshrate = 60.0;
+ }
+ CLog::Log(LOGINFO, "Found possible resolution for display {} with {} x {} @ {} Hz", disp, w,
+ h, refreshrate);
+
+ // only add the resolution if it belongs to "our" screen
+ // all others are only logged above...
+ if (disp == dispIdx)
+ {
+ UpdateDesktopResolution(res, (dispName != nil) ? [dispName UTF8String] : "Unknown", w, h,
+ refreshrate, 0);
+ CServiceBroker::GetWinSystem()->GetGfxContext().ResetOverscan(res);
+ CDisplaySettings::GetInstance().AddResolutionInfo(res);
+ }
+ }
+ }
+ CFRelease(displayModes);
+ }
+}
+
+#pragma mark - Occlusion
+
+bool CWinSystemOSX::IsObscured()
+{
+ if (m_obscured)
+ CLog::LogF(LOGDEBUG, "Obscured");
+ return m_obscured;
+}
+
+void CWinSystemOSX::SetOcclusionState(bool occluded)
+{
+ // m_obscured = occluded;
+ // CLog::LogF(LOGDEBUG, "{}", occluded ? "true":"false");
+}
+
+void CWinSystemOSX::NotifyAppFocusChange(bool bGaining)
+{
+ if (!(m_bFullScreen && bGaining))
+ return;
+ @autoreleasepool
+ {
+ // find the window
+ NSOpenGLContext* context = NSOpenGLContext.currentContext;
+ if (context)
+ {
+ NSView* view;
+
+ view = context.view;
+ if (view)
+ {
+ NSWindow* window;
+ window = view.window;
+ if (window)
+ {
+ [window orderFront:nil];
+ }
+ }
+ }
+ }
+}
+
+#pragma mark - Window Move
+
+void CWinSystemOSX::OnMove(int x, int y)
+{
+ static double oldRefreshRate = m_refreshRate;
+ Cocoa_CVDisplayLinkUpdate();
+
+ int dummy = 0;
+ GetScreenResolution(&dummy, &dummy, &m_refreshRate, m_lastDisplayNr);
+
+ if (oldRefreshRate != m_refreshRate)
+ {
+ oldRefreshRate = m_refreshRate;
+
+ // send a message so that videoresolution (and refreshrate) is changed
+ NSWindow* win = m_appWindow;
+ NSRect frame = win.contentView.frame;
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_VIDEORESIZE, frame.size.width,
+ frame.size.height);
+ }
+}
+
+void CWinSystemOSX::WindowChangedScreen()
+{
+ // user has moved the window to a
+ // different screen
+ NSOpenGLContext* context = [NSOpenGLContext currentContext];
+ m_lastDisplayNr = -1;
+
+ // if we are here the user dragged the window to a different
+ // screen and we return the screen of the window
+ if (context)
+ {
+ NSView* view;
+
+ view = context.view;
+ if (view)
+ {
+ NSWindow* window;
+ window = view.window;
+ if (window)
+ {
+ m_lastDisplayNr = GetDisplayIndex(GetDisplayIDFromScreen(window.screen));
+ }
+ }
+ }
+ if (m_lastDisplayNr == -1)
+ m_lastDisplayNr = 0; // default to main screen
+}
+
+CGLContextObj CWinSystemOSX::GetCGLContextObj()
+{
+ __block CGLContextObj cglcontex = nullptr;
+ if (m_appWindow)
+ {
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ OSXGLView* contentView = m_appWindow.contentView;
+ cglcontex = contentView.getGLContext.CGLContextObj;
+ });
+ }
+
+ return cglcontex;
+}
+
+bool CWinSystemOSX::FlushBuffer()
+{
+ if (m_appWindow)
+ {
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ OSXGLView* contentView = m_appWindow.contentView;
+ NSOpenGLContext* glcontex = contentView.getGLContext;
+ [glcontex flushBuffer];
+ });
+ }
+
+ return true;
+}
+
+#pragma mark - Vsync
+
+void CWinSystemOSX::EnableVSync(bool enable)
+{
+ // OpenGL Flush synchronised with vertical retrace
+ GLint swapInterval = enable ? 1 : 0;
+ [NSOpenGLContext.currentContext setValues:&swapInterval
+ forParameter:NSOpenGLContextParameterSwapInterval];
+}
+
+std::unique_ptr<CVideoSync> CWinSystemOSX::GetVideoSync(void* clock)
+{
+ return std::make_unique<CVideoSyncOsx>(clock);
+}
+
+std::vector<std::string> CWinSystemOSX::GetConnectedOutputs()
+{
+ std::vector<std::string> outputs;
+ outputs.push_back("Default");
+
+ int numDisplays = [[NSScreen screens] count];
+
+ for (int disp = 0; disp < numDisplays; disp++)
+ {
+ NSString* dispName = screenNameForDisplay(GetDisplayID(disp));
+ outputs.push_back(dispName.UTF8String);
+ }
+
+ return outputs;
+}
+
+#pragma mark - OSScreenSaver
+
+std::unique_ptr<IOSScreenSaver> CWinSystemOSX::GetOSScreenSaverImpl()
+{
+ return std::make_unique<COSScreenSaverOSX>();
+}
+
+#pragma mark - Input
+
+bool CWinSystemOSX::MessagePump()
+{
+ return m_winEvents->MessagePump();
+}
+
+void CWinSystemOSX::enableInputEvents()
+{
+ m_winEvents->enableInputEvents();
+}
+
+void CWinSystemOSX::disableInputEvents()
+{
+ m_winEvents->disableInputEvents();
+}
+
+std::string CWinSystemOSX::GetClipboardText()
+{
+ std::string utf8_text;
+
+ const char* szStr = Cocoa_Paste();
+ if (szStr)
+ utf8_text = szStr;
+
+ return utf8_text;
+}
+
+void CWinSystemOSX::ShowOSMouse(bool show)
+{
+}
+
+#pragma mark - Unused
+
+CGDisplayFadeReservationToken DisplayFadeToBlack(bool fade)
+{
+ // Fade to black to hide resolution-switching flicker and garbage.
+ CGDisplayFadeReservationToken fade_token = kCGDisplayFadeReservationInvalidToken;
+ if (CGAcquireDisplayFadeReservation(5, &fade_token) == kCGErrorSuccess && fade)
+ CGDisplayFade(fade_token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0,
+ TRUE);
+
+ return (fade_token);
+}
+
+void DisplayFadeFromBlack(CGDisplayFadeReservationToken fade_token, bool fade)
+{
+ if (fade_token != kCGDisplayFadeReservationInvalidToken)
+ {
+ if (fade)
+ CGDisplayFade(fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0,
+ 0.0, FALSE);
+ CGReleaseDisplayFadeReservation(fade_token);
+ }
+}
diff --git a/xbmc/windowing/tvos/CMakeLists.txt b/xbmc/windowing/tvos/CMakeLists.txt
new file mode 100644
index 0000000..d9aa9cc
--- /dev/null
+++ b/xbmc/windowing/tvos/CMakeLists.txt
@@ -0,0 +1,10 @@
+set(SOURCES OSScreenSaverTVOS.mm
+ WinEventsTVOS.mm
+ WinSystemTVOS.mm
+ VideoSyncTVos.cpp)
+set(HEADERS OSScreenSaverTVOS.h
+ WinEventsTVOS.h
+ WinSystemTVOS.h
+ VideoSyncTVos.h)
+
+core_add_library(windowing_tvos)
diff --git a/xbmc/windowing/tvos/OSScreenSaverTVOS.h b/xbmc/windowing/tvos/OSScreenSaverTVOS.h
new file mode 100644
index 0000000..2b5d3bd
--- /dev/null
+++ b/xbmc/windowing/tvos/OSScreenSaverTVOS.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2017-2019 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 "windowing/OSScreenSaver.h"
+
+class COSScreenSaverTVOS : public KODI::WINDOWING::IOSScreenSaver
+{
+public:
+ COSScreenSaverTVOS() = default;
+ void Inhibit() override;
+ void Uninhibit() override;
+};
diff --git a/xbmc/windowing/tvos/OSScreenSaverTVOS.mm b/xbmc/windowing/tvos/OSScreenSaverTVOS.mm
new file mode 100644
index 0000000..7af5580
--- /dev/null
+++ b/xbmc/windowing/tvos/OSScreenSaverTVOS.mm
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017-2019 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.
+ */
+
+#import "OSScreenSaverTVOS.h"
+
+#import "platform/darwin/tvos/XBMCController.h"
+
+void COSScreenSaverTVOS::Inhibit()
+{
+ [g_xbmcController disableScreenSaver];
+}
+
+void COSScreenSaverTVOS::Uninhibit()
+{
+ [g_xbmcController enableScreenSaver];
+}
diff --git a/xbmc/windowing/tvos/VideoSyncTVos.cpp b/xbmc/windowing/tvos/VideoSyncTVos.cpp
new file mode 100644
index 0000000..c00aa60
--- /dev/null
+++ b/xbmc/windowing/tvos/VideoSyncTVos.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2015-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 "VideoSyncTVos.h"
+
+#include "cores/VideoPlayer/VideoReferenceClock.h"
+#include "utils/MathUtils.h"
+#include "utils/TimeUtils.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+#import "windowing/tvos/WinSystemTVOS.h"
+
+bool CVideoSyncTVos::Setup(PUPDATECLOCK func)
+{
+ CLog::Log(LOGDEBUG, "CVideoSyncTVos::{} setting up TVOS", __FUNCTION__);
+
+ //init the vblank timestamp
+ m_LastVBlankTime = CurrentHostCounter();
+ UpdateClock = func;
+ m_abortEvent.Reset();
+
+ bool setupOk = InitDisplayLink();
+ if (setupOk)
+ {
+ m_winSystem.Register(this);
+ }
+
+ return setupOk;
+}
+
+void CVideoSyncTVos::Run(CEvent& stopEvent)
+{
+ //because cocoa has a vblank callback, we just keep sleeping until we're asked to stop the thread
+ XbmcThreads::CEventGroup waitGroup{&stopEvent, &m_abortEvent};
+ waitGroup.wait();
+}
+
+void CVideoSyncTVos::Cleanup()
+{
+ CLog::Log(LOGDEBUG, "CVideoSyncTVos::{} cleaning up TVOS", __FUNCTION__);
+ DeinitDisplayLink();
+ m_winSystem.Unregister(this);
+}
+
+float CVideoSyncTVos::GetFps()
+{
+ m_fps = CServiceBroker::GetWinSystem()->GetGfxContext().GetFPS();
+ CLog::Log(LOGDEBUG, "CVideoSyncTVos::{} Detected refreshrate: {} hertz", __FUNCTION__, m_fps);
+ return m_fps;
+}
+
+void CVideoSyncTVos::OnResetDisplay()
+{
+ m_abortEvent.Set();
+}
+
+void CVideoSyncTVos::TVosVblankHandler()
+{
+ int64_t nowtime = CurrentHostCounter();
+
+ //calculate how many vblanks happened
+ double VBlankTime =
+ static_cast<double>(nowtime - m_LastVBlankTime) / static_cast<double>(CurrentHostFrequency());
+ int NrVBlanks = MathUtils::round_int(VBlankTime * static_cast<double>(m_fps));
+
+ //save the timestamp of this vblank so we can calculate how many happened next time
+ m_LastVBlankTime = nowtime;
+
+ //update the vblank timestamp, update the clock and send a signal that we got a vblank
+ UpdateClock(NrVBlanks, nowtime, m_refClock);
+}
+
+bool CVideoSyncTVos::InitDisplayLink()
+{
+ bool ret = true;
+ CLog::Log(LOGDEBUG, "CVideoSyncTVos: setting up displaylink");
+ if (!m_winSystem.InitDisplayLink(this))
+ {
+ CLog::Log(LOGDEBUG, "CVideoSyncTVos: InitDisplayLink failed");
+ ret = false;
+ }
+ return ret;
+}
+
+void CVideoSyncTVos::DeinitDisplayLink()
+{
+ m_winSystem.DeinitDisplayLink();
+}
diff --git a/xbmc/windowing/tvos/VideoSyncTVos.h b/xbmc/windowing/tvos/VideoSyncTVos.h
new file mode 100644
index 0000000..de78226
--- /dev/null
+++ b/xbmc/windowing/tvos/VideoSyncTVos.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2015-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"
+
+class CWinSystemTVOS;
+
+class CVideoSyncTVos : public CVideoSync, IDispResource
+{
+public:
+ CVideoSyncTVos(void* clock, CWinSystemTVOS& winSystem) : CVideoSync(clock), m_winSystem(winSystem)
+ {
+ }
+
+ // CVideoSync interface
+ bool Setup(PUPDATECLOCK func) override;
+ void Run(CEvent& stopEvent) override;
+ void Cleanup() override;
+ float GetFps() override;
+
+ // IDispResource interface
+ void OnResetDisplay() override;
+
+ // used in the displaylink callback
+ void TVosVblankHandler();
+
+private:
+ // CVideoSyncDarwin interface
+ virtual bool InitDisplayLink();
+ virtual void DeinitDisplayLink();
+
+ //timestamp of the last vblank, used for calculating how many vblanks happened
+ int64_t m_LastVBlankTime = 0;
+ CEvent m_abortEvent;
+ CWinSystemTVOS& m_winSystem;
+};
diff --git a/xbmc/windowing/tvos/WinEventsTVOS.h b/xbmc/windowing/tvos/WinEventsTVOS.h
new file mode 100644
index 0000000..fa30665
--- /dev/null
+++ b/xbmc/windowing/tvos/WinEventsTVOS.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2012-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/CriticalSection.h"
+#include "threads/Event.h"
+#include "threads/Thread.h"
+#include "windowing/WinEvents.h"
+
+#include <list>
+#include <queue>
+#include <string>
+#include <vector>
+
+class CWinEventsTVOS : public IWinEvents, public CThread
+{
+public:
+ CWinEventsTVOS();
+ ~CWinEventsTVOS();
+
+ void MessagePush(XBMC_Event* newEvent);
+ size_t GetQueueSize();
+
+ bool MessagePump();
+
+private:
+ CCriticalSection m_eventsCond;
+ std::list<XBMC_Event> m_events;
+};
diff --git a/xbmc/windowing/tvos/WinEventsTVOS.mm b/xbmc/windowing/tvos/WinEventsTVOS.mm
new file mode 100644
index 0000000..67e9e54
--- /dev/null
+++ b/xbmc/windowing/tvos/WinEventsTVOS.mm
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2012-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 "WinEventsTVOS.h"
+
+#include "ServiceBroker.h"
+#include "application/AppInboundProtocol.h"
+#include "guilib/GUIWindowManager.h"
+#include "input/InputManager.h"
+#include "input/XBMC_vkeys.h"
+#include "threads/CriticalSection.h"
+#include "utils/log.h"
+
+#include <list>
+#include <mutex>
+
+static CCriticalSection g_inputCond;
+
+static std::list<XBMC_Event> events;
+
+CWinEventsTVOS::CWinEventsTVOS() : CThread("CWinEventsTVOS")
+{
+ CLog::Log(LOGDEBUG, "CWinEventsTVOS::CWinEventsTVOS");
+ Create();
+}
+
+CWinEventsTVOS::~CWinEventsTVOS()
+{
+ m_bStop = true;
+ StopThread(true);
+}
+
+void CWinEventsTVOS::MessagePush(XBMC_Event* newEvent)
+{
+ std::unique_lock<CCriticalSection> lock(m_eventsCond);
+
+ m_events.push_back(*newEvent);
+}
+
+size_t CWinEventsTVOS::GetQueueSize()
+{
+ std::unique_lock<CCriticalSection> lock(g_inputCond);
+ return events.size();
+}
+
+
+bool CWinEventsTVOS::MessagePump()
+{
+ bool ret = false;
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+
+ // Do not always loop, only pump the initial queued count events. else if ui keep pushing
+ // events the loop won't finish then it will block xbmc main message loop.
+ for (size_t pumpEventCount = GetQueueSize(); pumpEventCount > 0; --pumpEventCount)
+ {
+ // Pop up only one event per time since in App::OnEvent it may init modal dialog which init
+ // deeper message loop and call the deeper MessagePump from there.
+ XBMC_Event pumpEvent;
+ {
+ std::unique_lock<CCriticalSection> lock(g_inputCond);
+ if (events.empty())
+ return ret;
+ pumpEvent = events.front();
+ events.pop_front();
+ }
+
+ if (appPort)
+ ret = appPort->OnEvent(pumpEvent);
+ }
+ return ret;
+}
diff --git a/xbmc/windowing/tvos/WinSystemTVOS.h b/xbmc/windowing/tvos/WinSystemTVOS.h
new file mode 100644
index 0000000..774b7e9
--- /dev/null
+++ b/xbmc/windowing/tvos/WinSystemTVOS.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2010-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 "rendering/gles/RenderSystemGLES.h"
+#include "threads/CriticalSection.h"
+#include "threads/Timer.h"
+#include "windowing/OSScreenSaver.h"
+#include "windowing/WinSystem.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <CoreVideo/CVOpenGLESTextureCache.h>
+
+class IDispResource;
+class CVideoSyncTVos;
+struct CADisplayLinkWrapper;
+
+class CWinSystemTVOS : public CWinSystemBase, public CRenderSystemGLES, public ITimerCallback
+{
+public:
+ CWinSystemTVOS();
+ virtual ~CWinSystemTVOS();
+
+ static void Register();
+ static std::unique_ptr<CWinSystemBase> CreateWinSystem();
+
+ // ITimerCallback interface
+ virtual void OnTimeout() override {}
+
+ void MessagePush(XBMC_Event* newEvent);
+ size_t GetQueueSize();
+ void AnnounceOnLostDevice();
+ void AnnounceOnResetDevice();
+ void StartLostDeviceTimer();
+ void StopLostDeviceTimer();
+ int GetDisplayIndexFromSettings();
+ // Implementation of CWinSystemBase
+ CRenderSystemBase* GetRenderSystem() override { return this; }
+ 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;
+ bool SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) override;
+ void UpdateResolutions() override;
+ bool CanDoWindowed() override { return false; }
+
+ void ShowOSMouse(bool show) override {}
+ bool HasCursor() override;
+
+ void NotifyAppActiveChange(bool bActivated) override;
+
+ bool Minimize() override;
+ bool Restore() override;
+ bool Hide() override;
+ bool Show(bool raise = true) override;
+
+ bool IsExtSupported(const char* extension) const override;
+
+ bool BeginRender() override;
+ bool EndRender() override;
+
+ void Register(IDispResource* resource) override;
+ void Unregister(IDispResource* resource) override;
+
+ //virtual std::unique_ptr<CVideoSync> GetVideoSync(void* clock) override;
+
+ std::vector<std::string> GetConnectedOutputs() override;
+
+ bool InitDisplayLink(CVideoSyncTVos* syncImpl);
+ void DeinitDisplayLink(void);
+ void OnAppFocusChange(bool focus);
+ bool IsBackgrounded() const { return m_bIsBackgrounded; }
+ CVEAGLContext GetEAGLContextObj();
+
+ // winevents override
+ bool MessagePump() override;
+
+protected:
+ virtual std::unique_ptr<KODI::WINDOWING::IOSScreenSaver> GetOSScreenSaverImpl() override;
+ void PresentRenderImpl(bool rendered) override;
+ void SetVSyncImpl(bool enable) override {}
+
+ void* m_glView; // EAGLView opaque
+ void* m_WorkingContext; // shared EAGLContext opaque
+ bool m_bWasFullScreenBeforeMinimize;
+ std::string m_eglext;
+ CCriticalSection m_resourceSection;
+ std::vector<IDispResource*> m_resources;
+ bool m_bIsBackgrounded;
+ CTimer m_lostDeviceTimer;
+
+private:
+ bool GetScreenResolution(int* w, int* h, double* fps, int screenIdx);
+ void FillInVideoModes(int screenIdx);
+ bool SwitchToVideoMode(int width, int height, double refreshrate);
+ CADisplayLinkWrapper* m_pDisplayLink;
+};
diff --git a/xbmc/windowing/tvos/WinSystemTVOS.mm b/xbmc/windowing/tvos/WinSystemTVOS.mm
new file mode 100644
index 0000000..bd47c6f
--- /dev/null
+++ b/xbmc/windowing/tvos/WinSystemTVOS.mm
@@ -0,0 +1,453 @@
+/*
+ * Copyright (C) 2010-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 "WinSystemTVOS.h"
+
+#include "ServiceBroker.h"
+#import "cores/AudioEngine/Sinks/AESinkDARWINTVOS.h"
+#include "cores/RetroPlayer/process/ios/RPProcessInfoIOS.h"
+#include "cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGLES.h"
+#include "cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.h"
+#include "cores/VideoPlayer/DVDCodecs/Video/VTB.h"
+#include "cores/VideoPlayer/Process/ios/ProcessInfoIOS.h"
+#include "cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVTBGLES.h"
+#include "cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.h"
+#include "cores/VideoPlayer/VideoRenderers/RenderFactory.h"
+#include "filesystem/SpecialProtocol.h"
+#include "guilib/DispResource.h"
+#include "guilib/Texture.h"
+#include "messaging/ApplicationMessenger.h"
+#include "settings/DisplaySettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+#include "windowing/OSScreenSaver.h"
+#include "windowing/WindowSystemFactory.h"
+#import "windowing/tvos/OSScreenSaverTVOS.h"
+#import "windowing/tvos/VideoSyncTVos.h"
+#import "windowing/tvos/WinEventsTVOS.h"
+
+#import "platform/darwin/DarwinUtils.h"
+#import "platform/darwin/tvos/TVOSDisplayManager.h"
+#import "platform/darwin/tvos/XBMCController.h"
+
+#include <memory>
+#include <mutex>
+#include <vector>
+
+#import <Foundation/Foundation.h>
+#import <OpenGLES/ES2/gl.h>
+#import <OpenGLES/ES2/glext.h>
+#import <QuartzCore/CADisplayLink.h>
+
+using namespace std::chrono_literals;
+
+#define CONST_HDMI "HDMI"
+
+// if there was a devicelost callback
+// but no device reset for 3 secs
+// a timeout fires the reset callback
+// (for ensuring that e.x. AE isn't stuck)
+constexpr auto LOST_DEVICE_TIMEOUT_MS{3000ms};
+
+// TVOSDisplayLinkCallback is defined in the lower part of the file
+@interface TVOSDisplayLinkCallback : NSObject
+{
+@private
+ CVideoSyncTVos* videoSyncImpl;
+}
+@property(nonatomic, setter=SetVideoSyncImpl:) CVideoSyncTVos* videoSyncImpl;
+- (void)runDisplayLink;
+@end
+
+using namespace KODI;
+using namespace MESSAGING;
+
+struct CADisplayLinkWrapper
+{
+ CADisplayLink* impl;
+ TVOSDisplayLinkCallback* callbackClass;
+};
+
+void CWinSystemTVOS::Register()
+{
+ KODI::WINDOWING::CWindowSystemFactory::RegisterWindowSystem(CreateWinSystem);
+}
+
+std::unique_ptr<CWinSystemBase> CWinSystemTVOS::CreateWinSystem()
+{
+ return std::make_unique<CWinSystemTVOS>();
+}
+
+void CWinSystemTVOS::MessagePush(XBMC_Event* newEvent)
+{
+ dynamic_cast<CWinEventsTVOS&>(*m_winEvents).MessagePush(newEvent);
+}
+
+size_t CWinSystemTVOS::GetQueueSize()
+{
+ return dynamic_cast<CWinEventsTVOS&>(*m_winEvents).GetQueueSize();
+}
+
+void CWinSystemTVOS::AnnounceOnLostDevice()
+{
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ // tell any shared resources
+ CLog::Log(LOGDEBUG, "CWinSystemTVOS::AnnounceOnLostDevice");
+ for (auto dispResource : m_resources)
+ dispResource->OnLostDisplay();
+}
+
+void CWinSystemTVOS::AnnounceOnResetDevice()
+{
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ // tell any shared resources
+ CLog::Log(LOGDEBUG, "CWinSystemTVOS::AnnounceOnResetDevice");
+ for (auto dispResource : m_resources)
+ dispResource->OnResetDisplay();
+}
+
+void CWinSystemTVOS::StartLostDeviceTimer()
+{
+ if (m_lostDeviceTimer.IsRunning())
+ m_lostDeviceTimer.Restart();
+ else
+ m_lostDeviceTimer.Start(LOST_DEVICE_TIMEOUT_MS, false);
+}
+
+void CWinSystemTVOS::StopLostDeviceTimer()
+{
+ m_lostDeviceTimer.Stop();
+}
+
+
+int CWinSystemTVOS::GetDisplayIndexFromSettings()
+{
+ // ATV only supports 1 screen with id = 0
+ return 0;
+}
+
+CWinSystemTVOS::CWinSystemTVOS() : CWinSystemBase(), m_lostDeviceTimer(this)
+{
+ m_bIsBackgrounded = false;
+ m_pDisplayLink = new CADisplayLinkWrapper;
+ m_pDisplayLink->callbackClass = [[TVOSDisplayLinkCallback alloc] init];
+
+ m_winEvents.reset(new CWinEventsTVOS());
+
+ CAESinkDARWINTVOS::Register();
+}
+
+CWinSystemTVOS::~CWinSystemTVOS()
+{
+ m_pDisplayLink->callbackClass = nil;
+ delete m_pDisplayLink;
+}
+
+bool CWinSystemTVOS::InitWindowSystem()
+{
+ return CWinSystemBase::InitWindowSystem();
+}
+
+bool CWinSystemTVOS::DestroyWindowSystem()
+{
+ return true;
+}
+
+std::unique_ptr<KODI::WINDOWING::IOSScreenSaver> CWinSystemTVOS::GetOSScreenSaverImpl()
+{
+ std::unique_ptr<KODI::WINDOWING::IOSScreenSaver> screensaver =
+ std::make_unique<COSScreenSaverTVOS>();
+ return screensaver;
+}
+
+bool CWinSystemTVOS::CreateNewWindow(const std::string& name, bool fullScreen, RESOLUTION_INFO& res)
+{
+ if (!SetFullScreen(fullScreen, res, false))
+ return false;
+
+ [g_xbmcController setFramebuffer];
+
+ m_bWindowCreated = true;
+
+ m_eglext = " ";
+
+ const char* tmpExtensions = reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS));
+ if (tmpExtensions != nullptr)
+ {
+ m_eglext += tmpExtensions;
+ m_eglext += " ";
+ }
+
+ CLog::Log(LOGDEBUG, "EGL_EXTENSIONS: {}", m_eglext);
+
+ // register platform dependent objects
+ CDVDFactoryCodec::ClearHWAccels();
+ VTB::CDecoder::Register();
+ VIDEOPLAYER::CRendererFactory::ClearRenderer();
+ CLinuxRendererGLES::Register();
+ CRendererVTB::Register();
+ VIDEOPLAYER::CProcessInfoIOS::Register();
+ RETRO::CRPProcessInfoIOS::Register();
+ RETRO::CRPProcessInfoIOS::RegisterRendererFactory(new RETRO::CRendererFactoryOpenGLES);
+
+ return true;
+}
+
+bool CWinSystemTVOS::DestroyWindow()
+{
+ return true;
+}
+
+bool CWinSystemTVOS::ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop)
+{
+ if (m_nWidth != newWidth || m_nHeight != newHeight)
+ {
+ m_nWidth = newWidth;
+ m_nHeight = newHeight;
+ }
+
+ CRenderSystemGLES::ResetRenderSystem(newWidth, newHeight);
+
+ return true;
+}
+
+bool CWinSystemTVOS::SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays)
+{
+ m_nWidth = res.iWidth;
+ m_nHeight = res.iHeight;
+ m_bFullScreen = fullScreen;
+
+ CLog::Log(LOGDEBUG, "About to switch to {} x {} @ {}", m_nWidth, m_nHeight, res.fRefreshRate);
+ SwitchToVideoMode(res.iWidth, res.iHeight, static_cast<double>(res.fRefreshRate));
+ CRenderSystemGLES::ResetRenderSystem(res.iWidth, res.iHeight);
+
+ return true;
+}
+
+bool CWinSystemTVOS::SwitchToVideoMode(int width, int height, double refreshrate)
+{
+ /*! @todo Currently support SDR dynamic range only. HDR shouldn't be done during
+ * a modeswitch. Look to create supplemental method to handle sdr/hdr enable
+ */
+ [g_xbmcController.displayManager displayRateSwitch:refreshrate
+ withDynamicRange:0 /*dynamicRange*/];
+ return true;
+}
+
+bool CWinSystemTVOS::GetScreenResolution(int* w, int* h, double* fps, int screenIdx)
+{
+ *w = [g_xbmcController.displayManager getScreenSize].width;
+ *h = [g_xbmcController.displayManager getScreenSize].height;
+ *fps = static_cast<double>([g_xbmcController.displayManager getDisplayRate]);
+
+ CLog::Log(LOGDEBUG, "Current resolution Screen: {} with {} x {} @ {}", screenIdx, *w, *h, *fps);
+ return true;
+}
+
+void CWinSystemTVOS::UpdateResolutions()
+{
+ // Add display resolution
+ int w, h;
+ double fps;
+ CWinSystemBase::UpdateResolutions();
+
+ int screenIdx = GetDisplayIndexFromSettings();
+
+ //first screen goes into the current desktop mode
+ if (GetScreenResolution(&w, &h, &fps, screenIdx))
+ UpdateDesktopResolution(CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP),
+ CONST_HDMI, w, h, fps, 0);
+
+ CDisplaySettings::GetInstance().ClearCustomResolutions();
+
+ //now just fill in the possible resolutions for the attached screens
+ //and push to the resolution info vector
+ FillInVideoModes(screenIdx);
+}
+
+void CWinSystemTVOS::FillInVideoModes(int screenIdx)
+{
+ // Potential refresh rates
+ std::vector<float> supportedDispRefreshRates = {23.976f, 24.000f, 25.000f, 29.970f,
+ 30.000f, 50.000f, 59.940f, 60.000f};
+
+ UIScreen* aScreen = UIScreen.screens[screenIdx];
+ UIScreenMode* mode = aScreen.currentMode;
+ int w = mode.size.width;
+ int h = mode.size.height;
+
+ //! @Todo handle different resolutions than native (ie 720p/1080p on a 4k display)
+
+ for (float refreshrate : supportedDispRefreshRates)
+ {
+ RESOLUTION_INFO res;
+ UpdateDesktopResolution(res, CONST_HDMI, w, h, refreshrate, 0);
+ CLog::Log(LOGINFO, "Found possible resolution for display {} with {} x {} RefreshRate:{} ",
+ screenIdx, w, h, refreshrate);
+
+ CServiceBroker::GetWinSystem()->GetGfxContext().ResetOverscan(res);
+ CDisplaySettings::GetInstance().AddResolutionInfo(res);
+ }
+}
+
+bool CWinSystemTVOS::IsExtSupported(const char* extension) const
+{
+ if (strncmp(extension, "EGL_", 4) != 0)
+ return CRenderSystemGLES::IsExtSupported(extension);
+
+ std::string name = ' ' + std::string(extension) + ' ';
+
+ return m_eglext.find(name) != std::string::npos;
+}
+
+
+bool CWinSystemTVOS::BeginRender()
+{
+ bool rtn;
+
+ [g_xbmcController setFramebuffer];
+
+ rtn = CRenderSystemGLES::BeginRender();
+ return rtn;
+}
+
+bool CWinSystemTVOS::EndRender()
+{
+ bool rtn;
+
+ rtn = CRenderSystemGLES::EndRender();
+ return rtn;
+}
+
+void CWinSystemTVOS::Register(IDispResource* resource)
+{
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ m_resources.push_back(resource);
+}
+
+void CWinSystemTVOS::Unregister(IDispResource* resource)
+{
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ std::vector<IDispResource*>::iterator i = find(m_resources.begin(), m_resources.end(), resource);
+ if (i != m_resources.end())
+ m_resources.erase(i);
+}
+
+void CWinSystemTVOS::OnAppFocusChange(bool focus)
+{
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ m_bIsBackgrounded = !focus;
+ CLog::Log(LOGDEBUG, "CWinSystemTVOS::OnAppFocusChange: {}", focus ? 1 : 0);
+ for (auto dispResource : m_resources)
+ dispResource->OnAppFocusChange(focus);
+}
+
+//--------------------------------------------------------------
+//-------------------DisplayLink stuff
+@implementation TVOSDisplayLinkCallback
+@synthesize videoSyncImpl;
+//--------------------------------------------------------------
+- (void)runDisplayLink
+{
+ @autoreleasepool
+ {
+ if (videoSyncImpl != nullptr)
+ videoSyncImpl->TVosVblankHandler();
+ }
+}
+@end
+
+bool CWinSystemTVOS::InitDisplayLink(CVideoSyncTVos* syncImpl)
+{
+ unsigned int currentScreenIdx = GetDisplayIndexFromSettings();
+ UIScreen* currentScreen = UIScreen.screens[currentScreenIdx];
+ m_pDisplayLink->callbackClass.videoSyncImpl = syncImpl;
+ m_pDisplayLink->impl = [currentScreen displayLinkWithTarget:m_pDisplayLink->callbackClass
+ selector:@selector(runDisplayLink)];
+
+ [m_pDisplayLink->impl addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
+ return m_pDisplayLink->impl != nil;
+}
+
+void CWinSystemTVOS::DeinitDisplayLink(void)
+{
+ if (m_pDisplayLink->impl)
+ {
+ [m_pDisplayLink->impl invalidate];
+ m_pDisplayLink->impl = nil;
+ [m_pDisplayLink->callbackClass SetVideoSyncImpl:nil];
+ }
+}
+//------------DisplayLink stuff end
+//--------------------------------------------------------------
+
+void CWinSystemTVOS::PresentRenderImpl(bool rendered)
+{
+ //glFlush;
+ if (rendered)
+ [g_xbmcController presentFramebuffer];
+}
+
+bool CWinSystemTVOS::HasCursor()
+{
+ return false;
+}
+
+void CWinSystemTVOS::NotifyAppActiveChange(bool bActivated)
+{
+ if (bActivated && m_bWasFullScreenBeforeMinimize &&
+ !CServiceBroker::GetWinSystem()->GetGfxContext().IsFullScreenRoot())
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_TOGGLEFULLSCREEN);
+}
+
+bool CWinSystemTVOS::Minimize()
+{
+ m_bWasFullScreenBeforeMinimize =
+ CServiceBroker::GetWinSystem()->GetGfxContext().IsFullScreenRoot();
+ if (m_bWasFullScreenBeforeMinimize)
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_TOGGLEFULLSCREEN);
+
+ return true;
+}
+
+bool CWinSystemTVOS::Restore()
+{
+ return false;
+}
+
+bool CWinSystemTVOS::Hide()
+{
+ return true;
+}
+
+bool CWinSystemTVOS::Show(bool raise)
+{
+ return true;
+}
+
+CVEAGLContext CWinSystemTVOS::GetEAGLContextObj()
+{
+ return [g_xbmcController getEAGLContextObj];
+}
+
+std::vector<std::string> CWinSystemTVOS::GetConnectedOutputs()
+{
+ std::vector<std::string> outputs;
+ outputs.emplace_back("Default");
+ outputs.emplace_back(CONST_HDMI);
+
+ return outputs;
+}
+
+bool CWinSystemTVOS::MessagePump()
+{
+ return m_winEvents->MessagePump();
+}
diff --git a/xbmc/windowing/wayland/CMakeLists.txt b/xbmc/windowing/wayland/CMakeLists.txt
new file mode 100644
index 0000000..5628ed8
--- /dev/null
+++ b/xbmc/windowing/wayland/CMakeLists.txt
@@ -0,0 +1,67 @@
+# from ArchSetup.cmake
+set_source_files_properties(${WAYLAND_EXTRA_PROTOCOL_GENERATED_DIR}/wayland-extra-protocols.cpp
+ ${WAYLAND_EXTRA_PROTOCOL_GENERATED_DIR}/wayland-extra-protocols.hpp
+ PROPERTIES GENERATED TRUE)
+
+set(SOURCES Connection.cpp
+ OptionalsReg.cpp
+ Output.cpp
+ InputProcessorKeyboard.h
+ InputProcessorPointer.h
+ InputProcessorTouch.h
+ OSScreenSaverIdleInhibitUnstableV1.cpp
+ Registry.cpp
+ Seat.cpp
+ SeatInputProcessing.cpp
+ SeatSelection.cpp
+ ShellSurface.cpp
+ ShellSurfaceWlShell.cpp
+ ShellSurfaceXdgShell.cpp
+ ShellSurfaceXdgShellUnstableV6.cpp
+ Util.cpp
+ VideoSyncWpPresentation.cpp
+ ${WAYLAND_EXTRA_PROTOCOL_GENERATED_DIR}/wayland-extra-protocols.cpp
+ WindowDecorator.cpp
+ WinEventsWayland.cpp
+ WinSystemWayland.cpp
+ XkbcommonKeymap.cpp)
+
+set(HEADERS Connection.h
+ OptionalsReg.h
+ Output.h
+ InputProcessorKeyboard.cpp
+ InputProcessorPointer.cpp
+ InputProcessorTouch.cpp
+ OSScreenSaverIdleInhibitUnstableV1.h
+ Registry.h
+ Seat.h
+ SeatInputProcessing.h
+ SeatSelection.h
+ ShellSurface.h
+ ShellSurfaceWlShell.h
+ ShellSurfaceXdgShell.h
+ ShellSurfaceXdgShellUnstableV6.h
+ Signals.h
+ VideoSyncWpPresentation.h
+ ${WAYLAND_EXTRA_PROTOCOL_GENERATED_DIR}/wayland-extra-protocols.hpp
+ WindowDecorator.h
+ WinEventsWayland.h
+ WinSystemWayland.h
+ XkbcommonKeymap.h)
+
+if(EGL_FOUND)
+ list(APPEND SOURCES WinSystemWaylandEGLContext.cpp)
+ list(APPEND HEADERS WinSystemWaylandEGLContext.h)
+endif()
+
+if(OPENGL_FOUND)
+ list(APPEND SOURCES WinSystemWaylandEGLContextGL.cpp)
+ list(APPEND HEADERS WinSystemWaylandEGLContextGL.h)
+endif()
+if(OPENGLES_FOUND)
+ list(APPEND SOURCES WinSystemWaylandEGLContextGLES.cpp)
+ list(APPEND HEADERS WinSystemWaylandEGLContextGLES.h)
+endif()
+
+
+core_add_library(windowing_WAYLAND)
diff --git a/xbmc/windowing/wayland/Connection.cpp b/xbmc/windowing/wayland/Connection.cpp
new file mode 100644
index 0000000..9d45be6
--- /dev/null
+++ b/xbmc/windowing/wayland/Connection.cpp
@@ -0,0 +1,39 @@
+/*
+ * 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 "Connection.h"
+
+#include "utils/log.h"
+
+#include <cassert>
+#include <stdexcept>
+
+using namespace KODI::WINDOWING::WAYLAND;
+
+CConnection::CConnection()
+{
+ try
+ {
+ m_display = std::make_unique<wayland::display_t>();
+ }
+ catch (const std::exception& err)
+ {
+ CLog::Log(LOGERROR, "Wayland connection error: {}", err.what());
+ }
+}
+
+bool CConnection::HasDisplay() const
+{
+ return static_cast<bool>(m_display);
+}
+
+wayland::display_t& CConnection::GetDisplay()
+{
+ assert(m_display);
+ return *m_display;
+}
diff --git a/xbmc/windowing/wayland/Connection.h b/xbmc/windowing/wayland/Connection.h
new file mode 100644
index 0000000..b1badd4
--- /dev/null
+++ b/xbmc/windowing/wayland/Connection.h
@@ -0,0 +1,39 @@
+/*
+ * 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 <memory>
+
+#include <wayland-client.hpp>
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace WAYLAND
+{
+
+/**
+ * Connection to Wayland compositor
+ */
+class CConnection
+{
+public:
+ CConnection();
+
+ bool HasDisplay() const;
+ wayland::display_t& GetDisplay();
+
+private:
+ std::unique_ptr<wayland::display_t> m_display;
+};
+
+}
+}
+}
diff --git a/xbmc/windowing/wayland/InputProcessorKeyboard.cpp b/xbmc/windowing/wayland/InputProcessorKeyboard.cpp
new file mode 100644
index 0000000..37b0240
--- /dev/null
+++ b/xbmc/windowing/wayland/InputProcessorKeyboard.cpp
@@ -0,0 +1,170 @@
+/*
+ * 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 "InputProcessorKeyboard.h"
+
+#include "utils/log.h"
+
+#include <cassert>
+#include <limits>
+
+using namespace KODI::WINDOWING::WAYLAND;
+
+namespace
+{
+// Offset between keyboard codes of Wayland (effectively evdev) and xkb_keycode_t
+constexpr int WL_KEYBOARD_XKB_CODE_OFFSET{8};
+}
+
+CInputProcessorKeyboard::CInputProcessorKeyboard(IInputHandlerKeyboard& handler)
+: m_handler{handler}, m_keyRepeatTimer{std::bind(&CInputProcessorKeyboard::KeyRepeatTimeout, this)}
+{
+}
+
+void CInputProcessorKeyboard::OnKeyboardKeymap(CSeat* seat, wayland::keyboard_keymap_format format, std::string const &keymap)
+{
+ if (format != wayland::keyboard_keymap_format::xkb_v1)
+ {
+ CLog::Log(LOGWARNING,
+ "Wayland compositor sent keymap in format {}, but we only understand xkbv1 - "
+ "keyboard input will not work",
+ static_cast<unsigned int>(format));
+ return;
+ }
+
+ m_keyRepeatTimer.Stop();
+
+ try
+ {
+ if (!m_xkbContext)
+ {
+ // Lazily initialize XkbcommonContext
+ m_xkbContext.reset(new CXkbcommonContext);
+ }
+
+ m_keymap = m_xkbContext->KeymapFromString(keymap);
+ }
+ catch(std::exception const& e)
+ {
+ CLog::Log(LOGERROR, "Could not parse keymap from compositor: {} - continuing without keymap",
+ e.what());
+ }
+}
+
+void CInputProcessorKeyboard::OnKeyboardEnter(CSeat* seat,
+ std::uint32_t serial,
+ const wayland::surface_t& surface,
+ const wayland::array_t& keys)
+{
+ m_handler.OnKeyboardEnter();
+}
+
+void CInputProcessorKeyboard::OnKeyboardLeave(CSeat* seat,
+ std::uint32_t serial,
+ const wayland::surface_t& surface)
+{
+ m_keyRepeatTimer.Stop();
+ m_handler.OnKeyboardLeave();
+}
+
+void CInputProcessorKeyboard::OnKeyboardKey(CSeat* seat, std::uint32_t serial, std::uint32_t time, std::uint32_t key, wayland::keyboard_key_state state)
+{
+ if (!m_keymap)
+ {
+ CLog::Log(LOGWARNING, "Key event for code {} without valid keymap, ignoring", key);
+ return;
+ }
+
+ ConvertAndSendKey(key, state == wayland::keyboard_key_state::pressed);
+}
+
+void CInputProcessorKeyboard::OnKeyboardModifiers(CSeat* seat, std::uint32_t serial, std::uint32_t modsDepressed, std::uint32_t modsLatched, std::uint32_t modsLocked, std::uint32_t group)
+{
+ if (!m_keymap)
+ {
+ CLog::Log(LOGWARNING, "Modifier event without valid keymap, ignoring");
+ return;
+ }
+
+ m_keyRepeatTimer.Stop();
+ m_keymap->UpdateMask(modsDepressed, modsLatched, modsLocked, group);
+}
+
+void CInputProcessorKeyboard::OnKeyboardRepeatInfo(CSeat* seat, std::int32_t rate, std::int32_t delay)
+{
+ CLog::Log(LOGDEBUG, "Key repeat rate: {} cps, delay {} ms", rate, delay);
+ // rate is in characters per second, so convert to msec interval
+ m_keyRepeatInterval = (rate != 0) ? static_cast<int> (1000.0f / rate) : 0;
+ m_keyRepeatDelay = delay;
+}
+
+void CInputProcessorKeyboard::ConvertAndSendKey(std::uint32_t scancode, bool pressed)
+{
+ std::uint32_t xkbCode{scancode + WL_KEYBOARD_XKB_CODE_OFFSET};
+ XBMCKey xbmcKey{m_keymap->XBMCKeyForKeycode(xkbCode)};
+ std::uint32_t utf32{m_keymap->UnicodeCodepointForKeycode(xkbCode)};
+
+ if (utf32 > std::numeric_limits<std::uint16_t>::max())
+ {
+ // Kodi event system only supports UTF16, so ignore the codepoint if
+ // it does not fit
+ utf32 = 0;
+ }
+ if (scancode > std::numeric_limits<unsigned char>::max())
+ {
+ // Kodi scancodes are limited to unsigned char, pretend the scancode is unknown
+ // on overflow
+ scancode = 0;
+ }
+
+ XBMC_Event event{SendKey(scancode, xbmcKey, static_cast<std::uint16_t> (utf32), pressed)};
+
+ if (pressed && m_keymap->ShouldKeycodeRepeat(xkbCode) && m_keyRepeatInterval > 0)
+ {
+ // Can't modify keyToRepeat until we're sure the thread isn't accessing it
+ m_keyRepeatTimer.Stop(true);
+ // Update/Set key
+ m_keyToRepeat = event;
+ // Start timer with initial delay
+ m_keyRepeatTimer.Start(std::chrono::milliseconds(m_keyRepeatDelay), false);
+ }
+ else
+ {
+ m_keyRepeatTimer.Stop();
+ }
+}
+
+XBMC_Event CInputProcessorKeyboard::SendKey(unsigned char scancode, XBMCKey key, std::uint16_t unicodeCodepoint, bool pressed)
+{
+ assert(m_keymap);
+
+ XBMC_Event event{};
+ event.type = pressed ? XBMC_KEYDOWN : XBMC_KEYUP;
+ event.key.keysym =
+ {
+ .scancode = scancode,
+ .sym = key,
+ .mod = m_keymap->ActiveXBMCModifiers(),
+ .unicode = unicodeCodepoint
+ };
+ m_handler.OnKeyboardEvent(event);
+ // Return created event for convenience (key repeat)
+ return event;
+}
+
+void CInputProcessorKeyboard::KeyRepeatTimeout()
+{
+ // Reset ourselves
+ m_keyRepeatTimer.RestartAsync(std::chrono::milliseconds(m_keyRepeatInterval));
+ // Simulate repeat: Key up and down
+ XBMC_Event event = m_keyToRepeat;
+ event.type = XBMC_KEYUP;
+ m_handler.OnKeyboardEvent(event);
+ event.type = XBMC_KEYDOWN;
+ m_handler.OnKeyboardEvent(event);
+}
diff --git a/xbmc/windowing/wayland/InputProcessorKeyboard.h b/xbmc/windowing/wayland/InputProcessorKeyboard.h
new file mode 100644
index 0000000..73a37ce
--- /dev/null
+++ b/xbmc/windowing/wayland/InputProcessorKeyboard.h
@@ -0,0 +1,78 @@
+/*
+ * 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 "Seat.h"
+#include "XkbcommonKeymap.h"
+#include "input/XBMC_keysym.h"
+#include "threads/Timer.h"
+#include "windowing/XBMC_events.h"
+
+#include <atomic>
+#include <cstdint>
+#include <memory>
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace WAYLAND
+{
+
+class IInputHandlerKeyboard
+{
+public:
+ virtual void OnKeyboardEnter() {}
+ virtual void OnKeyboardLeave() {}
+ virtual void OnKeyboardEvent(XBMC_Event& event) = 0;
+ virtual ~IInputHandlerKeyboard() = default;
+};
+
+class CInputProcessorKeyboard final : public IRawInputHandlerKeyboard
+{
+public:
+ CInputProcessorKeyboard(IInputHandlerKeyboard& handler);
+
+ void OnKeyboardKeymap(CSeat* seat, wayland::keyboard_keymap_format format, std::string const& keymap) override;
+ void OnKeyboardEnter(CSeat* seat,
+ std::uint32_t serial,
+ const wayland::surface_t& surface,
+ const wayland::array_t& keys) override;
+ void OnKeyboardLeave(CSeat* seat,
+ std::uint32_t serial,
+ const wayland::surface_t& surface) override;
+ void OnKeyboardKey(CSeat* seat, std::uint32_t serial, std::uint32_t time, std::uint32_t key, wayland::keyboard_key_state state) override;
+ void OnKeyboardModifiers(CSeat* seat, std::uint32_t serial, std::uint32_t modsDepressed, std::uint32_t modsLatched, std::uint32_t modsLocked, std::uint32_t group) override;
+ void OnKeyboardRepeatInfo(CSeat* seat, std::int32_t rate, std::int32_t delay) override;
+
+private:
+ CInputProcessorKeyboard(CInputProcessorKeyboard const& other) = delete;
+ CInputProcessorKeyboard& operator=(CInputProcessorKeyboard const& other) = delete;
+
+ void ConvertAndSendKey(std::uint32_t scancode, bool pressed);
+ XBMC_Event SendKey(unsigned char scancode, XBMCKey key, std::uint16_t unicodeCodepoint, bool pressed);
+ void KeyRepeatTimeout();
+
+ IInputHandlerKeyboard& m_handler;
+
+ std::unique_ptr<CXkbcommonContext> m_xkbContext;
+ std::unique_ptr<CXkbcommonKeymap> m_keymap;
+ // Default values are used if compositor does not send any
+ std::atomic<int> m_keyRepeatDelay{1000};
+ std::atomic<int> m_keyRepeatInterval{50};
+ // Save complete XBMC_Event so no keymap lookups which might not be thread-safe
+ // are needed in the repeat callback
+ XBMC_Event m_keyToRepeat;
+
+ CTimer m_keyRepeatTimer;
+};
+
+}
+}
+}
diff --git a/xbmc/windowing/wayland/InputProcessorPointer.cpp b/xbmc/windowing/wayland/InputProcessorPointer.cpp
new file mode 100644
index 0000000..6a2fe04
--- /dev/null
+++ b/xbmc/windowing/wayland/InputProcessorPointer.cpp
@@ -0,0 +1,136 @@
+/*
+ * 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 "InputProcessorPointer.h"
+
+#include "input/mouse/MouseStat.h"
+
+#include <cmath>
+
+#include <linux/input-event-codes.h>
+
+using namespace KODI::WINDOWING::WAYLAND;
+
+namespace
+{
+
+int WaylandToXbmcButton(std::uint32_t button)
+{
+ // Wayland button is evdev code
+ switch (button)
+ {
+ case BTN_LEFT:
+ return XBMC_BUTTON_LEFT;
+ case BTN_MIDDLE:
+ return XBMC_BUTTON_MIDDLE;
+ case BTN_RIGHT:
+ return XBMC_BUTTON_RIGHT;
+ default:
+ return -1;
+ }
+}
+
+}
+
+CInputProcessorPointer::CInputProcessorPointer(wayland::surface_t const& surface, IInputHandlerPointer& handler)
+: m_surface{surface}, m_handler{handler}
+{
+}
+
+void CInputProcessorPointer::OnPointerEnter(CSeat* seat,
+ std::uint32_t serial,
+ const wayland::surface_t& surface,
+ double surfaceX,
+ double surfaceY)
+{
+ if (surface == m_surface)
+ {
+ m_pointerOnSurface = true;
+ m_handler.OnPointerEnter(seat->GetGlobalName(), serial);
+ SetMousePosFromSurface({surfaceX, surfaceY});
+ SendMouseMotion();
+ }
+}
+
+void CInputProcessorPointer::OnPointerLeave(CSeat* seat,
+ std::uint32_t serial,
+ const wayland::surface_t& surface)
+{
+ if (m_pointerOnSurface)
+ {
+ m_handler.OnPointerLeave();
+ m_pointerOnSurface = false;
+ }
+}
+
+void CInputProcessorPointer::OnPointerMotion(CSeat* seat, std::uint32_t time, double surfaceX, double surfaceY)
+{
+ if (m_pointerOnSurface)
+ {
+ SetMousePosFromSurface({surfaceX, surfaceY});
+ SendMouseMotion();
+ }
+}
+
+void CInputProcessorPointer::OnPointerButton(CSeat* seat, std::uint32_t serial, std::uint32_t time, std::uint32_t button, wayland::pointer_button_state state)
+{
+ if (m_pointerOnSurface)
+ {
+ int xbmcButton = WaylandToXbmcButton(button);
+ if (xbmcButton < 0)
+ {
+ // Button is unmapped
+ return;
+ }
+
+ bool pressed = (state == wayland::pointer_button_state::pressed);
+ SendMouseButton(xbmcButton, pressed);
+ }
+}
+
+void CInputProcessorPointer::OnPointerAxis(CSeat* seat, std::uint32_t time, wayland::pointer_axis axis, double value)
+{
+ if (m_pointerOnSurface)
+ {
+ // For axis events we only care about the vector direction
+ // and not the scalar magnitude. Every axis event callback
+ // generates one scroll button event for XBMC
+
+ // Negative is up
+ auto xbmcButton = static_cast<unsigned char> ((value < 0.0) ? XBMC_BUTTON_WHEELUP : XBMC_BUTTON_WHEELDOWN);
+ // Simulate a single click of the wheel-equivalent "button"
+ SendMouseButton(xbmcButton, true);
+ SendMouseButton(xbmcButton, false);
+ }
+}
+
+std::uint16_t CInputProcessorPointer::ConvertMouseCoordinate(double coord) const
+{
+ return static_cast<std::uint16_t> (std::round(coord * m_coordinateScale));
+}
+
+void CInputProcessorPointer::SetMousePosFromSurface(CPointGen<double> position)
+{
+ m_pointerPosition = {ConvertMouseCoordinate(position.x), ConvertMouseCoordinate(position.y)};
+}
+
+void CInputProcessorPointer::SendMouseMotion()
+{
+ XBMC_Event event{};
+ event.type = XBMC_MOUSEMOTION;
+ event.motion = {m_pointerPosition.x, m_pointerPosition.y};
+ m_handler.OnPointerEvent(event);
+}
+
+void CInputProcessorPointer::SendMouseButton(unsigned char button, bool pressed)
+{
+ XBMC_Event event{};
+ event.type = pressed ? XBMC_MOUSEBUTTONDOWN : XBMC_MOUSEBUTTONUP;
+ event.button = {button, m_pointerPosition.x, m_pointerPosition.y};
+ m_handler.OnPointerEvent(event);
+}
diff --git a/xbmc/windowing/wayland/InputProcessorPointer.h b/xbmc/windowing/wayland/InputProcessorPointer.h
new file mode 100644
index 0000000..ce44601
--- /dev/null
+++ b/xbmc/windowing/wayland/InputProcessorPointer.h
@@ -0,0 +1,76 @@
+/*
+ * 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 "Seat.h"
+#include "input/XBMC_keysym.h"
+#include "utils/Geometry.h"
+#include "windowing/XBMC_events.h"
+
+#include <cstdint>
+
+#include <wayland-client-protocol.hpp>
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace WAYLAND
+{
+
+class IInputHandlerPointer
+{
+public:
+ virtual void OnPointerEnter(std::uint32_t seatGlobalName, std::uint32_t serial) {}
+ virtual void OnPointerLeave() {}
+ virtual void OnPointerEvent(XBMC_Event& event) = 0;
+protected:
+ ~IInputHandlerPointer() = default;
+};
+
+class CInputProcessorPointer final : public IRawInputHandlerPointer
+{
+public:
+ CInputProcessorPointer(wayland::surface_t const& surface, IInputHandlerPointer& handler);
+ void SetCoordinateScale(std::int32_t scale) { m_coordinateScale = scale; }
+
+ void OnPointerEnter(CSeat* seat,
+ std::uint32_t serial,
+ const wayland::surface_t& surface,
+ double surfaceX,
+ double surfaceY) override;
+ void OnPointerLeave(CSeat* seat,
+ std::uint32_t serial,
+ const wayland::surface_t& surface) override;
+ void OnPointerMotion(CSeat* seat, std::uint32_t time, double surfaceX, double surfaceY) override;
+ void OnPointerButton(CSeat* seat, std::uint32_t serial, std::uint32_t time, std::uint32_t button, wayland::pointer_button_state state) override;
+ void OnPointerAxis(CSeat* seat, std::uint32_t time, wayland::pointer_axis axis, double value) override;
+
+private:
+ CInputProcessorPointer(CInputProcessorPointer const& other) = delete;
+ CInputProcessorPointer& operator=(CInputProcessorPointer const& other) = delete;
+
+ std::uint16_t ConvertMouseCoordinate(double coord) const;
+ void SetMousePosFromSurface(CPointGen<double> position);
+ void SendMouseMotion();
+ void SendMouseButton(unsigned char button, bool pressed);
+
+ wayland::surface_t m_surface;
+ IInputHandlerPointer& m_handler;
+
+ bool m_pointerOnSurface{};
+
+ // Pointer position in *scaled* coordinates
+ CPointGen<std::uint16_t> m_pointerPosition;
+ std::int32_t m_coordinateScale{1};
+};
+
+}
+}
+}
diff --git a/xbmc/windowing/wayland/InputProcessorTouch.cpp b/xbmc/windowing/wayland/InputProcessorTouch.cpp
new file mode 100644
index 0000000..a664e32
--- /dev/null
+++ b/xbmc/windowing/wayland/InputProcessorTouch.cpp
@@ -0,0 +1,130 @@
+/*
+ * 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 "InputProcessorTouch.h"
+
+#include "input/touch/generic/GenericTouchInputHandler.h"
+
+using namespace KODI::WINDOWING::WAYLAND;
+
+CInputProcessorTouch::CInputProcessorTouch(wayland::surface_t const& surface)
+: m_surface{surface}
+{
+}
+
+void CInputProcessorTouch::OnTouchDown(CSeat* seat,
+ std::uint32_t serial,
+ std::uint32_t time,
+ const wayland::surface_t& surface,
+ std::int32_t id,
+ double x,
+ double y)
+{
+ if (surface != m_surface)
+ {
+ return;
+ }
+
+ // Find free Kodi pointer number
+ int kodiPointer{-1};
+ // Not optimal, but irrelevant for the small number of iterations
+ for (int testPointer{0}; testPointer < CGenericTouchInputHandler::MAX_POINTERS; testPointer++)
+ {
+ if (std::all_of(m_touchPoints.cbegin(), m_touchPoints.cend(),
+ [=](decltype(m_touchPoints)::value_type const& pair)
+ {
+ return (pair.second.kodiPointerNumber != testPointer);
+ }))
+ {
+ kodiPointer = testPointer;
+ break;
+ }
+ }
+
+ if (kodiPointer != -1)
+ {
+ auto it = m_touchPoints.emplace(std::piecewise_construct, std::forward_as_tuple(id), std::forward_as_tuple(time, kodiPointer, x * m_coordinateScale, y * m_coordinateScale, 0.0f)).first;
+ SendTouchPointEvent(TouchInputDown, it->second);
+ }
+}
+
+void CInputProcessorTouch::OnTouchUp(CSeat* seat, std::uint32_t serial, std::uint32_t time, std::int32_t id)
+{
+ auto it = m_touchPoints.find(id);
+ if (it != m_touchPoints.end())
+ {
+ auto& point = it->second;
+ point.lastEventTime = time;
+ SendTouchPointEvent(TouchInputUp, point);
+ m_touchPoints.erase(it);
+ }
+}
+
+void CInputProcessorTouch::OnTouchMotion(CSeat* seat, std::uint32_t time, std::int32_t id, double x, double y)
+{
+ auto it = m_touchPoints.find(id);
+ if (it != m_touchPoints.end())
+ {
+ auto& point = it->second;
+ point.x = x * m_coordinateScale;
+ point.y = y * m_coordinateScale;
+ point.lastEventTime = time;
+ SendTouchPointEvent(TouchInputMove, point);
+ }
+}
+
+void CInputProcessorTouch::OnTouchCancel(CSeat* seat)
+{
+ AbortTouches();
+}
+
+void CInputProcessorTouch::OnTouchShape(CSeat* seat, std::int32_t id, double major, double minor)
+{
+ auto it = m_touchPoints.find(id);
+ if (it != m_touchPoints.end())
+ {
+ auto& point = it->second;
+ // Kodi only supports size without shape, so use average of both axes
+ point.size = ((major + minor) / 2.0) * m_coordinateScale;
+ UpdateTouchPoint(point);
+ }
+}
+
+CInputProcessorTouch::~CInputProcessorTouch() noexcept
+{
+ AbortTouches();
+}
+
+void CInputProcessorTouch::AbortTouches()
+{
+ // TouchInputAbort aborts for all pointers, so it does not matter which is specified
+ if (!m_touchPoints.empty())
+ {
+ SendTouchPointEvent(TouchInputAbort, m_touchPoints.begin()->second);
+ }
+ m_touchPoints.clear();
+}
+
+void CInputProcessorTouch::SendTouchPointEvent(TouchInput event, const TouchPoint& point)
+{
+ if (event == TouchInputMove)
+ {
+ for (auto const& point : m_touchPoints)
+ {
+ // Contrary to the docs, this must be called before HandleTouchInput or the
+ // position will not be updated and gesture detection will not work
+ UpdateTouchPoint(point.second);
+ }
+ }
+ CGenericTouchInputHandler::GetInstance().HandleTouchInput(event, point.x, point.y, point.lastEventTime * INT64_C(1000000), point.kodiPointerNumber, point.size);
+}
+
+void CInputProcessorTouch::UpdateTouchPoint(const TouchPoint& point)
+{
+ CGenericTouchInputHandler::GetInstance().UpdateTouchPointer(point.kodiPointerNumber, point.x, point.y, point.lastEventTime * INT64_C(1000000), point.size);
+}
diff --git a/xbmc/windowing/wayland/InputProcessorTouch.h b/xbmc/windowing/wayland/InputProcessorTouch.h
new file mode 100644
index 0000000..fa6f606
--- /dev/null
+++ b/xbmc/windowing/wayland/InputProcessorTouch.h
@@ -0,0 +1,82 @@
+/*
+ * 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 "Seat.h"
+#include "input/touch/ITouchInputHandler.h"
+
+#include <cstdint>
+#include <map>
+
+#include <wayland-client-protocol.hpp>
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace WAYLAND
+{
+
+/**
+ * Touch input processor
+ *
+ * Events go directly to \ref CGenericTouchInputHandler, so no callbacks here
+ */
+class CInputProcessorTouch final : public IRawInputHandlerTouch
+{
+public:
+ CInputProcessorTouch(wayland::surface_t const& surface);
+ ~CInputProcessorTouch() noexcept;
+ void SetCoordinateScale(std::int32_t scale) { m_coordinateScale = scale; }
+
+ void OnTouchDown(CSeat* seat,
+ std::uint32_t serial,
+ std::uint32_t time,
+ const wayland::surface_t& surface,
+ std::int32_t id,
+ double x,
+ double y) override;
+ void OnTouchUp(CSeat* seat, std::uint32_t serial, std::uint32_t time, std::int32_t id) override;
+ void OnTouchMotion(CSeat* seat, std::uint32_t time, std::int32_t id, double x, double y) override;
+ void OnTouchCancel(CSeat* seat) override;
+ void OnTouchShape(CSeat* seat, std::int32_t id, double major, double minor) override;
+
+private:
+ CInputProcessorTouch(CInputProcessorTouch const& other) = delete;
+ CInputProcessorTouch& operator=(CInputProcessorTouch const& other) = delete;
+
+ struct TouchPoint
+ {
+ std::uint32_t lastEventTime;
+ /// Pointer number passed to \ref ITouchInputHandler
+ std::int32_t kodiPointerNumber;
+ /**
+ * Last coordinates - needed for TouchInputUp events where Wayland does not
+ * send new coordinates but Kodi needs them anyway
+ */
+ float x, y, size;
+ TouchPoint(std::uint32_t initialEventTime, std::int32_t kodiPointerNumber, float x, float y, float size)
+ : lastEventTime{initialEventTime}, kodiPointerNumber{kodiPointerNumber}, x{x}, y{y}, size{size}
+ {}
+ };
+
+ void SendTouchPointEvent(TouchInput event, TouchPoint const& point);
+ void UpdateTouchPoint(TouchPoint const& point);
+ void AbortTouches();
+
+ wayland::surface_t m_surface;
+ std::int32_t m_coordinateScale{1};
+
+ /// Map of wl_touch point id to data
+ std::map<std::int32_t, TouchPoint> m_touchPoints;
+};
+
+}
+}
+}
diff --git a/xbmc/windowing/wayland/OSScreenSaverIdleInhibitUnstableV1.cpp b/xbmc/windowing/wayland/OSScreenSaverIdleInhibitUnstableV1.cpp
new file mode 100644
index 0000000..32dbbc6
--- /dev/null
+++ b/xbmc/windowing/wayland/OSScreenSaverIdleInhibitUnstableV1.cpp
@@ -0,0 +1,52 @@
+/*
+ * 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 "OSScreenSaverIdleInhibitUnstableV1.h"
+
+#include "Registry.h"
+
+#include <cassert>
+
+using namespace KODI::WINDOWING::WAYLAND;
+
+COSScreenSaverIdleInhibitUnstableV1* COSScreenSaverIdleInhibitUnstableV1::TryCreate(CConnection& connection, wayland::surface_t const& inhibitSurface)
+{
+ wayland::zwp_idle_inhibit_manager_v1_t manager;
+ CRegistry registry{connection};
+ registry.RequestSingleton(manager, 1, 1, false);
+ registry.Bind();
+
+ if (manager)
+ {
+ return new COSScreenSaverIdleInhibitUnstableV1(manager, inhibitSurface);
+ }
+ else
+ {
+ return nullptr;
+ }
+}
+
+COSScreenSaverIdleInhibitUnstableV1::COSScreenSaverIdleInhibitUnstableV1(const wayland::zwp_idle_inhibit_manager_v1_t& manager, const wayland::surface_t& inhibitSurface)
+: m_manager{manager}, m_surface{inhibitSurface}
+{
+ assert(m_manager);
+ assert(m_surface);
+}
+
+void COSScreenSaverIdleInhibitUnstableV1::Inhibit()
+{
+ if (!m_inhibitor)
+ {
+ m_inhibitor = m_manager.create_inhibitor(m_surface);
+ }
+}
+
+void COSScreenSaverIdleInhibitUnstableV1::Uninhibit()
+{
+ m_inhibitor.proxy_release();
+}
diff --git a/xbmc/windowing/wayland/OSScreenSaverIdleInhibitUnstableV1.h b/xbmc/windowing/wayland/OSScreenSaverIdleInhibitUnstableV1.h
new file mode 100644
index 0000000..0303d60
--- /dev/null
+++ b/xbmc/windowing/wayland/OSScreenSaverIdleInhibitUnstableV1.h
@@ -0,0 +1,39 @@
+/*
+ * 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 "Connection.h"
+
+#include <wayland-extra-protocols.hpp>
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace WAYLAND
+{
+
+class COSScreenSaverIdleInhibitUnstableV1 : public IOSScreenSaver
+{
+public:
+ COSScreenSaverIdleInhibitUnstableV1(wayland::zwp_idle_inhibit_manager_v1_t const& manager, wayland::surface_t const& inhibitSurface);
+ static COSScreenSaverIdleInhibitUnstableV1* TryCreate(CConnection& connection, wayland::surface_t const& inhibitSurface);
+ void Inhibit() override;
+ void Uninhibit() override;
+
+private:
+ wayland::zwp_idle_inhibit_manager_v1_t m_manager;
+ wayland::zwp_idle_inhibitor_v1_t m_inhibitor;
+ wayland::surface_t m_surface;
+};
+
+}
+}
+}
diff --git a/xbmc/windowing/wayland/OptionalsReg.cpp b/xbmc/windowing/wayland/OptionalsReg.cpp
new file mode 100644
index 0000000..5b456eb
--- /dev/null
+++ b/xbmc/windowing/wayland/OptionalsReg.cpp
@@ -0,0 +1,136 @@
+/*
+ * 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) && defined(HAS_EGL)
+#include <va/va_wayland.h>
+#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
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace WAYLAND
+{
+
+class CVaapiProxy : public VAAPI::IVaapiWinSystem
+{
+public:
+ CVaapiProxy() = default;
+ virtual ~CVaapiProxy() = default;
+ VADisplay GetVADisplay() override { return vaGetDisplayWl(dpy); };
+ void *GetEGLDisplay() override { return eglDisplay; };
+
+ wl_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<wl_display*>(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 = vaGetDisplayWl(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 = vaGetDisplayWl(winSystem->dpy);
+ CRendererVAAPIGLES::Register(winSystem, vaDpy, eglDpy, general, deepColor);
+}
+#endif
+
+} // namespace WAYLAND
+} // namespace WINDOWING
+} // namespace KODI
+
+#else
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace WAYLAND
+{
+
+class CVaapiProxy
+{
+};
+
+CVaapiProxy* VaapiProxyCreate()
+{
+ return nullptr;
+}
+
+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
+
+} // namespace WAYLAND
+} // namespace WINDOWING
+} // namespace KODI
+
+#endif
+
diff --git a/xbmc/windowing/wayland/OptionalsReg.h b/xbmc/windowing/wayland/OptionalsReg.h
new file mode 100644
index 0000000..293c9c3
--- /dev/null
+++ b/xbmc/windowing/wayland/OptionalsReg.h
@@ -0,0 +1,37 @@
+/*
+ * 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
+
+
+//-----------------------------------------------------------------------------
+// VAAPI
+//-----------------------------------------------------------------------------
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace WAYLAND
+{
+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
+
+} // namespace WAYLAND
+} // namespace WINDOWING
+} // namespace KODI
diff --git a/xbmc/windowing/wayland/Output.cpp b/xbmc/windowing/wayland/Output.cpp
new file mode 100644
index 0000000..5dd840a
--- /dev/null
+++ b/xbmc/windowing/wayland/Output.cpp
@@ -0,0 +1,145 @@
+/*
+ * 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 "Output.h"
+
+#include <cassert>
+#include <cmath>
+#include <mutex>
+#include <set>
+#include <stdexcept>
+#include <utility>
+
+using namespace KODI::WINDOWING::WAYLAND;
+
+COutput::COutput(std::uint32_t globalName,
+ wayland::output_t const& output,
+ std::function<void()> doneHandler)
+ : m_globalName{globalName}, m_output{output}, m_doneHandler{std::move(doneHandler)}
+{
+ assert(m_output);
+
+ m_output.on_geometry() = [this](std::int32_t x, std::int32_t y, std::int32_t physWidth,
+ std::int32_t physHeight, wayland::output_subpixel,
+ std::string const& make, std::string const& model,
+ const wayland::output_transform&)
+ {
+ std::unique_lock<CCriticalSection> lock(m_geometryCriticalSection);
+ m_position = {x, y};
+ // Some monitors report invalid (negative) values that would cause an exception
+ // with CSizeInt
+ if (physWidth < 0 || physHeight < 0)
+ m_physicalSize = {};
+ else
+ m_physicalSize = {physWidth, physHeight};
+ m_make = make;
+ m_model = model;
+ };
+ m_output.on_mode() = [this](const wayland::output_mode& flags, std::int32_t width,
+ std::int32_t height, std::int32_t refresh) {
+ // std::set.emplace returns pair of iterator to the (possibly) inserted
+ // element and boolean information whether the element was actually added
+ // which we do not need
+ auto modeIterator = m_modes.emplace(CSizeInt{width, height}, refresh).first;
+ std::unique_lock<CCriticalSection> lock(m_iteratorCriticalSection);
+ // Remember current and preferred mode
+ // Current mode is the last one that was sent with current flag set
+ if (flags & wayland::output_mode::current)
+ {
+ m_currentMode = modeIterator;
+ }
+ if (flags & wayland::output_mode::preferred)
+ {
+ m_preferredMode = modeIterator;
+ }
+ };
+ m_output.on_scale() = [this](std::int32_t scale)
+ {
+ m_scale = scale;
+ };
+
+ m_output.on_done() = [this]()
+ {
+ m_doneHandler();
+ };
+}
+
+COutput::~COutput() noexcept
+{
+ // Reset event handlers - someone might still hold a reference to the output_t,
+ // causing events to be dispatched. They should not go to a deleted class.
+ m_output.on_geometry() = nullptr;
+ m_output.on_mode() = nullptr;
+ m_output.on_done() = nullptr;
+ m_output.on_scale() = nullptr;
+}
+
+const COutput::Mode& COutput::GetCurrentMode() const
+{
+ std::unique_lock<CCriticalSection> lock(m_iteratorCriticalSection);
+ if (m_currentMode == m_modes.end())
+ {
+ throw std::runtime_error("Current mode not set");
+ }
+ return *m_currentMode;
+}
+
+const COutput::Mode& COutput::GetPreferredMode() const
+{
+ std::unique_lock<CCriticalSection> lock(m_iteratorCriticalSection);
+ if (m_preferredMode == m_modes.end())
+ {
+ throw std::runtime_error("Preferred mode not set");
+ }
+ return *m_preferredMode;
+}
+
+float COutput::GetPixelRatioForMode(const Mode& mode) const
+{
+ std::unique_lock<CCriticalSection> lock(m_geometryCriticalSection);
+ if (m_physicalSize.IsZero() || mode.size.IsZero())
+ {
+ return 1.0f;
+ }
+ else
+ {
+ return (
+ (static_cast<float> (m_physicalSize.Width()) / static_cast<float> (mode.size.Width()))
+ /
+ (static_cast<float> (m_physicalSize.Height()) / static_cast<float> (mode.size.Height()))
+ );
+ }
+}
+
+float COutput::GetDpiForMode(const Mode& mode) const
+{
+ std::unique_lock<CCriticalSection> lock(m_geometryCriticalSection);
+ if (m_physicalSize.IsZero())
+ {
+ // We really have no idea, so use a "sane" default
+ return 96.0;
+ }
+ else
+ {
+ constexpr float INCH_MM_RATIO{25.4f};
+
+ float diagonalPixels = std::sqrt(mode.size.Width() * mode.size.Width() + mode.size.Height() * mode.size.Height());
+ // physicalWidth/physicalHeight is in millimeters
+ float diagonalInches =
+ std::sqrt(static_cast<float>(m_physicalSize.Width() * m_physicalSize.Width() +
+ m_physicalSize.Height() * m_physicalSize.Height())) /
+ INCH_MM_RATIO;
+
+ return diagonalPixels / diagonalInches;
+ }
+}
+
+float COutput::GetCurrentDpi() const
+{
+ return GetDpiForMode(GetCurrentMode());
+}
diff --git a/xbmc/windowing/wayland/Output.h b/xbmc/windowing/wayland/Output.h
new file mode 100644
index 0000000..2408a6f
--- /dev/null
+++ b/xbmc/windowing/wayland/Output.h
@@ -0,0 +1,152 @@
+/*
+ * 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 "threads/CriticalSection.h"
+#include "utils/Geometry.h"
+
+#include <atomic>
+#include <cstdint>
+#include <mutex>
+#include <set>
+#include <tuple>
+
+#include <wayland-client-protocol.hpp>
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace WAYLAND
+{
+
+/**
+ * wl_output handler that collects information from the compositor and then
+ * passes it on when everything is available
+ */
+class COutput
+{
+public:
+ COutput(std::uint32_t globalName, wayland::output_t const & output, std::function<void()> doneHandler);
+ ~COutput() noexcept;
+
+ wayland::output_t const& GetWaylandOutput() const
+ {
+ return m_output;
+ }
+ std::uint32_t GetGlobalName() const
+ {
+ return m_globalName;
+ }
+ /**
+ * Get output position in compositor coordinate space
+ * \return (x, y) tuple of output position
+ */
+ CPointInt GetPosition() const
+ {
+ std::unique_lock<CCriticalSection> lock(m_geometryCriticalSection);
+ return m_position;
+ }
+ /**
+ * Get output physical size in millimeters
+ * \return (width, height) tuple of output physical size in millimeters
+ */
+ CSizeInt GetPhysicalSize() const
+ {
+ std::unique_lock<CCriticalSection> lock(m_geometryCriticalSection);
+ return m_physicalSize;
+ }
+ std::string const& GetMake() const
+ {
+ std::unique_lock<CCriticalSection> lock(m_geometryCriticalSection);
+ return m_make;
+ }
+ std::string const& GetModel() const
+ {
+ std::unique_lock<CCriticalSection> lock(m_geometryCriticalSection);
+ return m_model;
+ }
+ std::int32_t GetScale() const
+ {
+ return m_scale;
+ }
+
+ struct Mode
+ {
+ CSizeInt size;
+ std::int32_t refreshMilliHz;
+ Mode(CSizeInt size, std::int32_t refreshMilliHz)
+ : size{size}, refreshMilliHz(refreshMilliHz)
+ {}
+
+ float GetRefreshInHz() const
+ {
+ return refreshMilliHz / 1000.0f;
+ }
+
+ std::tuple<std::int32_t, std::int32_t, std::int32_t> AsTuple() const
+ {
+ return std::make_tuple(size.Width(), size.Height(), refreshMilliHz);
+ }
+
+ // Comparison operator needed for std::set
+ bool operator<(const Mode& right) const
+ {
+ return AsTuple() < right.AsTuple();
+ }
+
+ bool operator==(const Mode& right) const
+ {
+ return AsTuple() == right.AsTuple();
+ }
+
+ bool operator!=(const Mode& right) const
+ {
+ return !(*this == right);
+ }
+ };
+
+ std::set<Mode> const& GetModes() const
+ {
+ return m_modes;
+ }
+ Mode const& GetCurrentMode() const;
+ Mode const& GetPreferredMode() const;
+
+ float GetPixelRatioForMode(Mode const& mode) const;
+ float GetDpiForMode(Mode const& mode) const;
+ float GetCurrentDpi() const;
+
+private:
+ COutput(COutput const& other) = delete;
+ COutput& operator=(COutput const& other) = delete;
+
+ std::uint32_t m_globalName;
+ wayland::output_t m_output;
+ std::function<void()> m_doneHandler;
+
+ mutable CCriticalSection m_geometryCriticalSection;
+ mutable CCriticalSection m_iteratorCriticalSection;
+
+ CPointInt m_position;
+ CSizeInt m_physicalSize;
+ std::string m_make, m_model;
+ std::atomic<std::int32_t> m_scale{1}; // default scale of 1 if no wl_output::scale is sent
+
+ std::set<Mode> m_modes;
+ // For std::set, insertion never invalidates existing iterators, and modes are
+ // never removed, so the usage of iterators is safe
+ std::set<Mode>::iterator m_currentMode{m_modes.end()};
+ std::set<Mode>::iterator m_preferredMode{m_modes.end()};
+};
+
+
+}
+}
+}
diff --git a/xbmc/windowing/wayland/Registry.cpp b/xbmc/windowing/wayland/Registry.cpp
new file mode 100644
index 0000000..68cd745
--- /dev/null
+++ b/xbmc/windowing/wayland/Registry.cpp
@@ -0,0 +1,164 @@
+/*
+ * 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 "Registry.h"
+
+#include "WinEventsWayland.h"
+#include "utils/log.h"
+
+#include <wayland-client-protocol.h>
+
+using namespace KODI::WINDOWING::WAYLAND;
+
+namespace
+{
+
+void TryBind(wayland::registry_t& registry, wayland::proxy_t& target, std::uint32_t name, std::string const& interface, std::uint32_t minVersion, std::uint32_t maxVersion, std::uint32_t offeredVersion)
+{
+ if (offeredVersion < minVersion)
+ {
+ CLog::Log(LOGWARNING,
+ "Not binding Wayland protocol {} because server has only version {} (we need at "
+ "least version {})",
+ interface, offeredVersion, minVersion);
+ }
+ else
+ {
+ // Binding below the offered version is OK
+ auto bindVersion = std::min(maxVersion, offeredVersion);
+ CLog::Log(LOGDEBUG, "Binding Wayland protocol {} version {} (server has version {})", interface,
+ bindVersion, offeredVersion);
+ registry.bind(name, target, bindVersion);
+ }
+}
+
+}
+
+CRegistry::CRegistry(CConnection& connection)
+: m_connection{connection}
+{
+}
+
+void CRegistry::RequestSingletonInternal(wayland::proxy_t& target, std::string const& interfaceName, std::uint32_t minVersion, std::uint32_t maxVersion, bool required)
+{
+ if (m_registry)
+ {
+ throw std::logic_error("Cannot request more binds from registry after binding has started");
+ }
+ m_singletonBinds.emplace(std::piecewise_construct, std::forward_as_tuple(interfaceName), std::forward_as_tuple(target, minVersion, maxVersion, required));
+}
+
+void CRegistry::RequestInternal(std::function<wayland::proxy_t()> constructor, const std::string& interfaceName, std::uint32_t minVersion, std::uint32_t maxVersion, AddHandler addHandler, RemoveHandler removeHandler)
+{
+ if (m_registry)
+ {
+ throw std::logic_error("Cannot request more binds from registry after binding has started");
+ }
+ m_binds.emplace(std::piecewise_construct, std::forward_as_tuple(interfaceName), std::forward_as_tuple(constructor, minVersion, maxVersion, addHandler, removeHandler));
+}
+
+void CRegistry::Bind()
+{
+ if (m_registry)
+ {
+ throw std::logic_error("Cannot start binding on registry twice");
+ }
+
+ // We want to block in this function until we have received the global interfaces
+ // from the compositor - no matter whether the global event pump is running
+ // or not.
+ // If it is running, we have to take special precautions not to drop events between
+ // the creation of the registry and attaching event handlers, so we create
+ // an extra queue and use that to dispatch the singleton globals. Then
+ // we switch back to the global queue for further dispatch of interfaces
+ // added/removed dynamically.
+
+ auto registryRoundtripQueue = m_connection.GetDisplay().create_queue();
+
+ auto displayProxy = m_connection.GetDisplay().proxy_create_wrapper();
+ displayProxy.set_queue(registryRoundtripQueue);
+
+ m_registry = displayProxy.get_registry();
+
+ m_registry.on_global() = [this](std::uint32_t name, const std::string& interface,
+ std::uint32_t version) {
+ {
+ auto it = m_singletonBinds.find(interface);
+ if (it != m_singletonBinds.end())
+ {
+ auto& bind = it->second;
+ auto registryProxy = m_registry.proxy_create_wrapper();
+ // Events on the bound global should always go to the main queue
+ registryProxy.set_queue(wayland::event_queue_t());
+ TryBind(registryProxy, bind.target, name, interface, bind.minVersion, bind.maxVersion, version);
+ return;
+ }
+ }
+
+ {
+ auto it = m_binds.find(interface);
+ if (it != m_binds.end())
+ {
+ auto& bind = it->second;
+ wayland::proxy_t target{bind.constructor()};
+ auto registryProxy = m_registry.proxy_create_wrapper();
+ // Events on the bound global should always go to the main queue
+ registryProxy.set_queue(wayland::event_queue_t());
+ TryBind(registryProxy, target, name, interface, bind.minVersion, bind.maxVersion, version);
+ if (target)
+ {
+ m_boundNames.emplace(name, bind);
+ bind.addHandler(name, std::move(target));
+ }
+ return;
+ }
+ }
+ };
+
+ m_registry.on_global_remove() = [this] (std::uint32_t name)
+ {
+ auto it = m_boundNames.find(name);
+ if (it != m_boundNames.end())
+ {
+ it->second.get().removeHandler(name);
+ m_boundNames.erase(it);
+ }
+ };
+
+ CLog::Log(LOGDEBUG, "Wayland connection: Waiting for global interfaces");
+ m_connection.GetDisplay().roundtrip_queue(registryRoundtripQueue);
+ CLog::Log(LOGDEBUG, "Wayland connection: Roundtrip complete");
+
+ CheckRequired();
+
+ // Now switch it to the global queue for further runtime binds
+ m_registry.set_queue(wayland::event_queue_t());
+ // Roundtrip extra queue one last time in case something got queued up there.
+ // Do it on the event thread so it does not race/run in parallel with the
+ // dispatch of newly arrived registry messages in the default queue.
+ CWinEventsWayland::RoundtripQueue(registryRoundtripQueue);
+}
+
+void CRegistry::UnbindSingletons()
+{
+ for (auto& bind : m_singletonBinds)
+ {
+ bind.second.target.proxy_release();
+ }
+}
+
+void CRegistry::CheckRequired()
+{
+ for (auto const& bind : m_singletonBinds)
+ {
+ if (bind.second.required && !bind.second.target)
+ {
+ throw std::runtime_error(std::string("Missing required ") + bind.first + " protocol");
+ }
+ }
+} \ No newline at end of file
diff --git a/xbmc/windowing/wayland/Registry.h b/xbmc/windowing/wayland/Registry.h
new file mode 100644
index 0000000..f440782
--- /dev/null
+++ b/xbmc/windowing/wayland/Registry.h
@@ -0,0 +1,162 @@
+/*
+ * 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 "Connection.h"
+
+#include <cstdint>
+#include <functional>
+#include <map>
+#include <utility>
+
+#include <wayland-client-protocol.hpp>
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace WAYLAND
+{
+
+/**
+ * Handle Wayland globals
+ *
+ * Request singletons (bound once) with \ref RequestSingleton, non-singletons
+ * such as wl_output with \ref Request, then call \ref Bind once.
+ *
+ * Make sure to destroy all registries before destroying the \ref CConnection.
+ */
+class CRegistry
+{
+public:
+ explicit CRegistry(CConnection& connection);
+
+ /**
+ * Request a static singleton global to be bound to a proxy
+ *
+ * You should only use this if the singleton is announced at registry bind time
+ * (not dynamically) and you do not need to catch events that are sent immediately
+ * in response to the bind. Use \ref Request in that case, even if there is
+ * ever only one instance of the object at maximum.
+ *
+ * Cannot be called after \ref Bind has been called.
+ *
+ * \param target target of waylandpp proxy type
+ * \param minVersion minimum version to bind
+ * \param maxVersion maximum version to bind
+ * \param required whether to throw an exception when the bind cannot be satisfied
+ * by the compositor - exception is thrown in \ref Bind
+ */
+ template<typename T>
+ void RequestSingleton(T& target, std::uint32_t minVersion, std::uint32_t maxVersion, bool required = true)
+ {
+ RequestSingletonInternal(target, T::interface_name, minVersion, maxVersion, required);
+ }
+ using AddHandler = std::function<void(std::uint32_t /* name */, wayland::proxy_t&& /* object */)>;
+ using RemoveHandler = std::function<void(std::uint32_t) /* name */>;
+ /**
+ * Request a callback when a dynamic global appears or disappears
+ *
+ * The callbacks may be called from the thread that calls \ref Bind or the
+ * global Wayland message pump thread during \ref Bind (but never at the same
+ * time) and only from the global thread after \ref Bind returns.
+ *
+ * Events that occur immediately upon binding are only delivered reliably
+ * if \ref Bind is called from the Wayland message pump thread.
+ *
+ * Cannot be called after \ref Bind has been called.
+ *
+ * \param minVersion minimum version to bind
+ * \param maxVersion maximum version to bind
+ * \param addHandler function that is called when a global of the requested
+ * type is added
+ * \param removeHandler function that is called when a global of the requested
+ * type is removed
+ */
+ template<typename T>
+ void Request(std::uint32_t minVersion,
+ std::uint32_t maxVersion,
+ const AddHandler& addHandler,
+ const RemoveHandler& removeHandler)
+ {
+ RequestInternal([]{ return T(); }, T::interface_name, minVersion, maxVersion, addHandler, removeHandler);
+ }
+
+ /**
+ * Create a registry object at the compositor and do an roundtrip to bind
+ * objects
+ *
+ * This function blocks until the initial roundtrip is complete. All statically
+ * requested singletons that were available will be bound then.
+ *
+ * Neither statically nor dynamically requested proxies will be bound before this
+ * function is called.
+ *
+ * May throw std::runtime_error if a required global was not found.
+ *
+ * Can only be called once for the same \ref CRegistry object.
+ */
+ void Bind();
+ /**
+ * Unbind all singletons requested with \ref RequestSingleton
+ */
+ void UnbindSingletons();
+
+private:
+ CRegistry(CRegistry const& other) = delete;
+ CRegistry& operator=(CRegistry const& other) = delete;
+
+ void RequestSingletonInternal(wayland::proxy_t& target, std::string const& interfaceName, std::uint32_t minVersion, std::uint32_t maxVersion, bool required);
+ void RequestInternal(std::function<wayland::proxy_t()> constructor, std::string const& interfaceName, std::uint32_t minVersion, std::uint32_t maxVersion, AddHandler addHandler, RemoveHandler removeHandler);
+ void CheckRequired();
+
+ CConnection& m_connection;
+ wayland::registry_t m_registry;
+
+ struct SingletonBindInfo
+ {
+ wayland::proxy_t& target;
+ // Throw exception if trying to bind below this version and required
+ std::uint32_t minVersion;
+ // Limit bind version to the minimum of this and compositor version
+ std::uint32_t maxVersion;
+ bool required;
+ SingletonBindInfo(wayland::proxy_t& target, std::uint32_t minVersion, std::uint32_t maxVersion, bool required)
+ : target{target}, minVersion{minVersion}, maxVersion{maxVersion}, required{required}
+ {}
+ };
+ std::map<std::string, SingletonBindInfo> m_singletonBinds;
+
+ struct BindInfo
+ {
+ std::function<wayland::proxy_t()> constructor;
+ std::uint32_t minVersion;
+ std::uint32_t maxVersion;
+ AddHandler addHandler;
+ RemoveHandler removeHandler;
+ BindInfo(std::function<wayland::proxy_t()> constructor,
+ std::uint32_t minVersion,
+ std::uint32_t maxVersion,
+ AddHandler addHandler,
+ RemoveHandler removeHandler)
+ : constructor{std::move(constructor)},
+ minVersion{minVersion},
+ maxVersion{maxVersion},
+ addHandler{std::move(addHandler)},
+ removeHandler{std::move(removeHandler)}
+ {}
+ };
+ std::map<std::string, BindInfo> m_binds;
+
+ std::map<std::uint32_t, std::reference_wrapper<BindInfo>> m_boundNames;
+};
+
+}
+}
+}
diff --git a/xbmc/windowing/wayland/Seat.cpp b/xbmc/windowing/wayland/Seat.cpp
new file mode 100644
index 0000000..56709b3
--- /dev/null
+++ b/xbmc/windowing/wayland/Seat.cpp
@@ -0,0 +1,275 @@
+/*
+ * 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 "Seat.h"
+
+#include "utils/log.h"
+
+#include "platform/posix/utils/FileHandle.h"
+#include "platform/posix/utils/Mmap.h"
+
+#include <cassert>
+#include <utility>
+
+#include <unistd.h>
+
+using namespace KODI::WINDOWING::WAYLAND;
+using namespace std::placeholders;
+
+namespace
+{
+
+/**
+ * Handle change of availability of a wl_seat input capability
+ *
+ * This checks whether the capability is currently available with the wl_seat
+ * and whether it was bound to a protocol object. If there is a mismatch between
+ * these two, the protocol proxy is released if a capability was removed or bound
+ * if a capability was added.
+ *
+ * \param caps new capabilities
+ * \param cap capability to check for
+ * \param seatName human-readable name of the seat for log messages
+ * \param capName human-readable name of the capability for log messages
+ * \param proxy proxy object that should be filled with a new instance or reset
+ * \param instanceProvider function that functions as factory for the Wayland
+ * protocol instance if the capability has been added
+ */
+template<typename T, typename InstanceProviderT>
+bool HandleCapabilityChange(const wayland::seat_capability& caps,
+ const wayland::seat_capability& cap,
+ std::string const& seatName,
+ std::string const& capName,
+ T& proxy,
+ InstanceProviderT instanceProvider)
+{
+ bool hasCapability = caps & cap;
+
+ if ((!!proxy) != hasCapability)
+ {
+ // Capability changed
+
+ if (hasCapability)
+ {
+ // The capability was added
+ CLog::Log(LOGDEBUG, "Wayland seat {} gained capability {}", seatName, capName);
+ proxy = instanceProvider();
+ return true;
+ }
+ else
+ {
+ // The capability was removed
+ CLog::Log(LOGDEBUG, "Wayland seat {} lost capability {}", seatName, capName);
+ proxy.proxy_release();
+ }
+ }
+
+ return false;
+}
+
+}
+
+CSeat::CSeat(std::uint32_t globalName, wayland::seat_t const& seat, CConnection& connection)
+: m_globalName{globalName}, m_seat{seat}, m_selection{connection, seat}
+{
+ m_seat.on_name() = [this](std::string name) { m_name = std::move(name); };
+ m_seat.on_capabilities() = std::bind(&CSeat::HandleOnCapabilities, this, std::placeholders::_1);
+}
+
+CSeat::~CSeat() noexcept = default;
+
+void CSeat::AddRawInputHandlerKeyboard(KODI::WINDOWING::WAYLAND::IRawInputHandlerKeyboard *rawKeyboardHandler)
+{
+ assert(rawKeyboardHandler);
+ m_rawKeyboardHandlers.emplace(rawKeyboardHandler);
+}
+
+void CSeat::RemoveRawInputHandlerKeyboard(KODI::WINDOWING::WAYLAND::IRawInputHandlerKeyboard *rawKeyboardHandler)
+{
+ m_rawKeyboardHandlers.erase(rawKeyboardHandler);
+}
+
+void CSeat::AddRawInputHandlerPointer(IRawInputHandlerPointer* rawPointerHandler)
+{
+ assert(rawPointerHandler);
+ m_rawPointerHandlers.emplace(rawPointerHandler);
+}
+
+void CSeat::RemoveRawInputHandlerPointer(KODI::WINDOWING::WAYLAND::IRawInputHandlerPointer *rawPointerHandler)
+{
+ m_rawPointerHandlers.erase(rawPointerHandler);
+}
+
+void CSeat::AddRawInputHandlerTouch(IRawInputHandlerTouch* rawTouchHandler)
+{
+ assert(rawTouchHandler);
+ m_rawTouchHandlers.emplace(rawTouchHandler);
+}
+
+void CSeat::RemoveRawInputHandlerTouch(KODI::WINDOWING::WAYLAND::IRawInputHandlerTouch *rawTouchHandler)
+{
+ m_rawTouchHandlers.erase(rawTouchHandler);
+}
+
+void CSeat::HandleOnCapabilities(const wayland::seat_capability& caps)
+{
+ if (HandleCapabilityChange(caps, wayland::seat_capability::keyboard, GetName(), "keyboard", m_keyboard, std::bind(&wayland::seat_t::get_keyboard, m_seat)))
+ {
+ HandleKeyboardCapability();
+ }
+ if (HandleCapabilityChange(caps, wayland::seat_capability::pointer, GetName(), "pointer", m_pointer, std::bind(&wayland::seat_t::get_pointer, m_seat)))
+ {
+ HandlePointerCapability();
+ }
+ if (HandleCapabilityChange(caps, wayland::seat_capability::touch, GetName(), "touch", m_touch, std::bind(&wayland::seat_t::get_touch, m_seat)))
+ {
+ HandleTouchCapability();
+ }
+}
+
+void CSeat::SetCursor(std::uint32_t serial, wayland::surface_t const &surface, std::int32_t hotspotX, std::int32_t hotspotY)
+{
+ if (m_pointer)
+ {
+ m_pointer.set_cursor(serial, surface, hotspotX, hotspotY);
+ }
+}
+
+void CSeat::HandleKeyboardCapability()
+{
+ m_keyboard.on_keymap() = [this](wayland::keyboard_keymap_format format, int fd, std::uint32_t size)
+ {
+ KODI::UTILS::POSIX::CFileHandle fdGuard{fd};
+ KODI::UTILS::POSIX::CMmap mmap{nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0};
+ std::string keymap{static_cast<const char*> (mmap.Data()), size};
+ for (auto handler : m_rawKeyboardHandlers)
+ {
+ handler->OnKeyboardKeymap(this, format, keymap);
+ }
+ };
+ m_keyboard.on_enter() = [this](std::uint32_t serial, const wayland::surface_t& surface,
+ const wayland::array_t& keys) {
+ for (auto handler : m_rawKeyboardHandlers)
+ {
+ handler->OnKeyboardEnter(this, serial, surface, keys);
+ }
+ };
+ m_keyboard.on_leave() = [this](std::uint32_t serial, const wayland::surface_t& surface) {
+ for (auto handler : m_rawKeyboardHandlers)
+ {
+ handler->OnKeyboardLeave(this, serial, surface);
+ }
+ };
+ m_keyboard.on_key() = [this](std::uint32_t serial, std::uint32_t time, std::uint32_t key, wayland::keyboard_key_state state)
+ {
+ for (auto handler : m_rawKeyboardHandlers)
+ {
+ handler->OnKeyboardKey(this, serial, time, key, state);
+ }
+ };
+ m_keyboard.on_modifiers() = [this](std::uint32_t serial, std::uint32_t modsDepressed, std::uint32_t modsLatched, std::uint32_t modsLocked, std::uint32_t group)
+ {
+ for (auto handler : m_rawKeyboardHandlers)
+ {
+ handler->OnKeyboardModifiers(this, serial, modsDepressed, modsLatched, modsLocked, group);
+ }
+ };
+ m_keyboard.on_repeat_info() = [this](std::int32_t rate, std::int32_t delay)
+ {
+ for (auto handler : m_rawKeyboardHandlers)
+ {
+ handler->OnKeyboardRepeatInfo(this, rate, delay);
+ }
+ };
+}
+
+
+void CSeat::HandlePointerCapability()
+{
+ m_pointer.on_enter() = [this](std::uint32_t serial, const wayland::surface_t& surface,
+ double surfaceX, double surfaceY) {
+ for (auto handler : m_rawPointerHandlers)
+ {
+ handler->OnPointerEnter(this, serial, surface, surfaceX, surfaceY);
+ }
+ };
+ m_pointer.on_leave() = [this](std::uint32_t serial, const wayland::surface_t& surface) {
+ for (auto handler : m_rawPointerHandlers)
+ {
+ handler->OnPointerLeave(this, serial, surface);
+ }
+ };
+ m_pointer.on_motion() = [this](std::uint32_t time, double surfaceX, double surfaceY)
+ {
+ for (auto handler : m_rawPointerHandlers)
+ {
+ handler->OnPointerMotion(this, time, surfaceX, surfaceY);
+ }
+ };
+ m_pointer.on_button() = [this](std::uint32_t serial, std::uint32_t time, std::uint32_t button, wayland::pointer_button_state state)
+ {
+ for (auto handler : m_rawPointerHandlers)
+ {
+ handler->OnPointerButton(this, serial, time, button, state);
+ }
+ };
+ m_pointer.on_axis() = [this](std::uint32_t time, wayland::pointer_axis axis, double value)
+ {
+ for (auto handler : m_rawPointerHandlers)
+ {
+ handler->OnPointerAxis(this, time, axis, value);
+ }
+ };
+ // Wayland groups pointer events, but right now there is no benefit in
+ // treating them in groups. The main use case for doing so seems to be
+ // multi-axis (i.e. diagonal) scrolling, but we do not support this anyway.
+ /*m_pointer.on_frame() = [this]()
+ {
+
+ };*/
+}
+
+void CSeat::HandleTouchCapability()
+{
+ m_touch.on_down() = [this](std::uint32_t serial, std::uint32_t time,
+ const wayland::surface_t& surface, std::int32_t id, double x,
+ double y) {
+ for (auto handler : m_rawTouchHandlers)
+ {
+ handler->OnTouchDown(this, serial, time, surface, id, x, y);
+ }
+ };
+ m_touch.on_up() = [this](std::uint32_t serial, std::uint32_t time, std::int32_t id)
+ {
+ for (auto handler : m_rawTouchHandlers)
+ {
+ handler->OnTouchUp(this, serial, time, id);
+ }
+ };
+ m_touch.on_motion() = [this](std::uint32_t time, std::int32_t id, double x, double y)
+ {
+ for (auto handler : m_rawTouchHandlers)
+ {
+ handler->OnTouchMotion(this, time, id, x, y);
+ }
+ };
+ m_touch.on_cancel() = [this]()
+ {
+ for (auto handler : m_rawTouchHandlers)
+ {
+ handler->OnTouchCancel(this);
+ }
+ };
+ m_touch.on_shape() = [this](std::int32_t id, double major, double minor)
+ {
+ for (auto handler : m_rawTouchHandlers)
+ {
+ handler->OnTouchShape(this, id, major, minor);
+ }
+ };
+}
diff --git a/xbmc/windowing/wayland/Seat.h b/xbmc/windowing/wayland/Seat.h
new file mode 100644
index 0000000..095f18d
--- /dev/null
+++ b/xbmc/windowing/wayland/Seat.h
@@ -0,0 +1,203 @@
+/*
+ * 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 "SeatSelection.h"
+
+#include <cstdint>
+#include <set>
+
+#include <wayland-client-protocol.hpp>
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace WAYLAND
+{
+
+class CSeat;
+
+/**
+ * Handler for raw wl_keyboard events
+ *
+ * All functions are identical to wl_keyboard, except for the keymap which is
+ * retrieved from its fd and put into a string
+ */
+class IRawInputHandlerKeyboard
+{
+public:
+ virtual void OnKeyboardKeymap(CSeat* seat, wayland::keyboard_keymap_format format, std::string const& keymap) {}
+ virtual void OnKeyboardEnter(CSeat* seat,
+ std::uint32_t serial,
+ const wayland::surface_t& surface,
+ const wayland::array_t& keys)
+ {
+ }
+ virtual void OnKeyboardLeave(CSeat* seat, std::uint32_t serial, const wayland::surface_t& surface)
+ {
+ }
+ virtual void OnKeyboardKey(CSeat* seat, std::uint32_t serial, std::uint32_t time, std::uint32_t key, wayland::keyboard_key_state state) {}
+ virtual void OnKeyboardModifiers(CSeat* seat, std::uint32_t serial, std::uint32_t modsDepressed, std::uint32_t modsLatched, std::uint32_t modsLocked, std::uint32_t group) {}
+ virtual void OnKeyboardRepeatInfo(CSeat* seat, std::int32_t rate, std::int32_t delay) {}
+protected:
+ ~IRawInputHandlerKeyboard() = default;
+};
+
+/**
+ * Handler for raw wl_pointer events
+ *
+ * All functions are identical to wl_pointer
+ */
+class IRawInputHandlerPointer
+{
+public:
+ virtual void OnPointerEnter(CSeat* seat,
+ std::uint32_t serial,
+ const wayland::surface_t& surface,
+ double surfaceX,
+ double surfaceY)
+ {
+ }
+ virtual void OnPointerLeave(CSeat* seat, std::uint32_t serial, const wayland::surface_t& surface)
+ {
+ }
+ virtual void OnPointerMotion(CSeat* seat, std::uint32_t time, double surfaceX, double surfaceY) {}
+ virtual void OnPointerButton(CSeat* seat, std::uint32_t serial, std::uint32_t time, std::uint32_t button, wayland::pointer_button_state state) {}
+ virtual void OnPointerAxis(CSeat* seat, std::uint32_t time, wayland::pointer_axis axis, double value) {}
+protected:
+ ~IRawInputHandlerPointer() = default;
+};
+
+/**
+ * Handler for raw wl_touch events
+ *
+ * All functions are identical to wl_touch
+ */
+class IRawInputHandlerTouch
+{
+public:
+ virtual void OnTouchDown(CSeat* seat,
+ std::uint32_t serial,
+ std::uint32_t time,
+ const wayland::surface_t& surface,
+ std::int32_t id,
+ double x,
+ double y)
+ {
+ }
+ virtual void OnTouchUp(CSeat* seat, std::uint32_t serial, std::uint32_t time, std::int32_t id) {}
+ virtual void OnTouchMotion(CSeat* seat, std::uint32_t time, std::int32_t id, double x, double y) {}
+ virtual void OnTouchCancel(CSeat* seat) {}
+ virtual void OnTouchShape(CSeat* seat, std::int32_t id, double major, double minor) {}
+protected:
+ ~IRawInputHandlerTouch() = default;
+};
+
+/**
+ * Handle all events and requests related to one seat (including input and selection)
+ *
+ * The primary purpose of this class is to act as entry point of Wayland events into
+ * the Kodi world and distribute them further as necessary.
+ * Input events are forwarded to (potentially multiple) handlers. As the Wayland
+ * protocol is not very specific on having multiple wl_seat/wl_pointer instances
+ * and how they interact, having one central instance and then handling everything
+ * in Kodi with multiple handlers is better than each handler having its own
+ * protocol object instance.
+ */
+class CSeat
+{
+public:
+ /**
+ * Construct seat handler
+ * \param globalName Wayland numeric global name of the seat
+ * \param seat bound seat_t instance
+ * \param connection connection for retrieving additional globals
+ */
+ CSeat(std::uint32_t globalName, wayland::seat_t const& seat, CConnection& connection);
+ ~CSeat() noexcept;
+
+ void AddRawInputHandlerKeyboard(IRawInputHandlerKeyboard* rawKeyboardHandler);
+ void RemoveRawInputHandlerKeyboard(IRawInputHandlerKeyboard* rawKeyboardHandler);
+ void AddRawInputHandlerPointer(IRawInputHandlerPointer* rawPointerHandler);
+ void RemoveRawInputHandlerPointer(IRawInputHandlerPointer* rawPointerHandler);
+ void AddRawInputHandlerTouch(IRawInputHandlerTouch* rawTouchHandler);
+ void RemoveRawInputHandlerTouch(IRawInputHandlerTouch* rawTouchHandler);
+
+ std::uint32_t GetGlobalName() const
+ {
+ return m_globalName;
+ }
+ std::string const& GetName() const
+ {
+ return m_name;
+ }
+ bool HasPointerCapability() const
+ {
+ return !!m_pointer;
+ }
+ bool HasKeyboardCapability() const
+ {
+ return !!m_keyboard;
+ }
+ bool HasTouchCapability() const
+ {
+ return !!m_touch;
+ }
+ std::string GetSelectionText() const
+ {
+ return m_selection.GetSelectionText();
+ }
+ /**
+ * Get the wl_seat underlying this seat
+ *
+ * The wl_seat should only be used when strictly necessary, e.g. when
+ * starting a move operation with shell interfaces.
+ * It may not be used to derive further wl_pointer etc. instances.
+ */
+ wayland::seat_t const& GetWlSeat()
+ {
+ return m_seat;
+ }
+
+ /**
+ * Set the cursor of the pointer of this seat
+ *
+ * Parameters are identical wo wl_pointer.set_cursor().
+ * If the seat does not currently have the pointer capability, this is a no-op.
+ */
+ void SetCursor(std::uint32_t serial, wayland::surface_t const& surface, std::int32_t hotspotX, std::int32_t hotspotY);
+
+private:
+ CSeat(CSeat const& other) = delete;
+ CSeat& operator=(CSeat const& other) = delete;
+
+ void HandleOnCapabilities(const wayland::seat_capability& caps);
+ void HandlePointerCapability();
+ void HandleKeyboardCapability();
+ void HandleTouchCapability();
+
+ std::uint32_t m_globalName;
+ std::string m_name{"<unknown>"};
+
+ wayland::seat_t m_seat;
+ wayland::pointer_t m_pointer;
+ wayland::keyboard_t m_keyboard;
+ wayland::touch_t m_touch;
+
+ std::set<IRawInputHandlerKeyboard*> m_rawKeyboardHandlers;
+ std::set<IRawInputHandlerPointer*> m_rawPointerHandlers;
+ std::set<IRawInputHandlerTouch*> m_rawTouchHandlers;
+
+ CSeatSelection m_selection;
+};
+
+}
+}
+}
diff --git a/xbmc/windowing/wayland/SeatInputProcessing.cpp b/xbmc/windowing/wayland/SeatInputProcessing.cpp
new file mode 100644
index 0000000..6430aad
--- /dev/null
+++ b/xbmc/windowing/wayland/SeatInputProcessing.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 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 "SeatInputProcessing.h"
+
+#include <cassert>
+
+using namespace KODI::WINDOWING::WAYLAND;
+
+CSeatInputProcessing::CSeatInputProcessing(wayland::surface_t const& inputSurface, IInputHandler& handler)
+: m_inputSurface{inputSurface}, m_handler{handler}
+{
+}
+
+void CSeatInputProcessing::AddSeat(CSeat* seat)
+{
+ assert(m_seats.find(seat->GetGlobalName()) == m_seats.end());
+ auto& seatState = m_seats.emplace(seat->GetGlobalName(), seat).first->second;
+
+ seatState.keyboardProcessor.reset(new CInputProcessorKeyboard(*this));
+ seat->AddRawInputHandlerKeyboard(seatState.keyboardProcessor.get());
+ seatState.pointerProcessor.reset(new CInputProcessorPointer(m_inputSurface, *this));
+ seat->AddRawInputHandlerPointer(seatState.pointerProcessor.get());
+ seatState.touchProcessor.reset(new CInputProcessorTouch(m_inputSurface));
+ seat->AddRawInputHandlerTouch(seatState.touchProcessor.get());
+}
+
+void CSeatInputProcessing::RemoveSeat(CSeat* seat)
+{
+ auto seatStateI = m_seats.find(seat->GetGlobalName());
+ if (seatStateI != m_seats.end())
+ {
+ seat->RemoveRawInputHandlerKeyboard(seatStateI->second.keyboardProcessor.get());
+ seat->RemoveRawInputHandlerPointer(seatStateI->second.pointerProcessor.get());
+ seat->RemoveRawInputHandlerTouch(seatStateI->second.touchProcessor.get());
+ m_seats.erase(seatStateI);
+ }
+}
+
+void CSeatInputProcessing::OnPointerEnter(std::uint32_t seatGlobalName, std::uint32_t serial)
+{
+ m_handler.OnSetCursor(seatGlobalName, serial);
+ m_handler.OnEnter(InputType::POINTER);
+}
+
+void CSeatInputProcessing::OnPointerLeave()
+{
+ m_handler.OnLeave(InputType::POINTER);
+}
+
+void CSeatInputProcessing::OnPointerEvent(XBMC_Event& event)
+{
+ m_handler.OnEvent(InputType::POINTER, event);
+}
+
+void CSeatInputProcessing::OnKeyboardEnter()
+{
+ m_handler.OnEnter(InputType::KEYBOARD);
+}
+
+void CSeatInputProcessing::OnKeyboardLeave()
+{
+ m_handler.OnLeave(InputType::KEYBOARD);
+}
+
+void CSeatInputProcessing::OnKeyboardEvent(XBMC_Event& event)
+{
+ m_handler.OnEvent(InputType::KEYBOARD, event);
+}
+
+void CSeatInputProcessing::SetCoordinateScale(std::int32_t scale)
+{
+ for (auto& seatPair : m_seats)
+ {
+ seatPair.second.touchProcessor->SetCoordinateScale(scale);
+ seatPair.second.pointerProcessor->SetCoordinateScale(scale);
+ }
+}
diff --git a/xbmc/windowing/wayland/SeatInputProcessing.h b/xbmc/windowing/wayland/SeatInputProcessing.h
new file mode 100644
index 0000000..ce1f0ee
--- /dev/null
+++ b/xbmc/windowing/wayland/SeatInputProcessing.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 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 "InputProcessorKeyboard.h"
+#include "InputProcessorPointer.h"
+#include "InputProcessorTouch.h"
+#include "Seat.h"
+
+#include <cstdint>
+#include <map>
+#include <memory>
+
+#include <wayland-client-protocol.hpp>
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace WAYLAND
+{
+
+enum class InputType
+{
+ POINTER,
+ KEYBOARD,
+ TOUCH
+};
+
+/**
+ * Handler interface for input events from \ref CSeatInputProcessor
+ */
+class IInputHandler
+{
+public:
+ /**
+ * Handle input event
+ * \param type input device type that caused the event
+ * \param event XBMC event data
+ */
+ virtual void OnEvent(InputType type, XBMC_Event& event) {}
+ /**
+ * Handle focus enter
+ * \param type input device type for which the surface has gained the focus
+ */
+ virtual void OnEnter(InputType type) {}
+ /**
+ * Handle focus leave
+ * \param type input device type for which the surface has lost the focus
+ */
+ virtual void OnLeave(InputType type) {}
+ /**
+ * Handle request for setting the cursor
+ *
+ * When the client gains pointer focus for a surface, a cursor image must be
+ * attached to the pointer. Otherwise the previous pointer image would
+ * be used.
+ *
+ * This request is sent in addition to \ref OnEnter for \ref InputType::POINTER.
+ *
+ * \param seatGlobalName numeric Wayland global name of the seat the event occurred on
+ * \param pointer pointer instance that needs its cursor set
+ * \param serial Wayland protocol message serial that must be sent back in set_cursor
+ */
+ virtual void OnSetCursor(std::uint32_t seatGlobalName, std::uint32_t serial) {}
+
+ virtual ~IInputHandler() = default;
+};
+
+/**
+ * Receive events from all registered wl_seats and process them into Kodi events
+ *
+ * Multi-seat support is not currently implemented completely, but each seat has
+ * separate state.
+ */
+class CSeatInputProcessing final : IInputHandlerPointer, IInputHandlerKeyboard
+{
+public:
+ /**
+ * Construct a seat input processor
+ *
+ * \param inputSurface Surface that events should be processed on (all other surfaces are ignored)
+ * \param handler Mandatory handler for processed input events
+ */
+ CSeatInputProcessing(wayland::surface_t const& inputSurface, IInputHandler& handler);
+ void AddSeat(CSeat* seat);
+ void RemoveSeat(CSeat* seat);
+
+ /**
+ * Set the scale the coordinates should be interpreted at
+ *
+ * Wayland input events are always in surface coordinates, but Kodi only uses
+ * buffer coordinates internally. Use this function to set the scaling
+ * factor between the two and multiply the surface coordinates accordingly
+ * for Kodi events.
+ *
+ * \param scale new buffer-to-surface pixel ratio
+ */
+ void SetCoordinateScale(std::int32_t scale);
+
+private:
+ wayland::surface_t m_inputSurface;
+ IInputHandler& m_handler;
+
+ void OnPointerEnter(std::uint32_t seatGlobalName, std::uint32_t serial) override;
+ void OnPointerLeave() override;
+ void OnPointerEvent(XBMC_Event& event) override;
+
+ void OnKeyboardEnter() override;
+ void OnKeyboardLeave() override;
+ void OnKeyboardEvent(XBMC_Event& event) override;
+
+ struct SeatState
+ {
+ CSeat* seat;
+ std::unique_ptr<CInputProcessorKeyboard> keyboardProcessor;
+ std::unique_ptr<CInputProcessorPointer> pointerProcessor;
+ std::unique_ptr<CInputProcessorTouch> touchProcessor;
+
+ SeatState(CSeat* seat)
+ : seat{seat}
+ {}
+ };
+ std::map<std::uint32_t, SeatState> m_seats;
+};
+
+}
+}
+}
diff --git a/xbmc/windowing/wayland/SeatSelection.cpp b/xbmc/windowing/wayland/SeatSelection.cpp
new file mode 100644
index 0000000..f66555a
--- /dev/null
+++ b/xbmc/windowing/wayland/SeatSelection.cpp
@@ -0,0 +1,199 @@
+/*
+ * 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 "SeatSelection.h"
+
+#include "Connection.h"
+#include "Registry.h"
+#include "WinEventsWayland.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include "platform/posix/utils/FileHandle.h"
+
+#include <cerrno>
+#include <chrono>
+#include <cstring>
+#include <mutex>
+#include <system_error>
+#include <utility>
+
+#include <poll.h>
+#include <unistd.h>
+
+using namespace KODI::UTILS::POSIX;
+using namespace KODI::WINDOWING::WAYLAND;
+
+namespace
+{
+
+const std::vector<std::string> MIME_TYPES_PREFERENCE =
+{
+ "text/plain;charset=utf-8",
+ "text/plain;charset=iso-8859-1",
+ "text/plain;charset=us-ascii",
+ "text/plain"
+};
+
+}
+
+CSeatSelection::CSeatSelection(CConnection& connection, wayland::seat_t const& seat)
+{
+ wayland::data_device_manager_t manager;
+ {
+ CRegistry registry{connection};
+ registry.RequestSingleton(manager, 1, 3, false);
+ registry.Bind();
+ }
+
+ if (!manager)
+ {
+ CLog::Log(LOGWARNING, "No data device manager announced by compositor, clipboard will not be available");
+ return;
+ }
+
+ m_dataDevice = manager.get_data_device(seat);
+
+ // Class is created in response to seat add events - so no events can get lost
+ m_dataDevice.on_data_offer() = [this](wayland::data_offer_t offer)
+ {
+ // We don't know yet whether this is drag-and-drop or selection, so collect
+ // MIME types in either case
+ m_currentOffer = std::move(offer);
+ m_mimeTypeOffers.clear();
+ m_currentOffer.on_offer() = [this](std::string mime)
+ {
+ m_mimeTypeOffers.push_back(std::move(mime));
+ };
+ };
+ m_dataDevice.on_selection() = [this](const wayland::data_offer_t& offer)
+ {
+ std::unique_lock<CCriticalSection> lock(m_currentSelectionMutex);
+ m_matchedMimeType.clear();
+
+ if (offer != m_currentOffer)
+ {
+ // Selection was not previously introduced by offer (could be NULL for example)
+ m_currentSelection.proxy_release();
+ }
+ else
+ {
+ m_currentSelection = offer;
+ std::string offers = StringUtils::Join(m_mimeTypeOffers, ", ");
+
+ // Match MIME type by priority: Find first preferred MIME type that is in the
+ // set of offered types
+ // Charset is not case-sensitive in MIME type spec, so match case-insensitively
+ auto mimeIt = std::find_first_of(MIME_TYPES_PREFERENCE.cbegin(), MIME_TYPES_PREFERENCE.cend(),
+ m_mimeTypeOffers.cbegin(), m_mimeTypeOffers.cend(),
+ // static_cast needed for overload resolution
+ static_cast<bool (*)(std::string const&, std::string const&)> (&StringUtils::EqualsNoCase));
+ if (mimeIt != MIME_TYPES_PREFERENCE.cend())
+ {
+ m_matchedMimeType = *mimeIt;
+ CLog::Log(LOGDEBUG, "Chose selection MIME type {} out of offered {}", m_matchedMimeType,
+ offers);
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, "Could not find compatible MIME type for selection data (offered: {})",
+ offers);
+ }
+ }
+ };
+}
+
+std::string CSeatSelection::GetSelectionText() const
+{
+ std::unique_lock<CCriticalSection> lock(m_currentSelectionMutex);
+ if (!m_currentSelection || m_matchedMimeType.empty())
+ {
+ return "";
+ }
+
+ std::array<int, 2> fds;
+ if (pipe(fds.data()) != 0)
+ {
+ CLog::LogF(LOGERROR, "Could not open pipe for selection data transfer: {}",
+ std::strerror(errno));
+ return "";
+ }
+
+ CFileHandle readFd{fds[0]};
+ CFileHandle writeFd{fds[1]};
+
+ m_currentSelection.receive(m_matchedMimeType, writeFd);
+ lock.unlock();
+ // Make sure the other party gets the request as soon as possible
+ CWinEventsWayland::Flush();
+ // Fd now gets sent to the other party -> make sure our write end is closed
+ // so we get POLLHUP when the other party closes its write fd
+ writeFd.reset();
+
+ pollfd fd =
+ {
+ .fd = readFd,
+ .events = POLLIN,
+ .revents = 0
+ };
+
+ // UI will block in this function when Ctrl+V is pressed, so timeout should be
+ // rather short!
+ const std::chrono::seconds TIMEOUT{1};
+ const std::size_t MAX_SIZE{4096};
+ std::array<char, MAX_SIZE> buffer;
+
+ auto start = std::chrono::steady_clock::now();
+ std::size_t totalBytesRead{0};
+
+ do
+ {
+ auto now = std::chrono::steady_clock::now();
+ // Do not permit negative timeouts (would cause infinitely long poll)
+ auto remainingTimeout = std::max(std::chrono::milliseconds(0), std::chrono::duration_cast<std::chrono::milliseconds> (TIMEOUT - (now - start))).count();
+ // poll() for changes until poll signals POLLHUP and the remaining data was read
+ int ret{poll(&fd, 1, remainingTimeout)};
+ if (ret == 0)
+ {
+ // Timeout
+ CLog::LogF(LOGERROR, "Reading from selection data pipe timed out");
+ return "";
+ }
+ else if (ret < 0 && errno == EINTR)
+ {
+ continue;
+ }
+ else if (ret < 0)
+ {
+ throw std::system_error(errno, std::generic_category(), "Error polling selection pipe");
+ }
+ else if (fd.revents & POLLNVAL || fd.revents & POLLERR)
+ {
+ CLog::LogF(LOGERROR, "poll() indicated error on selection pipe");
+ return "";
+ }
+ else if (fd.revents & POLLIN)
+ {
+ if (totalBytesRead >= buffer.size())
+ {
+ CLog::LogF(LOGERROR, "Selection data is too big, aborting read");
+ return "";
+ }
+ ssize_t readBytes{read(fd.fd, buffer.data() + totalBytesRead, buffer.size() - totalBytesRead)};
+ if (readBytes < 0)
+ {
+ CLog::LogF(LOGERROR, "read() from selection pipe failed: {}", std::strerror(errno));
+ return "";
+ }
+ totalBytesRead += readBytes;
+ }
+ }
+ while (!(fd.revents & POLLHUP));
+
+ return std::string(buffer.data(), totalBytesRead);
+}
diff --git a/xbmc/windowing/wayland/SeatSelection.h b/xbmc/windowing/wayland/SeatSelection.h
new file mode 100644
index 0000000..ec99a0d
--- /dev/null
+++ b/xbmc/windowing/wayland/SeatSelection.h
@@ -0,0 +1,49 @@
+/*
+ * 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 "threads/CriticalSection.h"
+
+#include <string>
+#include <vector>
+
+#include <wayland-client-protocol.hpp>
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace WAYLAND
+{
+
+class CConnection;
+
+/**
+ * Retrieve and accept selection (clipboard) offers on the data device of a seat
+ */
+class CSeatSelection
+{
+public:
+ CSeatSelection(CConnection& connection, wayland::seat_t const& seat);
+ std::string GetSelectionText() const;
+
+private:
+ wayland::data_device_t m_dataDevice;
+ wayland::data_offer_t m_currentOffer;
+ mutable wayland::data_offer_t m_currentSelection;
+
+ std::vector<std::string> m_mimeTypeOffers;
+ std::string m_matchedMimeType;
+
+ mutable CCriticalSection m_currentSelectionMutex;
+};
+
+}
+}
+}
diff --git a/xbmc/windowing/wayland/ShellSurface.cpp b/xbmc/windowing/wayland/ShellSurface.cpp
new file mode 100644
index 0000000..5836689
--- /dev/null
+++ b/xbmc/windowing/wayland/ShellSurface.cpp
@@ -0,0 +1,35 @@
+/*
+ * 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 "ShellSurface.h"
+
+#include "utils/StringUtils.h"
+
+using namespace KODI::WINDOWING::WAYLAND;
+
+std::string IShellSurface::StateToString(StateBitset state)
+{
+ std::vector<std::string> parts;
+ if (state.test(STATE_ACTIVATED))
+ {
+ parts.emplace_back("activated");
+ }
+ if (state.test(STATE_FULLSCREEN))
+ {
+ parts.emplace_back("fullscreen");
+ }
+ if (state.test(STATE_MAXIMIZED))
+ {
+ parts.emplace_back("maximized");
+ }
+ if (state.test(STATE_RESIZING))
+ {
+ parts.emplace_back("resizing");
+ }
+ return parts.empty() ? "none" : StringUtils::Join(parts, ",");
+} \ No newline at end of file
diff --git a/xbmc/windowing/wayland/ShellSurface.h b/xbmc/windowing/wayland/ShellSurface.h
new file mode 100644
index 0000000..08690cc
--- /dev/null
+++ b/xbmc/windowing/wayland/ShellSurface.h
@@ -0,0 +1,92 @@
+/*
+ * 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 "utils/Geometry.h"
+
+#include <bitset>
+#include <cstdint>
+
+#include <wayland-client.hpp>
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace WAYLAND
+{
+
+class IShellSurfaceHandler;
+
+/**
+ * Abstraction for shell surfaces to support multiple protocols
+ * such as wl_shell (for compatibility) and xdg_shell (for features)
+ *
+ * The interface itself is modeled after xdg_shell, so see there for the meaning
+ * of e.g. the surface states
+ */
+class IShellSurface
+{
+public:
+ // Not enum class since it must be used like a bitfield
+ enum State
+ {
+ STATE_MAXIMIZED = 0,
+ STATE_FULLSCREEN,
+ STATE_RESIZING,
+ STATE_ACTIVATED,
+ STATE_COUNT
+ };
+ using StateBitset = std::bitset<STATE_COUNT>;
+ static std::string StateToString(StateBitset state);
+
+ /**
+ * Initialize shell surface
+ *
+ * The event loop thread MUST NOT be running when this function is called.
+ * The difference to the constructor is that in this function callbacks may
+ * already be called.
+ */
+ virtual void Initialize() = 0;
+
+ virtual void SetFullScreen(wayland::output_t const& output, float refreshRate) = 0;
+ virtual void SetWindowed() = 0;
+ virtual void SetMaximized() = 0;
+ virtual void UnsetMaximized() = 0;
+ virtual void SetMinimized() = 0;
+ virtual void SetWindowGeometry(CRectInt geometry) = 0;
+
+ virtual void AckConfigure(std::uint32_t serial) = 0;
+
+ virtual void StartMove(wayland::seat_t const& seat, std::uint32_t serial) = 0;
+ virtual void StartResize(wayland::seat_t const& seat, std::uint32_t serial, wayland::shell_surface_resize edge) = 0;
+ virtual void ShowShellContextMenu(wayland::seat_t const& seat, std::uint32_t serial, CPointInt position) = 0;
+
+ virtual ~IShellSurface() = default;
+
+protected:
+ IShellSurface() noexcept = default;
+
+private:
+ IShellSurface(IShellSurface const& other) = delete;
+ IShellSurface& operator=(IShellSurface const& other) = delete;
+};
+
+class IShellSurfaceHandler
+{
+public:
+ virtual void OnConfigure(std::uint32_t serial, CSizeInt size, IShellSurface::StateBitset state) = 0;
+ virtual void OnClose() = 0;
+
+ virtual ~IShellSurfaceHandler() = default;
+};
+
+}
+}
+}
diff --git a/xbmc/windowing/wayland/ShellSurfaceWlShell.cpp b/xbmc/windowing/wayland/ShellSurfaceWlShell.cpp
new file mode 100644
index 0000000..2c91858
--- /dev/null
+++ b/xbmc/windowing/wayland/ShellSurfaceWlShell.cpp
@@ -0,0 +1,101 @@
+/*
+ * 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 "ShellSurfaceWlShell.h"
+
+#include "Registry.h"
+
+#include <cmath>
+
+using namespace KODI::WINDOWING::WAYLAND;
+using namespace std::placeholders;
+
+CShellSurfaceWlShell::CShellSurfaceWlShell(IShellSurfaceHandler& handler,
+ CConnection& connection,
+ const wayland::surface_t& surface,
+ const std::string& title,
+ const std::string& class_)
+ : m_handler{handler}
+{
+ {
+ CRegistry registry{connection};
+ registry.RequestSingleton(m_shell, 1, 1);
+ registry.Bind();
+ }
+
+ m_shellSurface = m_shell.get_shell_surface(surface);
+
+ m_surfaceState.set(STATE_ACTIVATED);
+ m_shellSurface.set_class(class_);
+ m_shellSurface.set_title(title);
+ m_shellSurface.on_ping() = [this](std::uint32_t serial)
+ {
+ m_shellSurface.pong(serial);
+ };
+ m_shellSurface.on_configure() = [this](const wayland::shell_surface_resize&, std::int32_t width,
+ std::int32_t height) {
+ // wl_shell does not have serials
+ m_handler.OnConfigure(0, {width, height}, m_surfaceState);
+ };
+}
+
+void CShellSurfaceWlShell::AckConfigure(std::uint32_t)
+{
+}
+
+void CShellSurfaceWlShell::Initialize()
+{
+ // Nothing to do here - constructor already handles it
+ // This is not a problem since the constructor is guaranteed not to call
+ // handler functions since the event loop is not running.
+}
+
+void CShellSurfaceWlShell::SetFullScreen(const wayland::output_t& output, float refreshRate)
+{
+ m_shellSurface.set_fullscreen(wayland::shell_surface_fullscreen_method::driver, std::round(refreshRate * 1000.0f), output);
+ m_surfaceState.set(STATE_FULLSCREEN);
+}
+
+void CShellSurfaceWlShell::SetWindowed()
+{
+ m_shellSurface.set_toplevel();
+ m_surfaceState.reset(STATE_FULLSCREEN);
+}
+
+void CShellSurfaceWlShell::SetMaximized()
+{
+ m_shellSurface.set_maximized(wayland::output_t());
+ m_surfaceState.set(STATE_MAXIMIZED);
+}
+
+void CShellSurfaceWlShell::UnsetMaximized()
+{
+ m_surfaceState.reset(STATE_MAXIMIZED);
+}
+
+void CShellSurfaceWlShell::SetMinimized()
+{
+}
+
+void CShellSurfaceWlShell::SetWindowGeometry(CRectInt)
+{
+}
+
+void CShellSurfaceWlShell::StartMove(const wayland::seat_t& seat, std::uint32_t serial)
+{
+ m_shellSurface.move(seat, serial);
+}
+
+void CShellSurfaceWlShell::StartResize(const wayland::seat_t& seat, std::uint32_t serial, wayland::shell_surface_resize edge)
+{
+ m_shellSurface.resize(seat, serial, edge);
+}
+
+void CShellSurfaceWlShell::ShowShellContextMenu(const wayland::seat_t&, std::uint32_t, CPointInt)
+{
+}
diff --git a/xbmc/windowing/wayland/ShellSurfaceWlShell.h b/xbmc/windowing/wayland/ShellSurfaceWlShell.h
new file mode 100644
index 0000000..8b60d60
--- /dev/null
+++ b/xbmc/windowing/wayland/ShellSurfaceWlShell.h
@@ -0,0 +1,63 @@
+/*
+ * 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 "Connection.h"
+#include "ShellSurface.h"
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace WAYLAND
+{
+
+class CShellSurfaceWlShell : public IShellSurface
+{
+public:
+ /**
+ * Construct wl_shell_surface for given surface
+ *
+ * \parma handler shell surface handler
+ * \param connection connection global
+ * \param surface surface to make shell surface for
+ * \param title title of the surfae
+ * \param class_ class of the surface, which should match the name of the
+ * .desktop file of the application
+ */
+ CShellSurfaceWlShell(IShellSurfaceHandler& handler,
+ CConnection& connection,
+ wayland::surface_t const& surface,
+ const std::string& title,
+ const std::string& class_);
+
+ void Initialize() override;
+
+ void SetFullScreen(wayland::output_t const& output, float refreshRate) override;
+ void SetWindowed() override;
+ void SetMaximized() override;
+ void UnsetMaximized() override;
+ void SetMinimized() override;
+ void SetWindowGeometry(CRectInt geometry) override;
+ void AckConfigure(std::uint32_t serial) override;
+
+ void StartMove(const wayland::seat_t& seat, std::uint32_t serial) override;
+ void StartResize(const wayland::seat_t& seat, std::uint32_t serial, wayland::shell_surface_resize edge) override;
+ void ShowShellContextMenu(const wayland::seat_t& seat, std::uint32_t serial, CPointInt position) override;
+
+private:
+ IShellSurfaceHandler& m_handler;
+ wayland::shell_t m_shell;
+ wayland::shell_surface_t m_shellSurface;
+ StateBitset m_surfaceState;
+};
+
+}
+}
+}
diff --git a/xbmc/windowing/wayland/ShellSurfaceXdgShell.cpp b/xbmc/windowing/wayland/ShellSurfaceXdgShell.cpp
new file mode 100644
index 0000000..e92d33c
--- /dev/null
+++ b/xbmc/windowing/wayland/ShellSurfaceXdgShell.cpp
@@ -0,0 +1,151 @@
+/*
+ * 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 "ShellSurfaceXdgShell.h"
+
+#include "Registry.h"
+#include "messaging/ApplicationMessenger.h"
+
+using namespace KODI::WINDOWING::WAYLAND;
+
+namespace
+{
+
+IShellSurface::State ConvertStateFlag(wayland::xdg_toplevel_state flag)
+{
+ switch(flag)
+ {
+ case wayland::xdg_toplevel_state::activated:
+ return IShellSurface::STATE_ACTIVATED;
+ case wayland::xdg_toplevel_state::fullscreen:
+ return IShellSurface::STATE_FULLSCREEN;
+ case wayland::xdg_toplevel_state::maximized:
+ return IShellSurface::STATE_MAXIMIZED;
+ case wayland::xdg_toplevel_state::resizing:
+ return IShellSurface::STATE_RESIZING;
+ default:
+ throw std::runtime_error(std::string("Unknown xdg_toplevel state flag ") + std::to_string(static_cast<std::underlying_type<decltype(flag)>::type> (flag)));
+ }
+}
+
+}
+
+CShellSurfaceXdgShell* CShellSurfaceXdgShell::TryCreate(IShellSurfaceHandler& handler, CConnection& connection, const wayland::surface_t& surface, std::string const& title, std::string const& class_)
+{
+ wayland::xdg_wm_base_t shell;
+ CRegistry registry{connection};
+ registry.RequestSingleton(shell, 1, 1, false);
+ registry.Bind();
+
+ if (shell)
+ {
+ return new CShellSurfaceXdgShell(handler, connection.GetDisplay(), shell, surface, title, class_);
+ }
+ else
+ {
+ return nullptr;
+ }
+}
+
+CShellSurfaceXdgShell::CShellSurfaceXdgShell(IShellSurfaceHandler& handler, wayland::display_t& display, const wayland::xdg_wm_base_t& shell, const wayland::surface_t& surface, std::string const& title, std::string const& app_id)
+: m_handler{handler}, m_display{display}, m_shell{shell}, m_surface{surface}, m_xdgSurface{m_shell.get_xdg_surface(m_surface)}, m_xdgToplevel{m_xdgSurface.get_toplevel()}
+{
+ m_shell.on_ping() = [this](std::uint32_t serial)
+ {
+ m_shell.pong(serial);
+ };
+ m_xdgSurface.on_configure() = [this](std::uint32_t serial)
+ {
+ m_handler.OnConfigure(serial, m_configuredSize, m_configuredState);
+ };
+ m_xdgToplevel.on_close() = [this]()
+ {
+ m_handler.OnClose();
+ };
+ m_xdgToplevel.on_configure() = [this](std::int32_t width, std::int32_t height,
+ const std::vector<wayland::xdg_toplevel_state>& states) {
+ m_configuredSize.Set(width, height);
+ m_configuredState.reset();
+ for (auto state : states)
+ {
+ m_configuredState.set(ConvertStateFlag(state));
+ }
+ };
+ m_xdgToplevel.set_app_id(app_id);
+ m_xdgToplevel.set_title(title);
+ // Set sensible minimum size
+ m_xdgToplevel.set_min_size(300, 200);
+}
+
+void CShellSurfaceXdgShell::Initialize()
+{
+ // Commit surface to confirm role
+ // Don't do it in constructor since SetFullScreen might be called before
+ m_surface.commit();
+ // Make sure we get the initial configure before continuing
+ m_display.roundtrip();
+}
+
+void CShellSurfaceXdgShell::AckConfigure(std::uint32_t serial)
+{
+ m_xdgSurface.ack_configure(serial);
+}
+
+CShellSurfaceXdgShell::~CShellSurfaceXdgShell() noexcept
+{
+ // xdg_shell is picky: must destroy toplevel role before surface
+ m_xdgToplevel.proxy_release();
+ m_xdgSurface.proxy_release();
+}
+
+void CShellSurfaceXdgShell::SetFullScreen(const wayland::output_t& output, float)
+{
+ // xdg_shell does not support refresh rate setting at the moment
+ m_xdgToplevel.set_fullscreen(output);
+}
+
+void CShellSurfaceXdgShell::SetWindowed()
+{
+ m_xdgToplevel.unset_fullscreen();
+}
+
+void CShellSurfaceXdgShell::SetMaximized()
+{
+ m_xdgToplevel.set_maximized();
+}
+
+void CShellSurfaceXdgShell::UnsetMaximized()
+{
+ m_xdgToplevel.unset_maximized();
+}
+
+void CShellSurfaceXdgShell::SetMinimized()
+{
+ m_xdgToplevel.set_minimized();
+}
+
+void CShellSurfaceXdgShell::SetWindowGeometry(CRectInt geometry)
+{
+ m_xdgSurface.set_window_geometry(geometry.x1, geometry.y1, geometry.Width(), geometry.Height());
+}
+
+void CShellSurfaceXdgShell::StartMove(const wayland::seat_t& seat, std::uint32_t serial)
+{
+ m_xdgToplevel.move(seat, serial);
+}
+
+void CShellSurfaceXdgShell::StartResize(const wayland::seat_t& seat, std::uint32_t serial, wayland::shell_surface_resize edge)
+{
+ // wl_shell shell_surface_resize is identical to xdg_shell resize_edge
+ m_xdgToplevel.resize(seat, serial, static_cast<std::uint32_t> (edge));
+}
+
+void CShellSurfaceXdgShell::ShowShellContextMenu(const wayland::seat_t& seat, std::uint32_t serial, CPointInt position)
+{
+ m_xdgToplevel.show_window_menu(seat, serial, position.x, position.y);
+}
diff --git a/xbmc/windowing/wayland/ShellSurfaceXdgShell.h b/xbmc/windowing/wayland/ShellSurfaceXdgShell.h
new file mode 100644
index 0000000..718b572
--- /dev/null
+++ b/xbmc/windowing/wayland/ShellSurfaceXdgShell.h
@@ -0,0 +1,73 @@
+/*
+ * 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 "Connection.h"
+#include "ShellSurface.h"
+
+#include <wayland-extra-protocols.hpp>
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace WAYLAND
+{
+
+/**
+ * Shell surface implementation for stable xdg_shell
+ */
+class CShellSurfaceXdgShell : public IShellSurface
+{
+public:
+ /**
+ * Construct xdg_shell toplevel object for given surface
+ *
+ * \param handler the shell surface handler
+ * \param display the wl_display global (for initial roundtrip)
+ * \param shell the xdg_wm_base global
+ * \param surface surface to make shell surface for
+ * \param title title of the surfae
+ * \param class_ class of the surface, which should match the name of the
+ * .desktop file of the application
+ */
+ CShellSurfaceXdgShell(IShellSurfaceHandler& handler, wayland::display_t& display, wayland::xdg_wm_base_t const& shell, wayland::surface_t const& surface, std::string const& title, std::string const& class_);
+ ~CShellSurfaceXdgShell() noexcept override;
+
+ static CShellSurfaceXdgShell* TryCreate(IShellSurfaceHandler& handler, CConnection& connection, wayland::surface_t const& surface, std::string const& title, std::string const& class_);
+
+ void Initialize() override;
+
+ void SetFullScreen(wayland::output_t const& output, float refreshRate) override;
+ void SetWindowed() override;
+ void SetMaximized() override;
+ void UnsetMaximized() override;
+ void SetMinimized() override;
+ void SetWindowGeometry(CRectInt geometry) override;
+ void AckConfigure(std::uint32_t serial) override;
+
+ void StartMove(const wayland::seat_t& seat, std::uint32_t serial) override;
+ void StartResize(const wayland::seat_t& seat, std::uint32_t serial, wayland::shell_surface_resize edge) override;
+ void ShowShellContextMenu(const wayland::seat_t& seat, std::uint32_t serial, CPointInt position) override;
+
+private:
+ IShellSurfaceHandler& m_handler;
+ wayland::display_t& m_display;
+ wayland::xdg_wm_base_t m_shell;
+ wayland::surface_t m_surface;
+ wayland::xdg_surface_t m_xdgSurface;
+ wayland::xdg_toplevel_t m_xdgToplevel;
+
+ CSizeInt m_configuredSize;
+ StateBitset m_configuredState;
+};
+
+}
+}
+}
diff --git a/xbmc/windowing/wayland/ShellSurfaceXdgShellUnstableV6.cpp b/xbmc/windowing/wayland/ShellSurfaceXdgShellUnstableV6.cpp
new file mode 100644
index 0000000..ca68b98
--- /dev/null
+++ b/xbmc/windowing/wayland/ShellSurfaceXdgShellUnstableV6.cpp
@@ -0,0 +1,152 @@
+/*
+ * 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 "ShellSurfaceXdgShellUnstableV6.h"
+
+#include "Registry.h"
+#include "messaging/ApplicationMessenger.h"
+
+using namespace KODI::WINDOWING::WAYLAND;
+
+namespace
+{
+
+IShellSurface::State ConvertStateFlag(wayland::zxdg_toplevel_v6_state flag)
+{
+ switch(flag)
+ {
+ case wayland::zxdg_toplevel_v6_state::activated:
+ return IShellSurface::STATE_ACTIVATED;
+ case wayland::zxdg_toplevel_v6_state::fullscreen:
+ return IShellSurface::STATE_FULLSCREEN;
+ case wayland::zxdg_toplevel_v6_state::maximized:
+ return IShellSurface::STATE_MAXIMIZED;
+ case wayland::zxdg_toplevel_v6_state::resizing:
+ return IShellSurface::STATE_RESIZING;
+ default:
+ throw std::runtime_error(std::string("Unknown xdg_toplevel state flag ") + std::to_string(static_cast<std::underlying_type<decltype(flag)>::type> (flag)));
+ }
+}
+
+}
+
+CShellSurfaceXdgShellUnstableV6* CShellSurfaceXdgShellUnstableV6::TryCreate(IShellSurfaceHandler& handler, CConnection& connection, const wayland::surface_t& surface, std::string const& title, std::string const& class_)
+{
+ wayland::zxdg_shell_v6_t shell;
+ CRegistry registry{connection};
+ registry.RequestSingleton(shell, 1, 1, false);
+ registry.Bind();
+
+ if (shell)
+ {
+ return new CShellSurfaceXdgShellUnstableV6(handler, connection.GetDisplay(), shell, surface, title, class_);
+ }
+ else
+ {
+ return nullptr;
+ }
+}
+
+CShellSurfaceXdgShellUnstableV6::CShellSurfaceXdgShellUnstableV6(IShellSurfaceHandler& handler, wayland::display_t& display, const wayland::zxdg_shell_v6_t& shell, const wayland::surface_t& surface, std::string const& title, std::string const& app_id)
+: m_handler{handler}, m_display{display}, m_shell{shell}, m_surface{surface}, m_xdgSurface{m_shell.get_xdg_surface(m_surface)}, m_xdgToplevel{m_xdgSurface.get_toplevel()}
+{
+ m_shell.on_ping() = [this](std::uint32_t serial)
+ {
+ m_shell.pong(serial);
+ };
+ m_xdgSurface.on_configure() = [this](std::uint32_t serial)
+ {
+ m_handler.OnConfigure(serial, m_configuredSize, m_configuredState);
+ };
+ m_xdgToplevel.on_close() = [this]()
+ {
+ m_handler.OnClose();
+ };
+ m_xdgToplevel.on_configure() =
+ [this](std::int32_t width, std::int32_t height,
+ const std::vector<wayland::zxdg_toplevel_v6_state>& states) {
+ m_configuredSize.Set(width, height);
+ m_configuredState.reset();
+ for (auto state : states)
+ {
+ m_configuredState.set(ConvertStateFlag(state));
+ }
+ };
+ m_xdgToplevel.set_app_id(app_id);
+ m_xdgToplevel.set_title(title);
+ // Set sensible minimum size
+ m_xdgToplevel.set_min_size(300, 200);
+}
+
+void CShellSurfaceXdgShellUnstableV6::Initialize()
+{
+ // Commit surface to confirm role
+ // Don't do it in constructor since SetFullScreen might be called before
+ m_surface.commit();
+ // Make sure we get the initial configure before continuing
+ m_display.roundtrip();
+}
+
+void CShellSurfaceXdgShellUnstableV6::AckConfigure(std::uint32_t serial)
+{
+ m_xdgSurface.ack_configure(serial);
+}
+
+CShellSurfaceXdgShellUnstableV6::~CShellSurfaceXdgShellUnstableV6() noexcept
+{
+ // xdg_shell is picky: must destroy toplevel role before surface
+ m_xdgToplevel.proxy_release();
+ m_xdgSurface.proxy_release();
+}
+
+void CShellSurfaceXdgShellUnstableV6::SetFullScreen(const wayland::output_t& output, float)
+{
+ // xdg_shell does not support refresh rate setting at the moment
+ m_xdgToplevel.set_fullscreen(output);
+}
+
+void CShellSurfaceXdgShellUnstableV6::SetWindowed()
+{
+ m_xdgToplevel.unset_fullscreen();
+}
+
+void CShellSurfaceXdgShellUnstableV6::SetMaximized()
+{
+ m_xdgToplevel.set_maximized();
+}
+
+void CShellSurfaceXdgShellUnstableV6::UnsetMaximized()
+{
+ m_xdgToplevel.unset_maximized();
+}
+
+void CShellSurfaceXdgShellUnstableV6::SetMinimized()
+{
+ m_xdgToplevel.set_minimized();
+}
+
+void CShellSurfaceXdgShellUnstableV6::SetWindowGeometry(CRectInt geometry)
+{
+ m_xdgSurface.set_window_geometry(geometry.x1, geometry.y1, geometry.Width(), geometry.Height());
+}
+
+void CShellSurfaceXdgShellUnstableV6::StartMove(const wayland::seat_t& seat, std::uint32_t serial)
+{
+ m_xdgToplevel.move(seat, serial);
+}
+
+void CShellSurfaceXdgShellUnstableV6::StartResize(const wayland::seat_t& seat, std::uint32_t serial, wayland::shell_surface_resize edge)
+{
+ // wl_shell shell_surface_resize is identical to xdg_shell resize_edge
+ m_xdgToplevel.resize(seat, serial, static_cast<std::uint32_t> (edge));
+}
+
+void CShellSurfaceXdgShellUnstableV6::ShowShellContextMenu(const wayland::seat_t& seat, std::uint32_t serial, CPointInt position)
+{
+ m_xdgToplevel.show_window_menu(seat, serial, position.x, position.y);
+} \ No newline at end of file
diff --git a/xbmc/windowing/wayland/ShellSurfaceXdgShellUnstableV6.h b/xbmc/windowing/wayland/ShellSurfaceXdgShellUnstableV6.h
new file mode 100644
index 0000000..d84f4a5
--- /dev/null
+++ b/xbmc/windowing/wayland/ShellSurfaceXdgShellUnstableV6.h
@@ -0,0 +1,77 @@
+/*
+ * 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 "Connection.h"
+#include "ShellSurface.h"
+
+#include <wayland-extra-protocols.hpp>
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace WAYLAND
+{
+
+/**
+ * Shell surface implementation for unstable xdg_shell in version 6
+ *
+ * xdg_shell was accepted as a stable protocol in wayland-protocols, which
+ * means this class is deprecated and can be safely removed once the relevant
+ * compositors have made the switch.
+ */
+class CShellSurfaceXdgShellUnstableV6 : public IShellSurface
+{
+public:
+ /**
+ * Construct xdg_shell toplevel object for given surface
+ *
+ * \param handler the shell surface handler
+ * \param display the wl_display global (for initial roundtrip)
+ * \param shell the zxdg_shell_v6 global
+ * \param surface surface to make shell surface for
+ * \param title title of the surfae
+ * \param class_ class of the surface, which should match the name of the
+ * .desktop file of the application
+ */
+ CShellSurfaceXdgShellUnstableV6(IShellSurfaceHandler& handler, wayland::display_t& display, wayland::zxdg_shell_v6_t const& shell, wayland::surface_t const& surface, std::string const& title, std::string const& class_);
+ ~CShellSurfaceXdgShellUnstableV6() noexcept override;
+
+ static CShellSurfaceXdgShellUnstableV6* TryCreate(IShellSurfaceHandler& handler, CConnection& connection, wayland::surface_t const& surface, std::string const& title, std::string const& class_);
+
+ void Initialize() override;
+
+ void SetFullScreen(wayland::output_t const& output, float refreshRate) override;
+ void SetWindowed() override;
+ void SetMaximized() override;
+ void UnsetMaximized() override;
+ void SetMinimized() override;
+ void SetWindowGeometry(CRectInt geometry) override;
+ void AckConfigure(std::uint32_t serial) override;
+
+ void StartMove(const wayland::seat_t& seat, std::uint32_t serial) override;
+ void StartResize(const wayland::seat_t& seat, std::uint32_t serial, wayland::shell_surface_resize edge) override;
+ void ShowShellContextMenu(const wayland::seat_t& seat, std::uint32_t serial, CPointInt position) override;
+
+private:
+ IShellSurfaceHandler& m_handler;
+ wayland::display_t& m_display;
+ wayland::zxdg_shell_v6_t m_shell;
+ wayland::surface_t m_surface;
+ wayland::zxdg_surface_v6_t m_xdgSurface;
+ wayland::zxdg_toplevel_v6_t m_xdgToplevel;
+
+ CSizeInt m_configuredSize;
+ StateBitset m_configuredState;
+};
+
+}
+}
+}
diff --git a/xbmc/windowing/wayland/Signals.h b/xbmc/windowing/wayland/Signals.h
new file mode 100644
index 0000000..59a1fdf
--- /dev/null
+++ b/xbmc/windowing/wayland/Signals.h
@@ -0,0 +1,151 @@
+/*
+ * 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 "threads/CriticalSection.h"
+
+#include <map>
+#include <memory>
+#include <mutex>
+
+namespace KODI
+{
+
+using RegistrationIdentifierType = int;
+
+class ISignalHandlerData
+{
+protected:
+ ~ISignalHandlerData() = default;
+
+public:
+ virtual void Unregister(RegistrationIdentifierType id) = 0;
+};
+
+class CSignalRegistration
+{
+ std::weak_ptr<ISignalHandlerData> m_list;
+ RegistrationIdentifierType m_registration;
+
+ template<typename ManagedT>
+ friend class CSignalHandlerList;
+
+ CSignalRegistration(std::shared_ptr<ISignalHandlerData> const& list, RegistrationIdentifierType registration)
+ : m_list{list}, m_registration{registration}
+ {
+ }
+
+ CSignalRegistration(CSignalRegistration const& other) = delete;
+ CSignalRegistration& operator=(CSignalRegistration const& other) = delete;
+
+public:
+ CSignalRegistration() noexcept = default;
+
+ CSignalRegistration(CSignalRegistration&& other) noexcept
+ {
+ *this = std::move(other);
+ }
+
+ inline CSignalRegistration& operator=(CSignalRegistration&& other) noexcept
+ {
+ Unregister();
+ std::swap(m_list, other.m_list);
+ m_registration = other.m_registration;
+ return *this;
+ }
+
+ ~CSignalRegistration() noexcept
+ {
+ Unregister();
+ }
+
+ inline void Unregister()
+ {
+ if (auto list = m_list.lock())
+ {
+ list->Unregister(m_registration);
+ list.reset();
+ }
+ }
+};
+
+template<typename ManagedT>
+class CSignalHandlerList
+{
+ /**
+ * Internal storage for handler list
+ *
+ * Extra struct so memory handling with shared_ptr and weak_ptr can be done
+ * on this level
+ */
+ struct Data final : public ISignalHandlerData
+ {
+ CCriticalSection m_handlerCriticalSection;
+ std::map<RegistrationIdentifierType, ManagedT> m_handlers;
+
+ void Unregister(RegistrationIdentifierType id) override
+ {
+ std::unique_lock<CCriticalSection> lock(m_handlerCriticalSection);
+ m_handlers.erase(id);
+ }
+ };
+
+ std::shared_ptr<Data> m_data;
+ RegistrationIdentifierType m_lastRegistrationId{};
+
+ CSignalHandlerList(CSignalHandlerList const& other) = delete;
+ CSignalHandlerList& operator=(CSignalHandlerList const& other) = delete;
+
+public:
+ CSignalHandlerList()
+ : m_data{new Data}
+ {}
+
+ CSignalRegistration Register(ManagedT const& handler)
+ {
+ std::unique_lock<CCriticalSection> lock(m_data->m_handlerCriticalSection);
+ bool inserted{false};
+ while(!inserted)
+ {
+ inserted = m_data->m_handlers.emplace(++m_lastRegistrationId, handler).second;
+ }
+ return {m_data, m_lastRegistrationId};
+ }
+
+ /**
+ * Invoke all registered signal handlers with the provided arguments
+ * when the signal type is a std::function or otherwise implements
+ * operator()
+ */
+ template<typename... ArgsT>
+ void Invoke(ArgsT&&... args)
+ {
+ std::unique_lock<CCriticalSection> lock(m_data->m_handlerCriticalSection);
+ for (auto const& handler : *this)
+ {
+ handler.second(std::forward<ArgsT>(args)...);
+ }
+ }
+
+ auto begin() const { return m_data->m_handlers.cbegin(); }
+
+ auto end() const { return m_data->m_handlers.cend(); }
+
+ /**
+ * Get critical section for accessing the handler list
+ * \note You must lock this yourself if you iterate through the handler
+ * list manually without using \ref Invoke or similar.
+ */
+ CCriticalSection const& CriticalSection() const
+ {
+ return m_data->m_handlerCriticalSection;
+ }
+};
+
+}
diff --git a/xbmc/windowing/wayland/Util.cpp b/xbmc/windowing/wayland/Util.cpp
new file mode 100644
index 0000000..707da11
--- /dev/null
+++ b/xbmc/windowing/wayland/Util.cpp
@@ -0,0 +1,88 @@
+/*
+ * 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 "Util.h"
+
+#include <map>
+#include <string>
+
+#include <wayland-cursor.hpp>
+
+namespace
+{
+/*
+ * List from gdkcursor-wayland.c
+ *
+ * GDK - The GIMP Drawing Kit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ * See LICENSES/README.md for more information.
+ */
+static std::map<std::string, std::string> CursorFallbackNameMap =
+{
+ { "default", "left_ptr" },
+ { "help", "question_arrow" },
+ { "context-menu", "left_ptr" },
+ { "pointer", "hand" },
+ { "progress", "left_ptr_watch" },
+ { "wait", "watch" },
+ { "cell", "crosshair" },
+ { "crosshair", "cross" },
+ { "text", "xterm" },
+ { "vertical-text","xterm" },
+ { "alias", "dnd-link" },
+ { "copy", "dnd-copy" },
+ { "move", "dnd-move" },
+ { "no-drop", "dnd-none" },
+ { "dnd-ask", "dnd-copy" }, // not CSS, but we want to guarantee it anyway
+ { "not-allowed", "crossed_circle" },
+ { "grab", "hand2" },
+ { "grabbing", "hand2" },
+ { "all-scroll", "left_ptr" },
+ { "col-resize", "h_double_arrow" },
+ { "row-resize", "v_double_arrow" },
+ { "n-resize", "top_side" },
+ { "e-resize", "right_side" },
+ { "s-resize", "bottom_side" },
+ { "w-resize", "left_side" },
+ { "ne-resize", "top_right_corner" },
+ { "nw-resize", "top_left_corner" },
+ { "se-resize", "bottom_right_corner" },
+ { "sw-resize", "bottom_left_corner" },
+ { "ew-resize", "h_double_arrow" },
+ { "ns-resize", "v_double_arrow" },
+ { "nesw-resize", "fd_double_arrow" },
+ { "nwse-resize", "bd_double_arrow" },
+ { "zoom-in", "left_ptr" },
+ { "zoom-out", "left_ptr" }
+};
+
+}
+
+using namespace KODI::WINDOWING::WAYLAND;
+
+wayland::cursor_t CCursorUtil::LoadFromTheme(wayland::cursor_theme_t const& theme, std::string const& name)
+{
+ try
+ {
+ return theme.get_cursor(name);
+ }
+ catch (std::exception const&)
+ {
+ auto i = CursorFallbackNameMap.find(name);
+ if (i == CursorFallbackNameMap.end())
+ {
+ throw;
+ }
+ else
+ {
+ return theme.get_cursor(i->second);
+ }
+ }
+}
diff --git a/xbmc/windowing/wayland/Util.h b/xbmc/windowing/wayland/Util.h
new file mode 100644
index 0000000..ab13e29
--- /dev/null
+++ b/xbmc/windowing/wayland/Util.h
@@ -0,0 +1,54 @@
+/*
+ * 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 <cstdint>
+#include <string>
+
+#include <wayland-client.hpp>
+#include <wayland-cursor.hpp>
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace WAYLAND
+{
+
+struct WaylandCPtrCompare
+{
+ bool operator()(wayland::proxy_t const& p1, wayland::proxy_t const& p2) const
+ {
+ return reinterpret_cast<std::uintptr_t>(p1.c_ptr()) < reinterpret_cast<std::uintptr_t>(p2.c_ptr());
+ }
+};
+
+class CCursorUtil
+{
+public:
+ /**
+ * Load a cursor from a theme with automatic fallback
+ *
+ * Modern cursor themes use CSS names for the cursors as defined in
+ * the XDG cursor-spec (draft at the moment), but older themes
+ * might still use the cursor names that were popular with X11.
+ * This function tries to load a cursor by the given CSS name and
+ * automatically falls back to the corresponding X11 name if the
+ * load fails.
+ *
+ * \param theme cursor theme to load from
+ * \param name CSS cursor name to load
+ * \return requested cursor
+ */
+ static wayland::cursor_t LoadFromTheme(wayland::cursor_theme_t const& theme, std::string const& name);
+};
+
+}
+}
+}
diff --git a/xbmc/windowing/wayland/VideoSyncWpPresentation.cpp b/xbmc/windowing/wayland/VideoSyncWpPresentation.cpp
new file mode 100644
index 0000000..e050a2d
--- /dev/null
+++ b/xbmc/windowing/wayland/VideoSyncWpPresentation.cpp
@@ -0,0 +1,83 @@
+/*
+ * 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 "VideoSyncWpPresentation.h"
+
+#include "settings/AdvancedSettings.h"
+#include "utils/TimeUtils.h"
+#include "utils/log.h"
+#include "windowing/wayland/WinSystemWayland.h"
+
+#include <cinttypes>
+#include <functional>
+
+using namespace KODI::WINDOWING::WAYLAND;
+using namespace std::placeholders;
+
+CVideoSyncWpPresentation::CVideoSyncWpPresentation(void* clock, CWinSystemWayland& winSystem)
+: CVideoSync(clock), m_winSystem(winSystem)
+{
+}
+
+bool CVideoSyncWpPresentation::Setup(PUPDATECLOCK func)
+{
+ UpdateClock = func;
+ m_stopEvent.Reset();
+ m_fps = m_winSystem.GetSyncOutputRefreshRate();
+
+ return true;
+}
+
+void CVideoSyncWpPresentation::Run(CEvent& stopEvent)
+{
+ m_presentationHandler = m_winSystem.RegisterOnPresentationFeedback(std::bind(&CVideoSyncWpPresentation::HandlePresentation, this, _1, _2, _3, _4, _5));
+
+ XbmcThreads::CEventGroup waitGroup{&stopEvent, &m_stopEvent};
+ waitGroup.wait();
+
+ m_presentationHandler.Unregister();
+}
+
+void CVideoSyncWpPresentation::Cleanup()
+{
+}
+
+float CVideoSyncWpPresentation::GetFps()
+{
+ return m_fps;
+}
+
+void CVideoSyncWpPresentation::HandlePresentation(timespec tv, std::uint32_t refresh, std::uint32_t syncOutputID, float syncOutputRefreshRate, std::uint64_t msc)
+{
+ auto mscDiff = msc - m_lastMsc;
+
+ CLog::Log(LOGDEBUG, LOGAVTIMING,
+ "VideoSyncWpPresentation: tv {}.{:09} s next refresh in +{} ns (fps {:f}) sync output "
+ "id {} fps {:f} msc {} mscdiff {}",
+ static_cast<std::uint64_t>(tv.tv_sec), static_cast<std::uint64_t>(tv.tv_nsec), refresh,
+ 1.0e9 / refresh, syncOutputID, syncOutputRefreshRate, msc, mscDiff);
+
+ if (m_fps != syncOutputRefreshRate || (m_syncOutputID != 0 && m_syncOutputID != syncOutputID))
+ {
+ // Restart if fps changes or sync output changes (which means that the msc jumps)
+ CLog::Log(LOGDEBUG, "fps or sync output changed, restarting Wayland video sync");
+ m_stopEvent.Set();
+ }
+ m_syncOutputID = syncOutputID;
+
+ if (m_lastMsc == 0)
+ {
+ // If this is the first time or MSC is not supported, assume we moved one frame
+ mscDiff = 1;
+ }
+ m_lastMsc = msc;
+
+ // FIXME use timespec instead of currenthostcounter()? Possibly difficult
+ // due to different clock base
+ UpdateClock(mscDiff, CurrentHostCounter(), m_refClock);
+}
diff --git a/xbmc/windowing/wayland/VideoSyncWpPresentation.h b/xbmc/windowing/wayland/VideoSyncWpPresentation.h
new file mode 100644
index 0000000..9d05550
--- /dev/null
+++ b/xbmc/windowing/wayland/VideoSyncWpPresentation.h
@@ -0,0 +1,47 @@
+/*
+ * 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 "Signals.h"
+#include "windowing/VideoSync.h"
+
+#include <cstdint>
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace WAYLAND
+{
+
+class CWinSystemWayland;
+
+class CVideoSyncWpPresentation : public CVideoSync
+{
+public:
+ explicit CVideoSyncWpPresentation(void* clock, CWinSystemWayland& winSystem);
+
+ float GetFps() override;
+ bool Setup(PUPDATECLOCK func) override;
+ void Run(CEvent& stop) override;
+ void Cleanup() override;
+
+private:
+ void HandlePresentation(timespec tv, std::uint32_t refresh, std::uint32_t syncOutputID, float syncOutputRefreshRate, std::uint64_t msc);
+
+ CEvent m_stopEvent;
+ CSignalRegistration m_presentationHandler;
+ std::uint64_t m_lastMsc{};
+ std::uint32_t m_syncOutputID{};
+ CWinSystemWayland &m_winSystem;
+};
+
+}
+}
+}
diff --git a/xbmc/windowing/wayland/WinEventsWayland.cpp b/xbmc/windowing/wayland/WinEventsWayland.cpp
new file mode 100644
index 0000000..4345cf0
--- /dev/null
+++ b/xbmc/windowing/wayland/WinEventsWayland.cpp
@@ -0,0 +1,277 @@
+/*
+ * 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 "WinEventsWayland.h"
+
+#include "ServiceBroker.h"
+#include "application/AppInboundProtocol.h"
+#include "threads/CriticalSection.h"
+#include "threads/Thread.h"
+#include "utils/log.h"
+
+#include "platform/posix/utils/FileHandle.h"
+
+#include <exception>
+#include <memory>
+#include <mutex>
+#include <system_error>
+
+#include <sys/poll.h>
+#include <unistd.h>
+#include <wayland-client.hpp>
+
+using namespace KODI::UTILS::POSIX;
+using namespace KODI::WINDOWING::WAYLAND;
+
+namespace
+{
+/**
+ * Thread for processing Wayland events
+ *
+ * While not strictly needed, reading from the Wayland display file descriptor
+ * and dispatching the resulting events is done in an extra thread here.
+ * Sometime in the future, MessagePump() might be gone and then the
+ * transition will be easier since this extra thread is already here.
+ */
+class CWinEventsWaylandThread : CThread
+{
+ wayland::display_t& m_display;
+ // Pipe used for cancelling poll() on shutdown
+ CFileHandle m_pipeRead;
+ CFileHandle m_pipeWrite;
+
+ CCriticalSection m_roundtripQueueMutex;
+ std::atomic<wayland::event_queue_t*> m_roundtripQueue{nullptr};
+ CEvent m_roundtripQueueEvent;
+
+public:
+ CWinEventsWaylandThread(wayland::display_t& display)
+ : CThread("Wayland message pump"), m_display{display}
+ {
+ std::array<int, 2> fds;
+ if (pipe(fds.data()) < 0)
+ {
+ throw std::system_error(errno, std::generic_category(), "Error creating pipe for Wayland message pump cancellation");
+ }
+ m_pipeRead.attach(fds[0]);
+ m_pipeWrite.attach(fds[1]);
+ Create();
+ }
+
+ ~CWinEventsWaylandThread() override
+ {
+ Stop();
+ // Wait for roundtrip invocation to finish
+ std::unique_lock<CCriticalSection> lock(m_roundtripQueueMutex);
+ }
+
+ void Stop()
+ {
+ CLog::Log(LOGDEBUG, "Stopping Wayland message pump");
+ // Set m_bStop
+ StopThread(false);
+ InterruptPoll();
+ // Now wait for actual exit
+ StopThread(true);
+ }
+
+ void RoundtripQueue(wayland::event_queue_t const& queue)
+ {
+ wayland::event_queue_t queueCopy{queue};
+
+ // Serialize invocations of this function - it's used very rarely and usually
+ // not in parallel anyway, and doing it avoids lots of complications
+ std::unique_lock<CCriticalSection> lock(m_roundtripQueueMutex);
+
+ m_roundtripQueueEvent.Reset();
+ // We can just set the value here since there is no other writer in parallel
+ m_roundtripQueue.store(&queueCopy);
+ // Dispatching can happen now
+
+ // Make sure we don't wait for an event to happen on the socket
+ InterruptPoll();
+
+ if (m_bStop)
+ return;
+
+ m_roundtripQueueEvent.Wait();
+ }
+
+ wayland::display_t& GetDisplay()
+ {
+ return m_display;
+ }
+
+private:
+ void InterruptPoll()
+ {
+ char c = 0;
+ if (write(m_pipeWrite, &c, 1) != 1)
+ throw std::runtime_error("Failed to write to wayland message pipe");
+ }
+
+ void Process() override
+ {
+ try
+ {
+ std::array<pollfd, 2> pollFds;
+ pollfd& waylandPoll = pollFds[0];
+ pollfd& cancelPoll = pollFds[1];
+ // Wayland filedescriptor
+ waylandPoll.fd = m_display.get_fd();
+ waylandPoll.events = POLLIN;
+ waylandPoll.revents = 0;
+ // Read end of the cancellation pipe
+ cancelPoll.fd = m_pipeRead;
+ cancelPoll.events = POLLIN;
+ cancelPoll.revents = 0;
+
+ CLog::Log(LOGDEBUG, "Starting Wayland message pump");
+
+ // Run until cancelled or error
+ while (!m_bStop)
+ {
+ // dispatch() provides no way to cancel a blocked read from the socket
+ // wl_display_disconnect would just close the socket, leading to problems
+ // with the poll() that dispatch() uses internally - so we have to implement
+ // cancellation ourselves here
+
+ // Acquire global read intent
+ wayland::read_intent readIntent = m_display.obtain_read_intent();
+ m_display.flush();
+
+ if (poll(pollFds.data(), pollFds.size(), -1) < 0)
+ {
+ if (errno == EINTR)
+ {
+ continue;
+ }
+ else
+ {
+ throw std::system_error(errno, std::generic_category(), "Error polling on Wayland socket");
+ }
+ }
+
+ if (cancelPoll.revents & POLLERR || cancelPoll.revents & POLLHUP || cancelPoll.revents & POLLNVAL)
+ {
+ throw std::runtime_error("poll() signalled error condition on poll interruption socket");
+ }
+
+ if (waylandPoll.revents & POLLERR || waylandPoll.revents & POLLHUP || waylandPoll.revents & POLLNVAL)
+ {
+ throw std::runtime_error("poll() signalled error condition on Wayland socket");
+ }
+
+ // Read events and release intent; this does not block
+ readIntent.read();
+ // Dispatch default event queue
+ m_display.dispatch_pending();
+
+ if (auto* roundtripQueue = m_roundtripQueue.exchange(nullptr))
+ {
+ m_display.roundtrip_queue(*roundtripQueue);
+ m_roundtripQueueEvent.Set();
+ }
+ if (cancelPoll.revents & POLLIN)
+ {
+ // Read away the char so we don't get another notification
+ // Indepentent from m_roundtripQueue so there are no races
+ char c;
+ if (read(m_pipeRead, &c, 1) != 1)
+ throw std::runtime_error("Error reading from wayland message pipe");
+ }
+ }
+
+ CLog::Log(LOGDEBUG, "Wayland message pump stopped");
+ }
+ catch (std::exception const& e)
+ {
+ // FIXME CThread::OnException is very badly named and should probably go away
+ // FIXME Thread exception handling is seriously broken:
+ // Exceptions will be swallowed and do not terminate the program.
+ // Even XbmcCommons::UncheckedException which claims to be there for just this
+ // purpose does not cause termination, the log message will just be slightly different.
+
+ // But here, going on would be meaningless, so do a hard exit
+ CLog::Log(LOGFATAL, "Exception in Wayland message pump, exiting: {}", e.what());
+ std::terminate();
+ }
+
+ // Wake up if someone is still waiting for roundtrip, won't happen anytime soon...
+ m_roundtripQueueEvent.Set();
+ }
+};
+
+std::unique_ptr<CWinEventsWaylandThread> g_WlMessagePump{nullptr};
+
+}
+
+void CWinEventsWayland::SetDisplay(wayland::display_t* display)
+{
+ if (display && !g_WlMessagePump)
+ {
+ // Start message processing as soon as we have a display
+ g_WlMessagePump.reset(new CWinEventsWaylandThread(*display));
+ }
+ else if (g_WlMessagePump)
+ {
+ // Stop if display is set to nullptr
+ g_WlMessagePump.reset();
+ }
+}
+
+void CWinEventsWayland::Flush()
+{
+ if (g_WlMessagePump)
+ {
+ g_WlMessagePump->GetDisplay().flush();
+ }
+}
+
+void CWinEventsWayland::RoundtripQueue(const wayland::event_queue_t& queue)
+{
+ if (g_WlMessagePump)
+ {
+ g_WlMessagePump->RoundtripQueue(queue);
+ }
+}
+
+bool CWinEventsWayland::MessagePump()
+{
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ // Forward any events that may have been pushed to our queue
+ while (true)
+ {
+ XBMC_Event event;
+ {
+ // Scoped lock for reentrancy
+ std::unique_lock<CCriticalSection> lock(m_queueMutex);
+
+ if (m_queue.empty())
+ {
+ break;
+ }
+
+ // First get event and remove it from the queue, then pass it on - be aware that this
+ // function must be reentrant
+ event = m_queue.front();
+ m_queue.pop();
+ }
+
+ if (appPort)
+ appPort->OnEvent(event);
+ }
+
+ return true;
+}
+
+void CWinEventsWayland::MessagePush(XBMC_Event* ev)
+{
+ std::unique_lock<CCriticalSection> lock(m_queueMutex);
+ m_queue.emplace(*ev);
+}
diff --git a/xbmc/windowing/wayland/WinEventsWayland.h b/xbmc/windowing/wayland/WinEventsWayland.h
new file mode 100644
index 0000000..9390956
--- /dev/null
+++ b/xbmc/windowing/wayland/WinEventsWayland.h
@@ -0,0 +1,49 @@
+/*
+ * 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 "../WinEvents.h"
+#include "threads/CriticalSection.h"
+
+#include <queue>
+
+namespace wayland
+{
+class event_queue_t;
+class display_t;
+}
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace WAYLAND
+{
+
+class CWinEventsWayland : public IWinEvents
+{
+public:
+ bool MessagePump() override;
+ void MessagePush(XBMC_Event* ev);
+ /// Write buffered messages to the compositor
+ static void Flush();
+ /// Do a roundtrip on the specified queue from the event processing thread
+ static void RoundtripQueue(wayland::event_queue_t const& queue);
+
+private:
+ friend class CWinSystemWayland;
+ static void SetDisplay(wayland::display_t* display);
+
+ CCriticalSection m_queueMutex;
+ std::queue<XBMC_Event> m_queue;
+};
+
+}
+}
+}
diff --git a/xbmc/windowing/wayland/WinSystemWayland.cpp b/xbmc/windowing/wayland/WinSystemWayland.cpp
new file mode 100644
index 0000000..9733339
--- /dev/null
+++ b/xbmc/windowing/wayland/WinSystemWayland.cpp
@@ -0,0 +1,1568 @@
+/*
+ * 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 "WinSystemWayland.h"
+
+#include "CompileInfo.h"
+#include "Connection.h"
+#include "OSScreenSaverIdleInhibitUnstableV1.h"
+#include "OptionalsReg.h"
+#include "Registry.h"
+#include "ServiceBroker.h"
+#include "ShellSurfaceWlShell.h"
+#include "ShellSurfaceXdgShell.h"
+#include "ShellSurfaceXdgShellUnstableV6.h"
+#include "Util.h"
+#include "VideoSyncWpPresentation.h"
+#include "WinEventsWayland.h"
+#include "WindowDecorator.h"
+#include "application/Application.h"
+#include "cores/RetroPlayer/process/wayland/RPProcessInfoWayland.h"
+#include "cores/VideoPlayer/Process/wayland/ProcessInfoWayland.h"
+#include "guilib/DispResource.h"
+#include "guilib/LocalizeStrings.h"
+#include "input/InputManager.h"
+#include "input/touch/generic/GenericTouchActionHandler.h"
+#include "input/touch/generic/GenericTouchInputHandler.h"
+#include "messaging/ApplicationMessenger.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/DisplaySettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "settings/lib/Setting.h"
+#include "utils/ActorProtocol.h"
+#include "utils/MathUtils.h"
+#include "utils/StringUtils.h"
+#include "utils/TimeUtils.h"
+#include "utils/log.h"
+#include "windowing/linux/OSScreenSaverFreedesktop.h"
+
+#include "platform/linux/TimeUtils.h"
+
+#include <algorithm>
+#include <limits>
+#include <mutex>
+#include <numeric>
+
+#if defined(HAS_DBUS)
+# include "windowing/linux/OSScreenSaverFreedesktop.h"
+#endif
+
+using namespace KODI::WINDOWING;
+using namespace KODI::WINDOWING::WAYLAND;
+using namespace std::placeholders;
+using namespace std::chrono_literals;
+
+namespace
+{
+
+RESOLUTION FindMatchingCustomResolution(CSizeInt size, float refreshRate)
+{
+ for (size_t res{RES_DESKTOP}; res < CDisplaySettings::GetInstance().ResolutionInfoSize(); ++res)
+ {
+ auto const& resInfo = CDisplaySettings::GetInstance().GetResolutionInfo(res);
+ if (resInfo.iWidth == size.Width() && resInfo.iHeight == size.Height() && MathUtils::FloatEquals(resInfo.fRefreshRate, refreshRate, 0.0005f))
+ {
+ return static_cast<RESOLUTION> (res);
+ }
+ }
+ return RES_INVALID;
+}
+
+struct OutputScaleComparer
+{
+ bool operator()(std::shared_ptr<COutput> const& output1, std::shared_ptr<COutput> const& output2)
+ {
+ return output1->GetScale() < output2->GetScale();
+ }
+};
+
+struct OutputCurrentRefreshRateComparer
+{
+ bool operator()(std::shared_ptr<COutput> const& output1, std::shared_ptr<COutput> const& output2)
+ {
+ return output1->GetCurrentMode().refreshMilliHz < output2->GetCurrentMode().refreshMilliHz;
+ }
+};
+
+/// Scope guard for Actor::Message
+class MessageHandle : public KODI::UTILS::CScopeGuard<Actor::Message*, nullptr, void(Actor::Message*)>
+{
+public:
+ MessageHandle() : CScopeGuard{std::bind(&Actor::Message::Release, std::placeholders::_1), nullptr} {}
+ explicit MessageHandle(Actor::Message* message) : CScopeGuard{std::bind(&Actor::Message::Release, std::placeholders::_1), message} {}
+ Actor::Message* Get() { return static_cast<Actor::Message*> (*this); }
+};
+
+/**
+ * Protocol for communication between Wayland event thread and main thread
+ *
+ * Many messages received from the Wayland compositor must be processed at a
+ * defined time between frame rendering, such as resolution switches. Thus
+ * they are pushed to the main thread for processing.
+ *
+ * The protocol is strictly uni-directional from event to main thread at the moment,
+ * so \ref Actor::Protocol is mainly used as an event queue.
+ */
+namespace WinSystemWaylandProtocol
+{
+
+enum OutMessage
+{
+ CONFIGURE,
+ OUTPUT_HOTPLUG,
+ BUFFER_SCALE
+};
+
+struct MsgConfigure
+{
+ std::uint32_t serial;
+ CSizeInt surfaceSize;
+ IShellSurface::StateBitset state;
+};
+
+struct MsgBufferScale
+{
+ int scale;
+};
+
+};
+
+}
+
+CWinSystemWayland::CWinSystemWayland()
+: CWinSystemBase{}, m_protocol{"WinSystemWaylandInternal"}
+{
+ m_winEvents.reset(new CWinEventsWayland());
+}
+
+CWinSystemWayland::~CWinSystemWayland() noexcept
+{
+ DestroyWindowSystem();
+}
+
+bool CWinSystemWayland::InitWindowSystem()
+{
+ const char* env = getenv("WAYLAND_DISPLAY");
+ if (!env)
+ {
+ CLog::Log(LOGDEBUG, "CWinSystemWayland::{} - WAYLAND_DISPLAY env not set", __FUNCTION__);
+ return false;
+ }
+
+ wayland::set_log_handler([](const std::string& message)
+ { CLog::Log(LOGWARNING, "wayland-client log message: {}", message); });
+
+ CLog::LogF(LOGINFO, "Connecting to Wayland server");
+ m_connection = std::make_unique<CConnection>();
+ if (!m_connection->HasDisplay())
+ return false;
+
+ VIDEOPLAYER::CProcessInfoWayland::Register();
+ RETRO::CRPProcessInfoWayland::Register();
+
+ m_registry.reset(new CRegistry{*m_connection});
+
+ m_registry->RequestSingleton(m_compositor, 1, 4);
+ m_registry->RequestSingleton(m_shm, 1, 1);
+ m_registry->RequestSingleton(m_presentation, 1, 1, false);
+ // version 2 adds done() -> required
+ // version 3 adds destructor -> optional
+ m_registry->Request<wayland::output_t>(2, 3, std::bind(&CWinSystemWayland::OnOutputAdded, this, _1, _2), std::bind(&CWinSystemWayland::OnOutputRemoved, this, _1));
+
+ m_registry->Bind();
+
+ if (m_presentation)
+ {
+ m_presentation.on_clock_id() = [this](std::uint32_t clockId)
+ {
+ CLog::Log(LOGINFO, "Wayland presentation clock: {}", clockId);
+ m_presentationClock = static_cast<clockid_t> (clockId);
+ };
+ }
+
+ // Do another roundtrip to get initial wl_output information
+ m_connection->GetDisplay().roundtrip();
+ if (m_outputs.empty())
+ {
+ throw std::runtime_error("No outputs received from compositor");
+ }
+
+ // Event loop is started in CreateWindow
+
+ // pointer is by default not on this window, will be immediately rectified
+ // by the enter() events if it is
+ CServiceBroker::GetInputManager().SetMouseActive(false);
+ // Always use the generic touch action handler
+ CGenericTouchInputHandler::GetInstance().RegisterHandler(&CGenericTouchActionHandler::GetInstance());
+
+ CServiceBroker::GetSettingsComponent()
+ ->GetSettings()
+ ->GetSetting(CSettings::SETTING_VIDEOSCREEN_LIMITEDRANGE)
+ ->SetVisible(true);
+
+ return CWinSystemBase::InitWindowSystem();
+}
+
+bool CWinSystemWayland::DestroyWindowSystem()
+{
+ DestroyWindow();
+ // wl_display_disconnect frees all proxy objects, so we have to make sure
+ // all stuff is gone on the C++ side before that
+ m_cursorSurface = wayland::surface_t{};
+ m_cursorBuffer = wayland::buffer_t{};
+ m_cursorImage = wayland::cursor_image_t{};
+ m_cursorTheme = wayland::cursor_theme_t{};
+ m_outputsInPreparation.clear();
+ m_outputs.clear();
+ m_frameCallback = wayland::callback_t{};
+ m_screenSaverManager.reset();
+
+ m_seatInputProcessing.reset();
+
+ if (m_registry)
+ {
+ m_registry->UnbindSingletons();
+ }
+ m_registry.reset();
+ m_connection.reset();
+
+ CGenericTouchInputHandler::GetInstance().UnregisterHandler();
+
+ return CWinSystemBase::DestroyWindowSystem();
+}
+
+bool CWinSystemWayland::CreateNewWindow(const std::string& name,
+ bool fullScreen,
+ RESOLUTION_INFO& res)
+{
+ CLog::LogF(LOGINFO, "Starting {} size {}x{}", fullScreen ? "full screen" : "windowed", res.iWidth,
+ res.iHeight);
+
+ m_surface = m_compositor.create_surface();
+ m_surface.on_enter() = [this](const wayland::output_t& wloutput) {
+ if (auto output = FindOutputByWaylandOutput(wloutput))
+ {
+ CLog::Log(LOGDEBUG, "Entering output \"{}\" with scale {} and {:.3f} dpi",
+ UserFriendlyOutputName(output), output->GetScale(), output->GetCurrentDpi());
+ std::unique_lock<CCriticalSection> lock(m_surfaceOutputsMutex);
+ m_surfaceOutputs.emplace(output);
+ lock.unlock();
+ UpdateBufferScale();
+ UpdateTouchDpi();
+ }
+ else
+ {
+ CLog::Log(LOGWARNING, "Entering output that was not configured yet, ignoring");
+ }
+ };
+ m_surface.on_leave() = [this](const wayland::output_t& wloutput) {
+ if (auto output = FindOutputByWaylandOutput(wloutput))
+ {
+ CLog::Log(LOGDEBUG, "Leaving output \"{}\" with scale {}", UserFriendlyOutputName(output),
+ output->GetScale());
+ std::unique_lock<CCriticalSection> lock(m_surfaceOutputsMutex);
+ m_surfaceOutputs.erase(output);
+ lock.unlock();
+ UpdateBufferScale();
+ UpdateTouchDpi();
+ }
+ else
+ {
+ CLog::Log(LOGWARNING, "Leaving output that was not configured yet, ignoring");
+ }
+ };
+
+ m_windowDecorator.reset(new CWindowDecorator(*this, *m_connection, m_surface));
+
+ m_seatInputProcessing.reset(new CSeatInputProcessing(m_surface, *this));
+ m_seatRegistry.reset(new CRegistry{*m_connection});
+ // version 2 adds name event -> optional
+ // version 4 adds wl_keyboard repeat_info -> optional
+ // version 5 adds discrete axis events in wl_pointer -> unused
+ m_seatRegistry->Request<wayland::seat_t>(1, 5, std::bind(&CWinSystemWayland::OnSeatAdded, this, _1, _2), std::bind(&CWinSystemWayland::OnSeatRemoved, this, _1));
+ m_seatRegistry->Bind();
+
+ if (m_seats.empty())
+ {
+ CLog::Log(LOGWARNING, "Wayland compositor did not announce a wl_seat - you will not have any input devices for the time being");
+ }
+
+ if (fullScreen)
+ {
+ m_shellSurfaceState.set(IShellSurface::STATE_FULLSCREEN);
+ }
+ // Assume we're active on startup until someone tells us otherwise
+ m_shellSurfaceState.set(IShellSurface::STATE_ACTIVATED);
+ // Try with this resolution if compositor does not say otherwise
+ UpdateSizeVariables({res.iWidth, res.iHeight}, m_scale, m_shellSurfaceState, false);
+
+ // Use AppName as the desktop file name. This is required to lookup the app icon of the same name.
+ m_shellSurface.reset(CShellSurfaceXdgShell::TryCreate(*this, *m_connection, m_surface, name,
+ std::string(CCompileInfo::GetAppName())));
+ if (!m_shellSurface)
+ {
+ m_shellSurface.reset(CShellSurfaceXdgShellUnstableV6::TryCreate(
+ *this, *m_connection, m_surface, name, std::string(CCompileInfo::GetAppName())));
+ }
+ if (!m_shellSurface)
+ {
+ CLog::LogF(LOGWARNING, "Compositor does not support xdg_shell protocol (stable or unstable v6) - falling back to wl_shell, not all features might work");
+ m_shellSurface.reset(new CShellSurfaceWlShell(*this, *m_connection, m_surface, name,
+ std::string(CCompileInfo::GetAppName())));
+ }
+
+ if (fullScreen)
+ {
+ // Try to start on correct monitor and with correct buffer scale
+ auto output = FindOutputByUserFriendlyName(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_VIDEOSCREEN_MONITOR));
+ auto wlOutput = output ? output->GetWaylandOutput() : wayland::output_t{};
+ m_lastSetOutput = wlOutput;
+ m_shellSurface->SetFullScreen(wlOutput, res.fRefreshRate);
+ if (output && m_surface.can_set_buffer_scale())
+ {
+ m_scale = output->GetScale();
+ ApplyBufferScale();
+ }
+ }
+
+ // Just remember initial width/height for context creation in OnConfigure
+ // This is used for sizing the EGLSurface
+ m_shellSurfaceInitializing = true;
+ m_shellSurface->Initialize();
+ m_shellSurfaceInitializing = false;
+
+ // Apply window decorations if necessary
+ m_windowDecorator->SetState(m_configuredSize, m_scale, m_shellSurfaceState);
+
+ // Set initial opaque region and window geometry
+ ApplyOpaqueRegion();
+ ApplyWindowGeometry();
+
+ // Update resolution with real size as it could have changed due to configure()
+ UpdateDesktopResolution(res, res.strOutput, m_bufferSize.Width(), m_bufferSize.Height(), res.fRefreshRate, 0);
+ res.bFullScreen = fullScreen;
+
+ // Now start processing events
+ //
+ // There are two stages to the event handling:
+ // * Initialization (which ends here): Everything runs synchronously and init
+ // code that needs events processed must call roundtrip().
+ // This is done for simplicity because it is a lot easier than to make
+ // everything event-based and thread-safe everywhere in the startup code,
+ // which is also not really necessary.
+ // * Runtime (which starts here): Every object creation from now on
+ // needs to take great care to be thread-safe:
+ // Since the event pump is always running now, there is a tiny window between
+ // creating an object and attaching the C++ event handlers during which
+ // events can get queued and dispatched for the object but the handlers have
+ // not been set yet. Consequently, the events would get lost.
+ // However, this does not apply to objects that are created in response to
+ // compositor events. Since the callbacks are called from the event processing
+ // thread and ran strictly sequentially, no other events are dispatched during
+ // the runtime of a callback. Luckily this applies to global binding like
+ // wl_output and wl_seat and thus to most if not all runtime object creation
+ // cases we have to support.
+ // There is another problem when Wayland objects are destructed from the main
+ // thread: An event handler could be running in parallel, resulting in certain
+ // doom. So objects should only be deleted in response to compositor events, too.
+ // They might be hiding behind class member variables, so be wary.
+ // Note that this does not apply to global teardown since the event pump is
+ // stopped then.
+ CWinEventsWayland::SetDisplay(&m_connection->GetDisplay());
+
+ return true;
+}
+
+bool CWinSystemWayland::DestroyWindow()
+{
+ // Make sure no more events get processed when we kill the instances
+ CWinEventsWayland::SetDisplay(nullptr);
+
+ m_shellSurface.reset();
+ // waylandpp automatically calls wl_surface_destroy when the last reference is removed
+ m_surface = wayland::surface_t();
+ m_windowDecorator.reset();
+ m_seats.clear();
+ m_lastSetOutput.proxy_release();
+ m_surfaceOutputs.clear();
+ m_surfaceSubmissions.clear();
+ m_seatRegistry.reset();
+
+ return true;
+}
+
+bool CWinSystemWayland::CanDoWindowed()
+{
+ return true;
+}
+
+std::vector<std::string> CWinSystemWayland::GetConnectedOutputs()
+{
+ std::unique_lock<CCriticalSection> lock(m_outputsMutex);
+ std::vector<std::string> outputs;
+ std::transform(m_outputs.cbegin(), m_outputs.cend(), std::back_inserter(outputs),
+ [this](decltype(m_outputs)::value_type const& pair)
+ { return UserFriendlyOutputName(pair.second); });
+
+ return outputs;
+}
+
+bool CWinSystemWayland::UseLimitedColor()
+{
+ return CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOSCREEN_LIMITEDRANGE);
+}
+
+void CWinSystemWayland::UpdateResolutions()
+{
+ CWinSystemBase::UpdateResolutions();
+
+ CDisplaySettings::GetInstance().ClearCustomResolutions();
+
+ // Mimic X11:
+ // Only show resolutions for the currently selected output
+ std::string userOutput = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_VIDEOSCREEN_MONITOR);
+
+ std::unique_lock<CCriticalSection> lock(m_outputsMutex);
+
+ if (m_outputs.empty())
+ {
+ // *Usually* this should not happen - just give up
+ return;
+ }
+
+ auto output = FindOutputByUserFriendlyName(userOutput);
+ if (!output && m_lastSetOutput)
+ {
+ // Fallback to current output
+ output = FindOutputByWaylandOutput(m_lastSetOutput);
+ }
+ if (!output)
+ {
+ // Well just use the first one
+ output = m_outputs.begin()->second;
+ }
+
+ std::string outputName = UserFriendlyOutputName(output);
+
+ auto const& modes = output->GetModes();
+ auto const& currentMode = output->GetCurrentMode();
+ auto physicalSize = output->GetPhysicalSize();
+ CLog::LogF(LOGINFO,
+ "User wanted output \"{}\", we now have \"{}\" size {}x{} mm with {} mode(s):",
+ userOutput, outputName, physicalSize.Width(), physicalSize.Height(), modes.size());
+
+ for (auto const& mode : modes)
+ {
+ bool isCurrent = (mode == currentMode);
+ float pixelRatio = output->GetPixelRatioForMode(mode);
+ CLog::LogF(LOGINFO, "- {}x{} @{:.3f} Hz pixel ratio {:.3f}{}", mode.size.Width(),
+ mode.size.Height(), mode.refreshMilliHz / 1000.0f, pixelRatio,
+ isCurrent ? " current" : "");
+
+ RESOLUTION_INFO res;
+ UpdateDesktopResolution(res, outputName, mode.size.Width(), mode.size.Height(), mode.GetRefreshInHz(), 0);
+ res.fPixelRatio = pixelRatio;
+
+ if (isCurrent)
+ {
+ CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP) = res;
+ }
+ else
+ {
+ CDisplaySettings::GetInstance().AddResolutionInfo(res);
+ }
+ }
+
+ CDisplaySettings::GetInstance().ApplyCalibrations();
+}
+
+std::shared_ptr<COutput> CWinSystemWayland::FindOutputByUserFriendlyName(const std::string& name)
+{
+ std::unique_lock<CCriticalSection> lock(m_outputsMutex);
+ auto outputIt = std::find_if(m_outputs.begin(), m_outputs.end(),
+ [this, &name](decltype(m_outputs)::value_type const& entry)
+ {
+ return (name == UserFriendlyOutputName(entry.second));
+ });
+
+ return (outputIt == m_outputs.end() ? nullptr : outputIt->second);
+}
+
+std::shared_ptr<COutput> CWinSystemWayland::FindOutputByWaylandOutput(wayland::output_t const& output)
+{
+ std::unique_lock<CCriticalSection> lock(m_outputsMutex);
+ auto outputIt = std::find_if(m_outputs.begin(), m_outputs.end(),
+ [&output](decltype(m_outputs)::value_type const& entry)
+ {
+ return (output == entry.second->GetWaylandOutput());
+ });
+
+ return (outputIt == m_outputs.end() ? nullptr : outputIt->second);
+}
+
+/**
+ * Change resolution and window state on Kodi request
+ *
+ * This function is used for updating resolution when Kodi initiates a resolution
+ * change, such as when changing between full screen and windowed mode or when
+ * selecting a different monitor or resolution in the settings.
+ *
+ * Size updates originating from compositor events (such as configure or buffer
+ * scale changes) should not use this function, but \ref SetResolutionInternal
+ * instead.
+ *
+ * \param fullScreen whether to go full screen or windowed
+ * \param res resolution to set
+ * \return whether the requested resolution was actually set - is false e.g.
+ * when already in full screen mode since the application cannot
+ * set the size then
+ */
+bool CWinSystemWayland::SetResolutionExternal(bool fullScreen, RESOLUTION_INFO const& res)
+{
+ // In fullscreen modes, we never change the surface size on Kodi's request,
+ // but only when the compositor tells us to. At least xdg_shell specifies
+ // that with state fullscreen the dimensions given in configure() must
+ // always be observed.
+ // This does mean that the compositor has no way of knowing which resolution
+ // we would (in theory) want. Since no compositor implements dynamic resolution
+ // switching at the moment, this is not a problem. If it is some day implemented
+ // in compositors, this code must be changed to match the behavior that is
+ // expected then anyway.
+
+ // We can honor the Kodi-requested size only if we are not bound by configure rules,
+ // which applies for maximized and fullscreen states.
+ // Also, setting an unconfigured size when just going fullscreen makes no sense.
+ // Give precedence to the size we have still pending, if any.
+ bool mustHonorSize{m_waitingForApply || m_shellSurfaceState.test(IShellSurface::STATE_MAXIMIZED) || m_shellSurfaceState.test(IShellSurface::STATE_FULLSCREEN) || fullScreen};
+
+ CLog::LogF(LOGINFO, "Kodi asked to switch mode to {}x{} @{:.3f} Hz on output \"{}\" {}",
+ res.iWidth, res.iHeight, res.fRefreshRate, res.strOutput,
+ fullScreen ? "full screen" : "windowed");
+
+ if (fullScreen)
+ {
+ // Try to match output
+ auto output = FindOutputByUserFriendlyName(res.strOutput);
+ auto wlOutput = output ? output->GetWaylandOutput() : wayland::output_t{};
+ if (!m_shellSurfaceState.test(IShellSurface::STATE_FULLSCREEN) || (m_lastSetOutput != wlOutput))
+ {
+ // Remember the output we set last so we don't set it again until we
+ // either go windowed or were on a different output
+ m_lastSetOutput = wlOutput;
+
+ if (output)
+ {
+ CLog::LogF(LOGDEBUG, "Resolved output \"{}\" to bound Wayland global {}", res.strOutput,
+ output->GetGlobalName());
+ }
+ else
+ {
+ CLog::LogF(LOGINFO,
+ "Could not match output \"{}\" to a currently available Wayland output, falling "
+ "back to default output",
+ res.strOutput);
+ }
+
+ CLog::LogF(LOGDEBUG, "Setting full-screen with refresh rate {:.3f}", res.fRefreshRate);
+ m_shellSurface->SetFullScreen(wlOutput, res.fRefreshRate);
+ }
+ else
+ {
+ CLog::LogF(LOGDEBUG, "Not setting full screen: already full screen on requested output");
+ }
+ }
+ else
+ {
+ if (m_shellSurfaceState.test(IShellSurface::STATE_FULLSCREEN))
+ {
+ CLog::LogF(LOGDEBUG, "Setting windowed");
+ m_shellSurface->SetWindowed();
+ }
+ else
+ {
+ CLog::LogF(LOGDEBUG, "Not setting windowed: already windowed");
+ }
+ }
+
+ // Set Kodi-provided size only if we are free to choose any size, otherwise
+ // wait for the compositor configure
+ if (!mustHonorSize)
+ {
+ CLog::LogF(LOGDEBUG, "Directly setting windowed size {}x{} on Kodi request", res.iWidth,
+ res.iHeight);
+ // Kodi is directly setting window size, apply
+ auto updateResult = UpdateSizeVariables({res.iWidth, res.iHeight}, m_scale, m_shellSurfaceState, false);
+ ApplySizeUpdate(updateResult);
+ }
+
+ bool wasInitialSetFullScreen{m_isInitialSetFullScreen};
+ m_isInitialSetFullScreen = false;
+
+ // Need to return true
+ // * when this SetFullScreen() call was free to change the context size (and possibly did so)
+ // * on first SetFullScreen so GraphicsContext gets resolution
+ // Otherwise, Kodi must keep the old resolution.
+ return !mustHonorSize || wasInitialSetFullScreen;
+}
+
+bool CWinSystemWayland::ResizeWindow(int, int, int, int)
+{
+ // CGraphicContext is "smart" and calls ResizeWindow or SetFullScreen depending
+ // on some state like whether we were already fullscreen. But actually the processing
+ // here is always identical, so we are using a common function to handle both.
+ const auto& res = CDisplaySettings::GetInstance().GetResolutionInfo(RES_WINDOW);
+ // The newWidth/newHeight parameters are taken from RES_WINDOW anyway, so we can just
+ // ignore them
+ return SetResolutionExternal(false, res);
+}
+
+bool CWinSystemWayland::SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool)
+{
+ return SetResolutionExternal(fullScreen, res);
+}
+
+void CWinSystemWayland::ApplySizeUpdate(SizeUpdateInformation update)
+{
+ if (update.bufferScaleChanged)
+ {
+ // Buffer scale must also match egl size configuration
+ ApplyBufferScale();
+ }
+ if (update.surfaceSizeChanged)
+ {
+ // Update opaque region here so size always matches the configured egl surface
+ ApplyOpaqueRegion();
+ }
+ if (update.configuredSizeChanged)
+ {
+ // Update window decoration state
+ m_windowDecorator->SetState(m_configuredSize, m_scale, m_shellSurfaceState);
+ ApplyWindowGeometry();
+ }
+ // Set always, because of initialization order GL context has to keep track of
+ // whether the size changed. If we skip based on update.bufferSizeChanged here,
+ // GL context will never get its initial size set.
+ SetContextSize(m_bufferSize);
+}
+
+void CWinSystemWayland::ApplyOpaqueRegion()
+{
+ // Mark everything opaque so the compositor can render it faster
+ CLog::LogF(LOGDEBUG, "Setting opaque region size {}x{}", m_surfaceSize.Width(),
+ m_surfaceSize.Height());
+ wayland::region_t opaqueRegion{m_compositor.create_region()};
+ opaqueRegion.add(0, 0, m_surfaceSize.Width(), m_surfaceSize.Height());
+ m_surface.set_opaque_region(opaqueRegion);
+}
+
+void CWinSystemWayland::ApplyWindowGeometry()
+{
+ m_shellSurface->SetWindowGeometry(m_windowDecorator->GetWindowGeometry());
+}
+
+void CWinSystemWayland::ProcessMessages()
+{
+ if (m_waitingForApply)
+ {
+ // Do not put multiple size updates into the pipeline, this would only make
+ // it more complicated without any real benefit. Wait until the size was reconfigured,
+ // then process events again.
+ return;
+ }
+
+ Actor::Message* message{};
+ MessageHandle lastConfigureMessage;
+ int skippedConfigures{-1};
+ int newScale{m_scale};
+
+ while (m_protocol.ReceiveOutMessage(&message))
+ {
+ MessageHandle guard{message};
+ switch (message->signal)
+ {
+ case WinSystemWaylandProtocol::CONFIGURE:
+ // Do not directly process configures, get the last one queued:
+ // While resizing, the compositor will usually send a configure event
+ // each time the mouse moves without any throttling (i.e. multiple times
+ // per rendered frame).
+ // Going through all those and applying them would waste a lot of time when
+ // we already know that the size is not final and will change again anyway.
+ skippedConfigures++;
+ lastConfigureMessage = std::move(guard);
+ break;
+ case WinSystemWaylandProtocol::OUTPUT_HOTPLUG:
+ {
+ CLog::LogF(LOGDEBUG, "Output hotplug, re-reading resolutions");
+ UpdateResolutions();
+ std::unique_lock<CCriticalSection> lock(m_outputsMutex);
+ auto const& desktopRes = CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP);
+ auto output = FindOutputByUserFriendlyName(desktopRes.strOutput);
+ auto const& wlOutput = output->GetWaylandOutput();
+ // Maybe the output that was added was the one we should be on?
+ if (m_bFullScreen && m_lastSetOutput != wlOutput)
+ {
+ CLog::LogF(LOGDEBUG, "Output hotplug resulted in monitor set in settings appearing, switching");
+ // Switch to this output
+ m_lastSetOutput = wlOutput;
+ m_shellSurface->SetFullScreen(wlOutput, desktopRes.fRefreshRate);
+ // SetOutput will result in a configure that updates the actual context size
+ }
+ }
+ break;
+ case WinSystemWaylandProtocol::BUFFER_SCALE:
+ // Never update buffer scale if not possible to set it
+ if (m_surface.can_set_buffer_scale())
+ {
+ newScale = (reinterpret_cast<WinSystemWaylandProtocol::MsgBufferScale*> (message->data))->scale;
+ }
+ break;
+ }
+ }
+
+ if (lastConfigureMessage)
+ {
+ if (skippedConfigures > 0)
+ {
+ CLog::LogF(LOGDEBUG, "Skipped {} configures", skippedConfigures);
+ }
+ // Wayland will tell us here the size of the surface that was actually created,
+ // which might be different from what we expected e.g. when fullscreening
+ // on an output we chose - the compositor might have decided to use a different
+ // output for example
+ // It is very important that the EGL native module and the rendering system use the
+ // Wayland-announced size for rendering or corrupted graphics output will result.
+ auto configure = reinterpret_cast<WinSystemWaylandProtocol::MsgConfigure*> (lastConfigureMessage.Get()->data);
+ CLog::LogF(LOGDEBUG, "Configure serial {}: size {}x{} state {}", configure->serial,
+ configure->surfaceSize.Width(), configure->surfaceSize.Height(),
+ IShellSurface::StateToString(configure->state));
+
+
+ CSizeInt size = configure->surfaceSize;
+ bool sizeIncludesDecoration = true;
+
+ if (size.IsZero())
+ {
+ if (configure->state.test(IShellSurface::STATE_FULLSCREEN))
+ {
+ // Do not change current size - UpdateWithConfiguredSize must be called regardless in case
+ // scale or something else changed
+ size = m_configuredSize;
+ }
+ else
+ {
+ // Compositor has no preference and we're windowed
+ // -> adopt windowed size that Kodi wants
+ auto const& windowed = CDisplaySettings::GetInstance().GetResolutionInfo(RES_WINDOW);
+ // Kodi resolution is buffer size, but SetResolutionInternal expects
+ // surface size, so divide by m_scale
+ size = CSizeInt{windowed.iWidth, windowed.iHeight} / newScale;
+ CLog::LogF(LOGDEBUG, "Adapting Kodi windowed size {}x{}", size.Width(), size.Height());
+ sizeIncludesDecoration = false;
+ }
+ }
+
+ SetResolutionInternal(size, newScale, configure->state, sizeIncludesDecoration, true, configure->serial);
+ }
+ // If we were also configured, scale is already taken care of. But it could
+ // also be a scale change without configure, so apply that.
+ else if (m_scale != newScale)
+ {
+ SetResolutionInternal(m_configuredSize, newScale, m_shellSurfaceState, true, false);
+ }
+}
+
+void CWinSystemWayland::ApplyShellSurfaceState(IShellSurface::StateBitset state)
+{
+ m_windowDecorator->SetState(m_configuredSize, m_scale, state);
+ m_shellSurfaceState = state;
+}
+
+void CWinSystemWayland::OnConfigure(std::uint32_t serial, CSizeInt size, IShellSurface::StateBitset state)
+{
+ if (m_shellSurfaceInitializing)
+ {
+ CLog::LogF(LOGDEBUG, "Initial configure serial {}: size {}x{} state {}", serial, size.Width(),
+ size.Height(), IShellSurface::StateToString(state));
+ m_shellSurfaceState = state;
+ if (!size.IsZero())
+ {
+ UpdateSizeVariables(size, m_scale, m_shellSurfaceState, true);
+ }
+ AckConfigure(serial);
+ }
+ else
+ {
+ WinSystemWaylandProtocol::MsgConfigure msg{serial, size, state};
+ m_protocol.SendOutMessage(WinSystemWaylandProtocol::CONFIGURE, &msg, sizeof(msg));
+ }
+}
+
+void CWinSystemWayland::AckConfigure(std::uint32_t serial)
+{
+ // Send ack if we have a new serial number or this is the first time
+ // this function is called
+ if (serial != m_lastAckedSerial || !m_firstSerialAcked)
+ {
+ CLog::LogF(LOGDEBUG, "Acking serial {}", serial);
+ m_shellSurface->AckConfigure(serial);
+ m_lastAckedSerial = serial;
+ m_firstSerialAcked = true;
+ }
+}
+
+/**
+ * Recalculate sizes from given parameters, apply them and update Kodi CDisplaySettings
+ * resolution if necessary
+ *
+ * This function should be called when events internal to the windowing system
+ * such as a compositor configure lead to a size change.
+ *
+ * Call only from main thread.
+ *
+ * \param size configured size, can be zero if compositor does not have a preference
+ * \param scale new buffer scale
+ * \param sizeIncludesDecoration whether size includes the size of the window decorations if present
+ */
+void CWinSystemWayland::SetResolutionInternal(CSizeInt size, std::int32_t scale, IShellSurface::StateBitset state, bool sizeIncludesDecoration, bool mustAck, std::uint32_t configureSerial)
+{
+ // This should never be called while a size set is pending
+ assert(!m_waitingForApply);
+
+ bool fullScreen{state.test(IShellSurface::STATE_FULLSCREEN)};
+ auto sizes = CalculateSizes(size, scale, state, sizeIncludesDecoration);
+
+ CLog::LogF(LOGDEBUG, "Set size for serial {}: {}x{} {} decoration at scale {} state {}",
+ configureSerial, size.Width(), size.Height(),
+ sizeIncludesDecoration ? "including" : "excluding", scale,
+ IShellSurface::StateToString(state));
+
+ // Get actual frame rate from monitor, take highest frame rate if multiple
+ float refreshRate{m_fRefreshRate};
+ {
+ std::unique_lock<CCriticalSection> lock(m_surfaceOutputsMutex);
+ auto maxRefreshIt = std::max_element(m_surfaceOutputs.cbegin(), m_surfaceOutputs.cend(), OutputCurrentRefreshRateComparer());
+ if (maxRefreshIt != m_surfaceOutputs.cend())
+ {
+ refreshRate = (*maxRefreshIt)->GetCurrentMode().GetRefreshInHz();
+ CLog::LogF(LOGDEBUG, "Resolved actual (maximum) refresh rate to {:.3f} Hz on output \"{}\"",
+ refreshRate, UserFriendlyOutputName(*maxRefreshIt));
+ }
+ }
+
+ m_next.mustBeAcked = mustAck;
+ m_next.configureSerial = configureSerial;
+ m_next.configuredSize = sizes.configuredSize;
+ m_next.scale = scale;
+ m_next.shellSurfaceState = state;
+
+ // Check if any parameters of the Kodi resolution configuration changed
+ if (refreshRate != m_fRefreshRate || sizes.bufferSize != m_bufferSize || m_bFullScreen != fullScreen)
+ {
+ if (!fullScreen)
+ {
+ if (m_bFullScreen)
+ {
+ XBMC_Event msg{};
+ msg.type = XBMC_MODECHANGE;
+ msg.mode.res = RES_WINDOW;
+ SetWindowResolution(sizes.bufferSize.Width(), sizes.bufferSize.Height());
+ // FIXME
+ dynamic_cast<CWinEventsWayland&>(*m_winEvents).MessagePush(&msg);
+ m_waitingForApply = true;
+ CLog::LogF(LOGDEBUG, "Queued change to windowed mode size {}x{}", sizes.bufferSize.Width(),
+ sizes.bufferSize.Height());
+ }
+ else
+ {
+ XBMC_Event msg{};
+ msg.type = XBMC_VIDEORESIZE;
+ msg.resize = {sizes.bufferSize.Width(), sizes.bufferSize.Height()};
+ // FIXME
+ dynamic_cast<CWinEventsWayland&>(*m_winEvents).MessagePush(&msg);
+ m_waitingForApply = true;
+ CLog::LogF(LOGDEBUG, "Queued change to windowed buffer size {}x{}",
+ sizes.bufferSize.Width(), sizes.bufferSize.Height());
+ }
+ }
+ else
+ {
+ // Find matching Kodi resolution member
+ RESOLUTION res{FindMatchingCustomResolution(sizes.bufferSize, refreshRate)};
+ if (res == RES_INVALID)
+ {
+ // Add new resolution if none found
+ RESOLUTION_INFO newResInfo;
+ // we just assume the compositor put us on the right output
+ UpdateDesktopResolution(newResInfo, CDisplaySettings::GetInstance().GetCurrentResolutionInfo().strOutput, sizes.bufferSize.Width(), sizes.bufferSize.Height(), refreshRate, 0);
+ CDisplaySettings::GetInstance().AddResolutionInfo(newResInfo);
+ CDisplaySettings::GetInstance().ApplyCalibrations();
+ res = static_cast<RESOLUTION> (CDisplaySettings::GetInstance().ResolutionInfoSize() - 1);
+ }
+
+ XBMC_Event msg{};
+ msg.type = XBMC_MODECHANGE;
+ msg.mode.res = res;
+ // FIXME
+ dynamic_cast<CWinEventsWayland&>(*m_winEvents).MessagePush(&msg);
+ m_waitingForApply = true;
+ CLog::LogF(LOGDEBUG, "Queued change to resolution {} surface size {}x{} scale {} state {}",
+ res, sizes.surfaceSize.Width(), sizes.surfaceSize.Height(), scale,
+ IShellSurface::StateToString(state));
+ }
+ }
+ else
+ {
+ // Apply directly, Kodi resolution does not change
+ ApplyNextState();
+ }
+}
+
+void CWinSystemWayland::FinishModeChange(RESOLUTION res)
+{
+ const auto& resInfo = CDisplaySettings::GetInstance().GetResolutionInfo(res);
+
+ ApplyNextState();
+
+ m_fRefreshRate = resInfo.fRefreshRate;
+ m_bFullScreen = resInfo.bFullScreen;
+ m_waitingForApply = false;
+}
+
+void CWinSystemWayland::FinishWindowResize(int, int)
+{
+ ApplyNextState();
+ m_waitingForApply = false;
+}
+
+void CWinSystemWayland::ApplyNextState()
+{
+ CLog::LogF(LOGDEBUG, "Applying next state: serial {} configured size {}x{} scale {} state {}",
+ m_next.configureSerial, m_next.configuredSize.Width(), m_next.configuredSize.Height(),
+ m_next.scale, IShellSurface::StateToString(m_next.shellSurfaceState));
+
+ ApplyShellSurfaceState(m_next.shellSurfaceState);
+ auto updateResult = UpdateSizeVariables(m_next.configuredSize, m_next.scale, m_next.shellSurfaceState, true);
+ ApplySizeUpdate(updateResult);
+
+ if (m_next.mustBeAcked)
+ {
+ AckConfigure(m_next.configureSerial);
+ }
+}
+
+CWinSystemWayland::Sizes CWinSystemWayland::CalculateSizes(CSizeInt size, int scale, IShellSurface::StateBitset state, bool sizeIncludesDecoration)
+{
+ Sizes result;
+
+ // Clamp to a sensible range
+ constexpr int MIN_WIDTH{300};
+ constexpr int MIN_HEIGHT{200};
+ if (size.Width() < MIN_WIDTH)
+ {
+ CLog::LogF(LOGWARNING, "Width {} is very small, clamping to {}", size.Width(), MIN_WIDTH);
+ size.SetWidth(MIN_WIDTH);
+ }
+ if (size.Height() < MIN_HEIGHT)
+ {
+ CLog::LogF(LOGWARNING, "Height {} is very small, clamping to {}", size.Height(), MIN_HEIGHT);
+ size.SetHeight(MIN_HEIGHT);
+ }
+
+ // Depending on whether the size has decorations included (i.e. comes from the
+ // compositor or from Kodi), we need to calculate differently
+ if (sizeIncludesDecoration)
+ {
+ result.configuredSize = size;
+ result.surfaceSize = m_windowDecorator->CalculateMainSurfaceSize(size, state);
+ }
+ else
+ {
+ result.surfaceSize = size;
+ result.configuredSize = m_windowDecorator->CalculateFullSurfaceSize(size, state);
+ }
+
+ result.bufferSize = result.surfaceSize * scale;
+
+ return result;
+}
+
+
+/**
+ * Calculate internal resolution from surface size and set variables
+ *
+ * \param next surface size
+ * \param scale new buffer scale
+ * \param state window state to determine whether decorations are enabled at all
+ * \param sizeIncludesDecoration if true, given size includes potential window decorations
+ * \return whether main buffer (not surface) size changed
+ */
+CWinSystemWayland::SizeUpdateInformation CWinSystemWayland::UpdateSizeVariables(CSizeInt size, int scale, IShellSurface::StateBitset state, bool sizeIncludesDecoration)
+{
+ CLog::LogF(LOGDEBUG, "Set size {}x{} scale {} {} decorations with state {}", size.Width(),
+ size.Height(), scale, sizeIncludesDecoration ? "including" : "excluding",
+ IShellSurface::StateToString(state));
+
+ auto oldSurfaceSize = m_surfaceSize;
+ auto oldBufferSize = m_bufferSize;
+ auto oldConfiguredSize = m_configuredSize;
+ auto oldBufferScale = m_scale;
+
+ m_scale = scale;
+ auto sizes = CalculateSizes(size, scale, state, sizeIncludesDecoration);
+ m_surfaceSize = sizes.surfaceSize;
+ m_bufferSize = sizes.bufferSize;
+ m_configuredSize = sizes.configuredSize;
+
+ SizeUpdateInformation changes{m_surfaceSize != oldSurfaceSize, m_bufferSize != oldBufferSize, m_configuredSize != oldConfiguredSize, m_scale != oldBufferScale};
+
+ if (changes.surfaceSizeChanged)
+ {
+ CLog::LogF(LOGINFO, "Surface size changed: {}x{} -> {}x{}", oldSurfaceSize.Width(),
+ oldSurfaceSize.Height(), m_surfaceSize.Width(), m_surfaceSize.Height());
+ }
+ if (changes.bufferSizeChanged)
+ {
+ CLog::LogF(LOGINFO, "Buffer size changed: {}x{} -> {}x{}", oldBufferSize.Width(),
+ oldBufferSize.Height(), m_bufferSize.Width(), m_bufferSize.Height());
+ }
+ if (changes.configuredSizeChanged)
+ {
+ CLog::LogF(LOGINFO, "Configured size changed: {}x{} -> {}x{}", oldConfiguredSize.Width(),
+ oldConfiguredSize.Height(), m_configuredSize.Width(), m_configuredSize.Height());
+ }
+ if (changes.bufferScaleChanged)
+ {
+ CLog::LogF(LOGINFO, "Buffer scale changed: {} -> {}", oldBufferScale, m_scale);
+ }
+
+ return changes;
+}
+
+std::string CWinSystemWayland::UserFriendlyOutputName(std::shared_ptr<COutput> const& output)
+{
+ std::vector<std::string> parts;
+ if (!output->GetMake().empty())
+ {
+ parts.emplace_back(output->GetMake());
+ }
+ if (!output->GetModel().empty())
+ {
+ parts.emplace_back(output->GetModel());
+ }
+ if (parts.empty())
+ {
+ // Fallback to "unknown" if no name received from compositor
+ parts.emplace_back(g_localizeStrings.Get(13205));
+ }
+
+ // Add position
+ auto pos = output->GetPosition();
+ if (pos.x != 0 || pos.y != 0)
+ {
+ parts.emplace_back(StringUtils::Format("@{}x{}", pos.x, pos.y));
+ }
+
+ return StringUtils::Join(parts, " ");
+}
+
+bool CWinSystemWayland::Minimize()
+{
+ m_shellSurface->SetMinimized();
+ return true;
+}
+
+bool CWinSystemWayland::HasCursor()
+{
+ std::unique_lock<CCriticalSection> lock(m_seatsMutex);
+ return std::any_of(m_seats.cbegin(), m_seats.cend(),
+ [](decltype(m_seats)::value_type const& entry)
+ {
+ return entry.second.HasPointerCapability();
+ });
+}
+
+void CWinSystemWayland::ShowOSMouse(bool show)
+{
+ m_osCursorVisible = show;
+}
+
+void CWinSystemWayland::LoadDefaultCursor()
+{
+ if (!m_cursorSurface)
+ {
+ // Load default cursor theme and default cursor
+ // Size of 24px is what most themes seem to have
+ m_cursorTheme = wayland::cursor_theme_t("", 24, m_shm);
+ wayland::cursor_t cursor;
+ try
+ {
+ cursor = CCursorUtil::LoadFromTheme(m_cursorTheme, "default");
+ }
+ catch (std::exception const& e)
+ {
+ CLog::Log(LOGWARNING, "Could not load default cursor from theme, continuing without OS cursor");
+ }
+ // Just use the first image, do not handle animation
+ m_cursorImage = cursor.image(0);
+ m_cursorBuffer = m_cursorImage.get_buffer();
+ m_cursorSurface = m_compositor.create_surface();
+ }
+ // Attach buffer to a surface - it seems that the compositor may change
+ // the cursor surface when the pointer leaves our surface, so we reattach the
+ // buffer each time
+ m_cursorSurface.attach(m_cursorBuffer, 0, 0);
+ m_cursorSurface.damage(0, 0, m_cursorImage.width(), m_cursorImage.height());
+ m_cursorSurface.commit();
+}
+
+void CWinSystemWayland::Register(IDispResource* resource)
+{
+ std::unique_lock<CCriticalSection> lock(m_dispResourcesMutex);
+ m_dispResources.emplace(resource);
+}
+
+void CWinSystemWayland::Unregister(IDispResource* resource)
+{
+ std::unique_lock<CCriticalSection> lock(m_dispResourcesMutex);
+ m_dispResources.erase(resource);
+}
+
+void CWinSystemWayland::OnSeatAdded(std::uint32_t name, wayland::proxy_t&& proxy)
+{
+ std::unique_lock<CCriticalSection> lock(m_seatsMutex);
+
+ wayland::seat_t seat(proxy);
+ auto newSeatEmplace = m_seats.emplace(std::piecewise_construct,
+ std::forward_as_tuple(name),
+ std::forward_as_tuple(name, seat, *m_connection));
+
+ auto& seatInst = newSeatEmplace.first->second;
+ m_seatInputProcessing->AddSeat(&seatInst);
+ m_windowDecorator->AddSeat(&seatInst);
+}
+
+void CWinSystemWayland::OnSeatRemoved(std::uint32_t name)
+{
+ std::unique_lock<CCriticalSection> lock(m_seatsMutex);
+
+ auto seatI = m_seats.find(name);
+ if (seatI != m_seats.end())
+ {
+ m_seatInputProcessing->RemoveSeat(&seatI->second);
+ m_windowDecorator->RemoveSeat(&seatI->second);
+ m_seats.erase(name);
+ }
+}
+
+void CWinSystemWayland::OnOutputAdded(std::uint32_t name, wayland::proxy_t&& proxy)
+{
+ wayland::output_t output(proxy);
+ // This is not accessed from multiple threads
+ m_outputsInPreparation.emplace(name, std::make_shared<COutput>(name, output, std::bind(&CWinSystemWayland::OnOutputDone, this, name)));
+}
+
+void CWinSystemWayland::OnOutputDone(std::uint32_t name)
+{
+ auto it = m_outputsInPreparation.find(name);
+ if (it != m_outputsInPreparation.end())
+ {
+ // This output was added for the first time - done is also sent when
+ // output parameters change later
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_outputsMutex);
+ // Move from m_outputsInPreparation to m_outputs
+ m_outputs.emplace(std::move(*it));
+ m_outputsInPreparation.erase(it);
+ }
+
+ m_protocol.SendOutMessage(WinSystemWaylandProtocol::OUTPUT_HOTPLUG);
+ }
+
+ UpdateBufferScale();
+}
+
+void CWinSystemWayland::OnOutputRemoved(std::uint32_t name)
+{
+ m_outputsInPreparation.erase(name);
+
+ std::unique_lock<CCriticalSection> lock(m_outputsMutex);
+ if (m_outputs.erase(name) != 0)
+ {
+ // Theoretically, the compositor should automatically put us on another
+ // (visible and connected) output if the output we were on is lost,
+ // so there is nothing in particular to do here
+ }
+}
+
+void CWinSystemWayland::SendFocusChange(bool focus)
+{
+ g_application.m_AppFocused = focus;
+ std::unique_lock<CCriticalSection> lock(m_dispResourcesMutex);
+ for (auto dispResource : m_dispResources)
+ {
+ dispResource->OnAppFocusChange(focus);
+ }
+}
+
+void CWinSystemWayland::OnEnter(InputType type)
+{
+ // Couple to keyboard focus
+ if (type == InputType::KEYBOARD)
+ {
+ SendFocusChange(true);
+ }
+ if (type == InputType::POINTER)
+ {
+ CServiceBroker::GetInputManager().SetMouseActive(true);
+ }
+}
+
+void CWinSystemWayland::OnLeave(InputType type)
+{
+ // Couple to keyboard focus
+ if (type == InputType::KEYBOARD)
+ {
+ SendFocusChange(false);
+ }
+ if (type == InputType::POINTER)
+ {
+ CServiceBroker::GetInputManager().SetMouseActive(false);
+ }
+}
+
+void CWinSystemWayland::OnEvent(InputType type, XBMC_Event& event)
+{
+ // FIXME
+ dynamic_cast<CWinEventsWayland&>(*m_winEvents).MessagePush(&event);
+}
+
+void CWinSystemWayland::OnSetCursor(std::uint32_t seatGlobalName, std::uint32_t serial)
+{
+ auto seatI = m_seats.find(seatGlobalName);
+ if (seatI == m_seats.end())
+ {
+ return;
+ }
+
+ if (m_osCursorVisible)
+ {
+ LoadDefaultCursor();
+ if (m_cursorSurface) // Cursor loading could have failed
+ {
+ seatI->second.SetCursor(serial, m_cursorSurface, m_cursorImage.hotspot_x(), m_cursorImage.hotspot_y());
+ }
+ }
+ else
+ {
+ seatI->second.SetCursor(serial, wayland::surface_t{}, 0, 0);
+ }
+}
+
+void CWinSystemWayland::UpdateBufferScale()
+{
+ // Adjust our surface size to the output with the biggest scale in order
+ // to get the best quality
+ auto const maxBufferScaleIt = std::max_element(m_surfaceOutputs.cbegin(), m_surfaceOutputs.cend(), OutputScaleComparer());
+ if (maxBufferScaleIt != m_surfaceOutputs.cend())
+ {
+ WinSystemWaylandProtocol::MsgBufferScale msg{(*maxBufferScaleIt)->GetScale()};
+ m_protocol.SendOutMessage(WinSystemWaylandProtocol::BUFFER_SCALE, &msg, sizeof(msg));
+ }
+}
+
+void CWinSystemWayland::ApplyBufferScale()
+{
+ CLog::LogF(LOGINFO, "Setting Wayland buffer scale to {}", m_scale);
+ m_surface.set_buffer_scale(m_scale);
+ m_windowDecorator->SetState(m_configuredSize, m_scale, m_shellSurfaceState);
+ m_seatInputProcessing->SetCoordinateScale(m_scale);
+}
+
+void CWinSystemWayland::UpdateTouchDpi()
+{
+ // If we have multiple outputs with wildly different DPI, this is really just
+ // guesswork to get a halfway reasonable value. min/max would probably also be OK.
+ float dpiSum = std::accumulate(m_surfaceOutputs.cbegin(), m_surfaceOutputs.cend(), 0.0f,
+ [](float acc, std::shared_ptr<COutput> const& output)
+ {
+ return acc + output->GetCurrentDpi();
+ });
+ float dpi = dpiSum / m_surfaceOutputs.size();
+ CLog::LogF(LOGDEBUG, "Computed average dpi of {:.3f} for touch handler", dpi);
+ CGenericTouchInputHandler::GetInstance().SetScreenDPI(dpi);
+}
+
+CWinSystemWayland::SurfaceSubmission::SurfaceSubmission(timespec const& submissionTime, wayland::presentation_feedback_t const& feedback)
+: submissionTime{submissionTime}, feedback{feedback}
+{
+}
+
+timespec CWinSystemWayland::GetPresentationClockTime()
+{
+ timespec time;
+ if (clock_gettime(m_presentationClock, &time) != 0)
+ {
+ throw std::system_error(errno, std::generic_category(), "Error getting time from Wayland presentation clock with clock_gettime");
+ }
+ return time;
+}
+
+void CWinSystemWayland::PrepareFramePresentation()
+{
+ // Continuously measure display latency (i.e. time between when the frame was rendered
+ // and when it becomes visible to the user) to correct AV sync
+ if (m_presentation)
+ {
+ auto tStart = GetPresentationClockTime();
+ // wp_presentation_feedback creation is coupled to the surface's commit().
+ // eglSwapBuffers() (which will be called after this) will call commit().
+ // This creates a new Wayland protocol object in the main thread, but this
+ // will not result in a race since the corresponding events are never sent
+ // before commit() on the surface, which only occurs afterwards.
+ auto feedback = m_presentation.feedback(m_surface);
+ // Save feedback objects in list so they don't get destroyed upon exit of this function
+ // Hand iterator to lambdas so they do not hold a (then circular) reference
+ // to the actual object
+ decltype(m_surfaceSubmissions)::iterator iter;
+ {
+ std::unique_lock<CCriticalSection> lock(m_surfaceSubmissionsMutex);
+ iter = m_surfaceSubmissions.emplace(m_surfaceSubmissions.end(), tStart, feedback);
+ }
+
+ feedback.on_sync_output() = [this](const wayland::output_t& wloutput) {
+ m_syncOutputID = wloutput.get_id();
+ auto output = FindOutputByWaylandOutput(wloutput);
+ if (output)
+ {
+ m_syncOutputRefreshRate = output->GetCurrentMode().GetRefreshInHz();
+ }
+ else
+ {
+ CLog::Log(LOGWARNING, "Could not find Wayland output that is supposedly the sync output");
+ }
+ };
+ feedback.on_presented() = [this, iter](std::uint32_t tvSecHi, std::uint32_t tvSecLo,
+ std::uint32_t tvNsec, std::uint32_t refresh,
+ std::uint32_t seqHi, std::uint32_t seqLo,
+ const wayland::presentation_feedback_kind& flags) {
+ timespec tv = { .tv_sec = static_cast<std::time_t> ((static_cast<std::uint64_t>(tvSecHi) << 32) + tvSecLo), .tv_nsec = static_cast<long>(tvNsec) };
+ std::int64_t latency{KODI::LINUX::TimespecDifference(iter->submissionTime, tv)};
+ std::uint64_t msc{(static_cast<std::uint64_t>(seqHi) << 32) + seqLo};
+ m_presentationFeedbackHandlers.Invoke(tv, refresh, m_syncOutputID, m_syncOutputRefreshRate, msc);
+
+ iter->latency = latency / 1e9f; // nanoseconds to seconds
+ float adjust{};
+ {
+ std::unique_lock<CCriticalSection> lock(m_surfaceSubmissionsMutex);
+ if (m_surfaceSubmissions.size() > LATENCY_MOVING_AVERAGE_SIZE)
+ {
+ adjust = - m_surfaceSubmissions.front().latency / LATENCY_MOVING_AVERAGE_SIZE;
+ m_surfaceSubmissions.pop_front();
+ }
+ }
+ m_latencyMovingAverage = m_latencyMovingAverage + iter->latency / LATENCY_MOVING_AVERAGE_SIZE + adjust;
+
+ CLog::Log(LOGDEBUG, LOGAVTIMING, "Presentation feedback: {} ns -> moving average {:f} s",
+ latency, static_cast<double>(m_latencyMovingAverage));
+ };
+ feedback.on_discarded() = [this,iter]()
+ {
+ CLog::Log(LOGDEBUG, "Presentation: Frame was discarded by compositor");
+ std::unique_lock<CCriticalSection> lock(m_surfaceSubmissionsMutex);
+ m_surfaceSubmissions.erase(iter);
+ };
+ }
+
+ // Now wait for the frame callback that tells us that it is a good time to start drawing
+ //
+ // To sum up, we:
+ // 1. wait until a frame() drawing hint from the compositor arrives,
+ // 2. request a new frame() hint for the next presentation
+ // 2. then commit the backbuffer to the surface and immediately
+ // return, i.e. drawing can start again
+ // This means that rendering is optimized for maximum time available for
+ // our repaint and reliable timing rather than latency. With weston, latency
+ // will usually be on the order of two frames plus a few milliseconds.
+ // The frame timings become irregular though when nothing is rendered because
+ // kodi then sleeps for a fixed time without swapping buffers. This makes us
+ // immediately attach the next buffer because the frame callback has already arrived when
+ // this function is called and step 1. above is skipped. As we render with full
+ // FPS during video playback anyway and the timing is otherwise not relevant,
+ // this should not be a problem.
+ if (m_frameCallback)
+ {
+ // If the window is e.g. minimized, chances are that we will *never* get frame
+ // callbacks from the compositor for optimization reasons.
+ // Still, the app should remain functional, which means that we can't
+ // just block forever here - if the render thread is blocked, Kodi will not
+ // function normally. It would also be impossible to close the application
+ // while it is minimized (since the wait needs to be interrupted for that).
+ // -> Use Wait with timeout here so we can maintain a reasonable frame rate
+ // even when the window is not visible and we do not get any frame callbacks.
+ if (m_frameCallbackEvent.Wait(50ms))
+ {
+ // Only reset frame callback object a callback was received so a
+ // new one is not requested continuously
+ m_frameCallback = {};
+ m_frameCallbackEvent.Reset();
+ }
+ }
+
+ if (!m_frameCallback)
+ {
+ // Get frame callback event for checking in the next call to this function
+ m_frameCallback = m_surface.frame();
+ m_frameCallback.on_done() = [this](std::uint32_t)
+ {
+ m_frameCallbackEvent.Set();
+ };
+ }
+}
+
+void CWinSystemWayland::FinishFramePresentation()
+{
+ ProcessMessages();
+
+ m_frameStartTime = std::chrono::steady_clock::now();
+}
+
+float CWinSystemWayland::GetFrameLatencyAdjustment()
+{
+ const auto now = std::chrono::steady_clock::now();
+ const std::chrono::duration<float, std::milli> duration = now - m_frameStartTime;
+ return duration.count();
+}
+
+float CWinSystemWayland::GetDisplayLatency()
+{
+ if (m_presentation)
+ {
+ return m_latencyMovingAverage * 1000.0f;
+ }
+ else
+ {
+ return CWinSystemBase::GetDisplayLatency();
+ }
+}
+
+float CWinSystemWayland::GetSyncOutputRefreshRate()
+{
+ return m_syncOutputRefreshRate;
+}
+
+KODI::CSignalRegistration CWinSystemWayland::RegisterOnPresentationFeedback(
+ const PresentationFeedbackHandler& handler)
+{
+ return m_presentationFeedbackHandlers.Register(handler);
+}
+
+std::unique_ptr<CVideoSync> CWinSystemWayland::GetVideoSync(void* clock)
+{
+ if (m_surface && m_presentation)
+ {
+ CLog::LogF(LOGINFO, "Using presentation protocol for video sync");
+ return std::unique_ptr<CVideoSync>(new CVideoSyncWpPresentation(clock, *this));
+ }
+ else
+ {
+ CLog::LogF(LOGINFO, "No supported method for video sync found");
+ return nullptr;
+ }
+}
+
+std::unique_ptr<IOSScreenSaver> CWinSystemWayland::GetOSScreenSaverImpl()
+{
+ if (m_surface)
+ {
+ std::unique_ptr<IOSScreenSaver> ptr;
+ ptr.reset(COSScreenSaverIdleInhibitUnstableV1::TryCreate(*m_connection, m_surface));
+ if (ptr)
+ {
+ CLog::LogF(LOGINFO, "Using idle-inhibit-unstable-v1 protocol for screen saver inhibition");
+ return ptr;
+ }
+ }
+
+#if defined(HAS_DBUS)
+ if (KODI::WINDOWING::LINUX::COSScreenSaverFreedesktop::IsAvailable())
+ {
+ CLog::LogF(LOGINFO, "Using freedesktop.org DBus interface for screen saver inhibition");
+ return std::unique_ptr<IOSScreenSaver>(new KODI::WINDOWING::LINUX::COSScreenSaverFreedesktop);
+ }
+#endif
+
+ CLog::LogF(LOGINFO, "No supported method for screen saver inhibition found");
+ return std::unique_ptr<IOSScreenSaver>(new CDummyOSScreenSaver);
+}
+
+std::string CWinSystemWayland::GetClipboardText()
+{
+ std::unique_lock<CCriticalSection> lock(m_seatsMutex);
+ // Get text of first seat with non-empty selection
+ // Actually, the value of the seat that received the Ctrl+V keypress should be used,
+ // but this would need a workaround or proper multi-seat support in Kodi - it's
+ // probably just not that relevant in practice
+ for (auto const& seat : m_seats)
+ {
+ auto text = seat.second.GetSelectionText();
+ if (text != "")
+ {
+ return text;
+ }
+ }
+ return "";
+}
+
+void CWinSystemWayland::OnWindowMove(const wayland::seat_t& seat, std::uint32_t serial)
+{
+ m_shellSurface->StartMove(seat, serial);
+}
+
+void CWinSystemWayland::OnWindowResize(const wayland::seat_t& seat, std::uint32_t serial, wayland::shell_surface_resize edge)
+{
+ m_shellSurface->StartResize(seat, serial, edge);
+}
+
+void CWinSystemWayland::OnWindowShowContextMenu(const wayland::seat_t& seat, std::uint32_t serial, CPointInt position)
+{
+ m_shellSurface->ShowShellContextMenu(seat, serial, position);
+}
+
+void CWinSystemWayland::OnWindowClose()
+{
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_QUIT);
+}
+
+void CWinSystemWayland::OnWindowMinimize()
+{
+ m_shellSurface->SetMinimized();
+}
+
+void CWinSystemWayland::OnWindowMaximize()
+{
+ if (m_shellSurfaceState.test(IShellSurface::STATE_MAXIMIZED))
+ {
+ m_shellSurface->UnsetMaximized();
+ }
+ else
+ {
+ m_shellSurface->SetMaximized();
+ }
+}
+
+void CWinSystemWayland::OnClose()
+{
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_QUIT);
+}
+
+bool CWinSystemWayland::MessagePump()
+{
+ return m_winEvents->MessagePump();
+}
diff --git a/xbmc/windowing/wayland/WinSystemWayland.h b/xbmc/windowing/wayland/WinSystemWayland.h
new file mode 100644
index 0000000..514b8d6
--- /dev/null
+++ b/xbmc/windowing/wayland/WinSystemWayland.h
@@ -0,0 +1,300 @@
+/*
+ * 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 "Connection.h"
+#include "Output.h"
+#include "Seat.h"
+#include "SeatInputProcessing.h"
+#include "ShellSurface.h"
+#include "Signals.h"
+#include "WindowDecorationHandler.h"
+#include "threads/CriticalSection.h"
+#include "threads/Event.h"
+#include "utils/ActorProtocol.h"
+#include "windowing/WinSystem.h"
+
+#include <atomic>
+#include <chrono>
+#include <ctime>
+#include <list>
+#include <map>
+#include <set>
+#include <time.h>
+
+#include <wayland-client.hpp>
+#include <wayland-cursor.hpp>
+#include <wayland-extra-protocols.hpp>
+
+class IDispResource;
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace WAYLAND
+{
+
+class CRegistry;
+class CWindowDecorator;
+
+class CWinSystemWayland : public CWinSystemBase, IInputHandler, IWindowDecorationHandler, IShellSurfaceHandler
+{
+public:
+ CWinSystemWayland();
+ ~CWinSystemWayland() noexcept override;
+
+ const std::string GetName() override { return "wayland"; }
+
+ 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;
+ bool SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) override;
+ void FinishModeChange(RESOLUTION res) override;
+ void FinishWindowResize(int newWidth, int newHeight) override;
+
+ bool UseLimitedColor() override;
+
+ void UpdateResolutions() override;
+
+ bool CanDoWindowed() override;
+ bool Minimize() override;
+
+ bool HasCursor() override;
+ void ShowOSMouse(bool show) override;
+
+ std::string GetClipboardText() override;
+
+ float GetSyncOutputRefreshRate();
+ float GetDisplayLatency() override;
+ float GetFrameLatencyAdjustment() override;
+ std::unique_ptr<CVideoSync> GetVideoSync(void* clock) override;
+
+ void Register(IDispResource* resource) override;
+ void Unregister(IDispResource* resource) override;
+
+ using PresentationFeedbackHandler = std::function<void(timespec /* tv */, std::uint32_t /* refresh */, std::uint32_t /* sync output id */, float /* sync output fps */, std::uint64_t /* msc */)>;
+ CSignalRegistration RegisterOnPresentationFeedback(const PresentationFeedbackHandler& handler);
+
+ std::vector<std::string> GetConnectedOutputs() override;
+
+ // winevents override
+ bool MessagePump() override;
+
+protected:
+ std::unique_ptr<KODI::WINDOWING::IOSScreenSaver> GetOSScreenSaverImpl() override;
+ CSizeInt GetBufferSize() const
+ {
+ return m_bufferSize;
+ }
+ std::unique_ptr<CConnection> const& GetConnection()
+ {
+ return m_connection;
+ }
+ wayland::surface_t GetMainSurface()
+ {
+ return m_surface;
+ }
+
+ void PrepareFramePresentation();
+ void FinishFramePresentation();
+ virtual void SetContextSize(CSizeInt size) = 0;
+
+private:
+ // IInputHandler
+ void OnEnter(InputType type) override;
+ void OnLeave(InputType type) override;
+ void OnEvent(InputType type, XBMC_Event& event) override;
+ void OnSetCursor(std::uint32_t seatGlobalName, std::uint32_t serial) override;
+
+ // IWindowDecorationHandler
+ void OnWindowMove(const wayland::seat_t& seat, std::uint32_t serial) override;
+ void OnWindowResize(const wayland::seat_t& seat, std::uint32_t serial, wayland::shell_surface_resize edge) override;
+ void OnWindowShowContextMenu(const wayland::seat_t& seat, std::uint32_t serial, CPointInt position) override;
+ void OnWindowClose() override;
+ void OnWindowMaximize() override;
+ void OnWindowMinimize() override;
+
+ // IShellSurfaceHandler
+ void OnConfigure(std::uint32_t serial, CSizeInt size, IShellSurface::StateBitset state) override;
+ void OnClose() override;
+
+ // Registry handlers
+ void OnSeatAdded(std::uint32_t name, wayland::proxy_t&& seat);
+ void OnSeatRemoved(std::uint32_t name);
+ void OnOutputAdded(std::uint32_t name, wayland::proxy_t&& output);
+ void OnOutputRemoved(std::uint32_t name);
+
+ void LoadDefaultCursor();
+ void SendFocusChange(bool focus);
+ bool SetResolutionExternal(bool fullScreen, RESOLUTION_INFO const& res);
+ void SetResolutionInternal(CSizeInt size, int scale, IShellSurface::StateBitset state, bool sizeIncludesDecoration, bool mustAck = false, std::uint32_t configureSerial = 0u);
+ struct Sizes
+ {
+ CSizeInt surfaceSize;
+ CSizeInt bufferSize;
+ CSizeInt configuredSize;
+ };
+ Sizes CalculateSizes(CSizeInt size, int scale, IShellSurface::StateBitset state, bool sizeIncludesDecoration);
+ struct SizeUpdateInformation
+ {
+ bool surfaceSizeChanged : 1;
+ bool bufferSizeChanged : 1;
+ bool configuredSizeChanged : 1;
+ bool bufferScaleChanged : 1;
+ };
+ SizeUpdateInformation UpdateSizeVariables(CSizeInt size, int scale, IShellSurface::StateBitset state, bool sizeIncludesDecoration);
+ void ApplySizeUpdate(SizeUpdateInformation update);
+ void ApplyNextState();
+
+ std::string UserFriendlyOutputName(std::shared_ptr<COutput> const& output);
+ std::shared_ptr<COutput> FindOutputByUserFriendlyName(std::string const& name);
+ std::shared_ptr<COutput> FindOutputByWaylandOutput(wayland::output_t const& output);
+
+ // Called when wl_output::done is received for an output, i.e. associated
+ // information like modes is available
+ void OnOutputDone(std::uint32_t name);
+ void UpdateBufferScale();
+ void ApplyBufferScale();
+ void ApplyOpaqueRegion();
+ void ApplyWindowGeometry();
+ void UpdateTouchDpi();
+ void ApplyShellSurfaceState(IShellSurface::StateBitset state);
+
+ void ProcessMessages();
+ void AckConfigure(std::uint32_t serial);
+
+ timespec GetPresentationClockTime();
+
+ // Globals
+ // -------
+ std::unique_ptr<CConnection> m_connection;
+ std::unique_ptr<CRegistry> m_registry;
+ /**
+ * Registry used exclusively for wayland::seat_t
+ *
+ * Use extra registry because seats can only be registered after the surface
+ * has been created
+ */
+ std::unique_ptr<CRegistry> m_seatRegistry;
+ wayland::compositor_t m_compositor;
+ wayland::shm_t m_shm;
+ wayland::presentation_t m_presentation;
+
+ std::unique_ptr<IShellSurface> m_shellSurface;
+
+ // Frame callback handling
+ // -----------------------
+ wayland::callback_t m_frameCallback;
+ CEvent m_frameCallbackEvent;
+
+ // Seat handling
+ // -------------
+ std::map<std::uint32_t, CSeat> m_seats;
+ CCriticalSection m_seatsMutex;
+ std::unique_ptr<CSeatInputProcessing> m_seatInputProcessing;
+ std::map<std::uint32_t, std::shared_ptr<COutput>> m_outputs;
+ /// outputs that did not receive their done event yet
+ std::map<std::uint32_t, std::shared_ptr<COutput>> m_outputsInPreparation;
+ CCriticalSection m_outputsMutex;
+
+ // Windowed mode
+ // -------------
+ std::unique_ptr<CWindowDecorator> m_windowDecorator;
+
+ // Cursor
+ // ------
+ bool m_osCursorVisible{true};
+ wayland::cursor_theme_t m_cursorTheme;
+ wayland::buffer_t m_cursorBuffer;
+ wayland::cursor_image_t m_cursorImage;
+ wayland::surface_t m_cursorSurface;
+
+ // Presentation feedback
+ // ---------------------
+ clockid_t m_presentationClock;
+ struct SurfaceSubmission
+ {
+ timespec submissionTime;
+ float latency;
+ wayland::presentation_feedback_t feedback;
+ SurfaceSubmission(timespec const& submissionTime, wayland::presentation_feedback_t const& feedback);
+ };
+ std::list<SurfaceSubmission> m_surfaceSubmissions;
+ CCriticalSection m_surfaceSubmissionsMutex;
+ /// Protocol object ID of the sync output returned by wp_presentation
+ std::uint32_t m_syncOutputID;
+ /// Refresh rate of sync output returned by wp_presentation
+ std::atomic<float> m_syncOutputRefreshRate{0.0f};
+ static constexpr int LATENCY_MOVING_AVERAGE_SIZE{30};
+ std::atomic<float> m_latencyMovingAverage;
+ CSignalHandlerList<PresentationFeedbackHandler> m_presentationFeedbackHandlers;
+
+ std::chrono::steady_clock::time_point m_frameStartTime{};
+
+ // IDispResource
+ // -------------
+ std::set<IDispResource*> m_dispResources;
+ CCriticalSection m_dispResourcesMutex;
+
+ // Surface state
+ // -------------
+ wayland::surface_t m_surface;
+ wayland::output_t m_lastSetOutput;
+ /// Set of outputs that show some part of our main surface as indicated by
+ /// compositor
+ std::set<std::shared_ptr<COutput>> m_surfaceOutputs;
+ CCriticalSection m_surfaceOutputsMutex;
+ /// Size of our surface in "surface coordinates" (i.e. without scaling applied)
+ /// and without window decorations
+ CSizeInt m_surfaceSize;
+ /// Size of the buffer that should back the surface (i.e. with scaling applied)
+ CSizeInt m_bufferSize;
+ /// Size of the whole window including window decorations as given by configure
+ CSizeInt m_configuredSize;
+ /// Scale in use for main surface buffer
+ int m_scale{1};
+ /// Shell surface state last acked
+ IShellSurface::StateBitset m_shellSurfaceState;
+ /// Whether the shell surface is waiting for initial configure
+ bool m_shellSurfaceInitializing{false};
+ struct
+ {
+ bool mustBeAcked{false};
+ std::uint32_t configureSerial{};
+ CSizeInt configuredSize;
+ int scale{1};
+ IShellSurface::StateBitset shellSurfaceState;
+ } m_next;
+ bool m_waitingForApply{false};
+
+ // Internal communication
+ // ----------------------
+ /// Protocol for communicating events to the main thread
+ Actor::Protocol m_protocol;
+
+ // Configure state
+ // ---------------
+ bool m_firstSerialAcked{false};
+ std::uint32_t m_lastAckedSerial{0u};
+ /// Whether this is the first call to SetFullScreen
+ bool m_isInitialSetFullScreen{true};
+};
+
+
+}
+}
+}
diff --git a/xbmc/windowing/wayland/WinSystemWaylandEGLContext.cpp b/xbmc/windowing/wayland/WinSystemWaylandEGLContext.cpp
new file mode 100644
index 0000000..a32cd14
--- /dev/null
+++ b/xbmc/windowing/wayland/WinSystemWaylandEGLContext.cpp
@@ -0,0 +1,150 @@
+/*
+ * 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 "WinSystemWaylandEGLContext.h"
+
+#include "Connection.h"
+#include "cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.h"
+#include "cores/VideoPlayer/VideoRenderers/RenderFactory.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+
+#include <EGL/eglext.h>
+
+using namespace KODI::WINDOWING::WAYLAND;
+using namespace KODI::WINDOWING::LINUX;
+
+CWinSystemWaylandEGLContext::CWinSystemWaylandEGLContext()
+ : CWinSystemEGL{EGL_PLATFORM_WAYLAND_EXT, "EGL_EXT_platform_wayland"}
+{}
+
+bool CWinSystemWaylandEGLContext::InitWindowSystemEGL(EGLint renderableType, EGLint apiType)
+{
+ VIDEOPLAYER::CRendererFactory::ClearRenderer();
+ CDVDFactoryCodec::ClearHWAccels();
+
+ if (!CWinSystemWayland::InitWindowSystem())
+ {
+ return false;
+ }
+
+ if (!m_eglContext.CreatePlatformDisplay(GetConnection()->GetDisplay(), GetConnection()->GetDisplay()))
+ {
+ return false;
+ }
+
+ if (!m_eglContext.InitializeDisplay(apiType))
+ {
+ return false;
+ }
+
+ if (!m_eglContext.ChooseConfig(renderableType))
+ {
+ return false;
+ }
+
+ return true;
+}
+
+bool CWinSystemWaylandEGLContext::CreateNewWindow(const std::string& name,
+ bool fullScreen,
+ RESOLUTION_INFO& res)
+{
+ if (!CWinSystemWayland::CreateNewWindow(name, fullScreen, res))
+ {
+ return false;
+ }
+
+ if (!CreateContext())
+ {
+ return false;
+ }
+
+ m_nativeWindow = wayland::egl_window_t{GetMainSurface(), GetBufferSize().Width(), GetBufferSize().Height()};
+
+ // CWinSystemWayland::CreateNewWindow sets internal m_bufferSize
+ // to the resolution that should be used for the initial surface size
+ // - the compositor might want something other than the resolution given
+ if (!m_eglContext.CreatePlatformSurface(
+ m_nativeWindow.c_ptr(), reinterpret_cast<khronos_uintptr_t>(m_nativeWindow.c_ptr())))
+ {
+ return false;
+ }
+
+ if (!m_eglContext.BindContext())
+ {
+ return false;
+ }
+
+ // Never enable the vsync of the EGL implementation, we handle that ourselves
+ // in WinSystemWayland
+ m_eglContext.SetVSync(false);
+
+ return true;
+}
+
+bool CWinSystemWaylandEGLContext::DestroyWindow()
+{
+ m_eglContext.DestroySurface();
+ m_nativeWindow = {};
+
+ return CWinSystemWayland::DestroyWindow();
+}
+
+bool CWinSystemWaylandEGLContext::DestroyWindowSystem()
+{
+ m_eglContext.Destroy();
+
+ return CWinSystemWayland::DestroyWindowSystem();
+}
+
+CSizeInt CWinSystemWaylandEGLContext::GetNativeWindowAttachedSize()
+{
+ int width, height;
+ m_nativeWindow.get_attached_size(width, height);
+ return {width, height};
+}
+
+void CWinSystemWaylandEGLContext::SetContextSize(CSizeInt size)
+{
+ // Change EGL surface size if necessary
+ if (GetNativeWindowAttachedSize() != size)
+ {
+ CLog::LogF(LOGDEBUG, "Updating egl_window size to {}x{}", size.Width(), size.Height());
+ m_nativeWindow.resize(size.Width(), size.Height(), 0, 0);
+ }
+}
+
+void CWinSystemWaylandEGLContext::PresentFrame(bool rendered)
+{
+ PrepareFramePresentation();
+
+ if (rendered)
+ {
+ if (!m_eglContext.TrySwapBuffers())
+ {
+ // For now we just hard fail if this fails
+ // Theoretically, EGL_CONTEXT_LOST could be handled, but it needs to be checked
+ // whether egl implementations actually use it (mesa does not)
+ CEGLUtils::Log(LOGERROR, "eglSwapBuffers failed");
+ throw std::runtime_error("eglSwapBuffers failed");
+ }
+ // eglSwapBuffers() (hopefully) calls commit on the surface and flushes
+ // ... well mesa does anyway
+ }
+ else
+ {
+ // For presentation feedback: Get notification of the next vblank even
+ // when contents did not change
+ GetMainSurface().commit();
+ // Make sure it reaches the compositor
+ GetConnection()->GetDisplay().flush();
+ }
+
+ FinishFramePresentation();
+}
diff --git a/xbmc/windowing/wayland/WinSystemWaylandEGLContext.h b/xbmc/windowing/wayland/WinSystemWaylandEGLContext.h
new file mode 100644
index 0000000..097b3e7
--- /dev/null
+++ b/xbmc/windowing/wayland/WinSystemWaylandEGLContext.h
@@ -0,0 +1,55 @@
+/*
+ * 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 "WinSystemWayland.h"
+#include "utils/EGLUtils.h"
+#include "windowing/linux/WinSystemEGL.h"
+
+#include <wayland-egl.hpp>
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace WAYLAND
+{
+
+class CWinSystemWaylandEGLContext : public KODI::WINDOWING::LINUX::CWinSystemEGL,
+ public CWinSystemWayland
+{
+public:
+ CWinSystemWaylandEGLContext();
+ ~CWinSystemWaylandEGLContext() override = default;
+
+ bool CreateNewWindow(const std::string& name,
+ bool fullScreen,
+ RESOLUTION_INFO& res) override;
+ bool DestroyWindow() override;
+ bool DestroyWindowSystem() override;
+
+protected:
+ /**
+ * Inheriting classes should override InitWindowSystem() without parameters
+ * and call this function there with appropriate parameters
+ */
+ bool InitWindowSystemEGL(EGLint renderableType, EGLint apiType);
+
+ CSizeInt GetNativeWindowAttachedSize();
+ void PresentFrame(bool rendered);
+ void SetContextSize(CSizeInt size) override;
+
+ virtual bool CreateContext() = 0;
+
+ wayland::egl_window_t m_nativeWindow;
+};
+
+}
+}
+}
diff --git a/xbmc/windowing/wayland/WinSystemWaylandEGLContextGL.cpp b/xbmc/windowing/wayland/WinSystemWaylandEGLContextGL.cpp
new file mode 100644
index 0000000..6e5a3b8
--- /dev/null
+++ b/xbmc/windowing/wayland/WinSystemWaylandEGLContextGL.cpp
@@ -0,0 +1,125 @@
+/*
+ * 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 "WinSystemWaylandEGLContextGL.h"
+
+#include "OptionalsReg.h"
+#include "cores/RetroPlayer/process/RPProcessInfo.h"
+#include "cores/RetroPlayer/rendering/VideoRenderers/RPRendererDMA.h"
+#include "cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGL.h"
+#include "cores/VideoPlayer/VideoRenderers/LinuxRendererGL.h"
+#include "rendering/gl/ScreenshotSurfaceGL.h"
+#include "utils/BufferObjectFactory.h"
+#include "utils/DMAHeapBufferObject.h"
+#include "utils/UDMABufferObject.h"
+#include "utils/log.h"
+#include "windowing/WindowSystemFactory.h"
+
+#include <EGL/eglext.h>
+
+using namespace KODI::WINDOWING::WAYLAND;
+
+void CWinSystemWaylandEGLContextGL::Register()
+{
+ CWindowSystemFactory::RegisterWindowSystem(CreateWinSystem, "wayland");
+}
+
+std::unique_ptr<CWinSystemBase> CWinSystemWaylandEGLContextGL::CreateWinSystem()
+{
+ return std::make_unique<CWinSystemWaylandEGLContextGL>();
+}
+
+bool CWinSystemWaylandEGLContextGL::InitWindowSystem()
+{
+ if (!CWinSystemWaylandEGLContext::InitWindowSystemEGL(EGL_OPENGL_BIT, EGL_OPENGL_API))
+ {
+ return false;
+ }
+
+ CLinuxRendererGL::Register();
+ RETRO::CRPProcessInfo::RegisterRendererFactory(new RETRO::CRendererFactoryDMA);
+ RETRO::CRPProcessInfo::RegisterRendererFactory(new RETRO::CRendererFactoryOpenGL);
+
+ bool general, deepColor;
+ m_vaapiProxy.reset(WAYLAND::VaapiProxyCreate());
+ WAYLAND::VaapiProxyConfig(m_vaapiProxy.get(), GetConnection()->GetDisplay(),
+ m_eglContext.GetEGLDisplay());
+ WAYLAND::VAAPIRegisterRenderGL(m_vaapiProxy.get(), general, deepColor);
+ if (general)
+ {
+ WAYLAND::VAAPIRegister(m_vaapiProxy.get(), deepColor);
+ }
+
+ CBufferObjectFactory::ClearBufferObjects();
+#if defined(HAVE_LINUX_MEMFD) && defined(HAVE_LINUX_UDMABUF)
+ CUDMABufferObject::Register();
+#endif
+#if defined(HAVE_LINUX_DMA_HEAP)
+ CDMAHeapBufferObject::Register();
+#endif
+
+ CScreenshotSurfaceGL::Register();
+
+ return true;
+}
+
+bool CWinSystemWaylandEGLContextGL::CreateContext()
+{
+ const EGLint glMajor = 3;
+ const EGLint glMinor = 2;
+
+ CEGLAttributesVec contextAttribs;
+ contextAttribs.Add({{EGL_CONTEXT_MAJOR_VERSION_KHR, glMajor},
+ {EGL_CONTEXT_MINOR_VERSION_KHR, glMinor},
+ {EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR}});
+
+ if (!m_eglContext.CreateContext(contextAttribs))
+ {
+ CEGLAttributesVec fallbackContextAttribs;
+ fallbackContextAttribs.Add({{EGL_CONTEXT_CLIENT_VERSION, 2}});
+
+ if (!m_eglContext.CreateContext(fallbackContextAttribs))
+ {
+ CLog::Log(LOGERROR, "EGL context creation failed");
+ return false;
+ }
+ else
+ {
+ CLog::Log(LOGWARNING, "Your OpenGL drivers do not support OpenGL {}.{} core profile. Kodi will run in compatibility mode, but performance may suffer.", glMajor, glMinor);
+ }
+ }
+
+ return true;
+}
+
+void CWinSystemWaylandEGLContextGL::SetContextSize(CSizeInt size)
+{
+ CWinSystemWaylandEGLContext::SetContextSize(size);
+
+ // Propagate changed dimensions to render system if necessary
+ if (CRenderSystemGL::m_width != size.Width() || CRenderSystemGL::m_height != size.Height())
+ {
+ CLog::LogF(LOGDEBUG, "Resetting render system to {}x{}", size.Width(), size.Height());
+ CRenderSystemGL::ResetRenderSystem(size.Width(), size.Height());
+ }
+}
+
+void CWinSystemWaylandEGLContextGL::SetVSyncImpl(bool enable)
+{
+ // Unsupported
+}
+
+void CWinSystemWaylandEGLContextGL::PresentRenderImpl(bool rendered)
+{
+ PresentFrame(rendered);
+}
+
+void CWinSystemWaylandEGLContextGL::delete_CVaapiProxy::operator()(CVaapiProxy *p) const
+{
+ WAYLAND::VaapiProxyDelete(p);
+}
diff --git a/xbmc/windowing/wayland/WinSystemWaylandEGLContextGL.h b/xbmc/windowing/wayland/WinSystemWaylandEGLContextGL.h
new file mode 100644
index 0000000..540a7b8
--- /dev/null
+++ b/xbmc/windowing/wayland/WinSystemWaylandEGLContextGL.h
@@ -0,0 +1,47 @@
+/*
+ * 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 "WinSystemWaylandEGLContext.h"
+#include "rendering/gl/RenderSystemGL.h"
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace WAYLAND
+{
+
+class CVaapiProxy;
+
+class CWinSystemWaylandEGLContextGL : public CWinSystemWaylandEGLContext, public CRenderSystemGL
+{
+public:
+ static void Register();
+ static std::unique_ptr<CWinSystemBase> CreateWinSystem();
+
+ // Implementation of CWinSystemBase via CWinSystemWaylandEGLContext
+ CRenderSystemBase *GetRenderSystem() override { return this; }
+ bool InitWindowSystem() override;
+
+protected:
+ bool CreateContext() override;
+ void SetContextSize(CSizeInt size) override;
+ void SetVSyncImpl(bool enable) override;
+ void PresentRenderImpl(bool rendered) override;
+ struct delete_CVaapiProxy
+ {
+ void operator()(CVaapiProxy *p) const;
+ };
+ std::unique_ptr<CVaapiProxy, delete_CVaapiProxy> m_vaapiProxy;
+};
+
+}
+}
+}
diff --git a/xbmc/windowing/wayland/WinSystemWaylandEGLContextGLES.cpp b/xbmc/windowing/wayland/WinSystemWaylandEGLContextGLES.cpp
new file mode 100644
index 0000000..d7283a7
--- /dev/null
+++ b/xbmc/windowing/wayland/WinSystemWaylandEGLContextGLES.cpp
@@ -0,0 +1,114 @@
+/*
+ * 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 "WinSystemWaylandEGLContextGLES.h"
+
+#include "OptionalsReg.h"
+#include "cores/RetroPlayer/process/RPProcessInfo.h"
+#include "cores/RetroPlayer/rendering/VideoRenderers/RPRendererDMA.h"
+#include "cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGLES.h"
+#include "cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.h"
+#include "cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIMEGLES.h"
+#include "cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.h"
+#include "cores/VideoPlayer/VideoRenderers/RenderFactory.h"
+#include "rendering/gles/ScreenshotSurfaceGLES.h"
+#include "utils/BufferObjectFactory.h"
+#include "utils/DMAHeapBufferObject.h"
+#include "utils/UDMABufferObject.h"
+#include "utils/log.h"
+#include "windowing/WindowSystemFactory.h"
+
+using namespace KODI::WINDOWING::WAYLAND;
+
+void CWinSystemWaylandEGLContextGLES::Register()
+{
+ CWindowSystemFactory::RegisterWindowSystem(CreateWinSystem, "wayland");
+}
+
+std::unique_ptr<CWinSystemBase> CWinSystemWaylandEGLContextGLES::CreateWinSystem()
+{
+ return std::make_unique<CWinSystemWaylandEGLContextGLES>();
+}
+
+bool CWinSystemWaylandEGLContextGLES::InitWindowSystem()
+{
+ if (!CWinSystemWaylandEGLContext::InitWindowSystemEGL(EGL_OPENGL_ES2_BIT, EGL_OPENGL_ES_API))
+ {
+ return false;
+ }
+
+ CLinuxRendererGLES::Register();
+
+ CDVDVideoCodecDRMPRIME::Register();
+ CRendererDRMPRIMEGLES::Register();
+
+ RETRO::CRPProcessInfo::RegisterRendererFactory(new RETRO::CRendererFactoryDMA);
+ RETRO::CRPProcessInfo::RegisterRendererFactory(new RETRO::CRendererFactoryOpenGLES);
+
+ bool general, deepColor;
+ m_vaapiProxy.reset(WAYLAND::VaapiProxyCreate());
+ WAYLAND::VaapiProxyConfig(m_vaapiProxy.get(), GetConnection()->GetDisplay(),
+ m_eglContext.GetEGLDisplay());
+ WAYLAND::VAAPIRegisterRenderGLES(m_vaapiProxy.get(), general, deepColor);
+ if (general)
+ {
+ WAYLAND::VAAPIRegister(m_vaapiProxy.get(), deepColor);
+ }
+
+ CBufferObjectFactory::ClearBufferObjects();
+#if defined(HAVE_LINUX_MEMFD) && defined(HAVE_LINUX_UDMABUF)
+ CUDMABufferObject::Register();
+#endif
+#if defined(HAVE_LINUX_DMA_HEAP)
+ CDMAHeapBufferObject::Register();
+#endif
+
+ CScreenshotSurfaceGLES::Register();
+
+ return true;
+}
+
+bool CWinSystemWaylandEGLContextGLES::CreateContext()
+{
+ CEGLAttributesVec contextAttribs;
+ contextAttribs.Add({{EGL_CONTEXT_CLIENT_VERSION, 2}});
+
+ if (!m_eglContext.CreateContext(contextAttribs))
+ {
+ CLog::Log(LOGERROR, "EGL context creation failed");
+ return false;
+ }
+ return true;
+}
+
+void CWinSystemWaylandEGLContextGLES::SetContextSize(CSizeInt size)
+{
+ CWinSystemWaylandEGLContext::SetContextSize(size);
+
+ // Propagate changed dimensions to render system if necessary
+ if (CRenderSystemGLES::m_width != size.Width() || CRenderSystemGLES::m_height != size.Height())
+ {
+ CLog::LogF(LOGDEBUG, "Resetting render system to {}x{}", size.Width(), size.Height());
+ CRenderSystemGLES::ResetRenderSystem(size.Width(), size.Height());
+ }
+}
+
+void CWinSystemWaylandEGLContextGLES::SetVSyncImpl(bool enable)
+{
+ // Unsupported
+}
+
+void CWinSystemWaylandEGLContextGLES::PresentRenderImpl(bool rendered)
+{
+ PresentFrame(rendered);
+}
+
+void CWinSystemWaylandEGLContextGLES::delete_CVaapiProxy::operator()(CVaapiProxy *p) const
+{
+ WAYLAND::VaapiProxyDelete(p);
+}
diff --git a/xbmc/windowing/wayland/WinSystemWaylandEGLContextGLES.h b/xbmc/windowing/wayland/WinSystemWaylandEGLContextGLES.h
new file mode 100644
index 0000000..0a3c71c
--- /dev/null
+++ b/xbmc/windowing/wayland/WinSystemWaylandEGLContextGLES.h
@@ -0,0 +1,47 @@
+/*
+ * 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 "WinSystemWaylandEGLContext.h"
+#include "rendering/gles/RenderSystemGLES.h"
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace WAYLAND
+{
+
+class CVaapiProxy;
+
+class CWinSystemWaylandEGLContextGLES : public CWinSystemWaylandEGLContext, public CRenderSystemGLES
+{
+public:
+ static void Register();
+ static std::unique_ptr<CWinSystemBase> CreateWinSystem();
+
+ // Implementation of CWinSystemBase via CWinSystemWaylandEGLContext
+ CRenderSystemBase *GetRenderSystem() override { return this; }
+ bool InitWindowSystem() override;
+
+protected:
+ bool CreateContext() override;
+ void SetContextSize(CSizeInt size) override;
+ void SetVSyncImpl(bool enable) override;
+ void PresentRenderImpl(bool rendered) override;
+ struct delete_CVaapiProxy
+ {
+ void operator()(CVaapiProxy *p) const;
+ };
+ std::unique_ptr<CVaapiProxy, delete_CVaapiProxy> m_vaapiProxy;
+};
+
+}
+}
+}
diff --git a/xbmc/windowing/wayland/WindowDecorationHandler.h b/xbmc/windowing/wayland/WindowDecorationHandler.h
new file mode 100644
index 0000000..b02f39b
--- /dev/null
+++ b/xbmc/windowing/wayland/WindowDecorationHandler.h
@@ -0,0 +1,41 @@
+/*
+ * 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 <cstdint>
+
+#include <wayland-client-protocol.hpp>
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace WAYLAND
+{
+
+/**
+ * Handler for reacting to events originating in window decorations, such as
+ * moving the window by clicking and dragging
+ */
+class IWindowDecorationHandler
+{
+public:
+ virtual void OnWindowMove(wayland::seat_t const& seat, std::uint32_t serial) = 0;
+ virtual void OnWindowResize(wayland::seat_t const& seat, std::uint32_t serial, wayland::shell_surface_resize edge) = 0;
+ virtual void OnWindowShowContextMenu(wayland::seat_t const& seat, std::uint32_t serial, CPointInt position) = 0;
+ virtual void OnWindowMinimize() = 0;
+ virtual void OnWindowMaximize() = 0;
+ virtual void OnWindowClose() = 0;
+
+ virtual ~IWindowDecorationHandler() = default;
+};
+
+}
+}
+}
diff --git a/xbmc/windowing/wayland/WindowDecorator.cpp b/xbmc/windowing/wayland/WindowDecorator.cpp
new file mode 100644
index 0000000..9f61481
--- /dev/null
+++ b/xbmc/windowing/wayland/WindowDecorator.cpp
@@ -0,0 +1,1043 @@
+/*
+ * 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 "WindowDecorator.h"
+
+#include "Util.h"
+#include "utils/EndianSwap.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <mutex>
+#include <vector>
+
+#include <linux/input-event-codes.h>
+
+using namespace KODI::UTILS::POSIX;
+using namespace KODI::WINDOWING::WAYLAND;
+using namespace std::placeholders;
+
+namespace
+{
+
+/// Bytes per pixel in shm storage
+constexpr int BYTES_PER_PIXEL{4};
+/// Width of the visible border around the whole window
+constexpr int VISIBLE_BORDER_WIDTH{5};
+/// Width of the invisible border around the whole window for easier resizing
+constexpr int RESIZE_BORDER_WIDTH{10};
+/// Total width of the border around the window
+constexpr int BORDER_WIDTH{VISIBLE_BORDER_WIDTH + RESIZE_BORDER_WIDTH};
+/// Height of the top bar
+constexpr int TOP_BAR_HEIGHT{33};
+/// Maximum distance from the window corner to consider position valid for resize
+constexpr int RESIZE_MAX_CORNER_DISTANCE{BORDER_WIDTH};
+/// Distance of buttons from edges of the top bar
+constexpr int BUTTONS_EDGE_DISTANCE{6};
+/// Distance from button inner edge to button content
+constexpr int BUTTON_INNER_SEPARATION{4};
+/// Button size
+constexpr int BUTTON_SIZE{21};
+
+constexpr std::uint32_t TRANSPARENT{0x00000000u};
+constexpr std::uint32_t BORDER_COLOR{0xFF000000u};
+constexpr std::uint32_t BUTTON_COLOR_ACTIVE{0xFFFFFFFFu};
+constexpr std::uint32_t BUTTON_COLOR_INACTIVE{0xFF777777u};
+constexpr std::uint32_t BUTTON_HOVER_COLOR{0xFF555555u};
+
+static_assert(BUTTON_SIZE <= TOP_BAR_HEIGHT - BUTTONS_EDGE_DISTANCE * 2, "Buttons must fit in top bar");
+
+/*
+ * Decorations consist of four surfaces, one for each edge of the window. It would
+ * also be possible to position one single large surface behind the main surface
+ * instead, but that would waste a lot of memory on big/high-density screens.
+ *
+ * The four surfaces are laid out as follows: Top and bottom surfaces go over the
+ * whole width of the main surface plus the left and right borders.
+ * Left and right surfaces only go over the height of the main surface without
+ * the top and bottom borders.
+ *
+ * ---------------------------------------------
+ * | TOP |
+ * ---------------------------------------------
+ * | | | |
+ * | L | | R |
+ * | E | | I |
+ * | F | Main surface | G |
+ * | T | | H |
+ * | | | T |
+ * | | | |
+ * ---------------------------------------------
+ * | BOTTOM |
+ * ---------------------------------------------
+ */
+
+
+CRectInt SurfaceGeometry(SurfaceIndex type, CSizeInt mainSurfaceSize)
+{
+ // Coordinates are relative to main surface
+ switch (type)
+ {
+ case SURFACE_TOP:
+ return {
+ CPointInt{-BORDER_WIDTH, -(BORDER_WIDTH + TOP_BAR_HEIGHT)},
+ CSizeInt{mainSurfaceSize.Width() + 2 * BORDER_WIDTH, TOP_BAR_HEIGHT + BORDER_WIDTH}
+ };
+ case SURFACE_RIGHT:
+ return {
+ CPointInt{mainSurfaceSize.Width(), 0},
+ CSizeInt{BORDER_WIDTH, mainSurfaceSize.Height()}
+ };
+ case SURFACE_BOTTOM:
+ return {
+ CPointInt{-BORDER_WIDTH, mainSurfaceSize.Height()},
+ CSizeInt{mainSurfaceSize.Width() + 2 * BORDER_WIDTH, BORDER_WIDTH}
+ };
+ case SURFACE_LEFT:
+ return {
+ CPointInt{-BORDER_WIDTH, 0},
+ CSizeInt{BORDER_WIDTH, mainSurfaceSize.Height()}
+ };
+ default:
+ throw std::logic_error("Invalid surface type");
+ }
+}
+
+CRectInt SurfaceOpaqueRegion(SurfaceIndex type, CSizeInt mainSurfaceSize)
+{
+ // Coordinates are relative to main surface
+ auto size = SurfaceGeometry(type, mainSurfaceSize).ToSize();
+ switch (type)
+ {
+ case SURFACE_TOP:
+ return {
+ CPointInt{RESIZE_BORDER_WIDTH, RESIZE_BORDER_WIDTH},
+ size - CSizeInt{RESIZE_BORDER_WIDTH * 2, RESIZE_BORDER_WIDTH}
+ };
+ case SURFACE_RIGHT:
+ return {
+ CPointInt{},
+ size - CSizeInt{RESIZE_BORDER_WIDTH, 0}
+ };
+ case SURFACE_BOTTOM:
+ return {
+ CPointInt{RESIZE_BORDER_WIDTH, 0},
+ size - CSizeInt{RESIZE_BORDER_WIDTH * 2, RESIZE_BORDER_WIDTH}
+ };
+ case SURFACE_LEFT:
+ return {
+ CPointInt{RESIZE_BORDER_WIDTH, 0},
+ size - CSizeInt{RESIZE_BORDER_WIDTH, 0}
+ };
+ default:
+ throw std::logic_error("Invalid surface type");
+ }
+}
+
+CRectInt SurfaceWindowRegion(SurfaceIndex type, CSizeInt mainSurfaceSize)
+{
+ return SurfaceOpaqueRegion(type, mainSurfaceSize);
+}
+
+/**
+ * Full size of decorations to be added to the main surface size
+ */
+CSizeInt DecorationSize()
+{
+ return {2 * VISIBLE_BORDER_WIDTH, 2 * VISIBLE_BORDER_WIDTH + TOP_BAR_HEIGHT};
+}
+
+std::size_t MemoryBytesForSize(CSizeInt windowSurfaceSize, int scale)
+{
+ std::size_t size{};
+
+ for (auto surface : { SURFACE_TOP, SURFACE_RIGHT, SURFACE_BOTTOM, SURFACE_LEFT })
+ {
+ size += SurfaceGeometry(surface, windowSurfaceSize).Area();
+ }
+
+ size *= scale * scale;
+
+ size *= BYTES_PER_PIXEL;
+
+ return size;
+}
+
+std::size_t PositionInBuffer(CWindowDecorator::Buffer& buffer, CPointInt position)
+{
+ if (position.x < 0 || position.y < 0)
+ {
+ throw std::invalid_argument("Position out of bounds");
+ }
+ std::size_t offset{static_cast<std::size_t> (buffer.size.Width() * position.y + position.x)};
+ if (offset * BYTES_PER_PIXEL >= buffer.dataSize)
+ {
+ throw std::invalid_argument("Position out of bounds");
+ }
+ return offset;
+}
+
+void DrawHorizontalLine(CWindowDecorator::Buffer& buffer, std::uint32_t color, CPointInt position, int length)
+{
+ auto offsetStart = PositionInBuffer(buffer, position);
+ auto offsetEnd = PositionInBuffer(buffer, position + CPointInt{length - 1, 0});
+ if (offsetEnd < offsetStart)
+ {
+ throw std::invalid_argument("Invalid drawing coordinates");
+ }
+ std::fill(buffer.RgbaBuffer() + offsetStart, buffer.RgbaBuffer() + offsetEnd + 1, Endian_SwapLE32(color));
+}
+
+void DrawLineWithStride(CWindowDecorator::Buffer& buffer, std::uint32_t color, CPointInt position, int length, int stride)
+{
+ auto offsetStart = PositionInBuffer(buffer, position);
+ auto offsetEnd = offsetStart + stride * (length - 1);
+ if (offsetEnd * BYTES_PER_PIXEL >= buffer.dataSize)
+ {
+ throw std::invalid_argument("Position out of bounds");
+ }
+ if (offsetEnd < offsetStart)
+ {
+ throw std::invalid_argument("Invalid drawing coordinates");
+ }
+ auto* memory = buffer.RgbaBuffer();
+ for (std::size_t offset = offsetStart; offset <= offsetEnd; offset += stride)
+ {
+ *(memory + offset) = Endian_SwapLE32(color);
+ }
+}
+
+void DrawVerticalLine(CWindowDecorator::Buffer& buffer, std::uint32_t color, CPointInt position, int length)
+{
+ DrawLineWithStride(buffer, color, position, length, buffer.size.Width());
+}
+
+/**
+ * Draw rectangle inside the specified buffer coordinates
+ */
+void DrawRectangle(CWindowDecorator::Buffer& buffer, std::uint32_t color, CRectInt rect)
+{
+ DrawHorizontalLine(buffer, color, rect.P1(), rect.Width());
+ DrawVerticalLine(buffer, color, rect.P1(), rect.Height());
+ DrawHorizontalLine(buffer, color, rect.P1() + CPointInt{1, rect.Height() - 1}, rect.Width() - 1);
+ DrawVerticalLine(buffer, color, rect.P1() + CPointInt{rect.Width() - 1, 1}, rect.Height() - 1);
+}
+
+void FillRectangle(CWindowDecorator::Buffer& buffer, std::uint32_t color, CRectInt rect)
+{
+ for (int y{rect.y1}; y <= rect.y2; y++)
+ {
+ DrawHorizontalLine(buffer, color, {rect.x1, y}, rect.Width() + 1);
+ }
+}
+
+void DrawHorizontalLine(CWindowDecorator::Surface& surface, std::uint32_t color, CPointInt position, int length)
+{
+ for (int i{0}; i < surface.scale; i++)
+ {
+ DrawHorizontalLine(surface.buffer, color, position * surface.scale + CPointInt{0, i}, length * surface.scale);
+ }
+}
+
+void DrawAngledLine(CWindowDecorator::Surface& surface, std::uint32_t color, CPointInt position, int length, int stride)
+{
+ for (int i{0}; i < surface.scale; i++)
+ {
+ DrawLineWithStride(surface.buffer, color, position * surface.scale + CPointInt{i, 0}, length * surface.scale, surface.buffer.size.Width() + stride);
+ }
+}
+
+void DrawVerticalLine(CWindowDecorator::Surface& surface, std::uint32_t color, CPointInt position, int length)
+{
+ DrawAngledLine(surface, color, position, length, 0);
+}
+
+/**
+ * Draw rectangle inside the specified surface coordinates
+ */
+void DrawRectangle(CWindowDecorator::Surface& surface, std::uint32_t color, CRectInt rect)
+{
+ for (int i{0}; i < surface.scale; i++)
+ {
+ DrawRectangle(surface.buffer, color, {rect.P1() * surface.scale + CPointInt{i, i}, (rect.P2() + CPointInt{1, 1}) * surface.scale - CPointInt{i, i} - CPointInt{1, 1}});
+ }
+}
+
+void FillRectangle(CWindowDecorator::Surface& surface, std::uint32_t color, CRectInt rect)
+{
+ FillRectangle(surface.buffer, color, {rect.P1() * surface.scale, (rect.P2() + CPointInt{1, 1}) * surface.scale - CPointInt{1, 1}});
+}
+
+void DrawButton(CWindowDecorator::Surface& surface, std::uint32_t lineColor, CRectInt rect, bool hover)
+{
+ if (hover)
+ {
+ FillRectangle(surface, BUTTON_HOVER_COLOR, rect);
+ }
+ DrawRectangle(surface, lineColor, rect);
+}
+
+wayland::shell_surface_resize ResizeEdgeForPosition(SurfaceIndex surface, CSizeInt surfaceSize, CPointInt position)
+{
+ switch (surface)
+ {
+ case SURFACE_TOP:
+ if (position.y <= RESIZE_MAX_CORNER_DISTANCE)
+ {
+ if (position.x <= RESIZE_MAX_CORNER_DISTANCE)
+ {
+ return wayland::shell_surface_resize::top_left;
+ }
+ else if (position.x >= surfaceSize.Width() - RESIZE_MAX_CORNER_DISTANCE)
+ {
+ return wayland::shell_surface_resize::top_right;
+ }
+ else
+ {
+ return wayland::shell_surface_resize::top;
+ }
+ }
+ else
+ {
+ if (position.x <= RESIZE_MAX_CORNER_DISTANCE)
+ {
+ return wayland::shell_surface_resize::left;
+ }
+ else if (position.x >= surfaceSize.Width() - RESIZE_MAX_CORNER_DISTANCE)
+ {
+ return wayland::shell_surface_resize::right;
+ }
+ else
+ {
+ // Inside title bar, not resizing
+ return wayland::shell_surface_resize::none;
+ }
+ }
+ case SURFACE_RIGHT:
+ if (position.y >= surfaceSize.Height() - RESIZE_MAX_CORNER_DISTANCE)
+ {
+ return wayland::shell_surface_resize::bottom_right;
+ }
+ else
+ {
+ return wayland::shell_surface_resize::right;
+ }
+ case SURFACE_BOTTOM:
+ if (position.x <= RESIZE_MAX_CORNER_DISTANCE)
+ {
+ return wayland::shell_surface_resize::bottom_left;
+ }
+ else if (position.x >= surfaceSize.Width() - RESIZE_MAX_CORNER_DISTANCE)
+ {
+ return wayland::shell_surface_resize::bottom_right;
+ }
+ else
+ {
+ return wayland::shell_surface_resize::bottom;
+ }
+ case SURFACE_LEFT:
+ if (position.y >= surfaceSize.Height() - RESIZE_MAX_CORNER_DISTANCE)
+ {
+ return wayland::shell_surface_resize::bottom_left;
+ }
+ else
+ {
+ return wayland::shell_surface_resize::left;
+ }
+
+ default:
+ return wayland::shell_surface_resize::none;
+ }
+}
+
+/**
+ * Get name for resize cursor according to xdg cursor-spec
+ */
+std::string CursorForResizeEdge(wayland::shell_surface_resize edge)
+{
+ if (edge == wayland::shell_surface_resize::top)
+ return "n-resize";
+ else if (edge == wayland::shell_surface_resize::bottom)
+ return "s-resize";
+ else if (edge == wayland::shell_surface_resize::left)
+ return "w-resize";
+ else if (edge == wayland::shell_surface_resize::top_left)
+ return "nw-resize";
+ else if (edge == wayland::shell_surface_resize::bottom_left)
+ return "sw-resize";
+ else if (edge == wayland::shell_surface_resize::right)
+ return "e-resize";
+ else if (edge == wayland::shell_surface_resize::top_right)
+ return "ne-resize";
+ else if (edge == wayland::shell_surface_resize::bottom_right)
+ return "se-resize";
+ else
+ return "";
+}
+
+template<typename T, typename InstanceProviderT>
+bool HandleCapabilityChange(const wayland::seat_capability& caps,
+ const wayland::seat_capability& cap,
+ T& proxy,
+ InstanceProviderT instanceProvider)
+{
+ bool hasCapability = caps & cap;
+
+ if ((!!proxy) != hasCapability)
+ {
+ // Capability changed
+
+ if (hasCapability)
+ {
+ // The capability was added
+ proxy = instanceProvider();
+ return true;
+ }
+ else
+ {
+ // The capability was removed
+ proxy.proxy_release();
+ }
+ }
+
+ return false;
+}
+
+}
+
+CWindowDecorator::CWindowDecorator(IWindowDecorationHandler& handler, CConnection& connection, wayland::surface_t const& mainSurface)
+: m_handler{handler}, m_registry{connection}, m_mainSurface{mainSurface}, m_buttonColor{BUTTON_COLOR_ACTIVE}
+{
+ static_assert(std::tuple_size<decltype(m_borderSurfaces)>::value == SURFACE_COUNT, "SURFACE_COUNT must match surfaces array size");
+
+ m_registry.RequestSingleton(m_compositor, 1, 4);
+ m_registry.RequestSingleton(m_subcompositor, 1, 1, false);
+ m_registry.RequestSingleton(m_shm, 1, 1);
+
+ m_registry.Bind();
+}
+
+void CWindowDecorator::AddSeat(CSeat* seat)
+{
+ m_seats.emplace(std::piecewise_construct, std::forward_as_tuple(seat->GetGlobalName()), std::forward_as_tuple(seat));
+ seat->AddRawInputHandlerTouch(this);
+ seat->AddRawInputHandlerPointer(this);
+}
+
+void CWindowDecorator::RemoveSeat(CSeat* seat)
+{
+ seat->RemoveRawInputHandlerTouch(this);
+ seat->RemoveRawInputHandlerPointer(this);
+ m_seats.erase(seat->GetGlobalName());
+ UpdateButtonHoverState();
+}
+
+void CWindowDecorator::OnPointerEnter(CSeat* seat,
+ std::uint32_t serial,
+ const wayland::surface_t& surface,
+ double surfaceX,
+ double surfaceY)
+{
+ auto seatStateI = m_seats.find(seat->GetGlobalName());
+ if (seatStateI == m_seats.end())
+ {
+ return;
+ }
+ auto& seatState = seatStateI->second;
+ // Reset first so we ignore events for surfaces we don't handle
+ seatState.currentSurface = SURFACE_COUNT;
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+ for (std::size_t i{0}; i < m_borderSurfaces.size(); i++)
+ {
+ if (m_borderSurfaces[i].surface.wlSurface == surface)
+ {
+ seatState.pointerEnterSerial = serial;
+ seatState.currentSurface = static_cast<SurfaceIndex> (i);
+ seatState.pointerPosition = {static_cast<float> (surfaceX), static_cast<float> (surfaceY)};
+ UpdateSeatCursor(seatState);
+ UpdateButtonHoverState();
+ break;
+ }
+ }
+}
+
+void CWindowDecorator::OnPointerLeave(CSeat* seat,
+ std::uint32_t serial,
+ const wayland::surface_t& surface)
+{
+ auto seatStateI = m_seats.find(seat->GetGlobalName());
+ if (seatStateI == m_seats.end())
+ {
+ return;
+ }
+ auto& seatState = seatStateI->second;
+ seatState.currentSurface = SURFACE_COUNT;
+ // Recreate cursor surface on reenter
+ seatState.cursorName.clear();
+ seatState.cursor.proxy_release();
+ UpdateButtonHoverState();
+}
+
+void CWindowDecorator::OnPointerMotion(CSeat* seat, std::uint32_t time, double surfaceX, double surfaceY)
+{
+ auto seatStateI = m_seats.find(seat->GetGlobalName());
+ if (seatStateI == m_seats.end())
+ {
+ return;
+ }
+ auto& seatState = seatStateI->second;
+ if (seatState.currentSurface != SURFACE_COUNT)
+ {
+ seatState.pointerPosition = {static_cast<float> (surfaceX), static_cast<float> (surfaceY)};
+ UpdateSeatCursor(seatState);
+ UpdateButtonHoverState();
+ }
+}
+
+void CWindowDecorator::OnPointerButton(CSeat* seat, std::uint32_t serial, std::uint32_t time, std::uint32_t button, wayland::pointer_button_state state)
+{
+ auto seatStateI = m_seats.find(seat->GetGlobalName());
+ if (seatStateI == m_seats.end())
+ {
+ return;
+ }
+ const auto& seatState = seatStateI->second;
+ if (seatState.currentSurface != SURFACE_COUNT && state == wayland::pointer_button_state::pressed)
+ {
+ HandleSeatClick(seatState, seatState.currentSurface, serial, button, seatState.pointerPosition);
+ }
+}
+
+void CWindowDecorator::OnTouchDown(CSeat* seat,
+ std::uint32_t serial,
+ std::uint32_t time,
+ const wayland::surface_t& surface,
+ std::int32_t id,
+ double x,
+ double y)
+{
+ auto seatStateI = m_seats.find(seat->GetGlobalName());
+ if (seatStateI == m_seats.end())
+ {
+ return;
+ }
+ auto& seatState = seatStateI->second;
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+ for (std::size_t i{0}; i < m_borderSurfaces.size(); i++)
+ {
+ if (m_borderSurfaces[i].surface.wlSurface == surface)
+ {
+ HandleSeatClick(seatState, static_cast<SurfaceIndex> (i), serial, BTN_LEFT, {static_cast<float> (x), static_cast<float> (y)});
+ }
+ }
+}
+
+void CWindowDecorator::UpdateSeatCursor(SeatState& seatState)
+{
+ if (seatState.currentSurface == SURFACE_COUNT)
+ {
+ // Don't set anything if not on any surface
+ return;
+ }
+
+ LoadCursorTheme();
+
+ std::string cursorName{"default"};
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+ auto resizeEdge = ResizeEdgeForPosition(seatState.currentSurface, SurfaceGeometry(seatState.currentSurface, m_mainSurfaceSize).ToSize(), CPointInt{seatState.pointerPosition});
+ if (resizeEdge != wayland::shell_surface_resize::none)
+ {
+ cursorName = CursorForResizeEdge(resizeEdge);
+ }
+ }
+
+ if (cursorName == seatState.cursorName)
+ {
+ // Don't reload cursor all the time when nothing is changing
+ return;
+ }
+ seatState.cursorName = cursorName;
+
+ wayland::cursor_t cursor;
+ try
+ {
+ cursor = CCursorUtil::LoadFromTheme(m_cursorTheme, cursorName);
+ }
+ catch (std::exception const& e)
+ {
+ CLog::LogF(LOGERROR, "Could not get required cursor {} from cursor theme: {}", cursorName,
+ e.what());
+ return;
+ }
+ auto cursorImage = cursor.image(0);
+
+ if (!seatState.cursor)
+ {
+ seatState.cursor = m_compositor.create_surface();
+ }
+ int calcScale{seatState.cursor.can_set_buffer_scale() ? m_scale : 1};
+
+ seatState.seat->SetCursor(seatState.pointerEnterSerial, seatState.cursor, cursorImage.hotspot_x() / calcScale, cursorImage.hotspot_y() / calcScale);
+ seatState.cursor.attach(cursorImage.get_buffer(), 0, 0);
+ seatState.cursor.damage(0, 0, cursorImage.width() / calcScale, cursorImage.height() / calcScale);
+ if (seatState.cursor.can_set_buffer_scale())
+ {
+ seatState.cursor.set_buffer_scale(m_scale);
+ }
+ seatState.cursor.commit();
+}
+
+void CWindowDecorator::UpdateButtonHoverState()
+{
+ std::vector<CPoint> pointerPositions;
+
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ for (auto const& seatPair : m_seats)
+ {
+ auto const& seat = seatPair.second;
+ if (seat.currentSurface == SURFACE_TOP)
+ {
+ pointerPositions.emplace_back(seat.pointerPosition);
+ }
+ }
+
+ bool changed{false};
+ for (auto& button : m_buttons)
+ {
+ bool wasHovered{button.hovered};
+ button.hovered = std::any_of(pointerPositions.cbegin(), pointerPositions.cend(), [&](CPoint point) { return button.position.PtInRect(CPointInt{point}); });
+ changed = changed || (button.hovered != wasHovered);
+ }
+
+ if (changed)
+ {
+ // Repaint!
+ Reset(false);
+ }
+}
+
+void CWindowDecorator::HandleSeatClick(SeatState const& seatState, SurfaceIndex surface, std::uint32_t serial, std::uint32_t button, CPoint position)
+{
+ switch (button)
+ {
+ case BTN_LEFT:
+ {
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+ auto resizeEdge = ResizeEdgeForPosition(surface, SurfaceGeometry(surface, m_mainSurfaceSize).ToSize(), CPointInt{position});
+ if (resizeEdge == wayland::shell_surface_resize::none)
+ {
+ for (auto const& button : m_buttons)
+ {
+ if (button.position.PtInRect(CPointInt{position}))
+ {
+ button.onClick();
+ return;
+ }
+ }
+
+ m_handler.OnWindowMove(seatState.seat->GetWlSeat(), serial);
+ }
+ else
+ {
+ m_handler.OnWindowResize(seatState.seat->GetWlSeat(), serial, resizeEdge);
+ }
+ }
+ break;
+ case BTN_RIGHT:
+ if (surface == SURFACE_TOP)
+ {
+ m_handler.OnWindowShowContextMenu(seatState.seat->GetWlSeat(), serial, CPointInt{position} - CPointInt{BORDER_WIDTH, BORDER_WIDTH + TOP_BAR_HEIGHT});
+ }
+ break;
+ }
+}
+
+CWindowDecorator::BorderSurface CWindowDecorator::MakeBorderSurface()
+{
+ Surface surface;
+ surface.wlSurface = m_compositor.create_surface();
+ auto subsurface = m_subcompositor.get_subsurface(surface.wlSurface, m_mainSurface);
+
+ CWindowDecorator::BorderSurface boarderSurface;
+ boarderSurface.surface = surface;
+ boarderSurface.subsurface = subsurface;
+
+ return boarderSurface;
+}
+
+bool CWindowDecorator::IsDecorationActive() const
+{
+ return StateHasWindowDecorations(m_windowState);
+}
+
+bool CWindowDecorator::StateHasWindowDecorations(IShellSurface::StateBitset state) const
+{
+ // No decorations possible if subcompositor not available
+ return m_subcompositor && !state.test(IShellSurface::STATE_FULLSCREEN);
+}
+
+CSizeInt CWindowDecorator::CalculateMainSurfaceSize(CSizeInt size, IShellSurface::StateBitset state) const
+{
+ if (StateHasWindowDecorations(state))
+ {
+ // Subtract decorations
+ return size - DecorationSize();
+ }
+ else
+ {
+ // Fullscreen -> no decorations
+ return size;
+ }
+}
+
+CSizeInt CWindowDecorator::CalculateFullSurfaceSize(CSizeInt size, IShellSurface::StateBitset state) const
+{
+ if (StateHasWindowDecorations(state))
+ {
+ // Add decorations
+ return size + DecorationSize();
+ }
+ else
+ {
+ // Fullscreen -> no decorations
+ return size;
+ }
+}
+
+void CWindowDecorator::SetState(CSizeInt size, int scale, IShellSurface::StateBitset state)
+{
+ CSizeInt mainSurfaceSize{CalculateMainSurfaceSize(size, state)};
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+ if (mainSurfaceSize == m_mainSurfaceSize && scale == m_scale && state == m_windowState)
+ {
+ return;
+ }
+
+ bool wasDecorations{IsDecorationActive()};
+ m_windowState = state;
+
+ m_buttonColor = m_windowState.test(IShellSurface::STATE_ACTIVATED) ? BUTTON_COLOR_ACTIVE : BUTTON_COLOR_INACTIVE;
+
+ CLog::Log(LOGDEBUG,
+ "CWindowDecorator::SetState: Setting full surface size {}x{} scale {} (main surface "
+ "size {}x{}), decorations active: {}",
+ size.Width(), size.Height(), scale, mainSurfaceSize.Width(), mainSurfaceSize.Height(),
+ IsDecorationActive());
+
+ if (mainSurfaceSize != m_mainSurfaceSize || scale != m_scale || wasDecorations != IsDecorationActive())
+ {
+ if (scale != m_scale)
+ {
+ // Reload cursor theme
+ CLog::Log(LOGDEBUG, "CWindowDecorator::SetState: Buffer scale changed, reloading cursor theme");
+ m_cursorTheme = wayland::cursor_theme_t{};
+ for (auto& seat : m_seats)
+ {
+ UpdateSeatCursor(seat.second);
+ }
+ }
+
+ m_mainSurfaceSize = mainSurfaceSize;
+ m_scale = scale;
+ CLog::Log(LOGDEBUG, "CWindowDecorator::SetState: Resetting decorations");
+ Reset(true);
+ }
+ else if (IsDecorationActive())
+ {
+ CLog::Log(LOGDEBUG, "CWindowDecorator::SetState: Repainting decorations");
+ // Only state differs, no reallocation needed
+ Reset(false);
+ }
+}
+
+void CWindowDecorator::Reset(bool reallocate)
+{
+ // The complete reset operation should be seen as one atomic update to the
+ // internal state, otherwise buffer/surface state might be mismatched
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+
+ if (reallocate)
+ {
+ ResetButtons();
+ ResetSurfaces();
+ ResetShm();
+ if (IsDecorationActive())
+ {
+ AllocateBuffers();
+ ReattachSubsurfaces();
+ PositionButtons();
+ }
+ }
+
+ if (IsDecorationActive())
+ {
+ Repaint();
+ }
+}
+
+void CWindowDecorator::ResetButtons()
+{
+ if (IsDecorationActive())
+ {
+ if (m_buttons.empty())
+ {
+ // Minimize
+ m_buttons.emplace_back();
+ Button& minimize = m_buttons.back();
+ minimize.draw = [this](Surface& surface, CRectInt position, bool hover)
+ {
+ DrawButton(surface, m_buttonColor, position, hover);
+ DrawHorizontalLine(surface, m_buttonColor, position.P1() + CPointInt{BUTTON_INNER_SEPARATION, position.Height() - BUTTON_INNER_SEPARATION - 1}, position.Width() - 2 * BUTTON_INNER_SEPARATION);
+ };
+ minimize.onClick = [this] { m_handler.OnWindowMinimize(); };
+
+ // Maximize
+ m_buttons.emplace_back();
+ Button& maximize = m_buttons.back();
+ maximize.draw = [this](Surface& surface, CRectInt position, bool hover)
+ {
+ DrawButton(surface, m_buttonColor, position, hover);
+ DrawRectangle(surface, m_buttonColor, {position.P1() + CPointInt{BUTTON_INNER_SEPARATION, BUTTON_INNER_SEPARATION}, position.P2() - CPointInt{BUTTON_INNER_SEPARATION, BUTTON_INNER_SEPARATION}});
+ DrawHorizontalLine(surface, m_buttonColor, position.P1() + CPointInt{BUTTON_INNER_SEPARATION, BUTTON_INNER_SEPARATION + 1}, position.Width() - 2 * BUTTON_INNER_SEPARATION);
+ };
+ maximize.onClick = [this] { m_handler.OnWindowMaximize(); };
+
+ // Close
+ m_buttons.emplace_back();
+ Button& close = m_buttons.back();
+ close.draw = [this](Surface& surface, CRectInt position, bool hover)
+ {
+ DrawButton(surface, m_buttonColor, position, hover);
+ auto height = position.Height() - 2 * BUTTON_INNER_SEPARATION;
+ DrawAngledLine(surface, m_buttonColor, position.P1() + CPointInt{BUTTON_INNER_SEPARATION, BUTTON_INNER_SEPARATION}, height, 1);
+ DrawAngledLine(surface, m_buttonColor, position.P1() + CPointInt{position.Width() - BUTTON_INNER_SEPARATION - 1, BUTTON_INNER_SEPARATION}, height, -1);
+ };
+ close.onClick = [this] { m_handler.OnWindowClose(); };
+ }
+ }
+ else
+ {
+ m_buttons.clear();
+ }
+}
+
+void CWindowDecorator::ResetSurfaces()
+{
+ if (IsDecorationActive())
+ {
+ if (!m_borderSurfaces.front().surface.wlSurface)
+ {
+ std::generate(m_borderSurfaces.begin(), m_borderSurfaces.end(), std::bind(&CWindowDecorator::MakeBorderSurface, this));
+ }
+ }
+ else
+ {
+ for (auto& borderSurface : m_borderSurfaces)
+ {
+ auto& wlSurface = borderSurface.surface.wlSurface;
+ if (wlSurface)
+ {
+ // Destroying the surface would cause some flicker because it takes effect
+ // immediately, before the next commit on the main surface - just make it
+ // invisible by attaching a NULL buffer
+ wlSurface.attach(wayland::buffer_t{}, 0, 0);
+ wlSurface.set_opaque_region(wayland::region_t{});
+ wlSurface.commit();
+ }
+ }
+ }
+}
+
+void CWindowDecorator::ReattachSubsurfaces()
+{
+ for (auto& surface : m_borderSurfaces)
+ {
+ surface.subsurface.set_position(surface.geometry.x1, surface.geometry.y1);
+ }
+}
+
+CRectInt CWindowDecorator::GetWindowGeometry() const
+{
+ CRectInt geometry{{0, 0}, m_mainSurfaceSize};
+
+ if (IsDecorationActive())
+ {
+ for (auto const& surface : m_borderSurfaces)
+ {
+ geometry.Union(surface.windowRect + surface.geometry.P1());
+ }
+ }
+
+ return geometry;
+}
+
+void CWindowDecorator::ResetShm()
+{
+ if (IsDecorationActive())
+ {
+ m_memory.reset(new CSharedMemory(MemoryBytesForSize(m_mainSurfaceSize, m_scale)));
+ m_memoryAllocatedSize = 0;
+ m_shmPool = m_shm.create_pool(m_memory->Fd(), m_memory->Size());
+ }
+ else
+ {
+ m_memory.reset();
+ m_shmPool.proxy_release();
+ }
+
+ for (auto& borderSurface : m_borderSurfaces)
+ {
+ // Buffers are invalid now, reset
+ borderSurface.surface.buffer = Buffer{};
+ }
+}
+
+void CWindowDecorator::PositionButtons()
+{
+ CPointInt position{m_borderSurfaces[SURFACE_TOP].surface.size.Width() - BORDER_WIDTH, BORDER_WIDTH + BUTTONS_EDGE_DISTANCE};
+ for (auto iter = m_buttons.rbegin(); iter != m_buttons.rend(); iter++)
+ {
+ position.x -= (BUTTONS_EDGE_DISTANCE + BUTTON_SIZE);
+ // Clamp if not enough space
+ position.x = std::max(0, position.x);
+
+ iter->position = CRectInt{position, position + CPointInt{BUTTON_SIZE, BUTTON_SIZE}};
+ }
+}
+
+CWindowDecorator::Buffer CWindowDecorator::GetBuffer(CSizeInt size)
+{
+ // We ignore tearing on the decorations for now.
+ // We can always implement a clever buffer management scheme later... :-)
+
+ auto totalSize{size.Area() * BYTES_PER_PIXEL};
+ if (m_memory->Size() < m_memoryAllocatedSize + totalSize)
+ {
+ // We miscalculated something
+ throw std::logic_error("Remaining SHM pool size is too small for requested buffer");
+ }
+ // argb8888 support is mandatory
+ auto buffer = m_shmPool.create_buffer(m_memoryAllocatedSize, size.Width(), size.Height(), size.Width() * BYTES_PER_PIXEL, wayland::shm_format::argb8888);
+
+ void* data{static_cast<std::uint8_t*> (m_memory->Data()) + m_memoryAllocatedSize};
+ m_memoryAllocatedSize += totalSize;
+
+ return { data, static_cast<std::size_t> (totalSize), size, std::move(buffer) };
+}
+
+void CWindowDecorator::AllocateBuffers()
+{
+ for (std::size_t i{0}; i < m_borderSurfaces.size(); i++)
+ {
+ auto& borderSurface = m_borderSurfaces[i];
+ if (!borderSurface.surface.buffer.data)
+ {
+ borderSurface.geometry = SurfaceGeometry(static_cast<SurfaceIndex> (i), m_mainSurfaceSize);
+ borderSurface.windowRect = SurfaceWindowRegion(static_cast<SurfaceIndex> (i), m_mainSurfaceSize);
+ borderSurface.surface.buffer = GetBuffer(borderSurface.geometry.ToSize() * m_scale);
+ borderSurface.surface.scale = m_scale;
+ borderSurface.surface.size = borderSurface.geometry.ToSize();
+ auto opaqueRegionGeometry = SurfaceOpaqueRegion(static_cast<SurfaceIndex> (i), m_mainSurfaceSize);
+ auto region = m_compositor.create_region();
+ region.add(opaqueRegionGeometry.x1, opaqueRegionGeometry.y1, opaqueRegionGeometry.Width(), opaqueRegionGeometry.Height());
+ borderSurface.surface.wlSurface.set_opaque_region(region);
+ if (borderSurface.surface.wlSurface.can_set_buffer_scale())
+ {
+ borderSurface.surface.wlSurface.set_buffer_scale(m_scale);
+ }
+ }
+ }
+}
+
+void CWindowDecorator::Repaint()
+{
+ // Fill transparent (outer) and color (inner)
+ for (auto& borderSurface : m_borderSurfaces)
+ {
+ std::fill_n(static_cast<std::uint32_t*> (borderSurface.surface.buffer.data), borderSurface.surface.buffer.size.Area(), Endian_SwapLE32(TRANSPARENT));
+ FillRectangle(borderSurface.surface, BORDER_COLOR, borderSurface.windowRect - CSizeInt{1, 1});
+ }
+ auto& topSurface = m_borderSurfaces[SURFACE_TOP].surface;
+ auto innerBorderColor = m_buttonColor;
+ // Draw rectangle
+ DrawHorizontalLine(topSurface, innerBorderColor, {BORDER_WIDTH - 1, BORDER_WIDTH - 1}, topSurface.size.Width() - 2 * BORDER_WIDTH + 2);
+ DrawVerticalLine(topSurface, innerBorderColor, {BORDER_WIDTH - 1, BORDER_WIDTH - 1}, topSurface.size.Height() - BORDER_WIDTH + 1);
+ DrawVerticalLine(topSurface, innerBorderColor, {topSurface.size.Width() - BORDER_WIDTH, BORDER_WIDTH - 1}, topSurface.size.Height() - BORDER_WIDTH + 1);
+ DrawVerticalLine(m_borderSurfaces[SURFACE_LEFT].surface, innerBorderColor, {BORDER_WIDTH - 1, 0}, m_borderSurfaces[SURFACE_LEFT].surface.size.Height());
+ DrawVerticalLine(m_borderSurfaces[SURFACE_RIGHT].surface, innerBorderColor, {0, 0}, m_borderSurfaces[SURFACE_RIGHT].surface.size.Height());
+ DrawHorizontalLine(m_borderSurfaces[SURFACE_BOTTOM].surface, innerBorderColor, {BORDER_WIDTH - 1, 0}, m_borderSurfaces[SURFACE_BOTTOM].surface.size.Width() - 2 * BORDER_WIDTH + 2);
+ // Draw white line into top bar as separator
+ DrawHorizontalLine(topSurface, innerBorderColor, {BORDER_WIDTH - 1, topSurface.size.Height() - 1}, topSurface.size.Width() - 2 * BORDER_WIDTH + 2);
+ // Draw buttons
+ for (auto& button : m_buttons)
+ {
+ button.draw(topSurface, button.position, button.hovered);
+ }
+
+ // Finally make everything visible
+ CommitAllBuffers();
+}
+
+void CWindowDecorator::CommitAllBuffers()
+{
+ std::unique_lock<CCriticalSection> lock(m_pendingBuffersMutex);
+
+ for (auto& borderSurface : m_borderSurfaces)
+ {
+ auto& wlSurface = borderSurface.surface.wlSurface;
+ auto& wlBuffer = borderSurface.surface.buffer.wlBuffer;
+ // Keep buffers in list so they are kept alive even when the Buffer gets
+ // recreated on size change
+ auto emplaceResult = m_pendingBuffers.emplace(wlBuffer);
+ if (emplaceResult.second)
+ {
+ // Buffer was not pending already
+ auto wlBufferC = reinterpret_cast<wl_buffer*> (wlBuffer.c_ptr());
+ // We can refer to the buffer neither by iterator (might be invalidated) nor by
+ // capturing the C++ instance in the lambda (would create a reference loop and
+ // never allow the object to be freed), so use the raw pointer for now
+ wlBuffer.on_release() = [this, wlBufferC]()
+ {
+ std::unique_lock<CCriticalSection> lock(m_pendingBuffersMutex);
+ // Construct a dummy object for searching the set
+ wayland::buffer_t findDummy(wlBufferC, wayland::proxy_t::wrapper_type::foreign);
+ auto iter = m_pendingBuffers.find(findDummy);
+ if (iter == m_pendingBuffers.end())
+ {
+ throw std::logic_error("Cannot release buffer that is not pending");
+ }
+
+ // Do not erase again until buffer is reattached (should not happen anyway, just to be safe)
+ // const_cast is OK since changing the function pointer does not affect
+ // the key in the set
+ const_cast<wayland::buffer_t&>(*iter).on_release() = nullptr;
+ m_pendingBuffers.erase(iter);
+ };
+ }
+
+ wlSurface.attach(wlBuffer, 0, 0);
+ wlSurface.damage(0, 0, borderSurface.surface.size.Width(), borderSurface.surface.size.Height());
+ wlSurface.commit();
+ }
+}
+
+void CWindowDecorator::LoadCursorTheme()
+{
+ std::unique_lock<CCriticalSection> lock(m_mutex);
+ if (!m_cursorTheme)
+ {
+ // Load default cursor theme
+ // Base size of 24px is what most cursor themes seem to have
+ m_cursorTheme = wayland::cursor_theme_t("", 24 * m_scale, m_shm);
+ }
+}
diff --git a/xbmc/windowing/wayland/WindowDecorator.h b/xbmc/windowing/wayland/WindowDecorator.h
new file mode 100644
index 0000000..23db237
--- /dev/null
+++ b/xbmc/windowing/wayland/WindowDecorator.h
@@ -0,0 +1,262 @@
+/*
+ * 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 "Connection.h"
+#include "Registry.h"
+#include "Seat.h"
+#include "ShellSurface.h"
+#include "Util.h"
+#include "WindowDecorationHandler.h"
+#include "threads/CriticalSection.h"
+#include "utils/Geometry.h"
+
+#include "platform/posix/utils/SharedMemory.h"
+
+#include <array>
+#include <memory>
+#include <set>
+
+#include <wayland-client-protocol.hpp>
+#include <wayland-cursor.hpp>
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace WAYLAND
+{
+
+enum SurfaceIndex
+{
+ SURFACE_TOP = 0,
+ SURFACE_RIGHT,
+ SURFACE_BOTTOM,
+ SURFACE_LEFT,
+ SURFACE_COUNT
+};
+
+/**
+ * Paint decorations around windows with subcompositing
+ *
+ * With Wayland, applications are responsible for drawing borders on their windows
+ * themselves (client-side decorations). To keep the impact on the overall architecture
+ * low, the Wayland platform implementation uses this very simple renderer to
+ * build ugly but fully functional decorations around the Kodi window. Since Kodi as a
+ * media center is usually not used in windowed mode anyway (except maybe for
+ * development), the impact of the visually not-so-pleasing decorations should
+ * be limited. Nice decorations would require more effort and external libraries for
+ * proper 2D drawing.
+ *
+ * If more platforms decide that client-side decorations would be a good idea to
+ * implement, the decorations could also be integrated with the Kodi skin system.
+ * Then this class could be removed.
+ *
+ * The decorations are positioned around the main surface automatically.
+ */
+class CWindowDecorator final : IRawInputHandlerTouch, IRawInputHandlerPointer
+{
+public:
+ /**
+ * Construct window decorator
+ * \param handler handler for window decoration events
+ * \param connection connection to get Wayland globals
+ * \param mainSurface main surface that decorations are constructed around
+ * \param windowSize full size of the window including decorations
+ * \param scale scale to use for buffers
+ * \param state surface state for adjusting decoration appearance
+ */
+ CWindowDecorator(IWindowDecorationHandler& handler, CConnection& connection, wayland::surface_t const& mainSurface);
+
+ /**
+ * Set decoration state and size by providing full surface size including decorations
+ *
+ * Calculates size of the main surface from size of all surfaces combined (including
+ * window decorations) by subtracting the decoration size
+ *
+ * Decorations will be disabled if state includes STATE_FULLSCREEN
+ *
+ * Call only from main thread
+ */
+ void SetState(CSizeInt size, int scale, IShellSurface::StateBitset state);
+ /**
+ * Get calculated size of main surface
+ */
+ CSizeInt GetMainSurfaceSize() const
+ {
+ return m_mainSurfaceSize;
+ }
+ /**
+ * Get full geometry of the window, including decorations if active
+ */
+ CRectInt GetWindowGeometry() const;
+ /**
+ * Calculate size of main surface given the size of the full area
+ * including decorations and a state
+ */
+ CSizeInt CalculateMainSurfaceSize(CSizeInt size, IShellSurface::StateBitset state) const;
+ /**
+ * Calculate size of full surface including decorations given the size of the
+ * main surface and a state
+ */
+ CSizeInt CalculateFullSurfaceSize(CSizeInt mainSurfaceSize, IShellSurface::StateBitset state) const;
+
+ bool IsDecorationActive() const;
+
+ void AddSeat(CSeat* seat);
+ void RemoveSeat(CSeat* seat);
+
+ struct Buffer
+ {
+ void* data{};
+ std::size_t dataSize{};
+ CSizeInt size{};
+ wayland::buffer_t wlBuffer;
+
+ Buffer() noexcept {}
+
+ Buffer(void* data, std::size_t dataSize, CSizeInt size, wayland::buffer_t&& buffer)
+ : data{data}, dataSize{dataSize}, size{size}, wlBuffer{std::move(buffer)}
+ {}
+
+ std::uint32_t* RgbaBuffer()
+ {
+ return static_cast<std::uint32_t*> (data);
+ }
+ };
+
+ struct Surface
+ {
+ wayland::surface_t wlSurface;
+ Buffer buffer;
+ CSizeInt size;
+ int scale{1};
+ };
+
+private:
+ CWindowDecorator(CWindowDecorator const& other) = delete;
+ CWindowDecorator& operator=(CWindowDecorator const& other) = delete;
+
+ // IRawInputHandlerTouch
+ void OnTouchDown(CSeat* seat,
+ std::uint32_t serial,
+ std::uint32_t time,
+ const wayland::surface_t& surface,
+ std::int32_t id,
+ double x,
+ double y) override;
+ // IRawInputHandlerPointer
+ void OnPointerEnter(CSeat* seat,
+ std::uint32_t serial,
+ const wayland::surface_t& surface,
+ double surfaceX,
+ double surfaceY) override;
+ void OnPointerLeave(CSeat* seat,
+ std::uint32_t serial,
+ const wayland::surface_t& surface) override;
+ void OnPointerMotion(CSeat* seat, std::uint32_t time, double surfaceX, double surfaceY) override;
+ void OnPointerButton(CSeat* seat, std::uint32_t serial, std::uint32_t time, std::uint32_t button, wayland::pointer_button_state state) override;
+
+ void Reset(bool reallocate);
+
+ // These functions should not be called directly as they may leave internal
+ // structures in an inconsistent state when called individually - only call
+ // Reset().
+ void ResetButtons();
+ void ResetSurfaces();
+ void ResetShm();
+ void ReattachSubsurfaces();
+ void PositionButtons();
+ void AllocateBuffers();
+ void Repaint();
+ void CommitAllBuffers();
+
+ bool StateHasWindowDecorations(IShellSurface::StateBitset state) const;
+
+ Buffer GetBuffer(CSizeInt size);
+
+ IWindowDecorationHandler& m_handler;
+
+ CSizeInt m_mainSurfaceSize;
+ int m_scale{1};
+ IShellSurface::StateBitset m_windowState;
+
+ CRegistry m_registry;
+ wayland::shm_t m_shm;
+ wayland::shm_pool_t m_shmPool;
+ wayland::compositor_t m_compositor;
+ wayland::subcompositor_t m_subcompositor;
+ wayland::surface_t m_mainSurface;
+
+ std::unique_ptr<KODI::UTILS::POSIX::CSharedMemory> m_memory;
+ std::size_t m_memoryAllocatedSize{};
+
+ struct BorderSurface
+ {
+ Surface surface;
+ wayland::subsurface_t subsurface;
+ CRectInt geometry;
+ /// Region of the surface that should count as being part of the window
+ CRectInt windowRect;
+ };
+ BorderSurface MakeBorderSurface();
+
+ /**
+ * Mutex for all surface/button state that is accessed from the event pump thread via seat events
+ * and the main thread:
+ * m_surfaces, m_buttons, m_windowSize, m_cursorTheme
+ *
+ * If size etc. is changing, mutex should be locked for the entire duration of the
+ * change until the internal state (surface, button size etc.) is consistent again.
+ */
+ CCriticalSection m_mutex;
+
+ std::array<BorderSurface, 4> m_borderSurfaces;
+
+ std::set<wayland::buffer_t, WaylandCPtrCompare> m_pendingBuffers;
+ CCriticalSection m_pendingBuffersMutex;
+
+ struct SeatState
+ {
+ CSeat* seat;
+ SurfaceIndex currentSurface{SURFACE_COUNT};
+ CPoint pointerPosition;
+
+ std::uint32_t pointerEnterSerial{};
+ std::string cursorName;
+ wayland::surface_t cursor;
+
+ explicit SeatState(CSeat* seat)
+ : seat{seat}
+ {}
+ };
+ std::map<std::uint32_t, SeatState> m_seats;
+
+ struct Button
+ {
+ CRectInt position;
+ bool hovered{};
+ std::function<void(Surface&, CRectInt, bool /* hover */)> draw;
+ std::function<void()> onClick;
+ };
+ std::vector<Button> m_buttons;
+
+ wayland::cursor_theme_t m_cursorTheme;
+ std::uint32_t m_buttonColor;
+
+ void LoadCursorTheme();
+
+ void UpdateSeatCursor(SeatState& seatState);
+ void UpdateButtonHoverState();
+ void HandleSeatClick(SeatState const& seatState, SurfaceIndex surface, std::uint32_t serial, std::uint32_t button, CPoint position);
+};
+
+}
+}
+}
diff --git a/xbmc/windowing/wayland/XkbcommonKeymap.cpp b/xbmc/windowing/wayland/XkbcommonKeymap.cpp
new file mode 100644
index 0000000..eb27cc9
--- /dev/null
+++ b/xbmc/windowing/wayland/XkbcommonKeymap.cpp
@@ -0,0 +1,333 @@
+/*
+ * 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 "XkbcommonKeymap.h"
+
+#include "Util.h"
+#include "utils/log.h"
+
+#include <iostream>
+#include <sstream>
+#include <stdexcept>
+#include <vector>
+
+using namespace KODI::WINDOWING::WAYLAND;
+
+namespace
+{
+
+struct ModifierNameXBMCMapping
+{
+ const char* name;
+ XBMCMod xbmc;
+};
+
+static const std::vector<ModifierNameXBMCMapping> ModifierNameXBMCMappings = {
+ { XKB_MOD_NAME_CTRL, XBMCKMOD_LCTRL },
+ { XKB_MOD_NAME_SHIFT, XBMCKMOD_LSHIFT },
+ { XKB_MOD_NAME_LOGO, XBMCKMOD_LSUPER },
+ { XKB_MOD_NAME_ALT, XBMCKMOD_LALT },
+ { "Meta", XBMCKMOD_LMETA },
+ { "RControl", XBMCKMOD_RCTRL },
+ { "RShift", XBMCKMOD_RSHIFT },
+ { "Hyper", XBMCKMOD_RSUPER },
+ { "AltGr", XBMCKMOD_RALT },
+ { XKB_LED_NAME_CAPS, XBMCKMOD_CAPS },
+ { XKB_LED_NAME_NUM, XBMCKMOD_NUM },
+ { XKB_LED_NAME_SCROLL, XBMCKMOD_MODE }
+};
+
+static const std::map<xkb_keycode_t, XBMCKey> XkbKeycodeXBMCMappings = {
+ // Function keys before start of ASCII printable character range
+ { XKB_KEY_BackSpace, XBMCK_BACKSPACE },
+ { XKB_KEY_Tab, XBMCK_TAB },
+ { XKB_KEY_Clear, XBMCK_CLEAR },
+ { XKB_KEY_Return, XBMCK_RETURN },
+ { XKB_KEY_Pause, XBMCK_PAUSE },
+ { XKB_KEY_Escape, XBMCK_ESCAPE },
+
+ // ASCII printable range - not included here
+
+ // Function keys after end of ASCII printable character range
+ { XKB_KEY_Delete, XBMCK_DELETE },
+
+ // Multimedia keys
+ { XKB_KEY_XF86Back, XBMCK_BROWSER_BACK },
+ { XKB_KEY_XF86Forward, XBMCK_BROWSER_FORWARD },
+ { XKB_KEY_XF86Refresh, XBMCK_BROWSER_REFRESH },
+ { XKB_KEY_XF86Stop, XBMCK_BROWSER_STOP },
+ { XKB_KEY_XF86Search, XBMCK_BROWSER_SEARCH },
+ // XKB_KEY_XF86Favorites could be XBMCK_BROWSER_FAVORITES or XBMCK_FAVORITES,
+ // XBMCK_FAVORITES was chosen here because it is more general
+ { XKB_KEY_XF86HomePage, XBMCK_BROWSER_HOME },
+ { XKB_KEY_XF86AudioMute, XBMCK_VOLUME_MUTE },
+ { XKB_KEY_XF86AudioLowerVolume, XBMCK_VOLUME_DOWN },
+ { XKB_KEY_XF86AudioRaiseVolume, XBMCK_VOLUME_UP },
+ { XKB_KEY_XF86AudioNext, XBMCK_MEDIA_NEXT_TRACK },
+ { XKB_KEY_XF86AudioPrev, XBMCK_MEDIA_PREV_TRACK },
+ { XKB_KEY_XF86AudioStop, XBMCK_MEDIA_STOP },
+ { XKB_KEY_XF86AudioPause, XBMCK_MEDIA_PLAY_PAUSE },
+ { XKB_KEY_XF86Mail, XBMCK_LAUNCH_MAIL },
+ { XKB_KEY_XF86Select, XBMCK_LAUNCH_MEDIA_SELECT },
+ { XKB_KEY_XF86Launch0, XBMCK_LAUNCH_APP1 },
+ { XKB_KEY_XF86Launch1, XBMCK_LAUNCH_APP2 },
+ { XKB_KEY_XF86WWW, XBMCK_LAUNCH_FILE_BROWSER },
+ { XKB_KEY_XF86AudioMedia, XBMCK_LAUNCH_MEDIA_CENTER },
+ { XKB_KEY_XF86AudioRewind, XBMCK_MEDIA_REWIND },
+ { XKB_KEY_XF86AudioForward, XBMCK_MEDIA_FASTFORWARD },
+
+ // Numeric keypad
+ { XKB_KEY_KP_0, XBMCK_KP0 },
+ { XKB_KEY_KP_1, XBMCK_KP1 },
+ { XKB_KEY_KP_2, XBMCK_KP2 },
+ { XKB_KEY_KP_3, XBMCK_KP3 },
+ { XKB_KEY_KP_4, XBMCK_KP4 },
+ { XKB_KEY_KP_5, XBMCK_KP5 },
+ { XKB_KEY_KP_6, XBMCK_KP6 },
+ { XKB_KEY_KP_7, XBMCK_KP7 },
+ { XKB_KEY_KP_8, XBMCK_KP8 },
+ { XKB_KEY_KP_9, XBMCK_KP9 },
+ { XKB_KEY_KP_Decimal, XBMCK_KP_PERIOD },
+ { XKB_KEY_KP_Divide, XBMCK_KP_DIVIDE },
+ { XKB_KEY_KP_Multiply, XBMCK_KP_MULTIPLY },
+ { XKB_KEY_KP_Subtract, XBMCK_KP_MINUS },
+ { XKB_KEY_KP_Add, XBMCK_KP_PLUS },
+ { XKB_KEY_KP_Enter, XBMCK_KP_ENTER },
+ { XKB_KEY_KP_Equal, XBMCK_KP_EQUALS },
+
+ // Arrows + Home/End pad
+ { XKB_KEY_Up, XBMCK_UP },
+ { XKB_KEY_Down, XBMCK_DOWN },
+ { XKB_KEY_Right, XBMCK_RIGHT },
+ { XKB_KEY_Left, XBMCK_LEFT },
+ { XKB_KEY_Insert, XBMCK_INSERT },
+ { XKB_KEY_Home, XBMCK_HOME },
+ { XKB_KEY_End, XBMCK_END },
+ { XKB_KEY_Page_Up, XBMCK_PAGEUP },
+ { XKB_KEY_Page_Down, XBMCK_PAGEDOWN },
+
+ // Function keys
+ { XKB_KEY_F1, XBMCK_F1 },
+ { XKB_KEY_F2, XBMCK_F2 },
+ { XKB_KEY_F3, XBMCK_F3 },
+ { XKB_KEY_F4, XBMCK_F4 },
+ { XKB_KEY_F5, XBMCK_F5 },
+ { XKB_KEY_F6, XBMCK_F6 },
+ { XKB_KEY_F7, XBMCK_F7 },
+ { XKB_KEY_F8, XBMCK_F8 },
+ { XKB_KEY_F9, XBMCK_F9 },
+ { XKB_KEY_F10, XBMCK_F10 },
+ { XKB_KEY_F11, XBMCK_F11 },
+ { XKB_KEY_F12, XBMCK_F12 },
+ { XKB_KEY_F13, XBMCK_F13 },
+ { XKB_KEY_F14, XBMCK_F14 },
+ { XKB_KEY_F15, XBMCK_F15 },
+
+ // Key state modifier keys
+ { XKB_KEY_Num_Lock, XBMCK_NUMLOCK },
+ { XKB_KEY_Caps_Lock, XBMCK_CAPSLOCK },
+ { XKB_KEY_Scroll_Lock, XBMCK_SCROLLOCK },
+ { XKB_KEY_Shift_R, XBMCK_RSHIFT },
+ { XKB_KEY_Shift_L, XBMCK_LSHIFT },
+ { XKB_KEY_Control_R, XBMCK_RCTRL },
+ { XKB_KEY_Control_L, XBMCK_LCTRL },
+ { XKB_KEY_Alt_R, XBMCK_RALT },
+ { XKB_KEY_Alt_L, XBMCK_LALT },
+ { XKB_KEY_Meta_R, XBMCK_RMETA },
+ { XKB_KEY_Meta_L, XBMCK_LMETA },
+ { XKB_KEY_Super_R, XBMCK_RSUPER },
+ { XKB_KEY_Super_L, XBMCK_LSUPER },
+ // XKB does not have XBMCK_MODE/"Alt Gr" - probably equal to XKB_KEY_Alt_R
+ { XKB_KEY_Multi_key, XBMCK_COMPOSE },
+
+ // Miscellaneous function keys
+ { XKB_KEY_Help, XBMCK_HELP },
+ { XKB_KEY_Print, XBMCK_PRINT },
+ // Unmapped: XBMCK_SYSREQ
+ { XKB_KEY_Break, XBMCK_BREAK },
+ { XKB_KEY_Menu, XBMCK_MENU },
+ { XKB_KEY_XF86PowerOff, XBMCK_POWER },
+ { XKB_KEY_EcuSign, XBMCK_EURO },
+ { XKB_KEY_Undo, XBMCK_UNDO },
+ { XKB_KEY_XF86Sleep, XBMCK_SLEEP },
+ // Unmapped: XBMCK_GUIDE, XBMCK_SETTINGS, XBMCK_INFO
+ { XKB_KEY_XF86Red, XBMCK_RED },
+ { XKB_KEY_XF86Green, XBMCK_GREEN },
+ { XKB_KEY_XF86Yellow, XBMCK_YELLOW },
+ { XKB_KEY_XF86Blue, XBMCK_BLUE },
+ // Unmapped: XBMCK_ZOOM, XBMCK_TEXT
+ { XKB_KEY_XF86Favorites, XBMCK_FAVORITES },
+ { XKB_KEY_XF86HomePage, XBMCK_HOMEPAGE },
+ // Unmapped: XBMCK_CONFIG, XBMCK_EPG
+
+ // Media keys
+ { XKB_KEY_XF86Eject, XBMCK_EJECT },
+ // XBMCK_STOP clashes with XBMCK_MEDIA_STOP
+ { XKB_KEY_XF86AudioRecord, XBMCK_RECORD },
+ // XBMCK_REWIND clashes with XBMCK_MEDIA_REWIND
+ { XKB_KEY_XF86Phone, XBMCK_PHONE },
+ { XKB_KEY_XF86AudioPlay, XBMCK_PLAY },
+ { XKB_KEY_XF86AudioRandomPlay, XBMCK_SHUFFLE }
+ // XBMCK_FASTFORWARD clashes with XBMCK_MEDIA_FASTFORWARD
+};
+
+}
+
+CXkbcommonContext::CXkbcommonContext(xkb_context_flags flags)
+: m_context{xkb_context_new(flags), XkbContextDeleter()}
+{
+ if (!m_context)
+ {
+ throw std::runtime_error("Failed to create xkb context");
+ }
+}
+
+void CXkbcommonContext::XkbContextDeleter::operator()(xkb_context* ctx) const
+{
+ xkb_context_unref(ctx);
+}
+
+std::unique_ptr<CXkbcommonKeymap> CXkbcommonContext::KeymapFromString(std::string const& keymap)
+{
+ std::unique_ptr<xkb_keymap, CXkbcommonKeymap::XkbKeymapDeleter> xkbKeymap{xkb_keymap_new_from_string(m_context.get(), keymap.c_str(), XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS), CXkbcommonKeymap::XkbKeymapDeleter()};
+
+ if (!xkbKeymap)
+ {
+ throw std::runtime_error("Failed to compile keymap");
+ }
+
+ return std::unique_ptr<CXkbcommonKeymap>{new CXkbcommonKeymap(std::move(xkbKeymap))};
+}
+
+std::unique_ptr<CXkbcommonKeymap> CXkbcommonContext::KeymapFromNames(const std::string& rules, const std::string& model, const std::string& layout, const std::string& variant, const std::string& options)
+{
+ xkb_rule_names names = {
+ rules.c_str(),
+ model.c_str(),
+ layout.c_str(),
+ variant.c_str(),
+ options.c_str()
+ };
+
+ std::unique_ptr<xkb_keymap, CXkbcommonKeymap::XkbKeymapDeleter> keymap{xkb_keymap_new_from_names(m_context.get(), &names, XKB_KEYMAP_COMPILE_NO_FLAGS), CXkbcommonKeymap::XkbKeymapDeleter()};
+
+ if (!keymap)
+ {
+ throw std::runtime_error("Failed to compile keymap");
+ }
+
+ return std::unique_ptr<CXkbcommonKeymap>{new CXkbcommonKeymap(std::move(keymap))};
+}
+
+std::unique_ptr<xkb_state, CXkbcommonKeymap::XkbStateDeleter> CXkbcommonKeymap::CreateXkbStateFromKeymap(xkb_keymap* keymap)
+{
+ std::unique_ptr<xkb_state, XkbStateDeleter> state{xkb_state_new(keymap), XkbStateDeleter()};
+
+ if (!state)
+ {
+ throw std::runtime_error("Failed to create keyboard state");
+ }
+
+ return state;
+}
+
+CXkbcommonKeymap::CXkbcommonKeymap(std::unique_ptr<xkb_keymap, XkbKeymapDeleter> keymap)
+: m_keymap{std::move(keymap)}, m_state{CreateXkbStateFromKeymap(m_keymap.get())}
+{
+ // Lookup modifier indices and create new map - this is more efficient
+ // than looking the modifiers up by name each time
+ for (auto const& nameMapping : ModifierNameXBMCMappings)
+ {
+ xkb_mod_index_t index = xkb_keymap_mod_get_index(m_keymap.get(), nameMapping.name);
+ if (index != XKB_MOD_INVALID)
+ {
+ m_modifierMappings.emplace_back(index, nameMapping.xbmc);
+ }
+ }
+}
+
+void CXkbcommonKeymap::XkbStateDeleter::operator()(xkb_state* state) const
+{
+ xkb_state_unref(state);
+}
+
+void CXkbcommonKeymap::XkbKeymapDeleter::operator()(xkb_keymap* keymap) const
+{
+ xkb_keymap_unref(keymap);
+}
+
+xkb_keysym_t CXkbcommonKeymap::KeysymForKeycode(xkb_keycode_t code) const
+{
+ return xkb_state_key_get_one_sym(m_state.get(), code);
+}
+
+xkb_mod_mask_t CXkbcommonKeymap::CurrentModifiers() const
+{
+ return xkb_state_serialize_mods(m_state.get(), XKB_STATE_MODS_EFFECTIVE);
+}
+
+void CXkbcommonKeymap::UpdateMask(xkb_mod_mask_t depressed, xkb_mod_mask_t latched, xkb_mod_mask_t locked, xkb_mod_mask_t group)
+{
+ xkb_state_update_mask(m_state.get(), depressed, latched, locked, 0, 0, group);
+}
+
+XBMCMod CXkbcommonKeymap::ActiveXBMCModifiers() const
+{
+ xkb_mod_mask_t mask(CurrentModifiers());
+ XBMCMod xbmcModifiers = XBMCKMOD_NONE;
+
+ for (auto const& mapping : m_modifierMappings)
+ {
+ if (mask & (1 << mapping.xkb))
+ {
+ xbmcModifiers = static_cast<XBMCMod> (xbmcModifiers | mapping.xbmc);
+ }
+ }
+
+ return xbmcModifiers;
+}
+
+XBMCKey CXkbcommonKeymap::XBMCKeyForKeysym(xkb_keysym_t sym)
+{
+ if (sym >= 'A' && sym <= 'Z')
+ {
+ // Uppercase ASCII characters must be lowercased as XBMCKey is modifier-invariant
+ return static_cast<XBMCKey> (sym + 'a' - 'A');
+ }
+ else if (sym >= 0x20 /* ASCII space */ && sym <= 0x7E /* ASCII tilde */)
+ {
+ // Rest of ASCII printable character range is code-compatible
+ return static_cast<XBMCKey> (sym);
+ }
+
+ // Try mapping
+ auto mapping = XkbKeycodeXBMCMappings.find(sym);
+ if (mapping != XkbKeycodeXBMCMappings.end())
+ {
+ return mapping->second;
+ }
+ else
+ {
+ return XBMCK_UNKNOWN;
+ }
+}
+
+XBMCKey CXkbcommonKeymap::XBMCKeyForKeycode(xkb_keycode_t code) const
+{
+ return XBMCKeyForKeysym(KeysymForKeycode(code));
+}
+
+std::uint32_t CXkbcommonKeymap::UnicodeCodepointForKeycode(xkb_keycode_t code) const
+{
+ return xkb_state_key_get_utf32(m_state.get(), code);
+}
+
+bool CXkbcommonKeymap::ShouldKeycodeRepeat(xkb_keycode_t code) const
+{
+ return xkb_keymap_key_repeats(m_keymap.get(), code);
+}
diff --git a/xbmc/windowing/wayland/XkbcommonKeymap.h b/xbmc/windowing/wayland/XkbcommonKeymap.h
new file mode 100644
index 0000000..7718b02
--- /dev/null
+++ b/xbmc/windowing/wayland/XkbcommonKeymap.h
@@ -0,0 +1,140 @@
+/*
+ * 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 "input/XBMC_keysym.h"
+
+#include <cstdint>
+#include <memory>
+#include <vector>
+
+#include <xkbcommon/xkbcommon.h>
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace WAYLAND
+{
+
+/**
+ * A wrapper class around an xkbcommon keymap and state tracker.
+ *
+ * This class knows about some common modifier combinations and keeps
+ * track of the currently pressed keys and modifiers. It also has
+ * some utility functions to transform hardware keycodes into
+ * a common representation.
+ *
+ * Since this class is keeping track of all the pressed and depressed
+ * modifiers, IT MUST ALWAYS BE KEPT UP TO DATE WITH ANY NEWLY
+ * PRESSED MODIFIERS. Undefined behaviour will result if it is not
+ * kept up to date.
+ *
+ * Instances can be easily created from keymap strings with \ref CXkbcommonContext
+ */
+class CXkbcommonKeymap
+{
+public:
+ struct XkbKeymapDeleter
+ {
+ void operator()(xkb_keymap* keymap) const;
+ };
+
+ /**
+ * Construct for known xkb_keymap
+ */
+ explicit CXkbcommonKeymap(std::unique_ptr<xkb_keymap, XkbKeymapDeleter> keymap);
+
+ /**
+ * Get xkb keysym for keycode - only a single keysym is supported
+ */
+ xkb_keysym_t KeysymForKeycode(xkb_keycode_t code) const;
+ /**
+ * Updates the currently depressed, latched, locked and group
+ * modifiers for a keyboard being tracked.
+ *
+ * This function must be called whenever modifiers change, or the state will
+ * be wrong and keysym translation will be off.
+ */
+ void UpdateMask(xkb_mod_mask_t depressed,
+ xkb_mod_mask_t latched,
+ xkb_mod_mask_t locked,
+ xkb_mod_mask_t group);
+ /**
+ * Gets the currently depressed, latched and locked modifiers
+ * for the keyboard
+ */
+ xkb_mod_mask_t CurrentModifiers() const;
+ /**
+ * Get XBMCKey for provided keycode
+ */
+ XBMCKey XBMCKeyForKeycode(xkb_keycode_t code) const;
+ /**
+ * \ref CurrentModifiers with XBMC flags
+ */
+ XBMCMod ActiveXBMCModifiers() const;
+ /**
+ * Get Unicode codepoint/UTF32 code for provided keycode
+ */
+ std::uint32_t UnicodeCodepointForKeycode(xkb_keycode_t code) const;
+ /**
+ * Check whether a given keycode should have key repeat
+ */
+ bool ShouldKeycodeRepeat(xkb_keycode_t code) const;
+
+ static XBMCKey XBMCKeyForKeysym(xkb_keysym_t sym);
+
+private:
+ struct XkbStateDeleter
+ {
+ void operator()(xkb_state* state) const;
+ };
+ static std::unique_ptr<xkb_state, XkbStateDeleter> CreateXkbStateFromKeymap(xkb_keymap* keymap);
+
+ std::unique_ptr<xkb_keymap, XkbKeymapDeleter> m_keymap;
+ std::unique_ptr<xkb_state, XkbStateDeleter> m_state;
+
+ struct ModifierMapping
+ {
+ xkb_mod_index_t xkb;
+ XBMCMod xbmc;
+ ModifierMapping(xkb_mod_index_t xkb, XBMCMod xbmc)
+ : xkb{xkb}, xbmc{xbmc}
+ {}
+ };
+ std::vector<ModifierMapping> m_modifierMappings;
+};
+
+class CXkbcommonContext
+{
+public:
+ explicit CXkbcommonContext(xkb_context_flags flags = XKB_CONTEXT_NO_FLAGS);
+
+ /**
+ * Opens a shared memory region and parses the data in it to an
+ * xkbcommon keymap.
+ *
+ * This function does not own the file descriptor. It must not be closed
+ * from this function.
+ */
+ std::unique_ptr<CXkbcommonKeymap> KeymapFromString(std::string const& keymap);
+ std::unique_ptr<CXkbcommonKeymap> KeymapFromNames(const std::string &rules, const std::string &model, const std::string &layout, const std::string &variant, const std::string &options);
+
+private:
+ struct XkbContextDeleter
+ {
+ void operator()(xkb_context* ctx) const;
+ };
+ std::unique_ptr<xkb_context, XkbContextDeleter> m_context;
+};
+
+
+}
+}
+}
diff --git a/xbmc/windowing/win10/CMakeLists.txt b/xbmc/windowing/win10/CMakeLists.txt
new file mode 100644
index 0000000..7e41c17
--- /dev/null
+++ b/xbmc/windowing/win10/CMakeLists.txt
@@ -0,0 +1,12 @@
+set(SOURCES WinEventsWin10.cpp
+ WinSystemWin10.cpp
+ WinSystemWin10DX.cpp
+ ../windows/VideoSyncD3D.cpp)
+
+set(HEADERS WinEventsWin10.h
+ WinSystemWin10.h
+ WinSystemWin10DX.h
+ ../windows/VideoSyncD3D.h
+ ../windows/WinKeyMap.h)
+
+core_add_library(windowing_windowsstore)
diff --git a/xbmc/windowing/win10/WinEventsWin10.cpp b/xbmc/windowing/win10/WinEventsWin10.cpp
new file mode 100644
index 0000000..35da911
--- /dev/null
+++ b/xbmc/windowing/win10/WinEventsWin10.cpp
@@ -0,0 +1,658 @@
+/*
+ * 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 "WinEventsWin10.h"
+
+#include "GUIUserMessages.h"
+#include "ServiceBroker.h"
+#include "application/AppInboundProtocol.h"
+#include "application/Application.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "input/actions/Action.h"
+#include "input/actions/ActionIDs.h"
+#include "input/mouse/MouseStat.h"
+#include "input/touch/generic/GenericTouchInputHandler.h"
+#include "interfaces/AnnouncementManager.h"
+#include "messaging/ApplicationMessenger.h"
+#include "rendering/dx/DeviceResources.h"
+#include "rendering/dx/RenderContext.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/SystemInfo.h"
+#include "utils/Variant.h"
+#include "utils/log.h"
+#include "windowing/windows/WinKeyMap.h"
+
+#include "platform/win10/input/RemoteControlXbox.h"
+
+#include <winrt/Windows.Devices.Input.h>
+
+namespace winrt
+{
+ using namespace Windows::Foundation;
+}
+using namespace winrt::Windows::ApplicationModel::Core;
+using namespace winrt::Windows::Devices::Input;
+using namespace winrt::Windows::Graphics::Display;
+using namespace winrt::Windows::Media;
+using namespace winrt::Windows::System;
+using namespace winrt::Windows::UI::Core;
+using namespace winrt::Windows::UI::Input;
+using namespace winrt::Windows::UI::ViewManagement;
+
+using namespace PERIPHERALS;
+
+static winrt::Point GetScreenPoint(winrt::Point point)
+{
+ auto dpi = DX::DeviceResources::Get()->GetDpi();
+ return winrt::Point(DX::ConvertDipsToPixels(point.X, dpi), DX::ConvertDipsToPixels(point.Y, dpi));
+}
+
+CWinEventsWin10::CWinEventsWin10() = default;
+CWinEventsWin10::~CWinEventsWin10() = default;
+
+void CWinEventsWin10::InitOSKeymap(void)
+{
+ KODI::WINDOWING::WINDOWS::DIB_InitOSKeymap();
+}
+
+void CWinEventsWin10::MessagePush(XBMC_Event *newEvent)
+{
+ // push input events in the queue they may init modal dialog which init
+ // deeper message loop and call the deeper MessagePump from there.
+ if ( newEvent->type == XBMC_KEYDOWN
+ || newEvent->type == XBMC_KEYUP
+ || newEvent->type == XBMC_MOUSEMOTION
+ || newEvent->type == XBMC_MOUSEBUTTONDOWN
+ || newEvent->type == XBMC_MOUSEBUTTONUP
+ || newEvent->type == XBMC_TOUCH)
+ {
+ m_events.push(*newEvent);
+ }
+ else
+ {
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ appPort->OnEvent(*newEvent);
+ }
+}
+
+bool CWinEventsWin10::MessagePump()
+{
+ bool ret = false;
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+
+ // processes all pending events and exits immediately
+ CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);
+
+ XBMC_Event pumpEvent;
+ while (m_events.try_pop(pumpEvent))
+ {
+ if (appPort)
+ ret |= appPort->OnEvent(pumpEvent);
+
+ if (pumpEvent.type == XBMC_MOUSEBUTTONUP)
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage(GUI_MSG_UNFOCUS_ALL, 0, 0, 0, 0);
+ }
+ return ret;
+}
+
+size_t CWinEventsWin10::GetQueueSize()
+{
+ return m_events.unsafe_size();
+}
+
+void CWinEventsWin10::InitEventHandlers(const CoreWindow& window)
+{
+ CWinEventsWin10::InitOSKeymap();
+
+ //window->SetPointerCapture();
+
+ // window
+ window.SizeChanged({ this, &CWinEventsWin10::OnWindowSizeChanged });
+ window.ResizeStarted({ this, &CWinEventsWin10::OnWindowResizeStarted });
+ window.ResizeCompleted({ this, &CWinEventsWin10::OnWindowResizeCompleted });
+ window.Closed({ this, &CWinEventsWin10::OnWindowClosed});
+ window.VisibilityChanged(CWinEventsWin10::OnVisibilityChanged);
+ window.Activated(CWinEventsWin10::OnWindowActivationChanged);
+ // mouse, touch and pen
+ window.PointerPressed({ this, &CWinEventsWin10::OnPointerPressed });
+ window.PointerMoved({ this, &CWinEventsWin10::OnPointerMoved });
+ window.PointerReleased({ this, &CWinEventsWin10::OnPointerReleased });
+ window.PointerExited({ this, &CWinEventsWin10::OnPointerExited });
+ window.PointerWheelChanged({ this, &CWinEventsWin10::OnPointerWheelChanged });
+ // keyboard
+ window.Dispatcher().AcceleratorKeyActivated({ this, &CWinEventsWin10::OnAcceleratorKeyActivated });
+ // display
+ DisplayInformation currentDisplayInformation = DisplayInformation::GetForCurrentView();
+ currentDisplayInformation.DpiChanged(CWinEventsWin10::OnDpiChanged);
+ currentDisplayInformation.OrientationChanged(CWinEventsWin10::OnOrientationChanged);
+ DisplayInformation::DisplayContentsInvalidated(CWinEventsWin10::OnDisplayContentsInvalidated);
+ // system
+ SystemNavigationManager sysNavManager = SystemNavigationManager::GetForCurrentView();
+ sysNavManager.BackRequested(CWinEventsWin10::OnBackRequested);
+
+ // requirement for backgroup playback
+ m_smtc = SystemMediaTransportControls::GetForCurrentView();
+ if (m_smtc)
+ {
+ m_smtc.IsPlayEnabled(true);
+ m_smtc.IsPauseEnabled(true);
+ m_smtc.IsStopEnabled(true);
+ m_smtc.IsRecordEnabled(true);
+ m_smtc.IsNextEnabled(true);
+ m_smtc.IsPreviousEnabled(true);
+ m_smtc.IsFastForwardEnabled(true);
+ m_smtc.IsRewindEnabled(true);
+ m_smtc.IsChannelUpEnabled(true);
+ m_smtc.IsChannelDownEnabled(true);
+ if (CSysInfo::GetWindowsDeviceFamily() != CSysInfo::WindowsDeviceFamily::Xbox)
+ {
+ m_smtc.ButtonPressed(CWinEventsWin10::OnSystemMediaButtonPressed);
+ }
+ m_smtc.IsEnabled(true);;
+ CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this);
+ }
+ if (CSysInfo::GetWindowsDeviceFamily() == CSysInfo::WindowsDeviceFamily::Xbox)
+ {
+ m_remote = std::make_unique<CRemoteControlXbox>();
+ m_remote->Initialize();
+ }
+}
+
+void CWinEventsWin10::UpdateWindowSize()
+{
+ auto size = DX::DeviceResources::Get()->GetOutputSize();
+
+ CLog::Log(LOGDEBUG, __FUNCTION__ ": window resize event {:f} x {:f} (as:{})", size.Width,
+ size.Height,
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_fullScreen ? "true"
+ : "false");
+
+ auto appView = ApplicationView::GetForCurrentView();
+ appView.SetDesiredBoundsMode(ApplicationViewBoundsMode::UseCoreWindow);
+
+ // seems app has lost FS mode it may occurs if an user use core window's button
+ if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_fullScreen && !appView.IsFullScreenMode())
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_fullScreen = false;
+
+ XBMC_Event newEvent = {};
+ newEvent.type = XBMC_VIDEORESIZE;
+ newEvent.resize.w = size.Width;
+ newEvent.resize.h = size.Height;
+ if (g_application.GetRenderGUI() && !DX::Windowing()->IsAlteringWindow() && newEvent.resize.w > 0 && newEvent.resize.h > 0)
+ MessagePush(&newEvent);
+}
+
+void CWinEventsWin10::OnResize(float width, float height)
+{
+ CLog::Log(LOGDEBUG, __FUNCTION__": window size changed.");
+ m_logicalWidth = width;
+ m_logicalHeight = height;
+ m_bResized = true;
+
+ if (m_sizeChanging)
+ return;
+
+ HandleWindowSizeChanged();
+}
+
+// Window event handlers.
+void CWinEventsWin10::OnWindowSizeChanged(const CoreWindow&, const WindowSizeChangedEventArgs& args)
+{
+ OnResize(args.Size().Width, args.Size().Height);
+}
+
+void CWinEventsWin10::OnWindowResizeStarted(const CoreWindow& sender, const winrt::IInspectable&)
+{
+ CLog::Log(LOGDEBUG, __FUNCTION__": window resize started.");
+ m_logicalPosX = sender.Bounds().X;
+ m_logicalPosY = sender.Bounds().Y;
+ m_sizeChanging = true;
+}
+
+void CWinEventsWin10::OnWindowResizeCompleted(const CoreWindow& sender, const winrt::IInspectable&)
+{
+ CLog::Log(LOGDEBUG, __FUNCTION__": window resize completed.");
+ m_sizeChanging = false;
+
+ if (m_logicalPosX != sender.Bounds().X || m_logicalPosY != sender.Bounds().Y)
+ m_bMoved = true;
+
+ HandleWindowSizeChanged();
+}
+
+void CWinEventsWin10::HandleWindowSizeChanged()
+{
+ CLog::Log(LOGDEBUG, __FUNCTION__": window size/move handled.");
+ if (m_bMoved)
+ {
+ // it will get position from CoreWindow
+ DX::Windowing()->OnMove(0, 0);
+ }
+ if (m_bResized)
+ {
+ DX::Windowing()->OnResize(m_logicalWidth, m_logicalHeight);
+ UpdateWindowSize();
+ }
+ m_bResized = false;
+ m_bMoved = false;
+}
+
+void CWinEventsWin10::OnVisibilityChanged(const CoreWindow& sender, const VisibilityChangedEventArgs& args)
+{
+ bool active = g_application.GetRenderGUI();
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ appPort->SetRenderGUI(args.Visible());
+
+ if (g_application.GetRenderGUI() != active)
+ DX::Windowing()->NotifyAppActiveChange(g_application.GetRenderGUI());
+ CLog::Log(LOGDEBUG, __FUNCTION__ ": window is {}",
+ g_application.GetRenderGUI() ? "shown" : "hidden");
+}
+
+void CWinEventsWin10::OnWindowActivationChanged(const CoreWindow& sender, const WindowActivatedEventArgs& args)
+{
+ bool active = g_application.GetRenderGUI();
+ if (args.WindowActivationState() == CoreWindowActivationState::Deactivated)
+ {
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ appPort->SetRenderGUI(DX::Windowing()->WindowedMode());
+ }
+ else if (args.WindowActivationState() == CoreWindowActivationState::PointerActivated
+ || args.WindowActivationState() == CoreWindowActivationState::CodeActivated)
+ {
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+ if (appPort)
+ appPort->SetRenderGUI(true);
+ }
+ if (g_application.GetRenderGUI() != active)
+ DX::Windowing()->NotifyAppActiveChange(g_application.GetRenderGUI());
+ CLog::Log(LOGDEBUG, __FUNCTION__ ": window is {}",
+ g_application.GetRenderGUI() ? "active" : "inactive");
+}
+
+void CWinEventsWin10::OnWindowClosed(const CoreWindow& sender, const CoreWindowEventArgs& args)
+{
+ // send quit command to the application if it's still running
+ if (!g_application.m_bStop)
+ {
+ XBMC_Event newEvent = {};
+ newEvent.type = XBMC_QUIT;
+ MessagePush(&newEvent);
+ }
+}
+
+void CWinEventsWin10::OnPointerPressed(const CoreWindow&, const PointerEventArgs& args)
+{
+ XBMC_Event newEvent = {};
+
+ PointerPoint point = args.CurrentPoint();
+ auto position = GetScreenPoint(point.Position());
+
+ if (point.PointerDevice().PointerDeviceType() == PointerDeviceType::Touch)
+ {
+ CGenericTouchInputHandler::GetInstance().HandleTouchInput(TouchInputDown, position.X, position.Y, point.Timestamp(), 0, 10);
+ return;
+ }
+ else
+ {
+ newEvent.type = XBMC_MOUSEBUTTONDOWN;
+ newEvent.button.x = position.X;
+ newEvent.button.y = position.Y;
+ if (point.PointerDevice().PointerDeviceType() == PointerDeviceType::Mouse)
+ {
+ if (point.Properties().IsLeftButtonPressed())
+ newEvent.button.button = XBMC_BUTTON_LEFT;
+ else if (point.Properties().IsMiddleButtonPressed())
+ newEvent.button.button = XBMC_BUTTON_MIDDLE;
+ else if (point.Properties().IsRightButtonPressed())
+ newEvent.button.button = XBMC_BUTTON_RIGHT;
+ }
+ else if (point.PointerDevice().PointerDeviceType() == PointerDeviceType::Pen)
+ {
+ // pen
+ // TODO
+ }
+ }
+ MessagePush(&newEvent);
+}
+
+void CWinEventsWin10::OnPointerMoved(const CoreWindow&, const PointerEventArgs& args)
+{
+ PointerPoint point = args.CurrentPoint();
+ auto position = GetScreenPoint(point.Position());
+
+ if (point.PointerDevice().PointerDeviceType() == PointerDeviceType::Touch)
+ {
+ if (point.IsInContact())
+ {
+ CGenericTouchInputHandler::GetInstance().UpdateTouchPointer(0, position.X, position.Y, point.Timestamp(), 10.f);
+ CGenericTouchInputHandler::GetInstance().HandleTouchInput(TouchInputMove, position.X, position.Y, point.Timestamp(), 0, 10.f);
+ }
+ return;
+ }
+
+ XBMC_Event newEvent = {};
+ newEvent.type = XBMC_MOUSEMOTION;
+ newEvent.motion.x = position.X;
+ newEvent.motion.y = position.Y;
+ MessagePush(&newEvent);
+}
+
+void CWinEventsWin10::OnPointerReleased(const CoreWindow&, const PointerEventArgs& args)
+{
+ PointerPoint point = args.CurrentPoint();
+ auto position = GetScreenPoint(point.Position());
+
+ if (point.PointerDevice().PointerDeviceType() == PointerDeviceType::Touch)
+ {
+ CGenericTouchInputHandler::GetInstance().HandleTouchInput(TouchInputUp, position.X, position.Y, point.Timestamp(), 0, 10);
+ return;
+ }
+
+ XBMC_Event newEvent = {};
+ newEvent.type = XBMC_MOUSEBUTTONUP;
+ newEvent.button.x = position.X;
+ newEvent.button.y = position.Y;
+
+ if (point.Properties().PointerUpdateKind() == PointerUpdateKind::LeftButtonReleased)
+ newEvent.button.button = XBMC_BUTTON_LEFT;
+ else if (point.Properties().PointerUpdateKind() == PointerUpdateKind::MiddleButtonReleased)
+ newEvent.button.button = XBMC_BUTTON_MIDDLE;
+ else if (point.Properties().PointerUpdateKind() == PointerUpdateKind::RightButtonReleased)
+ newEvent.button.button = XBMC_BUTTON_RIGHT;
+
+ MessagePush(&newEvent);
+}
+
+void CWinEventsWin10::OnPointerExited(const CoreWindow&, const PointerEventArgs& args)
+{
+ const PointerPoint& point = args.CurrentPoint();
+ auto position = GetScreenPoint(point.Position());
+
+ if (point.PointerDevice().PointerDeviceType() == PointerDeviceType::Touch)
+ {
+ CGenericTouchInputHandler::GetInstance().HandleTouchInput(TouchInputAbort, position.X, position.Y, point.Timestamp(), 0, 10);
+ }
+}
+
+void CWinEventsWin10::OnPointerWheelChanged(const CoreWindow&, const PointerEventArgs& args)
+{
+ XBMC_Event newEvent = {};
+ newEvent.type = XBMC_MOUSEBUTTONDOWN;
+ newEvent.button.x = args.CurrentPoint().Position().X;
+ newEvent.button.y = args.CurrentPoint().Position().Y;
+ newEvent.button.button = args.CurrentPoint().Properties().MouseWheelDelta() > 0 ? XBMC_BUTTON_WHEELUP : XBMC_BUTTON_WHEELDOWN;
+ MessagePush(&newEvent);
+ newEvent.type = XBMC_MOUSEBUTTONUP;
+ MessagePush(&newEvent);
+}
+
+void CWinEventsWin10::Kodi_KeyEvent(unsigned int vkey, unsigned scancode, unsigned keycode, bool isDown)
+{
+ using State = CoreVirtualKeyStates;
+
+ XBMC_keysym keysym = {};
+ keysym.scancode = scancode;
+ keysym.sym = KODI::WINDOWING::WINDOWS::VK_keymap[vkey];
+ keysym.unicode = keycode;
+
+ auto window = CoreWindow::GetForCurrentThread();
+
+ uint16_t mod = (uint16_t)XBMCKMOD_NONE;
+ // If left control and right alt are down this usually means that AltGr is down
+ if ((window.GetKeyState(VirtualKey::LeftControl) & State::Down) == State::Down
+ && (window.GetKeyState(VirtualKey::RightMenu) & State::Down) == State::Down)
+ {
+ mod |= XBMCKMOD_MODE;
+ mod |= XBMCKMOD_MODE;
+ }
+ else
+ {
+ if ((window.GetKeyState(VirtualKey::LeftControl) & State::Down) == State::Down)
+ mod |= XBMCKMOD_LCTRL;
+ if ((window.GetKeyState(VirtualKey::RightMenu) & State::Down) == State::Down)
+ mod |= XBMCKMOD_RALT;
+ }
+
+ // Check the remaining modifiers
+ if ((window.GetKeyState(VirtualKey::LeftShift) & State::Down) == State::Down)
+ mod |= XBMCKMOD_LSHIFT;
+ if ((window.GetKeyState(VirtualKey::RightShift) & State::Down) == State::Down)
+ mod |= XBMCKMOD_RSHIFT;
+ if ((window.GetKeyState(VirtualKey::RightControl) & State::Down) == State::Down)
+ mod |= XBMCKMOD_RCTRL;
+ if ((window.GetKeyState(VirtualKey::LeftMenu) & State::Down) == State::Down)
+ mod |= XBMCKMOD_LALT;
+ if ((window.GetKeyState(VirtualKey::LeftWindows) & State::Down) == State::Down)
+ mod |= XBMCKMOD_LSUPER;
+ if ((window.GetKeyState(VirtualKey::RightWindows) & State::Down) == State::Down)
+ mod |= XBMCKMOD_LSUPER;
+
+ keysym.mod = static_cast<XBMCMod>(mod);
+
+ XBMC_Event newEvent = {};
+ newEvent.type = isDown ? XBMC_KEYDOWN : XBMC_KEYUP;
+ newEvent.key.keysym = keysym;
+ MessagePush(&newEvent);
+}
+
+void CWinEventsWin10::OnAcceleratorKeyActivated(const CoreDispatcher&, const AcceleratorKeyEventArgs& args)
+{
+ static auto lockedState = CoreVirtualKeyStates::Locked;
+ static VirtualKey keyStore = VirtualKey::None;
+
+ // skip if device is remote control
+ if (m_remote && m_remote->IsRemoteDevice(args.DeviceId().c_str()))
+ return;
+
+ bool isDown = false;
+ unsigned keyCode = 0;
+ unsigned vk = static_cast<unsigned>(args.VirtualKey());
+
+ auto window = CoreWindow::GetForCurrentThread();
+ bool numLockLocked = ((window.GetKeyState(VirtualKey::NumberKeyLock) & lockedState) == lockedState);
+
+ switch (args.EventType())
+ {
+ case CoreAcceleratorKeyEventType::KeyDown:
+ case CoreAcceleratorKeyEventType::SystemKeyDown:
+ {
+ if ( (vk == 0x08) // VK_BACK
+ || (vk == 0x09) // VK_TAB
+ || (vk == 0x0C) // VK_CLEAR
+ || (vk == 0x0D) // VK_RETURN
+ || (vk == 0x1B) // VK_ESCAPE
+ || (vk == 0x20) // VK_SPACE
+ || (vk >= 0x30 && vk <= 0x39) // numeric keys
+ || (vk >= 0x41 && vk <= 0x5A) // alphabetic keys
+ || (vk >= 0x60 && vk <= 0x69 && numLockLocked) // keypad numeric (if numlock is on)
+ || (vk >= 0x6A && vk <= 0x6F) // keypad keys except numeric
+ || (vk >= 0x92 && vk <= 0x96) // OEM specific
+ || (vk >= 0xBA && vk <= 0xC0) // OEM specific
+ || (vk >= 0xDB && vk <= 0xDF) // OEM specific
+ || (vk >= 0xE1 && vk <= 0xF5 && vk != 0xE5 && vk != 0xE7 && vk != 0xE8) // OEM specific
+ )
+ {
+ // store this for character events, because VirtualKey is key code on character event.
+ keyStore = args.VirtualKey();
+ return;
+ }
+ isDown = true;
+ break;
+ }
+ case CoreAcceleratorKeyEventType::KeyUp:
+ case CoreAcceleratorKeyEventType::SystemKeyUp:
+ break;
+ case CoreAcceleratorKeyEventType::Character:
+ case CoreAcceleratorKeyEventType::SystemCharacter:
+ case CoreAcceleratorKeyEventType::UnicodeCharacter:
+ case CoreAcceleratorKeyEventType::DeadCharacter:
+ case CoreAcceleratorKeyEventType::SystemDeadCharacter:
+ {
+ // VirtualKey is KeyCode
+ keyCode = static_cast<unsigned>(args.VirtualKey());
+ // rewrite vk with stored value
+ vk = static_cast<unsigned>(keyStore);
+ // reset stored value
+ keyStore = VirtualKey::None;
+ isDown = true;
+ }
+ default:
+ break;
+ }
+
+ Kodi_KeyEvent(vk, args.KeyStatus().ScanCode, keyCode, isDown);
+ args.Handled(true);
+}
+
+// DisplayInformation event handlers.
+void CWinEventsWin10::OnDpiChanged(const DisplayInformation& sender, const winrt::IInspectable&)
+{
+ // Note: The value for LogicalDpi retrieved here may not match the effective DPI of the app
+ // if it is being scaled for high resolution devices. Once the DPI is set on DeviceResources,
+ // you should always retrieve it using the GetDpi method.
+ // See DeviceResources.cpp for more details.
+ //critical_section::scoped_lock lock(m_deviceResources->GetCriticalSection());
+ RECT resizeRect = { 0,0,0,0 };
+ DX::Windowing()->DPIChanged(sender.LogicalDpi(), resizeRect);
+ CGenericTouchInputHandler::GetInstance().SetScreenDPI(DX::DisplayMetrics::Dpi100);
+}
+
+void CWinEventsWin10::OnOrientationChanged(const DisplayInformation&, const winrt::IInspectable&)
+{
+ //critical_section::scoped_lock lock(m_deviceResources->GetCriticalSection());
+ //m_deviceResources->SetCurrentOrientation(sender->CurrentOrientation);
+
+ //auto size = DX::DeviceResources::Get()->GetOutputSize();
+ //UpdateWindowSize(size.Width, size.Height);
+}
+
+void CWinEventsWin10::OnDisplayContentsInvalidated(const DisplayInformation&, const winrt::IInspectable&)
+{
+ CLog::Log(LOGDEBUG, __FUNCTION__": onevent.");
+ DX::DeviceResources::Get()->ValidateDevice();
+}
+
+void CWinEventsWin10::OnBackRequested(const winrt::IInspectable&, const BackRequestedEventArgs& args)
+{
+ // handle this only on windows mobile
+ if (CSysInfo::GetWindowsDeviceFamily() == CSysInfo::WindowsDeviceFamily::Mobile)
+ {
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1,
+ static_cast<void*>(new CAction(ACTION_NAV_BACK)));
+ }
+ args.Handled(true);
+}
+
+void CWinEventsWin10::OnSystemMediaButtonPressed(const SystemMediaTransportControls&, const SystemMediaTransportControlsButtonPressedEventArgs& args)
+{
+ int action = ACTION_NONE;
+ switch (args.Button())
+ {
+ case SystemMediaTransportControlsButton::ChannelDown:
+ action = ACTION_CHANNEL_DOWN;
+ break;
+ case SystemMediaTransportControlsButton::ChannelUp:
+ action = ACTION_CHANNEL_UP;
+ break;
+ case SystemMediaTransportControlsButton::FastForward:
+ action = ACTION_PLAYER_FORWARD;
+ break;
+ case SystemMediaTransportControlsButton::Rewind:
+ action = ACTION_PLAYER_REWIND;
+ break;
+ case SystemMediaTransportControlsButton::Next:
+ action = ACTION_NEXT_ITEM;
+ break;
+ case SystemMediaTransportControlsButton::Previous:
+ action = ACTION_PREV_ITEM;
+ break;
+ case SystemMediaTransportControlsButton::Pause:
+ case SystemMediaTransportControlsButton::Play:
+ action = ACTION_PLAYER_PLAYPAUSE;
+ break;
+ case SystemMediaTransportControlsButton::Stop:
+ action = ACTION_STOP;
+ break;
+ case SystemMediaTransportControlsButton::Record:
+ action = ACTION_RECORD;
+ break;
+ default:
+ break;
+ }
+ if (action != ACTION_NONE)
+ {
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1,
+ static_cast<void*>(new CAction(action)));
+ }
+}
+
+void CWinEventsWin10::Announce(ANNOUNCEMENT::AnnouncementFlag flag,
+ const std::string& sender,
+ const std::string& message,
+ const CVariant& data)
+{
+ if (flag & ANNOUNCEMENT::Player)
+ {
+ double speed = 1.0;
+ if (data.isMember("player") && data["player"].isMember("speed"))
+ speed = data["player"]["speed"].asDouble(1.0);
+
+ bool changed = false;
+ MediaPlaybackStatus status = MediaPlaybackStatus::Changing;
+
+ if (message == "OnPlay" || message == "OnResume")
+ {
+ changed = true;
+ status = MediaPlaybackStatus::Playing;
+ }
+ else if (message == "OnStop")
+ {
+ changed = true;
+ status = MediaPlaybackStatus::Stopped;
+ }
+ else if (message == "OnPause")
+ {
+ changed = true;
+ status = MediaPlaybackStatus::Paused;
+ }
+ else if (message == "OnSpeedChanged")
+ {
+ changed = true;
+ status = speed != 0.0 ? MediaPlaybackStatus::Playing : MediaPlaybackStatus::Paused;
+ }
+
+ if (changed)
+ {
+ try
+ {
+ auto dispatcher = CoreApplication::MainView().Dispatcher();
+ if (dispatcher)
+ {
+ dispatcher.RunAsync(CoreDispatcherPriority::Normal, DispatchedHandler([status, speed]
+ {
+ auto smtc = SystemMediaTransportControls::GetForCurrentView();
+ if (!smtc)
+ return;
+
+ smtc.PlaybackStatus(status);
+ smtc.PlaybackRate(speed);
+ }));
+ }
+ }
+ catch (const winrt::hresult_error&)
+ {
+ }
+ }
+ }
+}
diff --git a/xbmc/windowing/win10/WinEventsWin10.h b/xbmc/windowing/win10/WinEventsWin10.h
new file mode 100644
index 0000000..c4bd345
--- /dev/null
+++ b/xbmc/windowing/win10/WinEventsWin10.h
@@ -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.
+ */
+
+#pragma once
+
+#include "interfaces/IAnnouncer.h"
+#include "windowing/WinEvents.h"
+
+#include <concurrent_queue.h>
+#include <winrt/Windows.Media.h>
+
+class CRemoteControlXbox;
+
+class CWinEventsWin10 : public IWinEvents
+ , public ANNOUNCEMENT::IAnnouncer
+{
+public:
+ CWinEventsWin10();
+ virtual ~CWinEventsWin10();
+
+ void MessagePush(XBMC_Event *newEvent);
+ bool MessagePump() override;
+ virtual size_t GetQueueSize();
+
+ // initialization
+ void InitEventHandlers(const winrt::Windows::UI::Core::CoreWindow&);
+ static void InitOSKeymap(void);
+
+ // Window event handlers.
+ void OnWindowSizeChanged(const winrt::Windows::UI::Core::CoreWindow&, const winrt::Windows::UI::Core::WindowSizeChangedEventArgs&);
+ void OnWindowResizeStarted(const winrt::Windows::UI::Core::CoreWindow&, const winrt::Windows::Foundation::IInspectable&);
+ void OnWindowResizeCompleted(const winrt::Windows::UI::Core::CoreWindow&, const winrt::Windows::Foundation::IInspectable&);
+ void OnWindowClosed(const winrt::Windows::UI::Core::CoreWindow&, const winrt::Windows::UI::Core::CoreWindowEventArgs&);
+ static void OnWindowActivationChanged(const winrt::Windows::UI::Core::CoreWindow&, const winrt::Windows::UI::Core::WindowActivatedEventArgs&);
+ static void OnVisibilityChanged(const winrt::Windows::UI::Core::CoreWindow&, const winrt::Windows::UI::Core::VisibilityChangedEventArgs&);
+ // touch mouse and pen
+ void OnPointerPressed(const winrt::Windows::UI::Core::CoreWindow&, const winrt::Windows::UI::Core::PointerEventArgs&);
+ void OnPointerMoved(const winrt::Windows::UI::Core::CoreWindow&, const winrt::Windows::UI::Core::PointerEventArgs&);
+ void OnPointerReleased(const winrt::Windows::UI::Core::CoreWindow&, const winrt::Windows::UI::Core::PointerEventArgs&);
+ void OnPointerExited(const winrt::Windows::UI::Core::CoreWindow&, const winrt::Windows::UI::Core::PointerEventArgs&);
+ void OnPointerWheelChanged(const winrt::Windows::UI::Core::CoreWindow&, const winrt::Windows::UI::Core::PointerEventArgs&);
+ // keyboard
+ void OnAcceleratorKeyActivated(const winrt::Windows::UI::Core::CoreDispatcher&, const winrt::Windows::UI::Core::AcceleratorKeyEventArgs&);
+
+ // DisplayInformation event handlers.
+ static void OnDpiChanged(const winrt::Windows::Graphics::Display::DisplayInformation&, const winrt::Windows::Foundation::IInspectable&);
+ static void OnOrientationChanged(const winrt::Windows::Graphics::Display::DisplayInformation&, const winrt::Windows::Foundation::IInspectable&);
+ static void OnDisplayContentsInvalidated(const winrt::Windows::Graphics::Display::DisplayInformation&, const winrt::Windows::Foundation::IInspectable&);
+ // system
+ static void OnBackRequested(const winrt::Windows::Foundation::IInspectable&, const winrt::Windows::UI::Core::BackRequestedEventArgs&);
+ // system media handlers
+ static void OnSystemMediaButtonPressed(const winrt::Windows::Media::SystemMediaTransportControls&
+ , const winrt::Windows::Media::SystemMediaTransportControlsButtonPressedEventArgs&);
+ // IAnnouncer overrides
+ void Announce(ANNOUNCEMENT::AnnouncementFlag flag,
+ const std::string& sender,
+ const std::string& message,
+ const CVariant& data) override;
+
+private:
+ friend class CWinSystemWin10;
+
+ void OnResize(float width, float height);
+ void UpdateWindowSize();
+ void Kodi_KeyEvent(unsigned int vkey, unsigned scancode, unsigned keycode, bool isDown);
+ void HandleWindowSizeChanged();
+
+ Concurrency::concurrent_queue<XBMC_Event> m_events;
+ winrt::Windows::Media::SystemMediaTransportControls m_smtc{ nullptr };
+ bool m_bResized{ false };
+ bool m_bMoved{ false };
+ bool m_sizeChanging{ false };
+ float m_logicalWidth{ 0 };
+ float m_logicalHeight{ 0 };
+ float m_logicalPosX{ 0 };
+ float m_logicalPosY{ 0 };
+ std::unique_ptr<CRemoteControlXbox> m_remote;
+};
diff --git a/xbmc/windowing/win10/WinSystemWin10.cpp b/xbmc/windowing/win10/WinSystemWin10.cpp
new file mode 100644
index 0000000..6de3f78
--- /dev/null
+++ b/xbmc/windowing/win10/WinSystemWin10.cpp
@@ -0,0 +1,660 @@
+/*
+ * 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 "WinSystemWin10.h"
+
+#include "ServiceBroker.h"
+#include "WinEventsWin10.h"
+#include "application/Application.h"
+#include "cores/AudioEngine/AESinkFactory.h"
+#include "cores/AudioEngine/Sinks/AESinkWASAPI.h"
+#include "cores/AudioEngine/Sinks/AESinkXAudio.h"
+#include "rendering/dx/DirectXHelper.h"
+#include "rendering/dx/RenderContext.h"
+#include "rendering/dx/ScreenshotSurfaceWindows.h"
+#include "settings/DisplaySettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/SystemInfo.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+#include "windowing/windows/VideoSyncD3D.h"
+
+#include "platform/win10/AsyncHelpers.h"
+#include "platform/win32/CharsetConverter.h"
+
+#include <mutex>
+
+#pragma pack(push,8)
+
+#include <tpcshrd.h>
+#include <ppltasks.h>
+#include <winrt/Windows.ApplicationModel.DataTransfer.h>
+#include <winrt/Windows.Foundation.Metadata.h>
+#include <winrt/Windows.Graphics.Display.h>
+#include <winrt/Windows.Graphics.Display.Core.h>
+
+using namespace winrt::Windows::ApplicationModel::DataTransfer;
+using namespace winrt::Windows::Foundation::Metadata;
+using namespace winrt::Windows::Graphics::Display;
+using namespace winrt::Windows::Graphics::Display::Core;
+using namespace winrt::Windows::UI::Core;
+using namespace winrt::Windows::UI::ViewManagement;
+
+using namespace std::chrono_literals;
+
+CWinSystemWin10::CWinSystemWin10()
+ : CWinSystemBase()
+ , m_ValidWindowedPosition(false)
+ , m_IsAlteringWindow(false)
+ , m_delayDispReset(false)
+ , m_state(WINDOW_STATE_WINDOWED)
+ , m_fullscreenState(WINDOW_FULLSCREEN_STATE_FULLSCREEN_WINDOW)
+ , m_windowState(WINDOW_WINDOW_STATE_WINDOWED)
+ , m_inFocus(false)
+ , m_bMinimized(false)
+{
+ m_winEvents.reset(new CWinEventsWin10());
+
+ AE::CAESinkFactory::ClearSinks();
+ CAESinkXAudio::Register();
+ CAESinkWASAPI::Register();
+ CScreenshotSurfaceWindows::Register();
+}
+
+CWinSystemWin10::~CWinSystemWin10()
+{
+};
+
+bool CWinSystemWin10::InitWindowSystem()
+{
+ m_coreWindow = CoreWindow::GetForCurrentThread();
+ dynamic_cast<CWinEventsWin10&>(*m_winEvents).InitEventHandlers(m_coreWindow);
+
+ if (!CWinSystemBase::InitWindowSystem())
+ return false;
+
+ if (m_displays.empty())
+ {
+ CLog::Log(LOGERROR, "{} - no suitable monitor found, aborting...", __FUNCTION__);
+ return false;
+ }
+
+ return true;
+}
+
+bool CWinSystemWin10::DestroyWindowSystem()
+{
+ m_bWindowCreated = false;
+ RestoreDesktopResolution();
+ return true;
+}
+
+bool CWinSystemWin10::CanDoWindowed()
+{
+ return CSysInfo::GetWindowsDeviceFamily() == CSysInfo::Desktop;
+}
+
+bool CWinSystemWin10::CreateNewWindow(const std::string& name, bool fullScreen, RESOLUTION_INFO& res)
+{
+ UpdateStates(fullScreen);
+ // initialize the state
+ WINDOW_STATE state = GetState(fullScreen);
+
+ m_nWidth = res.iWidth;
+ m_nHeight = res.iHeight;
+ m_bFullScreen = fullScreen;
+ m_fRefreshRate = res.fRefreshRate;
+ m_inFocus = true;
+ m_bWindowCreated = true;
+ m_state = state;
+
+ m_coreWindow.Activate();
+
+ AdjustWindow();
+ // dispatch all events currently pending in the queue to show window's content
+ // and hide UWP splash, without this the Kodi's splash will not be shown
+ m_coreWindow.Dispatcher().ProcessEvents(CoreProcessEventsOption::ProcessOneAndAllPending);
+
+ return true;
+}
+
+bool CWinSystemWin10::ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop)
+{
+ m_nWidth = newWidth;
+ m_nHeight = newHeight;
+
+ if (newLeft > 0)
+ m_nLeft = newLeft;
+
+ if (newTop > 0)
+ m_nTop = newTop;
+
+ AdjustWindow();
+
+ return true;
+}
+
+void CWinSystemWin10::FinishWindowResize(int newWidth, int newHeight)
+{
+ m_nWidth = newWidth;
+ m_nHeight = newHeight;
+
+ float dpi = DX::DeviceResources::Get()->GetDpi();
+ int dipsWidth = round(DX::ConvertPixelsToDips(m_nWidth, dpi));
+ int dipsHeight = round(DX::ConvertPixelsToDips(m_nHeight, dpi));
+
+ ApplicationView::PreferredLaunchViewSize(winrt::Windows::Foundation::Size(dipsWidth, dipsHeight));
+ ApplicationView::PreferredLaunchWindowingMode(ApplicationViewWindowingMode::PreferredLaunchViewSize);
+}
+
+void CWinSystemWin10::AdjustWindow()
+{
+ CLog::Log(LOGDEBUG, __FUNCTION__": adjusting window if required.");
+
+ auto appView = ApplicationView::GetForCurrentView();
+ bool isInFullscreen = appView.IsFullScreenMode();
+
+ if (m_state == WINDOW_STATE_FULLSCREEN_WINDOW || m_state == WINDOW_STATE_FULLSCREEN)
+ {
+ if (!isInFullscreen)
+ {
+ if (appView.TryEnterFullScreenMode())
+ ApplicationView::PreferredLaunchWindowingMode(ApplicationViewWindowingMode::FullScreen);
+ }
+ }
+ else // m_state == WINDOW_STATE_WINDOWED
+ {
+ if (isInFullscreen)
+ {
+ appView.ExitFullScreenMode();
+ }
+
+ int viewWidth = appView.VisibleBounds().Width;
+ int viewHeight = appView.VisibleBounds().Height;
+
+ float dpi = DX::DeviceResources::Get()->GetDpi();
+ int dipsWidth = round(DX::ConvertPixelsToDips(m_nWidth, dpi));
+ int dipsHeight = round(DX::ConvertPixelsToDips(m_nHeight, dpi));
+
+ if (viewHeight != dipsHeight || viewWidth != dipsWidth)
+ {
+ if (!appView.TryResizeView(winrt::Windows::Foundation::Size(dipsWidth, dipsHeight)))
+ {
+ CLog::LogF(LOGDEBUG, __FUNCTION__, "resizing ApplicationView failed.");
+ }
+ }
+
+ ApplicationView::PreferredLaunchViewSize(winrt::Windows::Foundation::Size(dipsWidth, dipsHeight));
+ ApplicationView::PreferredLaunchWindowingMode(ApplicationViewWindowingMode::PreferredLaunchViewSize);
+ }
+}
+
+bool CWinSystemWin10::SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays)
+{
+ CWinSystemWin10::UpdateStates(fullScreen);
+ WINDOW_STATE state = GetState(fullScreen);
+
+ CLog::Log(LOGDEBUG, "{} ({}) with size {}x{}, refresh {:f}{}", __FUNCTION__,
+ window_state_names[state], res.iWidth, res.iHeight, res.fRefreshRate,
+ (res.dwFlags & D3DPRESENTFLAG_INTERLACED) ? "i" : "");
+
+ bool forceChange = false; // resolution/display is changed but window state isn't changed
+ bool stereoChange = IsStereoEnabled() != (CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode() == RENDER_STEREO_MODE_HARDWAREBASED);
+
+ if ( m_nWidth != res.iWidth || m_nHeight != res.iHeight || m_fRefreshRate != res.fRefreshRate ||
+ stereoChange || m_bFirstResChange)
+ {
+ forceChange = true;
+ }
+
+ if (state == m_state && !forceChange)
+ return true;
+
+ // entering to stereo mode, limit resolution to 1080p@23.976
+ if (stereoChange && !IsStereoEnabled() && res.iWidth > 1280)
+ {
+ res = CDisplaySettings::GetInstance().GetResolutionInfo(CResolutionUtils::ChooseBestResolution(24.f / 1.001f, 1920, 1080, true));
+ }
+
+ if (m_state == WINDOW_STATE_WINDOWED)
+ {
+ if (m_coreWindow)
+ {
+ m_nLeft = m_coreWindow.Bounds().X;
+ m_nTop = m_coreWindow.Bounds().Y;
+ m_ValidWindowedPosition = true;
+ }
+ }
+
+ m_IsAlteringWindow = true;
+ ReleaseBackBuffer();
+
+ m_bFirstResChange = false;
+ m_bFullScreen = fullScreen;
+ m_nWidth = res.iWidth;
+ m_nHeight = res.iHeight;
+ m_bBlankOtherDisplay = blankOtherDisplays;
+ m_fRefreshRate = res.fRefreshRate;
+
+ if (state == WINDOW_STATE_FULLSCREEN)
+ {
+ // isn't allowed in UWP
+ }
+ else if (m_state == WINDOW_STATE_FULLSCREEN || m_state == WINDOW_STATE_FULLSCREEN_WINDOW) // we're in fullscreen state now
+ {
+ if (state == WINDOW_STATE_WINDOWED) // go to a windowed state
+ {
+ // need to restore resolution if it was changed to not native
+ // because we do not support resolution change in windowed mode
+ RestoreDesktopResolution();
+ }
+ else if (state == WINDOW_STATE_FULLSCREEN_WINDOW) // enter fullscreen window instead
+ {
+ ChangeResolution(res, stereoChange);
+ }
+
+ m_state = state;
+ AdjustWindow();
+ }
+ else // we're in windowed state now
+ {
+ if (state == WINDOW_STATE_FULLSCREEN_WINDOW)
+ {
+ ChangeResolution(res, stereoChange);
+
+ m_state = state;
+ AdjustWindow();
+ }
+ }
+
+ CreateBackBuffer();
+ m_IsAlteringWindow = false;
+ return true;
+}
+
+bool CWinSystemWin10::DPIChanged(WORD dpi, RECT windowRect) const
+{
+ (void)dpi;
+ return true;
+}
+
+void CWinSystemWin10::RestoreDesktopResolution()
+{
+ CLog::Log(LOGDEBUG, __FUNCTION__": restoring default desktop resolution");
+ ChangeResolution(CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP));
+}
+
+const MONITOR_DETAILS* CWinSystemWin10::GetDefaultMonitor() const
+{
+ if (m_displays.empty())
+ return nullptr;
+
+ return &m_displays.front();
+}
+
+bool CWinSystemWin10::ChangeResolution(const RESOLUTION_INFO& res, bool forceChange /*= false*/)
+{
+ const MONITOR_DETAILS* details = GetDefaultMonitor();
+
+ if (!details)
+ return false;
+
+ if (ApiInformation::IsTypePresent(L"Windows.Graphics.Display.Core.HdmiDisplayInformation"))
+ {
+ bool changed = false;
+ auto hdmiInfo = HdmiDisplayInformation::GetForCurrentView();
+ if (hdmiInfo != nullptr)
+ {
+ // default mode not in list of supported display modes
+ if (res.iScreenWidth == details->ScreenWidth && res.iScreenHeight == details->ScreenHeight
+ && fabs(res.fRefreshRate - details->RefreshRate) <= 0.00001)
+ {
+ Wait(hdmiInfo.SetDefaultDisplayModeAsync());
+ changed = true;
+ }
+ else
+ {
+ bool needStereo = CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode() == RENDER_STEREO_MODE_HARDWAREBASED;
+ auto hdmiModes = hdmiInfo.GetSupportedDisplayModes();
+
+ HdmiDisplayMode selected = nullptr;
+ for (const auto& mode : hdmiModes)
+ {
+ if (res.iScreenWidth == mode.ResolutionWidthInRawPixels() && res.iScreenHeight == mode.ResolutionHeightInRawPixels()
+ && fabs(res.fRefreshRate - mode.RefreshRate()) <= 0.00001)
+ {
+ selected = mode;
+ if (needStereo == mode.StereoEnabled())
+ break;
+ }
+ }
+
+ if (selected != nullptr)
+ {
+ changed = Wait(hdmiInfo.RequestSetCurrentDisplayModeAsync(selected));
+ }
+ }
+ }
+
+ // changing display mode doesn't fire CoreWindow::SizeChanged event
+ if (changed && m_bWindowCreated)
+ {
+ // dispatch all events currently pending in the queue to change window's content
+ m_coreWindow.Dispatcher().ProcessEvents(CoreProcessEventsOption::ProcessOneAndAllPending);
+
+ float dpi = DisplayInformation::GetForCurrentView().LogicalDpi();
+ float dipsW = DX::ConvertPixelsToDips(m_nWidth, dpi);
+ float dipsH = DX::ConvertPixelsToDips(m_nHeight, dpi);
+
+ dynamic_cast<CWinEventsWin10&>(*m_winEvents).OnResize(dipsW, dipsH);
+ }
+ return changed;
+ }
+
+ CLog::LogF(LOGDEBUG, "Not supported.");
+ return false;
+}
+
+void CWinSystemWin10::UpdateResolutions()
+{
+ m_displays.clear();
+
+ CWinSystemBase::UpdateResolutions();
+ GetConnectedDisplays(m_displays);
+
+ const MONITOR_DETAILS* details = GetDefaultMonitor();
+ if (!details)
+ return;
+
+ float refreshRate;
+ int w = details->ScreenWidth;
+ int h = details->ScreenHeight;
+ uint32_t dwFlags = details->Interlaced ? D3DPRESENTFLAG_INTERLACED : 0;;
+
+ if (details->RefreshRate == 59 || details->RefreshRate == 29 || details->RefreshRate == 23)
+ refreshRate = static_cast<float>(details->RefreshRate + 1) / 1.001f;
+ else
+ refreshRate = static_cast<float>(details->RefreshRate);
+
+ RESOLUTION_INFO& primary_info = CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP);
+ UpdateDesktopResolution(primary_info, "Default", w, h, refreshRate, dwFlags);
+ CLog::Log(LOGINFO, "Primary mode: {}", primary_info.strMode);
+
+ // erase previous stored modes
+ CDisplaySettings::GetInstance().ClearCustomResolutions();
+
+ if (ApiInformation::IsTypePresent(L"Windows.Graphics.Display.Core.HdmiDisplayInformation"))
+ {
+ auto hdmiInfo = HdmiDisplayInformation::GetForCurrentView();
+ if (hdmiInfo != nullptr)
+ {
+ auto hdmiModes = hdmiInfo.GetSupportedDisplayModes();
+ for (const auto& mode : hdmiModes)
+ {
+ RESOLUTION_INFO res;
+ res.iWidth = mode.ResolutionWidthInRawPixels();
+ res.iHeight = mode.ResolutionHeightInRawPixels();
+ res.bFullScreen = true;
+ res.dwFlags = 0;
+ res.fRefreshRate = mode.RefreshRate();
+ res.fPixelRatio = 1.0f;
+ res.iScreenWidth = res.iWidth;
+ res.iScreenHeight = res.iHeight;
+ res.iSubtitles = res.iHeight;
+ res.strMode = StringUtils::Format("Default: {}x{} @ {:.2f}Hz", res.iWidth, res.iHeight,
+ res.fRefreshRate);
+ GetGfxContext().ResetOverscan(res);
+
+ if (AddResolution(res))
+ CLog::Log(LOGINFO, "Additional mode: {} {}", res.strMode,
+ mode.Is2086MetadataSupported() ? "(HDR)" : "");
+ }
+ }
+ }
+
+ CDisplaySettings::GetInstance().ApplyCalibrations();
+}
+
+bool CWinSystemWin10::AddResolution(const RESOLUTION_INFO &res)
+{
+ for (unsigned int i = RES_CUSTOM; i < CDisplaySettings::GetInstance().ResolutionInfoSize(); i++)
+ {
+ RESOLUTION_INFO& info = CDisplaySettings::GetInstance().GetResolutionInfo(i);
+ if ( info.iWidth == res.iWidth
+ && info.iHeight == res.iHeight
+ && info.iScreenWidth == res.iScreenWidth
+ && info.iScreenHeight == res.iScreenHeight
+ && info.fRefreshRate == res.fRefreshRate
+ && info.dwFlags == res.dwFlags)
+ return false; // already have this resolution
+ }
+
+ CDisplaySettings::GetInstance().AddResolutionInfo(res);
+ return true;
+}
+
+void CWinSystemWin10::GetConnectedDisplays(std::vector<MONITOR_DETAILS>& outputs)
+{
+ auto dispatcher = m_coreWindow.Dispatcher();
+ DispatchedHandler handler([&]()
+ {
+ MONITOR_DETAILS md = {};
+
+ auto displayInfo = DisplayInformation::GetForCurrentView();
+ bool flipResolution = false;
+ switch (displayInfo.NativeOrientation())
+ {
+ case DisplayOrientations::Landscape:
+ switch (displayInfo.CurrentOrientation())
+ {
+ case DisplayOrientations::Portrait:
+ case DisplayOrientations::PortraitFlipped:
+ flipResolution = true;
+ break;
+ }
+ break;
+ case DisplayOrientations::Portrait:
+ switch (displayInfo.CurrentOrientation())
+ {
+ case DisplayOrientations::Landscape:
+ case DisplayOrientations::LandscapeFlipped:
+ flipResolution = true;
+ break;
+ }
+ break;
+ }
+ md.ScreenWidth = flipResolution ? displayInfo.ScreenHeightInRawPixels() : displayInfo.ScreenWidthInRawPixels();
+ md.ScreenHeight = flipResolution ? displayInfo.ScreenWidthInRawPixels() : displayInfo.ScreenHeightInRawPixels();
+
+ if (ApiInformation::IsTypePresent(L"Windows.Graphics.Display.Core.HdmiDisplayInformation"))
+ {
+ auto hdmiInfo = HdmiDisplayInformation::GetForCurrentView();
+ if (hdmiInfo != nullptr)
+ {
+ auto currentMode = hdmiInfo.GetCurrentDisplayMode();
+ // On Xbox, 4K resolutions only are reported by HdmiDisplayInformation API
+ // so ScreenHeight & ScreenWidth are updated with info provided here
+ md.ScreenHeight = currentMode.ResolutionHeightInRawPixels();
+ md.ScreenWidth = currentMode.ResolutionWidthInRawPixels();
+ md.RefreshRate = currentMode.RefreshRate();
+ md.Bpp = currentMode.BitsPerPixel();
+ }
+ else
+ {
+ md.RefreshRate = 60.0;
+ md.Bpp = 24;
+ }
+ }
+ else
+ {
+ // note that refresh rate information is not available on Win10 UWP
+ md.RefreshRate = 60.0;
+ md.Bpp = 24;
+ }
+ md.Interlaced = false;
+
+ outputs.push_back(md);
+ });
+
+ if (dispatcher.HasThreadAccess())
+ handler();
+ else
+ Wait(dispatcher.RunAsync(CoreDispatcherPriority::High, handler));
+}
+
+void CWinSystemWin10::ShowOSMouse(bool show)
+{
+ if (!m_coreWindow)
+ return;
+
+ DispatchedHandler handler([this, show]()
+ {
+ CoreCursor cursor = nullptr;
+ if (show)
+ cursor = CoreCursor(CoreCursorType::Arrow, 1);
+ m_coreWindow.PointerCursor(cursor);
+ });
+
+ if (m_coreWindow.Dispatcher().HasThreadAccess())
+ handler();
+ else
+ m_coreWindow.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, handler);
+}
+
+bool CWinSystemWin10::Minimize()
+{
+ CLog::Log(LOGDEBUG, "{} is not implemented", __FUNCTION__);
+ return true;
+}
+bool CWinSystemWin10::Restore()
+{
+ CLog::Log(LOGDEBUG, "{} is not implemented", __FUNCTION__);
+ return true;
+}
+bool CWinSystemWin10::Hide()
+{
+ CLog::Log(LOGDEBUG, "{} is not implemented", __FUNCTION__);
+ return true;
+}
+bool CWinSystemWin10::Show(bool raise)
+{
+ CLog::Log(LOGDEBUG, "{} is not implemented", __FUNCTION__);
+ return true;
+}
+
+void CWinSystemWin10::Register(IDispResource *resource)
+{
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ m_resources.push_back(resource);
+}
+
+void CWinSystemWin10::Unregister(IDispResource* resource)
+{
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ std::vector<IDispResource*>::iterator i = find(m_resources.begin(), m_resources.end(), resource);
+ if (i != m_resources.end())
+ m_resources.erase(i);
+}
+
+void CWinSystemWin10::OnDisplayLost()
+{
+ CLog::Log(LOGDEBUG, "{} - notify display lost event", __FUNCTION__);
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ for (std::vector<IDispResource *>::iterator i = m_resources.begin(); i != m_resources.end(); ++i)
+ (*i)->OnLostDisplay();
+ }
+}
+
+void CWinSystemWin10::OnDisplayReset()
+{
+ if (!m_delayDispReset)
+ {
+ CLog::Log(LOGDEBUG, "{} - notify display reset event", __FUNCTION__);
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ for (std::vector<IDispResource *>::iterator i = m_resources.begin(); i != m_resources.end(); ++i)
+ (*i)->OnResetDisplay();
+ }
+}
+
+void CWinSystemWin10::OnDisplayBack()
+{
+ auto delay =
+ std::chrono::milliseconds(CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
+ "videoscreen.delayrefreshchange") *
+ 100);
+ if (delay > 0ms)
+ {
+ m_delayDispReset = true;
+ m_dispResetTimer.Set(delay);
+ }
+ OnDisplayReset();
+}
+
+void CWinSystemWin10::ResolutionChanged()
+{
+ OnDisplayLost();
+ OnDisplayBack();
+}
+
+std::unique_ptr<CVideoSync> CWinSystemWin10::GetVideoSync(void *clock)
+{
+ std::unique_ptr<CVideoSync> pVSync(new CVideoSyncD3D(clock));
+ return pVSync;
+}
+
+std::string CWinSystemWin10::GetClipboardText()
+{
+ std::wstring unicode_text;
+
+ auto contentView = Clipboard::GetContent();
+ if (contentView.Contains(StandardDataFormats::Text()))
+ {
+ auto text = Wait(contentView.GetTextAsync());
+ unicode_text.append(text.c_str());
+ }
+
+ return KODI::PLATFORM::WINDOWS::FromW(unicode_text);
+}
+
+bool CWinSystemWin10::UseLimitedColor()
+{
+ return CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOSCREEN_LIMITEDRANGE);
+}
+
+void CWinSystemWin10::NotifyAppFocusChange(bool bGaining)
+{
+ m_inFocus = bGaining;
+}
+
+void CWinSystemWin10::UpdateStates(bool fullScreen)
+{
+ m_fullscreenState = WINDOW_FULLSCREEN_STATE_FULLSCREEN_WINDOW; // currently only this allowed
+ m_windowState = WINDOW_WINDOW_STATE_WINDOWED; // currently only this allowed
+}
+
+WINDOW_STATE CWinSystemWin10::GetState(bool fullScreen) const
+{
+ return static_cast<WINDOW_STATE>(fullScreen ? m_fullscreenState : m_windowState);
+}
+
+bool CWinSystemWin10::MessagePump()
+{
+ return m_winEvents->MessagePump();
+}
+
+int CWinSystemWin10::GetGuiSdrPeakLuminance() const
+{
+ const auto settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+
+ return settings->GetInt(CSettings::SETTING_VIDEOSCREEN_GUISDRPEAKLUMINANCE);
+}
+
+#pragma pack(pop)
diff --git a/xbmc/windowing/win10/WinSystemWin10.h b/xbmc/windowing/win10/WinSystemWin10.h
new file mode 100644
index 0000000..0f2b41e
--- /dev/null
+++ b/xbmc/windowing/win10/WinSystemWin10.h
@@ -0,0 +1,156 @@
+/*
+ * 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/CriticalSection.h"
+#include "threads/SystemClock.h"
+#include "windowing/WinSystem.h"
+
+#include <vector>
+
+#pragma pack(push)
+#pragma pack(8)
+
+/* Controls the way the window appears and behaves. */
+enum WINDOW_STATE
+{
+ WINDOW_STATE_FULLSCREEN = 1, // Exclusive fullscreen
+ WINDOW_STATE_FULLSCREEN_WINDOW, // Non-exclusive fullscreen window
+ WINDOW_STATE_WINDOWED, //Movable window with border
+ WINDOW_STATE_BORDERLESS //Non-movable window with no border
+};
+
+static const char* window_state_names[] =
+{
+ "unknown",
+ "true fullscreen",
+ "windowed fullscreen",
+ "windowed",
+ "borderless"
+};
+
+/* WINDOW_STATE restricted to fullscreen modes. */
+enum WINDOW_FULLSCREEN_STATE
+{
+ WINDOW_FULLSCREEN_STATE_FULLSCREEN = WINDOW_STATE_FULLSCREEN,
+ WINDOW_FULLSCREEN_STATE_FULLSCREEN_WINDOW = WINDOW_STATE_FULLSCREEN_WINDOW
+};
+
+/* WINDOW_STATE restricted to windowed modes. */
+enum WINDOW_WINDOW_STATE
+{
+ WINDOW_WINDOW_STATE_WINDOWED = WINDOW_STATE_WINDOWED,
+ WINDOW_WINDOW_STATE_BORDERLESS = WINDOW_STATE_BORDERLESS
+};
+
+struct MONITOR_DETAILS
+{
+ // Windows desktop info
+ int ScreenWidth;
+ int ScreenHeight;
+ float RefreshRate;
+ int Bpp;
+ bool Interlaced;
+};
+
+class CWinSystemWin10 : public CWinSystemBase
+{
+public:
+ CWinSystemWin10();
+ virtual ~CWinSystemWin10();
+
+ // CWinSystemBase overrides
+ bool InitWindowSystem() override;
+ bool DestroyWindowSystem() override;
+ bool ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop) override;
+ void FinishWindowResize(int newWidth, int newHeight) override;
+ void UpdateResolutions() override;
+ void NotifyAppFocusChange(bool bGaining) override;
+ void ShowOSMouse(bool show) override;
+ bool HasInertialGestures() override { return true; }//if win32 has touchscreen - it uses the win32 gesture api for inertial scrolling
+ bool Minimize() override;
+ bool Restore() override;
+ bool Hide() override;
+ bool Show(bool raise = true) override;
+ std::string GetClipboardText() override;
+ bool UseLimitedColor() override;
+
+ // videosync
+ std::unique_ptr<CVideoSync> GetVideoSync(void *clock) override;
+
+ bool WindowedMode() const { return m_state != WINDOW_STATE_FULLSCREEN; }
+ bool SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) override;
+
+ // CWinSystemWin10
+ bool IsAlteringWindow() const { return m_IsAlteringWindow; }
+ void SetAlteringWindow(bool altering) { m_IsAlteringWindow = altering; }
+ bool IsTogglingHDR() const { return false; }
+ void SetTogglingHDR(bool toggling) {}
+ virtual bool DPIChanged(WORD dpi, RECT windowRect) const;
+ bool IsMinimized() const { return m_bMinimized; }
+ void SetMinimized(bool minimized) { m_bMinimized = minimized; }
+ int GetGuiSdrPeakLuminance() const;
+
+ bool CanDoWindowed() override;
+
+ // winevents override
+ bool MessagePump() override;
+
+protected:
+ bool CreateNewWindow(const std::string& name, bool fullScreen, RESOLUTION_INFO& res) override = 0;
+ virtual void UpdateStates(bool fullScreen);
+ WINDOW_STATE GetState(bool fullScreen) const;
+ virtual void SetDeviceFullScreen(bool fullScreen, RESOLUTION_INFO& res) = 0;
+ virtual void ReleaseBackBuffer() = 0;
+ virtual void CreateBackBuffer() = 0;
+ virtual void ResizeDeviceBuffers() = 0;
+ virtual bool IsStereoEnabled() = 0;
+ virtual void AdjustWindow();
+
+ virtual void Register(IDispResource *resource);
+ virtual void Unregister(IDispResource *resource);
+
+ bool ChangeResolution(const RESOLUTION_INFO& res, bool forceChange = false);
+ const MONITOR_DETAILS* GetDefaultMonitor() const;
+ void RestoreDesktopResolution();
+ void GetConnectedDisplays(std::vector<MONITOR_DETAILS>& outputs);
+
+ /*!
+ \brief Adds a resolution to the list of resolutions if we don't already have it
+ \param res resolution to add.
+ */
+ static bool AddResolution(const RESOLUTION_INFO &res);
+
+ void OnDisplayLost();
+ void OnDisplayReset();
+ void OnDisplayBack();
+ void ResolutionChanged();
+
+ std::vector<MONITOR_DETAILS> m_displays;
+ bool m_ValidWindowedPosition;
+ bool m_IsAlteringWindow;
+
+ CCriticalSection m_resourceSection;
+ std::vector<IDispResource*> m_resources;
+ bool m_delayDispReset;
+ XbmcThreads::EndTime<> m_dispResetTimer;
+
+ WINDOW_STATE m_state; // the state of the window
+ WINDOW_FULLSCREEN_STATE m_fullscreenState; // the state of the window when in fullscreen
+ WINDOW_WINDOW_STATE m_windowState; // the state of the window when in windowed
+ bool m_inFocus;
+ bool m_bMinimized;
+ bool m_bFirstResChange = true;
+
+ winrt::Windows::UI::Core::CoreWindow m_coreWindow = nullptr;
+};
+
+#pragma pack(pop)
+
diff --git a/xbmc/windowing/win10/WinSystemWin10DX.cpp b/xbmc/windowing/win10/WinSystemWin10DX.cpp
new file mode 100644
index 0000000..c3d2fca
--- /dev/null
+++ b/xbmc/windowing/win10/WinSystemWin10DX.cpp
@@ -0,0 +1,205 @@
+/*
+ * 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 "WinSystemWin10DX.h"
+
+#include "input/touch/generic/GenericTouchActionHandler.h"
+#include "input/touch/generic/GenericTouchInputHandler.h"
+#include "rendering/dx/DirectXHelper.h"
+#include "utils/XTimeUtils.h"
+#include "utils/log.h"
+#include "windowing/WindowSystemFactory.h"
+
+#include "platform/win32/WIN32Util.h"
+
+using namespace std::chrono_literals;
+
+void CWinSystemWin10DX::Register()
+{
+ KODI::WINDOWING::CWindowSystemFactory::RegisterWindowSystem(CreateWinSystem);
+}
+
+std::unique_ptr<CWinSystemBase> CWinSystemWin10DX::CreateWinSystem()
+{
+ return std::make_unique<CWinSystemWin10DX>();
+}
+
+CWinSystemWin10DX::CWinSystemWin10DX() : CRenderSystemDX()
+{
+}
+
+CWinSystemWin10DX::~CWinSystemWin10DX()
+{
+}
+
+void CWinSystemWin10DX::PresentRenderImpl(bool rendered)
+{
+ if (rendered)
+ m_deviceResources->Present();
+
+ if (m_delayDispReset && m_dispResetTimer.IsTimePast())
+ {
+ m_delayDispReset = false;
+ OnDisplayReset();
+ }
+
+ if (!rendered)
+ KODI::TIME::Sleep(40ms);
+}
+
+bool CWinSystemWin10DX::CreateNewWindow(const std::string& name, bool fullScreen, RESOLUTION_INFO& res)
+{
+ const MONITOR_DETAILS* monitor = GetDefaultMonitor();
+ if (!monitor)
+ return false;
+
+ m_deviceResources = DX::DeviceResources::Get();
+ m_deviceResources->SetWindow(m_coreWindow);
+
+ if (CWinSystemWin10::CreateNewWindow(name, fullScreen, res) && m_deviceResources->HasValidDevice())
+ {
+ CGenericTouchInputHandler::GetInstance().RegisterHandler(&CGenericTouchActionHandler::GetInstance());
+ CGenericTouchInputHandler::GetInstance().SetScreenDPI(m_deviceResources->GetDpi());
+ return true;
+ }
+ return false;
+}
+
+bool CWinSystemWin10DX::DestroyRenderSystem()
+{
+ CRenderSystemDX::DestroyRenderSystem();
+
+ m_deviceResources->Release();
+ m_deviceResources.reset();
+ return true;
+}
+
+void CWinSystemWin10DX::ShowSplash(const std::string & message)
+{
+ CRenderSystemBase::ShowSplash(message);
+
+ // this will prevent killing the app by watchdog timeout during loading
+ if (m_coreWindow != nullptr)
+ m_coreWindow.Dispatcher().ProcessEvents(winrt::Windows::UI::Core::CoreProcessEventsOption::ProcessAllIfPresent);
+}
+
+void CWinSystemWin10DX::SetDeviceFullScreen(bool fullScreen, RESOLUTION_INFO& res)
+{
+ m_deviceResources->SetFullScreen(fullScreen, res);
+}
+
+bool CWinSystemWin10DX::ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop)
+{
+ CWinSystemWin10::ResizeWindow(newWidth, newHeight, newLeft, newTop);
+ CRenderSystemDX::OnResize();
+
+ return true;
+}
+
+void CWinSystemWin10DX::OnMove(int x, int y)
+{
+ m_deviceResources->SetWindowPos(m_coreWindow.Bounds());
+}
+
+bool CWinSystemWin10DX::DPIChanged(WORD dpi, RECT windowRect) const
+{
+ m_deviceResources->SetDpi(dpi);
+ if (!IsAlteringWindow())
+ return CWinSystemWin10::DPIChanged(dpi, windowRect);
+
+ return true;
+}
+
+void CWinSystemWin10DX::ReleaseBackBuffer()
+{
+ m_deviceResources->ReleaseBackBuffer();
+}
+
+void CWinSystemWin10DX::CreateBackBuffer()
+{
+ m_deviceResources->CreateBackBuffer();
+}
+
+void CWinSystemWin10DX::ResizeDeviceBuffers()
+{
+ m_deviceResources->ResizeBuffers();
+}
+
+bool CWinSystemWin10DX::IsStereoEnabled()
+{
+ return m_deviceResources->IsStereoEnabled();
+}
+
+void CWinSystemWin10DX::OnResize(int width, int height)
+{
+ if (!m_deviceResources)
+ return;
+
+ if (!m_IsAlteringWindow)
+ ReleaseBackBuffer();
+
+ m_deviceResources->SetLogicalSize(width, height);
+
+ if (!m_IsAlteringWindow)
+ CreateBackBuffer();
+}
+
+bool CWinSystemWin10DX::SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays)
+{
+ bool const result = CWinSystemWin10::SetFullScreen(fullScreen, res, blankOtherDisplays);
+ CRenderSystemDX::OnResize();
+ return result;
+}
+
+void CWinSystemWin10DX::UninitHooks()
+{
+}
+
+void CWinSystemWin10DX::InitHooks(IDXGIOutput* pOutput)
+{
+}
+
+bool CWinSystemWin10DX::IsHDRDisplay()
+{
+ return false; // use tone mapping by default on Xbox
+}
+
+HDR_STATUS CWinSystemWin10DX::GetOSHDRStatus()
+{
+ return CWIN32Util::GetWindowsHDRStatus();
+}
+
+HDR_STATUS CWinSystemWin10DX::ToggleHDR()
+{
+ return m_deviceResources->ToggleHDR();
+}
+
+bool CWinSystemWin10DX::IsHDROutput() const
+{
+ return m_deviceResources->IsHDROutput();
+}
+
+bool CWinSystemWin10DX::IsTransferPQ() const
+{
+ return m_deviceResources->IsTransferPQ();
+}
+
+void CWinSystemWin10DX::SetHdrMetaData(DXGI_HDR_METADATA_HDR10& hdr10) const
+{
+ m_deviceResources->SetHdrMetaData(hdr10);
+}
+
+void CWinSystemWin10DX::SetHdrColorSpace(const DXGI_COLOR_SPACE_TYPE colorSpace) const
+{
+ m_deviceResources->SetHdrColorSpace(colorSpace);
+}
+
+DEBUG_INFO_RENDER CWinSystemWin10DX::GetDebugInfo()
+{
+ return m_deviceResources->GetDebugInfo();
+}
diff --git a/xbmc/windowing/win10/WinSystemWin10DX.h b/xbmc/windowing/win10/WinSystemWin10DX.h
new file mode 100644
index 0000000..84ad660
--- /dev/null
+++ b/xbmc/windowing/win10/WinSystemWin10DX.h
@@ -0,0 +1,90 @@
+/*
+ * 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 "HDRStatus.h"
+#include "WinSystemWin10.h"
+#include "rendering/dx/RenderSystemDX.h"
+
+class CWinSystemWin10DX : public CWinSystemWin10, public CRenderSystemDX
+{
+public:
+ CWinSystemWin10DX();
+ ~CWinSystemWin10DX();
+
+ static void Register();
+ static std::unique_ptr<CWinSystemBase> CreateWinSystem();
+
+ // Implementation of CWinSystemBase via CWinSystemWin10
+ 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;
+ bool SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) override;
+ void PresentRenderImpl(bool rendered) override;
+ bool DPIChanged(WORD dpi, RECT windowRect) const override;
+ bool DestroyRenderSystem() override;
+ void* GetHWContext() override { return m_deviceResources->GetD3DContext(); }
+
+ void UninitHooks();
+ void InitHooks(IDXGIOutput* pOutput);
+
+ void OnMove(int x, int y) override;
+ void OnResize(int width, int height);
+ winrt::Windows::Foundation::Size GetOutputSize() const { return m_deviceResources->GetOutputSize(); }
+ void TrimDevice() const { m_deviceResources->Trim(); }
+
+ /*!
+ \brief Register as a dependent of the DirectX Render System
+ Resources should call this on construction if they're dependent on the Render System
+ for survival. Any resources that registers will get callbacks on loss and reset of
+ device. In addition, callbacks for destruction and creation of the device are also called,
+ where any resources dependent on the DirectX device should be destroyed and recreated.
+ \sa Unregister, ID3DResource
+ */
+ void Register(ID3DResource *resource) const
+ {
+ m_deviceResources->Register(resource);
+ };
+ /*!
+ \brief Unregister as a dependent of the DirectX Render System
+ Resources should call this on destruction if they're a dependent on the Render System
+ \sa Register, ID3DResource
+ */
+ void Unregister(ID3DResource *resource) const
+ {
+ m_deviceResources->Unregister(resource);
+ };
+
+ void Register(IDispResource* resource) override { CWinSystemWin10::Register(resource); }
+ void Unregister(IDispResource* resource) override { CWinSystemWin10::Unregister(resource); }
+
+ void ShowSplash(const std::string& message) override;
+
+ // HDR OS/display override
+ bool IsHDRDisplay() override;
+ HDR_STATUS ToggleHDR() override;
+ HDR_STATUS GetOSHDRStatus() override;
+
+ // HDR support
+ bool IsHDROutput() const;
+ bool IsTransferPQ() const;
+ void SetHdrMetaData(DXGI_HDR_METADATA_HDR10& hdr10) const;
+ void SetHdrColorSpace(const DXGI_COLOR_SPACE_TYPE colorSpace) const;
+
+ // Get debug info from swapchain
+ DEBUG_INFO_RENDER GetDebugInfo() override;
+
+protected:
+ void SetDeviceFullScreen(bool fullScreen, RESOLUTION_INFO& res) override;
+ void ReleaseBackBuffer() override;
+ void CreateBackBuffer() override;
+ void ResizeDeviceBuffers() override;
+ bool IsStereoEnabled() override;
+};
+
diff --git a/xbmc/windowing/windows/CMakeLists.txt b/xbmc/windowing/windows/CMakeLists.txt
new file mode 100644
index 0000000..2b84cee
--- /dev/null
+++ b/xbmc/windowing/windows/CMakeLists.txt
@@ -0,0 +1,14 @@
+set(SOURCES VideoSyncD3D.cpp
+ Win32DPMSSupport.cpp
+ WinEventsWin32.cpp
+ WinSystemWin32.cpp
+ WinSystemWin32DX.cpp)
+
+set(HEADERS VideoSyncD3D.h
+ Win32DPMSSupport.h
+ WinEventsWin32.h
+ WinSystemWin32.h
+ WinSystemWin32DX.h
+ WinKeyMap.h)
+
+core_add_library(windowing_windows)
diff --git a/xbmc/windowing/windows/VideoSyncD3D.cpp b/xbmc/windowing/windows/VideoSyncD3D.cpp
new file mode 100644
index 0000000..d95374f
--- /dev/null
+++ b/xbmc/windowing/windows/VideoSyncD3D.cpp
@@ -0,0 +1,137 @@
+/*
+ * 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 "VideoSyncD3D.h"
+
+#include "Utils/MathUtils.h"
+#include "Utils/TimeUtils.h"
+#include "rendering/dx/DeviceResources.h"
+#include "rendering/dx/RenderContext.h"
+#include "utils/StringUtils.h"
+#include "utils/XTimeUtils.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+
+#include <mutex>
+
+using namespace std::chrono_literals;
+
+void CVideoSyncD3D::OnLostDisplay()
+{
+ if (!m_displayLost)
+ {
+ m_displayLost = true;
+ m_lostEvent.Wait();
+ }
+}
+
+void CVideoSyncD3D::OnResetDisplay()
+{
+ m_displayReset = true;
+}
+
+void CVideoSyncD3D::RefreshChanged()
+{
+ m_displayReset = true;
+}
+
+bool CVideoSyncD3D::Setup(PUPDATECLOCK func)
+{
+ CLog::Log(LOGDEBUG, "CVideoSyncD3D: Setting up Direct3d");
+ std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
+ DX::Windowing()->Register(this);
+ m_displayLost = false;
+ m_displayReset = false;
+ m_lostEvent.Reset();
+ UpdateClock = func;
+
+ // we need a high priority thread to get accurate timing
+ if (!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL))
+ CLog::Log(LOGDEBUG, "CVideoSyncD3D: SetThreadPriority failed");
+
+ return true;
+}
+
+void CVideoSyncD3D::Run(CEvent& stopEvent)
+{
+ int64_t Now;
+ int64_t LastVBlankTime;
+ int NrVBlanks;
+ double VBlankTime;
+ int64_t systemFrequency = CurrentHostFrequency();
+
+ // init the vblanktime
+ Now = CurrentHostCounter();
+ LastVBlankTime = Now;
+ m_lastUpdateTime = Now - systemFrequency;
+ while (!stopEvent.Signaled() && !m_displayLost && !m_displayReset)
+ {
+ // sleep until vblank
+ Microsoft::WRL::ComPtr<IDXGIOutput> pOutput;
+ DX::DeviceResources::Get()->GetOutput(&pOutput);
+ HRESULT hr = pOutput->WaitForVBlank();
+
+ // calculate how many vblanks happened
+ Now = CurrentHostCounter();
+ VBlankTime = (double)(Now - LastVBlankTime) / (double)systemFrequency;
+ NrVBlanks = MathUtils::round_int(VBlankTime * m_fps);
+
+ // update the vblank timestamp, update the clock and send a signal that we got a vblank
+ UpdateClock(NrVBlanks, Now, m_refClock);
+
+ // save the timestamp of this vblank so we can calculate how many vblanks happened next time
+ LastVBlankTime = Now;
+
+ if ((Now - m_lastUpdateTime) >= systemFrequency)
+ {
+ float fps = m_fps;
+ if (fps != GetFps())
+ break;
+ }
+
+ // because we had a vblank, sleep until half the refreshrate period because i think WaitForVBlank block any rendering stuf
+ // without sleeping we have freeze rendering
+ int SleepTime = (int)((LastVBlankTime + (systemFrequency / MathUtils::round_int(m_fps) / 2) - Now) * 1000 / systemFrequency);
+ if (SleepTime > 50)
+ SleepTime = 50; //failsafe
+ if (SleepTime > 0)
+ ::Sleep(SleepTime);
+ }
+
+ m_lostEvent.Set();
+ while (!stopEvent.Signaled() && m_displayLost && !m_displayReset)
+ {
+ KODI::TIME::Sleep(10ms);
+ }
+}
+
+void CVideoSyncD3D::Cleanup()
+{
+ CLog::Log(LOGDEBUG, "CVideoSyncD3D: Cleaning up Direct3d");
+
+ m_lostEvent.Set();
+ DX::Windowing()->Unregister(this);
+}
+
+float CVideoSyncD3D::GetFps()
+{
+ DXGI_MODE_DESC DisplayMode = {};
+ DX::DeviceResources::Get()->GetDisplayMode(&DisplayMode);
+
+ m_fps = (DisplayMode.RefreshRate.Denominator != 0) ? (float)DisplayMode.RefreshRate.Numerator / (float)DisplayMode.RefreshRate.Denominator : 0.0f;
+
+ if (m_fps == 0.0)
+ m_fps = 60.0f;
+
+ if (DX::Windowing()->Interlaced())
+ {
+ m_fps *= 2;
+ }
+ return m_fps;
+}
+
diff --git a/xbmc/windowing/windows/VideoSyncD3D.h b/xbmc/windowing/windows/VideoSyncD3D.h
new file mode 100644
index 0000000..e241650
--- /dev/null
+++ b/xbmc/windowing/windows/VideoSyncD3D.h
@@ -0,0 +1,37 @@
+/*
+ * 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"
+
+class CVideoSyncD3D : public CVideoSync, IDispResource
+{
+public:
+ CVideoSyncD3D(void* clock)
+ : CVideoSync(clock), m_displayLost(false), m_displayReset(false), m_lastUpdateTime(0)
+ {
+ }
+ bool Setup(PUPDATECLOCK func) override;
+ void Run(CEvent& stopEvent) override;
+ void Cleanup() override;
+ float GetFps() override;
+ void RefreshChanged() override;
+ // IDispResource overrides
+ void OnLostDisplay() override;
+ void OnResetDisplay() override;
+
+private:
+ volatile bool m_displayLost;
+ volatile bool m_displayReset;
+ CEvent m_lostEvent;
+ int64_t m_lastUpdateTime;
+};
+
diff --git a/xbmc/windowing/windows/Win32DPMSSupport.cpp b/xbmc/windowing/windows/Win32DPMSSupport.cpp
new file mode 100644
index 0000000..99adc10
--- /dev/null
+++ b/xbmc/windowing/windows/Win32DPMSSupport.cpp
@@ -0,0 +1,52 @@
+/*
+ * 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 "Win32DPMSSupport.h"
+
+#include "ServiceBroker.h"
+#include "rendering/dx/RenderContext.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+#include "windowing/windows/WinSystemWin32.h"
+
+CWin32DPMSSupport::CWin32DPMSSupport()
+{
+ m_supportedModes.push_back(OFF);
+ m_supportedModes.push_back(STANDBY);
+}
+
+bool CWin32DPMSSupport::EnablePowerSaving(PowerSavingMode mode)
+{
+ auto winSystem = dynamic_cast<CWinSystemWin32*>(CServiceBroker::GetWinSystem());
+ if (!winSystem)
+ return false;
+
+ if (!winSystem->GetGfxContext().IsFullScreenRoot())
+ {
+ CLog::Log(LOGDEBUG, "DPMS: not in fullscreen, power saving disabled");
+ return false;
+ }
+
+ switch (mode)
+ {
+ case OFF:
+ // Turn off display
+ return SendMessage(DX::Windowing()->GetHwnd(), WM_SYSCOMMAND, SC_MONITORPOWER, static_cast<LPARAM>(2)) == 0;
+ case STANDBY:
+ // Set display to low power
+ return SendMessage(DX::Windowing()->GetHwnd(), WM_SYSCOMMAND, SC_MONITORPOWER, static_cast<LPARAM>(1)) == 0;
+ default:
+ return true;
+ }
+}
+
+bool CWin32DPMSSupport::DisablePowerSaving()
+{
+ // Turn display on
+ return SendMessage(DX::Windowing()->GetHwnd(), WM_SYSCOMMAND, SC_MONITORPOWER, static_cast<LPARAM>(-1)) == 0;
+}
diff --git a/xbmc/windowing/windows/Win32DPMSSupport.h b/xbmc/windowing/windows/Win32DPMSSupport.h
new file mode 100644
index 0000000..75c192f
--- /dev/null
+++ b/xbmc/windowing/windows/Win32DPMSSupport.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 CWin32DPMSSupport : public CDPMSSupport
+{
+public:
+ CWin32DPMSSupport();
+ ~CWin32DPMSSupport() = default;
+
+protected:
+ bool EnablePowerSaving(PowerSavingMode mode) override;
+ bool DisablePowerSaving() override;
+};
diff --git a/xbmc/windowing/windows/WinEventsWin32.cpp b/xbmc/windowing/windows/WinEventsWin32.cpp
new file mode 100644
index 0000000..7b4cba3
--- /dev/null
+++ b/xbmc/windowing/windows/WinEventsWin32.cpp
@@ -0,0 +1,1074 @@
+/*
+ * 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.
+ */
+
+#ifndef _USE_MATH_DEFINES
+#define _USE_MATH_DEFINES
+#endif
+#include "WinEventsWin32.h"
+
+#include "ServiceBroker.h"
+#include "Util.h"
+#include "WinKeyMap.h"
+#include "application/AppInboundProtocol.h"
+#include "application/Application.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationPowerHandling.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIControl.h" // for EVENT_RESULT
+#include "guilib/GUIWindowManager.h"
+#include "input/InputManager.h"
+#include "input/mouse/MouseStat.h"
+#include "input/touch/generic/GenericTouchActionHandler.h"
+#include "input/touch/generic/GenericTouchSwipeDetector.h"
+#include "messaging/ApplicationMessenger.h"
+#include "network/Zeroconf.h"
+#include "network/ZeroconfBrowser.h"
+#include "peripherals/Peripherals.h"
+#include "rendering/dx/RenderContext.h"
+#include "storage/MediaManager.h"
+#include "utils/JobManager.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include "platform/win32/CharsetConverter.h"
+#include "platform/win32/WIN32Util.h"
+#include "platform/win32/powermanagement/Win32PowerSyscall.h"
+#include "platform/win32/storage/Win32StorageProvider.h"
+
+#include <array>
+#include <math.h>
+
+#include <Shlobj.h>
+#include <dbt.h>
+
+HWND g_hWnd = nullptr;
+
+#ifndef LODWORD
+#define LODWORD(longval) ((DWORD)((DWORDLONG)(longval)))
+#endif
+
+#define ROTATE_ANGLE_DEGREE(arg) GID_ROTATE_ANGLE_FROM_ARGUMENT(LODWORD(arg)) * 180 / M_PI
+
+#define GET_X_LPARAM(lp) ((int)(short)LOWORD(lp))
+#define GET_Y_LPARAM(lp) ((int)(short)HIWORD(lp))
+
+/* Masks for processing the windows KEYDOWN and KEYUP messages */
+#define REPEATED_KEYMASK (1<<30)
+#define EXTENDED_KEYMASK (1<<24)
+#define EXTKEYPAD(keypad) ((scancode & 0x100)?(mvke):(keypad))
+
+static GUID USB_HID_GUID = { 0x4D1E55B2, 0xF16F, 0x11CF, { 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 } };
+
+uint32_t g_uQueryCancelAutoPlay = 0;
+bool g_sizeMoveSizing = false;
+bool g_sizeMoveMoving = false;
+int g_sizeMoveWidth = 0;
+int g_sizeMoveHight = 0;
+int g_sizeMoveX = -10000;
+int g_sizeMoveY = -10000;
+
+int XBMC_TranslateUNICODE = 1;
+
+int CWinEventsWin32::m_originalZoomDistance = 0;
+Pointer CWinEventsWin32::m_touchPointer;
+CGenericTouchSwipeDetector* CWinEventsWin32::m_touchSwipeDetector = nullptr;
+
+// register to receive SD card events (insert/remove)
+// seen at http://www.codeproject.com/Messages/2897423/Re-No-message-triggered-on-SD-card-insertion-remov.aspx
+#define WM_MEDIA_CHANGE (WM_USER + 666)
+SHChangeNotifyEntry shcne;
+
+static int XBMC_MapVirtualKey(int scancode, WPARAM vkey)
+{
+ int mvke = MapVirtualKeyEx(scancode & 0xFF, 1, nullptr);
+
+ switch (vkey)
+ { /* These are always correct */
+ case VK_DIVIDE:
+ case VK_MULTIPLY:
+ case VK_SUBTRACT:
+ case VK_ADD:
+ case VK_LWIN:
+ case VK_RWIN:
+ case VK_APPS:
+ /* These are already handled */
+ case VK_LCONTROL:
+ case VK_RCONTROL:
+ case VK_LSHIFT:
+ case VK_RSHIFT:
+ case VK_LMENU:
+ case VK_RMENU:
+ case VK_SNAPSHOT:
+ case VK_PAUSE:
+ /* Multimedia keys are already handled */
+ case VK_BROWSER_BACK:
+ case VK_BROWSER_FORWARD:
+ case VK_BROWSER_REFRESH:
+ case VK_BROWSER_STOP:
+ case VK_BROWSER_SEARCH:
+ case VK_BROWSER_FAVORITES:
+ case VK_BROWSER_HOME:
+ case VK_VOLUME_MUTE:
+ case VK_VOLUME_DOWN:
+ case VK_VOLUME_UP:
+ case VK_MEDIA_NEXT_TRACK:
+ case VK_MEDIA_PREV_TRACK:
+ case VK_MEDIA_STOP:
+ case VK_MEDIA_PLAY_PAUSE:
+ case VK_LAUNCH_MAIL:
+ case VK_LAUNCH_MEDIA_SELECT:
+ case VK_LAUNCH_APP1:
+ case VK_LAUNCH_APP2:
+ return static_cast<int>(vkey);
+ default:;
+ }
+ switch (mvke)
+ { /* Distinguish between keypad and extended keys */
+ case VK_INSERT: return EXTKEYPAD(VK_NUMPAD0);
+ case VK_DELETE: return EXTKEYPAD(VK_DECIMAL);
+ case VK_END: return EXTKEYPAD(VK_NUMPAD1);
+ case VK_DOWN: return EXTKEYPAD(VK_NUMPAD2);
+ case VK_NEXT: return EXTKEYPAD(VK_NUMPAD3);
+ case VK_LEFT: return EXTKEYPAD(VK_NUMPAD4);
+ case VK_CLEAR: return EXTKEYPAD(VK_NUMPAD5);
+ case VK_RIGHT: return EXTKEYPAD(VK_NUMPAD6);
+ case VK_HOME: return EXTKEYPAD(VK_NUMPAD7);
+ case VK_UP: return EXTKEYPAD(VK_NUMPAD8);
+ case VK_PRIOR: return EXTKEYPAD(VK_NUMPAD9);
+ default:;
+ }
+ return mvke ? mvke : static_cast<int>(vkey);
+}
+
+
+static XBMC_keysym *TranslateKey(WPARAM vkey, UINT scancode, XBMC_keysym *keysym, int pressed)
+{
+ using namespace KODI::WINDOWING::WINDOWS;
+
+ uint8_t keystate[256];
+
+ /* Set the keysym information */
+ keysym->scancode = static_cast<unsigned char>(scancode);
+ keysym->unicode = 0;
+
+ if ((vkey == VK_RETURN) && (scancode & 0x100))
+ {
+ /* No VK_ code for the keypad enter key */
+ keysym->sym = XBMCK_KP_ENTER;
+ }
+ else
+ {
+ keysym->sym = VK_keymap[XBMC_MapVirtualKey(scancode, vkey)];
+ }
+
+ // Attempt to convert the keypress to a UNICODE character
+ GetKeyboardState(keystate);
+
+ if (pressed && XBMC_TranslateUNICODE)
+ {
+ std::array<uint16_t, 2> wchars;
+
+ /* Numlock isn't taken into account in ToUnicode,
+ * so we handle it as a special case here */
+ if ((keystate[VK_NUMLOCK] & 1) && vkey >= VK_NUMPAD0 && vkey <= VK_NUMPAD9)
+ {
+ keysym->unicode = static_cast<uint16_t>(vkey - VK_NUMPAD0 + '0');
+ }
+ else if (ToUnicode(static_cast<UINT>(vkey), scancode, keystate,
+ reinterpret_cast<LPWSTR>(wchars.data()), wchars.size(), 0) > 0)
+ {
+ keysym->unicode = wchars[0];
+ }
+ }
+
+ // Set the modifier bitmap
+
+ uint16_t mod = static_cast<uint16_t>(XBMCKMOD_NONE);
+
+ // If left control and right alt are down this usually means that
+ // AltGr is down
+ if ((keystate[VK_LCONTROL] & 0x80) && (keystate[VK_RMENU] & 0x80))
+ {
+ mod |= XBMCKMOD_MODE;
+ }
+ else
+ {
+ if (keystate[VK_LCONTROL] & 0x80) mod |= XBMCKMOD_LCTRL;
+ if (keystate[VK_RMENU] & 0x80) mod |= XBMCKMOD_RALT;
+ }
+
+ // Check the remaining modifiers
+ if (keystate[VK_LSHIFT] & 0x80) mod |= XBMCKMOD_LSHIFT;
+ if (keystate[VK_RSHIFT] & 0x80) mod |= XBMCKMOD_RSHIFT;
+ if (keystate[VK_RCONTROL] & 0x80) mod |= XBMCKMOD_RCTRL;
+ if (keystate[VK_LMENU] & 0x80) mod |= XBMCKMOD_LALT;
+ if (keystate[VK_LWIN] & 0x80) mod |= XBMCKMOD_LSUPER;
+ if (keystate[VK_RWIN] & 0x80) mod |= XBMCKMOD_LSUPER;
+ keysym->mod = static_cast<XBMCMod>(mod);
+
+ // Return the updated keysym
+ return(keysym);
+}
+
+
+bool CWinEventsWin32::MessagePump()
+{
+ MSG msg;
+ while( PeekMessage( &msg, nullptr, 0U, 0U, PM_REMOVE ) )
+ {
+ TranslateMessage( &msg );
+ DispatchMessage( &msg );
+ }
+ return true;
+}
+
+LRESULT CALLBACK CWinEventsWin32::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ using KODI::PLATFORM::WINDOWS::FromW;
+
+ XBMC_Event newEvent = {};
+ static HDEVNOTIFY hDeviceNotify;
+
+#if 0
+ if (uMsg == WM_NCCREATE)
+ {
+ // if available, enable DPI scaling of non-client portion of window (title bar, etc.)
+ if (g_Windowing.PtrEnableNonClientDpiScaling != NULL)
+ {
+ g_Windowing.PtrEnableNonClientDpiScaling(hWnd);
+ }
+ return DefWindowProc(hWnd, uMsg, wParam, lParam);
+ }
+#endif
+
+ if (uMsg == WM_CREATE)
+ {
+ g_hWnd = hWnd;
+ // need to set windows handle before WM_SIZE processing
+ DX::Windowing()->SetWindow(hWnd);
+
+ KODI::WINDOWING::WINDOWS::DIB_InitOSKeymap();
+
+ g_uQueryCancelAutoPlay = RegisterWindowMessage(TEXT("QueryCancelAutoPlay"));
+ shcne.pidl = nullptr;
+ shcne.fRecursive = TRUE;
+ long fEvents = SHCNE_DRIVEADD | SHCNE_DRIVEREMOVED | SHCNE_MEDIAREMOVED | SHCNE_MEDIAINSERTED;
+ SHChangeNotifyRegister(hWnd, SHCNRF_ShellLevel | SHCNRF_NewDelivery, fEvents, WM_MEDIA_CHANGE, 1, &shcne);
+ RegisterDeviceInterfaceToHwnd(USB_HID_GUID, hWnd, &hDeviceNotify);
+ return 0;
+ }
+
+ if (uMsg == WM_DESTROY)
+ g_hWnd = nullptr;
+
+ if(g_uQueryCancelAutoPlay != 0 && uMsg == g_uQueryCancelAutoPlay)
+ return S_FALSE;
+
+ std::shared_ptr<CAppInboundProtocol> appPort = CServiceBroker::GetAppPort();
+
+ switch (uMsg)
+ {
+ case WM_CLOSE:
+ case WM_QUIT:
+ case WM_DESTROY:
+ if (hDeviceNotify)
+ {
+ if (UnregisterDeviceNotification(hDeviceNotify))
+ hDeviceNotify = nullptr;
+ else
+ CLog::LogF(LOGINFO, "UnregisterDeviceNotification failed ({})", GetLastError());
+ }
+ newEvent.type = XBMC_QUIT;
+ if (appPort)
+ appPort->OnEvent(newEvent);
+ break;
+ case WM_SHOWWINDOW:
+ {
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPower = components.GetComponent<CApplicationPowerHandling>();
+ bool active = appPower->GetRenderGUI();
+ if (appPort)
+ appPort->SetRenderGUI(wParam != 0);
+ if (appPower->GetRenderGUI() != active)
+ DX::Windowing()->NotifyAppActiveChange(appPower->GetRenderGUI());
+ CLog::LogFC(LOGDEBUG, LOGWINDOWING, "WM_SHOWWINDOW -> window is {}",
+ wParam != 0 ? "shown" : "hidden");
+ }
+ break;
+ case WM_ACTIVATE:
+ {
+ CLog::LogFC(LOGDEBUG, LOGWINDOWING, "WM_ACTIVATE -> window is {}",
+ LOWORD(wParam) != WA_INACTIVE ? "active" : "inactive");
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPower = components.GetComponent<CApplicationPowerHandling>();
+ bool active = appPower->GetRenderGUI();
+ if (HIWORD(wParam))
+ {
+ if (appPort)
+ appPort->SetRenderGUI(false);
+ }
+ else
+ {
+ WINDOWPLACEMENT lpwndpl;
+ lpwndpl.length = sizeof(lpwndpl);
+ if (LOWORD(wParam) != WA_INACTIVE)
+ {
+ if (GetWindowPlacement(hWnd, &lpwndpl))
+ {
+ if (appPort)
+ appPort->SetRenderGUI(lpwndpl.showCmd != SW_HIDE);
+ }
+ }
+ else
+ {
+
+ }
+ }
+ if (appPower->GetRenderGUI() != active)
+ DX::Windowing()->NotifyAppActiveChange(appPower->GetRenderGUI());
+ CLog::LogFC(LOGDEBUG, LOGWINDOWING, "window is {}",
+ appPower->GetRenderGUI() ? "active" : "inactive");
+ }
+ break;
+ case WM_SETFOCUS:
+ case WM_KILLFOCUS:
+ g_application.m_AppFocused = uMsg == WM_SETFOCUS;
+ CLog::LogFC(LOGDEBUG, LOGWINDOWING, "window focus {}",
+ g_application.m_AppFocused ? "set" : "lost");
+
+ DX::Windowing()->NotifyAppFocusChange(g_application.m_AppFocused);
+ if (uMsg == WM_KILLFOCUS)
+ {
+ std::string procfile;
+ if (CWIN32Util::GetFocussedProcess(procfile))
+ CLog::LogFC(LOGDEBUG, LOGWINDOWING, "Focus switched to process {}", procfile);
+ }
+ break;
+ /* needs to be reviewed after frodo. we reset the system idle timer
+ and the display timer directly now (see m_screenSaverTimer).
+ case WM_SYSCOMMAND:
+ switch( wParam&0xFFF0 )
+ {
+ case SC_MONITORPOWER:
+ if (g_application.GetAppPlayer().IsPlaying() || g_application.GetAppPlayer().IsPausedPlayback())
+ return 0;
+ else if(CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_POWERMANAGEMENT_DISPLAYSOFF) == 0)
+ return 0;
+ break;
+ case SC_SCREENSAVE:
+ return 0;
+ }
+ break;*/
+ case WM_SYSKEYDOWN:
+ switch (wParam)
+ {
+ case VK_F4: //alt-f4, default event quit.
+ return(DefWindowProc(hWnd, uMsg, wParam, lParam));
+ case VK_RETURN: //alt-return
+ if ((lParam & REPEATED_KEYMASK) == 0)
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_TOGGLEFULLSCREEN);
+ return 0;
+ default:;
+ }
+ //deliberate fallthrough
+ case WM_KEYDOWN:
+ {
+ switch (wParam)
+ {
+ case VK_CONTROL:
+ if ( lParam & EXTENDED_KEYMASK )
+ wParam = VK_RCONTROL;
+ else
+ wParam = VK_LCONTROL;
+ break;
+ case VK_SHIFT:
+ /* EXTENDED trick doesn't work here */
+ if (GetKeyState(VK_LSHIFT) & 0x8000)
+ wParam = VK_LSHIFT;
+ else if (GetKeyState(VK_RSHIFT) & 0x8000)
+ wParam = VK_RSHIFT;
+ break;
+ case VK_MENU:
+ if ( lParam & EXTENDED_KEYMASK )
+ wParam = VK_RMENU;
+ else
+ wParam = VK_LMENU;
+ break;
+ default:;
+ }
+ XBMC_keysym keysym;
+ TranslateKey(wParam, HIWORD(lParam), &keysym, 1);
+
+ newEvent.type = XBMC_KEYDOWN;
+ newEvent.key.keysym = keysym;
+ if (appPort)
+ appPort->OnEvent(newEvent);
+ }
+ return(0);
+
+ case WM_SYSKEYUP:
+ case WM_KEYUP:
+ {
+ switch (wParam)
+ {
+ case VK_CONTROL:
+ if ( lParam&EXTENDED_KEYMASK )
+ wParam = VK_RCONTROL;
+ else
+ wParam = VK_LCONTROL;
+ break;
+ case VK_SHIFT:
+ {
+ uint32_t scanCodeL = MapVirtualKey(VK_LSHIFT, MAPVK_VK_TO_VSC);
+ uint32_t scanCodeR = MapVirtualKey(VK_RSHIFT, MAPVK_VK_TO_VSC);
+ uint32_t keyCode = static_cast<uint32_t>((lParam & 0xFF0000) >> 16);
+ if (keyCode == scanCodeL)
+ wParam = VK_LSHIFT;
+ else if (keyCode == scanCodeR)
+ wParam = VK_RSHIFT;
+ }
+ break;
+ case VK_MENU:
+ if ( lParam&EXTENDED_KEYMASK )
+ wParam = VK_RMENU;
+ else
+ wParam = VK_LMENU;
+ break;
+ default:;
+ }
+ XBMC_keysym keysym;
+ TranslateKey(wParam, HIWORD(lParam), &keysym, 1);
+
+ if (wParam == VK_SNAPSHOT)
+ newEvent.type = XBMC_KEYDOWN;
+ else
+ newEvent.type = XBMC_KEYUP;
+ newEvent.key.keysym = keysym;
+ if (appPort)
+ appPort->OnEvent(newEvent);
+ }
+ return(0);
+ case WM_APPCOMMAND: // MULTIMEDIA keys are mapped to APPCOMMANDS
+ {
+ const unsigned int appcmd = GET_APPCOMMAND_LPARAM(lParam);
+
+ CLog::LogFC(LOGDEBUG, LOGWINDOWING, "APPCOMMAND {}", appcmd);
+
+ // Reset the screen saver
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appPower = components.GetComponent<CApplicationPowerHandling>();
+ appPower->ResetScreenSaver();
+
+ // If we were currently in the screen saver wake up and don't process the
+ // appcommand
+ if (appPower->WakeUpScreenSaverAndDPMS())
+ return true;
+
+ // Retrieve the action associated with this appcommand from the mapping table
+ CKey key(appcmd | KEY_APPCOMMAND, 0U);
+ int iWin = CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindowOrDialog();
+
+ CAction appcmdaction = CServiceBroker::GetInputManager().GetAction(iWin, key);
+ if (appcmdaction.GetID())
+ {
+ CLog::LogFC(LOGDEBUG, LOGWINDOWING, "appcommand {}, action {}", appcmd,
+ appcmdaction.GetName());
+ CServiceBroker::GetInputManager().QueueAction(appcmdaction);
+ return true;
+ }
+
+ CLog::LogFC(LOGDEBUG, LOGWINDOWING, "unknown appcommand {}", appcmd);
+ return DefWindowProc(hWnd, uMsg, wParam, lParam);
+ }
+ case WM_GESTURENOTIFY:
+ {
+ OnGestureNotify(hWnd, lParam);
+ return DefWindowProc(hWnd, WM_GESTURENOTIFY, wParam, lParam);
+ }
+ case WM_GESTURE:
+ {
+ OnGesture(hWnd, lParam);
+ return 0;
+ }
+ case WM_SYSCHAR:
+ if (wParam == VK_RETURN) //stop system beep on alt-return
+ return 0;
+ break;
+ case WM_SETCURSOR:
+ if (HTCLIENT != LOWORD(lParam))
+ DX::Windowing()->ShowOSMouse(true);
+ break;
+ case WM_MOUSEMOVE:
+ newEvent.type = XBMC_MOUSEMOTION;
+ newEvent.motion.x = GET_X_LPARAM(lParam);
+ newEvent.motion.y = GET_Y_LPARAM(lParam);
+ if (appPort)
+ appPort->OnEvent(newEvent);
+ return(0);
+ case WM_LBUTTONDOWN:
+ case WM_MBUTTONDOWN:
+ case WM_RBUTTONDOWN:
+ newEvent.type = XBMC_MOUSEBUTTONDOWN;
+ newEvent.button.x = GET_X_LPARAM(lParam);
+ newEvent.button.y = GET_Y_LPARAM(lParam);
+ newEvent.button.button = 0;
+ if (uMsg == WM_LBUTTONDOWN) newEvent.button.button = XBMC_BUTTON_LEFT;
+ else if (uMsg == WM_MBUTTONDOWN) newEvent.button.button = XBMC_BUTTON_MIDDLE;
+ else if (uMsg == WM_RBUTTONDOWN) newEvent.button.button = XBMC_BUTTON_RIGHT;
+ if (appPort)
+ appPort->OnEvent(newEvent);
+ return(0);
+ case WM_LBUTTONUP:
+ case WM_MBUTTONUP:
+ case WM_RBUTTONUP:
+ newEvent.type = XBMC_MOUSEBUTTONUP;
+ newEvent.button.x = GET_X_LPARAM(lParam);
+ newEvent.button.y = GET_Y_LPARAM(lParam);
+ newEvent.button.button = 0;
+ if (uMsg == WM_LBUTTONUP) newEvent.button.button = XBMC_BUTTON_LEFT;
+ else if (uMsg == WM_MBUTTONUP) newEvent.button.button = XBMC_BUTTON_MIDDLE;
+ else if (uMsg == WM_RBUTTONUP) newEvent.button.button = XBMC_BUTTON_RIGHT;
+ if (appPort)
+ appPort->OnEvent(newEvent);
+ return(0);
+ case WM_MOUSEWHEEL:
+ {
+ // SDL, which our events system is based off, sends a MOUSEBUTTONDOWN message
+ // followed by a MOUSEBUTTONUP message. As this is a momentary event, we just
+ // react on the MOUSEBUTTONUP message, resetting the state after processing.
+ newEvent.type = XBMC_MOUSEBUTTONDOWN;
+ // the coordinates in WM_MOUSEWHEEL are screen, not client coordinates
+ POINT point;
+ point.x = GET_X_LPARAM(lParam);
+ point.y = GET_Y_LPARAM(lParam);
+ WindowFromScreenCoords(hWnd, &point);
+ newEvent.button.x = static_cast<uint16_t>(point.x);
+ newEvent.button.y = static_cast<uint16_t>(point.y);
+ newEvent.button.button = GET_Y_LPARAM(wParam) > 0 ? XBMC_BUTTON_WHEELUP : XBMC_BUTTON_WHEELDOWN;
+ if (appPort)
+ {
+ appPort->OnEvent(newEvent);
+ newEvent.type = XBMC_MOUSEBUTTONUP;
+ appPort->OnEvent(newEvent);
+ }
+ }
+ return(0);
+ case WM_DPICHANGED:
+ // This message tells the program that most of its window is on a
+ // monitor with a new DPI. The wParam contains the new DPI, and the
+ // lParam contains a rect which defines the window rectangle scaled
+ // the new DPI.
+ {
+ // get the suggested size of the window on the new display with a different DPI
+ uint16_t dpi = HIWORD(wParam);
+ RECT rc = *reinterpret_cast<RECT*>(lParam);
+ CLog::LogFC(LOGDEBUG, LOGWINDOWING, "dpi changed event -> {} ({}, {}, {}, {})", dpi, rc.left,
+ rc.top, rc.right, rc.bottom);
+ DX::Windowing()->DPIChanged(dpi, rc);
+ return(0);
+ }
+ case WM_DISPLAYCHANGE:
+ {
+ CLog::LogFC(LOGDEBUG, LOGWINDOWING, "display change event");
+ if (DX::Windowing()->IsTogglingHDR() || DX::Windowing()->IsAlteringWindow())
+ return (0);
+
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPower = components.GetComponent<CApplicationPowerHandling>();
+ if (appPower->GetRenderGUI() && GET_X_LPARAM(lParam) > 0 && GET_Y_LPARAM(lParam) > 0)
+ {
+ DX::Windowing()->UpdateResolutions();
+ }
+ return(0);
+ }
+ case WM_ENTERSIZEMOVE:
+ {
+ DX::Windowing()->SetSizeMoveMode(true);
+ }
+ return(0);
+ case WM_EXITSIZEMOVE:
+ {
+ DX::Windowing()->SetSizeMoveMode(false);
+ if (g_sizeMoveMoving)
+ {
+ g_sizeMoveMoving = false;
+ newEvent.type = XBMC_VIDEOMOVE;
+ newEvent.move.x = g_sizeMoveX;
+ newEvent.move.y = g_sizeMoveY;
+
+ // tell the device about new position
+ DX::Windowing()->OnMove(newEvent.move.x, newEvent.move.y);
+ // tell the application about new position
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPower = components.GetComponent<CApplicationPowerHandling>();
+ if (appPower->GetRenderGUI() && !DX::Windowing()->IsAlteringWindow())
+ {
+ if (appPort)
+ appPort->OnEvent(newEvent);
+ }
+ }
+ if (g_sizeMoveSizing)
+ {
+ g_sizeMoveSizing = false;
+ newEvent.type = XBMC_VIDEORESIZE;
+ newEvent.resize.w = g_sizeMoveWidth;
+ newEvent.resize.h = g_sizeMoveHight;
+
+ // tell the device about new size
+ DX::Windowing()->OnResize(newEvent.resize.w, newEvent.resize.h);
+ // tell the application about new size
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPower = components.GetComponent<CApplicationPowerHandling>();
+ if (appPower->GetRenderGUI() && !DX::Windowing()->IsAlteringWindow() &&
+ newEvent.resize.w > 0 && newEvent.resize.h > 0)
+ {
+ if (appPort)
+ appPort->OnEvent(newEvent);
+ }
+ }
+ }
+ return(0);
+ case WM_SIZE:
+ if (wParam == SIZE_MINIMIZED)
+ {
+ if (!DX::Windowing()->IsMinimized())
+ {
+ DX::Windowing()->SetMinimized(true);
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPower = components.GetComponent<CApplicationPowerHandling>();
+ if (appPower->GetRenderGUI())
+ {
+ if (appPort)
+ appPort->SetRenderGUI(false);
+ }
+ }
+ }
+ else if (DX::Windowing()->IsMinimized())
+ {
+ DX::Windowing()->SetMinimized(false);
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPower = components.GetComponent<CApplicationPowerHandling>();
+ if (!appPower->GetRenderGUI())
+ {
+ if (appPort)
+ appPort->SetRenderGUI(true);
+ }
+ }
+ else
+ {
+ g_sizeMoveWidth = GET_X_LPARAM(lParam);
+ g_sizeMoveHight = GET_Y_LPARAM(lParam);
+ if (DX::Windowing()->IsInSizeMoveMode())
+ {
+ // If an user is dragging the resize bars, we don't resize
+ // the buffers and don't rise XBMC_VIDEORESIZE here because
+ // as the user continuously resize the window, a lot of WM_SIZE
+ // messages are sent to the proc, and it'd be pointless (and slow)
+ // to resize for each WM_SIZE message received from dragging.
+ // So instead, we reset after the user is done resizing the
+ // window and releases the resize bars, which ends with WM_EXITSIZEMOVE.
+ g_sizeMoveSizing = true;
+ }
+ else
+ {
+ // API call such as SetWindowPos or SwapChain->SetFullscreenState
+ newEvent.type = XBMC_VIDEORESIZE;
+ newEvent.resize.w = g_sizeMoveWidth;
+ newEvent.resize.h = g_sizeMoveHight;
+
+ CLog::LogFC(LOGDEBUG, LOGWINDOWING, "window resize event {} x {}", newEvent.resize.w,
+ newEvent.resize.h);
+ // tell device about new size
+ DX::Windowing()->OnResize(newEvent.resize.w, newEvent.resize.h);
+ // tell application about size changes
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPower = components.GetComponent<CApplicationPowerHandling>();
+ if (appPower->GetRenderGUI() && !DX::Windowing()->IsAlteringWindow() &&
+ newEvent.resize.w > 0 && newEvent.resize.h > 0)
+ {
+ if (appPort)
+ appPort->OnEvent(newEvent);
+ }
+ }
+ }
+ return(0);
+ case WM_MOVE:
+ {
+ g_sizeMoveX = GET_X_LPARAM(lParam);
+ g_sizeMoveY = GET_Y_LPARAM(lParam);
+ if (DX::Windowing()->IsInSizeMoveMode())
+ {
+ // the same as WM_SIZE
+ g_sizeMoveMoving = true;
+ }
+ else
+ {
+ newEvent.type = XBMC_VIDEOMOVE;
+ newEvent.move.x = g_sizeMoveX;
+ newEvent.move.y = g_sizeMoveY;
+
+ CLog::LogFC(LOGDEBUG, LOGWINDOWING, "window move event");
+
+ // tell the device about new position
+ DX::Windowing()->OnMove(newEvent.move.x, newEvent.move.y);
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPower = components.GetComponent<CApplicationPowerHandling>();
+ if (appPower->GetRenderGUI() && !DX::Windowing()->IsAlteringWindow())
+ {
+ if (appPort)
+ appPort->OnEvent(newEvent);
+ }
+ }
+ }
+ return(0);
+ case WM_MEDIA_CHANGE:
+ {
+ // This event detects media changes of usb, sd card and optical media.
+ // It only works if the explorer.exe process is started. Because this
+ // isn't the case for all setups we use WM_DEVICECHANGE for usb and
+ // optical media because this event is also triggered without the
+ // explorer process. Since WM_DEVICECHANGE doesn't detect sd card changes
+ // we still use this event only for sd.
+ long lEvent;
+ PIDLIST_ABSOLUTE *ppidl;
+ HANDLE hLock = SHChangeNotification_Lock(reinterpret_cast<HANDLE>(wParam), static_cast<DWORD>(lParam), &ppidl, &lEvent);
+
+ if (hLock)
+ {
+ wchar_t drivePath[MAX_PATH+1];
+ if (!SHGetPathFromIDList(ppidl[0], drivePath))
+ break;
+
+ switch(lEvent)
+ {
+ case SHCNE_DRIVEADD:
+ case SHCNE_MEDIAINSERTED:
+ if (GetDriveType(drivePath) != DRIVE_CDROM)
+ {
+ CLog::LogF(LOGDEBUG, "Drive {} Media has arrived.", FromW(drivePath));
+ CWin32StorageProvider::SetEvent();
+ }
+ break;
+
+ case SHCNE_DRIVEREMOVED:
+ case SHCNE_MEDIAREMOVED:
+ if (GetDriveType(drivePath) != DRIVE_CDROM)
+ {
+ CLog::LogF(LOGDEBUG, "Drive {} Media was removed.", FromW(drivePath));
+ CWin32StorageProvider::SetEvent();
+ }
+ break;
+ default:;
+ }
+ SHChangeNotification_Unlock(hLock);
+ }
+ break;
+ }
+ case WM_POWERBROADCAST:
+ if (wParam==PBT_APMSUSPEND)
+ {
+ CLog::Log(LOGDEBUG,"WM_POWERBROADCAST: PBT_APMSUSPEND event was sent");
+ CWin32PowerSyscall::SetOnSuspend();
+ }
+ else if(wParam==PBT_APMRESUMEAUTOMATIC)
+ {
+ CLog::Log(LOGDEBUG,"WM_POWERBROADCAST: PBT_APMRESUMEAUTOMATIC event was sent");
+ CWin32PowerSyscall::SetOnResume();
+ }
+ break;
+ case WM_DEVICECHANGE:
+ {
+ switch(wParam)
+ {
+ case DBT_DEVNODES_CHANGED:
+ CServiceBroker::GetPeripherals().TriggerDeviceScan(PERIPHERALS::PERIPHERAL_BUS_USB);
+ break;
+ case DBT_DEVICEARRIVAL:
+ case DBT_DEVICEREMOVECOMPLETE:
+ if (((_DEV_BROADCAST_HEADER*) lParam)->dbcd_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
+ {
+ CServiceBroker::GetPeripherals().TriggerDeviceScan(PERIPHERALS::PERIPHERAL_BUS_USB);
+ }
+ // check if an usb or optical media was inserted or removed
+ if (((_DEV_BROADCAST_HEADER*) lParam)->dbcd_devicetype == DBT_DEVTYP_VOLUME)
+ {
+ PDEV_BROADCAST_VOLUME lpdbv = (PDEV_BROADCAST_VOLUME)((_DEV_BROADCAST_HEADER*) lParam);
+ // optical medium
+ if (lpdbv -> dbcv_flags & DBTF_MEDIA)
+ {
+ std::string strdrive = StringUtils::Format(
+ "{}:", CWIN32Util::FirstDriveFromMask(lpdbv->dbcv_unitmask));
+ if(wParam == DBT_DEVICEARRIVAL)
+ {
+ CLog::LogF(LOGDEBUG, "Drive {} Media has arrived.", strdrive);
+ CServiceBroker::GetJobManager()->AddJob(new CDetectDisc(strdrive, true), nullptr);
+ }
+ else
+ {
+ CLog::LogF(LOGDEBUG, "Drive {} Media was removed.", strdrive);
+ CMediaSource share;
+ share.strPath = strdrive;
+ share.strName = share.strPath;
+ CServiceBroker::GetMediaManager().RemoveAutoSource(share);
+ }
+ }
+ else
+ CWin32StorageProvider::SetEvent();
+ }
+ default:;
+ }
+ break;
+ }
+ case WM_PAINT:
+ {
+ //some other app has painted over our window, mark everything as dirty
+ CGUIComponent* component = CServiceBroker::GetGUI();
+ if (component)
+ component->GetWindowManager().MarkDirty();
+ break;
+ }
+ case BONJOUR_EVENT:
+ CZeroconf::GetInstance()->ProcessResults();
+ break;
+ case BONJOUR_BROWSER_EVENT:
+ CZeroconfBrowser::GetInstance()->ProcessResults();
+ break;
+ case TRAY_ICON_NOTIFY:
+ {
+ switch (LOWORD(lParam))
+ {
+ case WM_LBUTTONDBLCLK:
+ {
+ DX::Windowing()->SetMinimized(false);
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPower = components.GetComponent<CApplicationPowerHandling>();
+ if (!appPower->GetRenderGUI())
+ {
+ if (appPort)
+ appPort->SetRenderGUI(true);
+ }
+ break;
+ }
+ }
+ break;
+ }
+ case WM_TIMER:
+ {
+ if (wParam == ID_TIMER_HDR)
+ {
+ CLog::LogFC(LOGDEBUG, LOGWINDOWING, "finish toggling HDR event");
+ DX::Windowing()->SetTogglingHDR(false);
+ KillTimer(hWnd, wParam);
+ }
+ break;
+ }
+ case WM_INITMENU:
+ {
+ HMENU hm = GetSystemMenu(hWnd, FALSE);
+ if (hm)
+ {
+ if (DX::Windowing()->IsFullScreen())
+ EnableMenuItem(hm, SC_MOVE, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
+ else
+ EnableMenuItem(hm, SC_MOVE, MF_BYCOMMAND | MF_ENABLED);
+ }
+ break;
+ }
+ default:;
+ }
+ return(DefWindowProc(hWnd, uMsg, wParam, lParam));
+}
+
+void CWinEventsWin32::RegisterDeviceInterfaceToHwnd(GUID InterfaceClassGuid, HWND hWnd, HDEVNOTIFY *hDeviceNotify)
+{
+ DEV_BROADCAST_DEVICEINTERFACE NotificationFilter = {};
+
+ NotificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
+ NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
+ NotificationFilter.dbcc_classguid = InterfaceClassGuid;
+
+ *hDeviceNotify = RegisterDeviceNotification(
+ hWnd, // events recipient
+ &NotificationFilter, // type of device
+ DEVICE_NOTIFY_WINDOW_HANDLE // type of recipient handle
+ );
+}
+
+void CWinEventsWin32::WindowFromScreenCoords(HWND hWnd, POINT *point)
+{
+ if (!point) return;
+ RECT clientRect;
+ GetClientRect(hWnd, &clientRect);
+ POINT windowPos;
+ windowPos.x = clientRect.left;
+ windowPos.y = clientRect.top;
+ ClientToScreen(hWnd, &windowPos);
+ point->x -= windowPos.x;
+ point->y -= windowPos.y;
+}
+
+void CWinEventsWin32::OnGestureNotify(HWND hWnd, LPARAM lParam)
+{
+ // convert to window coordinates
+ PGESTURENOTIFYSTRUCT gn = reinterpret_cast<PGESTURENOTIFYSTRUCT>(lParam);
+ POINT point = { gn->ptsLocation.x, gn->ptsLocation.y };
+ WindowFromScreenCoords(hWnd, &point);
+
+ // by default we only want twofingertap and pressandtap gestures
+ // the other gestures are enabled best on supported gestures
+ GESTURECONFIG gc[] = {{ GID_ZOOM, 0, GC_ZOOM},
+ { GID_ROTATE, 0, GC_ROTATE},
+ { GID_PAN, 0, GC_PAN},
+ { GID_TWOFINGERTAP, GC_TWOFINGERTAP, GC_TWOFINGERTAP },
+ { GID_PRESSANDTAP, GC_PRESSANDTAP, GC_PRESSANDTAP }};
+
+ // send a message to see if a control wants any
+ int gestures;
+ if ((gestures = CGenericTouchActionHandler::GetInstance().QuerySupportedGestures(static_cast<float>(point.x), static_cast<float>(point.y))) != EVENT_RESULT_UNHANDLED)
+ {
+ if (gestures & EVENT_RESULT_ZOOM)
+ gc[0].dwWant |= GC_ZOOM;
+ if (gestures & EVENT_RESULT_ROTATE)
+ gc[1].dwWant |= GC_ROTATE;
+ if (gestures & EVENT_RESULT_PAN_VERTICAL)
+ gc[2].dwWant |= GC_PAN | GC_PAN_WITH_SINGLE_FINGER_VERTICALLY | GC_PAN_WITH_GUTTER | GC_PAN_WITH_INERTIA;
+ if (gestures & EVENT_RESULT_PAN_VERTICAL_WITHOUT_INERTIA)
+ gc[2].dwWant |= GC_PAN | GC_PAN_WITH_SINGLE_FINGER_VERTICALLY;
+ if (gestures & EVENT_RESULT_PAN_HORIZONTAL)
+ gc[2].dwWant |= GC_PAN | GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY | GC_PAN_WITH_GUTTER | GC_PAN_WITH_INERTIA;
+ if (gestures & EVENT_RESULT_PAN_HORIZONTAL_WITHOUT_INERTIA)
+ gc[2].dwWant |= GC_PAN | GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY;
+ if (gestures & EVENT_RESULT_SWIPE)
+ {
+ gc[2].dwWant |= GC_PAN | GC_PAN_WITH_SINGLE_FINGER_VERTICALLY | GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY | GC_PAN_WITH_GUTTER;
+
+ // create a new touch swipe detector
+ m_touchSwipeDetector = new CGenericTouchSwipeDetector(&CGenericTouchActionHandler::GetInstance(), 160.0f);
+ }
+
+ gc[0].dwBlock = gc[0].dwWant ^ 0x01;
+ gc[1].dwBlock = gc[1].dwWant ^ 0x01;
+ gc[2].dwBlock = gc[2].dwWant ^ 0x1F;
+ }
+ if (DX::Windowing()->PtrSetGestureConfig)
+ DX::Windowing()->PtrSetGestureConfig(hWnd, 0, 5, gc, sizeof(GESTURECONFIG));
+}
+
+void CWinEventsWin32::OnGesture(HWND hWnd, LPARAM lParam)
+{
+ if (!DX::Windowing()->PtrGetGestureInfo)
+ return;
+
+ GESTUREINFO gi = {};
+ gi.cbSize = sizeof(gi);
+ DX::Windowing()->PtrGetGestureInfo(reinterpret_cast<HGESTUREINFO>(lParam), &gi);
+
+ // convert to window coordinates
+ POINT point = { gi.ptsLocation.x, gi.ptsLocation.y };
+ WindowFromScreenCoords(hWnd, &point);
+
+ if (gi.dwID == GID_BEGIN)
+ m_touchPointer.reset();
+
+ // if there's a "current" touch from a previous event, copy it to "last"
+ if (m_touchPointer.current.valid())
+ m_touchPointer.last = m_touchPointer.current;
+
+ // set the "current" touch
+ m_touchPointer.current.x = static_cast<float>(point.x);
+ m_touchPointer.current.y = static_cast<float>(point.y);
+ m_touchPointer.current.time = time(nullptr);
+
+ switch (gi.dwID)
+ {
+ case GID_BEGIN:
+ {
+ // set the "down" touch
+ m_touchPointer.down = m_touchPointer.current;
+ m_originalZoomDistance = 0;
+
+ CGenericTouchActionHandler::GetInstance().OnTouchGestureStart(static_cast<float>(point.x), static_cast<float>(point.y));
+ }
+ break;
+
+ case GID_END:
+ CGenericTouchActionHandler::GetInstance().OnTouchGestureEnd(static_cast<float>(point.x), static_cast<float>(point.y), 0.0f, 0.0f, 0.0f, 0.0f);
+ break;
+
+ case GID_PAN:
+ {
+ if (!m_touchPointer.moving)
+ m_touchPointer.moving = true;
+
+ // calculate the velocity of the pan gesture
+ float velocityX, velocityY;
+ m_touchPointer.velocity(velocityX, velocityY);
+
+ CGenericTouchActionHandler::GetInstance().OnTouchGesturePan(m_touchPointer.current.x, m_touchPointer.current.y,
+ m_touchPointer.current.x - m_touchPointer.last.x, m_touchPointer.current.y - m_touchPointer.last.y,
+ velocityX, velocityY);
+
+ if (m_touchSwipeDetector != nullptr)
+ {
+ if (gi.dwFlags & GF_BEGIN)
+ {
+ m_touchPointer.down = m_touchPointer.current;
+ m_touchSwipeDetector->OnTouchDown(0, m_touchPointer);
+ }
+ else if (gi.dwFlags & GF_END)
+ {
+ m_touchSwipeDetector->OnTouchUp(0, m_touchPointer);
+
+ delete m_touchSwipeDetector;
+ m_touchSwipeDetector = nullptr;
+ }
+ else
+ m_touchSwipeDetector->OnTouchMove(0, m_touchPointer);
+ }
+ }
+ break;
+
+ case GID_ROTATE:
+ {
+ if (gi.dwFlags == GF_BEGIN)
+ break;
+
+ CGenericTouchActionHandler::GetInstance().OnRotate(static_cast<float>(point.x), static_cast<float>(point.y),
+ -static_cast<float>(ROTATE_ANGLE_DEGREE(gi.ullArguments)));
+ }
+ break;
+
+ case GID_ZOOM:
+ {
+ if (gi.dwFlags == GF_BEGIN)
+ {
+ m_originalZoomDistance = static_cast<int>(LODWORD(gi.ullArguments));
+ break;
+ }
+
+ // avoid division by 0
+ if (m_originalZoomDistance == 0)
+ break;
+
+ CGenericTouchActionHandler::GetInstance().OnZoomPinch(static_cast<float>(point.x), static_cast<float>(point.y),
+ static_cast<float>(LODWORD(gi.ullArguments)) / static_cast<float>(m_originalZoomDistance));
+ }
+ break;
+
+ case GID_TWOFINGERTAP:
+ CGenericTouchActionHandler::GetInstance().OnTap(static_cast<float>(point.x), static_cast<float>(point.y), 2);
+ break;
+
+ case GID_PRESSANDTAP:
+ default:
+ // You have encountered an unknown gesture
+ break;
+ }
+ if(DX::Windowing()->PtrCloseGestureInfoHandle)
+ DX::Windowing()->PtrCloseGestureInfoHandle(reinterpret_cast<HGESTUREINFO>(lParam));
+}
diff --git a/xbmc/windowing/windows/WinEventsWin32.h b/xbmc/windowing/windows/WinEventsWin32.h
new file mode 100644
index 0000000..466c755
--- /dev/null
+++ b/xbmc/windowing/windows/WinEventsWin32.h
@@ -0,0 +1,32 @@
+/*
+ * 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 "input/touch/TouchTypes.h"
+#include "windowing/WinEvents.h"
+
+class CGenericTouchSwipeDetector;
+
+class CWinEventsWin32 : public IWinEvents
+{
+public:
+ bool MessagePump() override;
+ static LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+private:
+ static void RegisterDeviceInterfaceToHwnd(GUID InterfaceClassGuid, HWND hWnd, HDEVNOTIFY *hDeviceNotify);
+ static void WindowFromScreenCoords(HWND hWnd, POINT *point);
+ static void OnGestureNotify(HWND hWnd, LPARAM lParam);
+ static void OnGesture(HWND hWnd, LPARAM lParam);
+
+ static int m_originalZoomDistance;
+ static Pointer m_touchPointer;
+ static CGenericTouchSwipeDetector *m_touchSwipeDetector;
+};
+
diff --git a/xbmc/windowing/windows/WinKeyMap.h b/xbmc/windowing/windows/WinKeyMap.h
new file mode 100644
index 0000000..c163747
--- /dev/null
+++ b/xbmc/windowing/windows/WinKeyMap.h
@@ -0,0 +1,181 @@
+/*
+ * 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 "ServiceBroker.h"
+#include "Util.h"
+#include "input/XBMC_keysym.h"
+#include "input/XBMC_vkeys.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
+
+#include <array>
+
+namespace KODI
+{
+namespace WINDOWING
+{
+namespace WINDOWS
+{
+
+static std::array<XBMCKey, XBMCK_LAST> VK_keymap;
+
+static void DIB_InitOSKeymap()
+{
+ /* Map the VK keysyms */
+ VK_keymap.fill(XBMCK_UNKNOWN);
+
+ VK_keymap[VK_BACK] = XBMCK_BACKSPACE;
+ VK_keymap[VK_TAB] = XBMCK_TAB;
+ VK_keymap[VK_CLEAR] = XBMCK_CLEAR;
+ VK_keymap[VK_RETURN] = XBMCK_RETURN;
+ VK_keymap[VK_PAUSE] = XBMCK_PAUSE;
+ VK_keymap[VK_ESCAPE] = XBMCK_ESCAPE;
+ VK_keymap[VK_SPACE] = XBMCK_SPACE;
+ VK_keymap[VK_APOSTROPHE] = XBMCK_QUOTE;
+ VK_keymap[VK_COMMA] = XBMCK_COMMA;
+ VK_keymap[VK_MINUS] = XBMCK_MINUS;
+ VK_keymap[VK_PERIOD] = XBMCK_PERIOD;
+ VK_keymap[VK_SLASH] = XBMCK_SLASH;
+ VK_keymap[VK_0] = XBMCK_0;
+ VK_keymap[VK_1] = XBMCK_1;
+ VK_keymap[VK_2] = XBMCK_2;
+ VK_keymap[VK_3] = XBMCK_3;
+ VK_keymap[VK_4] = XBMCK_4;
+ VK_keymap[VK_5] = XBMCK_5;
+ VK_keymap[VK_6] = XBMCK_6;
+ VK_keymap[VK_7] = XBMCK_7;
+ VK_keymap[VK_8] = XBMCK_8;
+ VK_keymap[VK_9] = XBMCK_9;
+ VK_keymap[VK_SEMICOLON] = XBMCK_SEMICOLON;
+ VK_keymap[VK_EQUALS] = XBMCK_EQUALS;
+ VK_keymap[VK_LBRACKET] = XBMCK_LEFTBRACKET;
+ VK_keymap[VK_BACKSLASH] = XBMCK_BACKSLASH;
+ VK_keymap[VK_OEM_102] = XBMCK_BACKSLASH;
+ VK_keymap[VK_RBRACKET] = XBMCK_RIGHTBRACKET;
+ VK_keymap[VK_GRAVE] = XBMCK_BACKQUOTE;
+ VK_keymap[VK_BACKTICK] = XBMCK_BACKQUOTE;
+ VK_keymap[VK_A] = XBMCK_a;
+ VK_keymap[VK_B] = XBMCK_b;
+ VK_keymap[VK_C] = XBMCK_c;
+ VK_keymap[VK_D] = XBMCK_d;
+ VK_keymap[VK_E] = XBMCK_e;
+ VK_keymap[VK_F] = XBMCK_f;
+ VK_keymap[VK_G] = XBMCK_g;
+ VK_keymap[VK_H] = XBMCK_h;
+ VK_keymap[VK_I] = XBMCK_i;
+ VK_keymap[VK_J] = XBMCK_j;
+ VK_keymap[VK_K] = XBMCK_k;
+ VK_keymap[VK_L] = XBMCK_l;
+ VK_keymap[VK_M] = XBMCK_m;
+ VK_keymap[VK_N] = XBMCK_n;
+ VK_keymap[VK_O] = XBMCK_o;
+ VK_keymap[VK_P] = XBMCK_p;
+ VK_keymap[VK_Q] = XBMCK_q;
+ VK_keymap[VK_R] = XBMCK_r;
+ VK_keymap[VK_S] = XBMCK_s;
+ VK_keymap[VK_T] = XBMCK_t;
+ VK_keymap[VK_U] = XBMCK_u;
+ VK_keymap[VK_V] = XBMCK_v;
+ VK_keymap[VK_W] = XBMCK_w;
+ VK_keymap[VK_X] = XBMCK_x;
+ VK_keymap[VK_Y] = XBMCK_y;
+ VK_keymap[VK_Z] = XBMCK_z;
+ VK_keymap[VK_DELETE] = XBMCK_DELETE;
+
+ VK_keymap[VK_NUMPAD0] = XBMCK_KP0;
+ VK_keymap[VK_NUMPAD1] = XBMCK_KP1;
+ VK_keymap[VK_NUMPAD2] = XBMCK_KP2;
+ VK_keymap[VK_NUMPAD3] = XBMCK_KP3;
+ VK_keymap[VK_NUMPAD4] = XBMCK_KP4;
+ VK_keymap[VK_NUMPAD5] = XBMCK_KP5;
+ VK_keymap[VK_NUMPAD6] = XBMCK_KP6;
+ VK_keymap[VK_NUMPAD7] = XBMCK_KP7;
+ VK_keymap[VK_NUMPAD8] = XBMCK_KP8;
+ VK_keymap[VK_NUMPAD9] = XBMCK_KP9;
+ VK_keymap[VK_DECIMAL] = XBMCK_KP_PERIOD;
+ VK_keymap[VK_DIVIDE] = XBMCK_KP_DIVIDE;
+ VK_keymap[VK_MULTIPLY] = XBMCK_KP_MULTIPLY;
+ VK_keymap[VK_SUBTRACT] = XBMCK_KP_MINUS;
+ VK_keymap[VK_ADD] = XBMCK_KP_PLUS;
+
+ VK_keymap[VK_UP] = XBMCK_UP;
+ VK_keymap[VK_DOWN] = XBMCK_DOWN;
+ VK_keymap[VK_RIGHT] = XBMCK_RIGHT;
+ VK_keymap[VK_LEFT] = XBMCK_LEFT;
+ VK_keymap[VK_INSERT] = XBMCK_INSERT;
+ VK_keymap[VK_HOME] = XBMCK_HOME;
+ VK_keymap[VK_END] = XBMCK_END;
+ VK_keymap[VK_PRIOR] = XBMCK_PAGEUP;
+ VK_keymap[VK_NEXT] = XBMCK_PAGEDOWN;
+
+ VK_keymap[VK_F1] = XBMCK_F1;
+ VK_keymap[VK_F2] = XBMCK_F2;
+ VK_keymap[VK_F3] = XBMCK_F3;
+ VK_keymap[VK_F4] = XBMCK_F4;
+ VK_keymap[VK_F5] = XBMCK_F5;
+ VK_keymap[VK_F6] = XBMCK_F6;
+ VK_keymap[VK_F7] = XBMCK_F7;
+ VK_keymap[VK_F8] = XBMCK_F8;
+ VK_keymap[VK_F9] = XBMCK_F9;
+ VK_keymap[VK_F10] = XBMCK_F10;
+ VK_keymap[VK_F11] = XBMCK_F11;
+ VK_keymap[VK_F12] = XBMCK_F12;
+ VK_keymap[VK_F13] = XBMCK_F13;
+ VK_keymap[VK_F14] = XBMCK_F14;
+ VK_keymap[VK_F15] = XBMCK_F15;
+
+ VK_keymap[VK_NUMLOCK] = XBMCK_NUMLOCK;
+ VK_keymap[VK_CAPITAL] = XBMCK_CAPSLOCK;
+ VK_keymap[VK_SCROLL] = XBMCK_SCROLLOCK;
+ VK_keymap[VK_RSHIFT] = XBMCK_RSHIFT;
+ VK_keymap[VK_LSHIFT] = XBMCK_LSHIFT;
+ VK_keymap[VK_RCONTROL] = XBMCK_RCTRL;
+ VK_keymap[VK_LCONTROL] = XBMCK_LCTRL;
+ VK_keymap[VK_RMENU] = XBMCK_RALT;
+ VK_keymap[VK_LMENU] = XBMCK_LALT;
+ VK_keymap[VK_RWIN] = XBMCK_RSUPER;
+ VK_keymap[VK_LWIN] = XBMCK_LSUPER;
+
+ VK_keymap[VK_HELP] = XBMCK_HELP;
+#ifdef VK_PRINT
+ VK_keymap[VK_PRINT] = XBMCK_PRINT;
+#endif
+ VK_keymap[VK_SNAPSHOT] = XBMCK_PRINT;
+ VK_keymap[VK_CANCEL] = XBMCK_BREAK;
+ VK_keymap[VK_APPS] = XBMCK_MENU;
+
+ // Only include the multimedia keys if they have been enabled in the
+ // advanced settings
+ if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_enableMultimediaKeys)
+ {
+ VK_keymap[VK_BROWSER_BACK] = XBMCK_BROWSER_BACK;
+ VK_keymap[VK_BROWSER_FORWARD] = XBMCK_BROWSER_FORWARD;
+ VK_keymap[VK_BROWSER_REFRESH] = XBMCK_BROWSER_REFRESH;
+ VK_keymap[VK_BROWSER_STOP] = XBMCK_BROWSER_STOP;
+ VK_keymap[VK_BROWSER_SEARCH] = XBMCK_BROWSER_SEARCH;
+ VK_keymap[VK_BROWSER_FAVORITES] = XBMCK_BROWSER_FAVORITES;
+ VK_keymap[VK_BROWSER_HOME] = XBMCK_BROWSER_HOME;
+ VK_keymap[VK_VOLUME_MUTE] = XBMCK_VOLUME_MUTE;
+ VK_keymap[VK_VOLUME_DOWN] = XBMCK_VOLUME_DOWN;
+ VK_keymap[VK_VOLUME_UP] = XBMCK_VOLUME_UP;
+ VK_keymap[VK_MEDIA_NEXT_TRACK] = XBMCK_MEDIA_NEXT_TRACK;
+ VK_keymap[VK_MEDIA_PREV_TRACK] = XBMCK_MEDIA_PREV_TRACK;
+ VK_keymap[VK_MEDIA_STOP] = XBMCK_MEDIA_STOP;
+ VK_keymap[VK_MEDIA_PLAY_PAUSE] = XBMCK_MEDIA_PLAY_PAUSE;
+ VK_keymap[VK_LAUNCH_MAIL] = XBMCK_LAUNCH_MAIL;
+ VK_keymap[VK_LAUNCH_MEDIA_SELECT] = XBMCK_LAUNCH_MEDIA_SELECT;
+ VK_keymap[VK_LAUNCH_APP1] = XBMCK_LAUNCH_APP1;
+ VK_keymap[VK_LAUNCH_APP2] = XBMCK_LAUNCH_APP2;
+ }
+}
+
+} // namespace WINDOWS
+} // namespace WINDOWING
+} // namespace KODI
diff --git a/xbmc/windowing/windows/WinSystemWin32.cpp b/xbmc/windowing/windows/WinSystemWin32.cpp
new file mode 100644
index 0000000..9394b4b
--- /dev/null
+++ b/xbmc/windowing/windows/WinSystemWin32.cpp
@@ -0,0 +1,1368 @@
+/*
+ * 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 "WinSystemWin32.h"
+
+#include "ServiceBroker.h"
+#include "VideoSyncD3D.h"
+#include "WIN32Util.h"
+#include "WinEventsWin32.h"
+#include "application/Application.h"
+#include "cores/AudioEngine/AESinkFactory.h"
+#include "cores/AudioEngine/Sinks/AESinkDirectSound.h"
+#include "cores/AudioEngine/Sinks/AESinkWASAPI.h"
+#include "filesystem/File.h"
+#include "filesystem/SpecialProtocol.h"
+#include "messaging/ApplicationMessenger.h"
+#include "platform/Environment.h"
+#include "rendering/dx/ScreenshotSurfaceWindows.h"
+#include "resource.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/DisplaySettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/StringUtils.h"
+#include "utils/SystemInfo.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+#include "windowing/windows/Win32DPMSSupport.h"
+
+#include "platform/win32/CharsetConverter.h"
+#include "platform/win32/input/IRServerSuite.h"
+
+#include <algorithm>
+#include <mutex>
+
+#include <tpcshrd.h>
+
+using namespace std::chrono_literals;
+
+const char* CWinSystemWin32::SETTING_WINDOW_TOP = "window.top";
+const char* CWinSystemWin32::SETTING_WINDOW_LEFT = "window.left";
+
+CWinSystemWin32::CWinSystemWin32()
+ : CWinSystemBase()
+ , PtrGetGestureInfo(nullptr)
+ , PtrSetGestureConfig(nullptr)
+ , PtrCloseGestureInfoHandle(nullptr)
+ , PtrEnableNonClientDpiScaling(nullptr)
+ , m_hWnd(nullptr)
+ , m_hMonitor(nullptr)
+ , m_hInstance(nullptr)
+ , m_hIcon(nullptr)
+ , m_ValidWindowedPosition(false)
+ , m_IsAlteringWindow(false)
+ , m_delayDispReset(false)
+ , m_state(WINDOW_STATE_WINDOWED)
+ , m_fullscreenState(WINDOW_FULLSCREEN_STATE_FULLSCREEN_WINDOW)
+ , m_windowState(WINDOW_WINDOW_STATE_WINDOWED)
+ , m_windowStyle(WINDOWED_STYLE)
+ , m_windowExStyle(WINDOWED_EX_STYLE)
+ , m_inFocus(false)
+ , m_bMinimized(false)
+{
+ std::string cacert = CEnvironment::getenv("SSL_CERT_FILE");
+ if (cacert.empty() || !XFILE::CFile::Exists(cacert))
+ {
+ cacert = CSpecialProtocol::TranslatePath("special://xbmc/system/certs/cacert.pem");
+ if (XFILE::CFile::Exists(cacert))
+ CEnvironment::setenv("SSL_CERT_FILE", cacert.c_str(), 1);
+ }
+
+ m_winEvents.reset(new CWinEventsWin32());
+ AE::CAESinkFactory::ClearSinks();
+ CAESinkDirectSound::Register();
+ CAESinkWASAPI::Register();
+ CScreenshotSurfaceWindows::Register();
+
+ if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_bScanIRServer)
+ {
+ m_irss.reset(new CIRServerSuite());
+ m_irss->Initialize();
+ }
+ m_dpms = std::make_shared<CWin32DPMSSupport>();
+}
+
+CWinSystemWin32::~CWinSystemWin32()
+{
+ if (m_hIcon)
+ {
+ DestroyIcon(m_hIcon);
+ m_hIcon = nullptr;
+ }
+};
+
+bool CWinSystemWin32::InitWindowSystem()
+{
+ if(!CWinSystemBase::InitWindowSystem())
+ return false;
+
+ return true;
+}
+
+bool CWinSystemWin32::DestroyWindowSystem()
+{
+ if (m_hMonitor)
+ {
+ MONITOR_DETAILS* details = GetDisplayDetails(m_hMonitor);
+ if (details)
+ RestoreDesktopResolution(details);
+ }
+ return true;
+}
+
+bool CWinSystemWin32::CreateNewWindow(const std::string& name, bool fullScreen, RESOLUTION_INFO& res)
+{
+ using KODI::PLATFORM::WINDOWS::ToW;
+ auto nameW = ToW(name);
+
+ m_hInstance = static_cast<HINSTANCE>(GetModuleHandle(nullptr));
+ if(m_hInstance == nullptr)
+ CLog::LogF(LOGDEBUG, " GetModuleHandle failed with {}", GetLastError());
+
+ // Load Win32 procs if available
+ HMODULE hUser32 = GetModuleHandle(L"user32");
+ if (hUser32)
+ {
+ PtrGetGestureInfo = reinterpret_cast<pGetGestureInfo>(GetProcAddress(hUser32, "GetGestureInfo"));
+ PtrSetGestureConfig = reinterpret_cast<pSetGestureConfig>(GetProcAddress(hUser32, "SetGestureConfig"));
+ PtrCloseGestureInfoHandle = reinterpret_cast<pCloseGestureInfoHandle>(GetProcAddress(hUser32, "CloseGestureInfoHandle"));
+ // if available, enable automatic DPI scaling of the non-client area portions of the window.
+ PtrEnableNonClientDpiScaling = reinterpret_cast<pEnableNonClientDpiScaling>(GetProcAddress(hUser32, "EnableNonClientDpiScaling"));
+ }
+
+ UpdateStates(fullScreen);
+ // initialize the state
+ WINDOW_STATE state = GetState(fullScreen);
+
+ m_nWidth = res.iWidth;
+ m_nHeight = res.iHeight;
+ m_bFullScreen = fullScreen;
+ m_fRefreshRate = res.fRefreshRate;
+
+ m_hIcon = LoadIcon(m_hInstance, MAKEINTRESOURCE(IDI_MAIN_ICON));
+
+ // Register the windows class
+ WNDCLASSEX wndClass = {};
+ wndClass.cbSize = sizeof(wndClass);
+ wndClass.style = CS_HREDRAW | CS_VREDRAW;
+ wndClass.lpfnWndProc = CWinEventsWin32::WndProc;
+ wndClass.cbClsExtra = 0;
+ wndClass.cbWndExtra = 0;
+ wndClass.hInstance = m_hInstance;
+ wndClass.hIcon = m_hIcon;
+ wndClass.hCursor = LoadCursor(nullptr, IDC_ARROW );
+ wndClass.hbrBackground = static_cast<HBRUSH>(GetStockObject(BLACK_BRUSH));
+ wndClass.lpszMenuName = nullptr;
+ wndClass.lpszClassName = nameW.c_str();
+
+ if( !RegisterClassExW( &wndClass ) )
+ {
+ CLog::LogF(LOGERROR, " RegisterClassExW failed with {}", GetLastError());
+ return false;
+ }
+
+ // put the window at desired display
+ RECT screenRect = ScreenRect(m_hMonitor);
+ m_nLeft = screenRect.left;
+ m_nTop = screenRect.top;
+
+ if (state == WINDOW_STATE_WINDOWED)
+ {
+ const auto settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ const int top = settings->GetInt(SETTING_WINDOW_TOP);
+ const int left = settings->GetInt(SETTING_WINDOW_LEFT);
+ const RECT vsRect = GetVirtualScreenRect();
+
+ // we check that window is inside of virtual screen rect (sum of all monitors)
+ // top 0 left 0 is a special position that centers the window on the screen
+ if ((top != 0 || left != 0) && top >= vsRect.top && top + m_nHeight <= vsRect.bottom &&
+ left >= vsRect.left && left + m_nWidth <= vsRect.right)
+ {
+ // restore previous window position
+ m_nLeft = left;
+ m_nTop = top;
+ }
+ else
+ {
+ // Windowed mode: position and size in settings and most places in Kodi
+ // are for the client part of the window.
+ RECT rcWorkArea = GetScreenWorkArea(m_hMonitor);
+
+ int workAreaWidth = rcWorkArea.right - rcWorkArea.left;
+ int workAreaHeight = rcWorkArea.bottom - rcWorkArea.top;
+
+ RECT rcNcArea = GetNcAreaOffsets(m_windowStyle, false, m_windowExStyle);
+ int maxClientWidth = (rcWorkArea.right - rcNcArea.right) - (rcWorkArea.left - rcNcArea.left);
+ int maxClientHeight = (rcWorkArea.bottom - rcNcArea.bottom) - (rcWorkArea.top - rcNcArea.top);
+
+ m_nWidth = std::min(m_nWidth, maxClientWidth);
+ m_nHeight = std::min(m_nHeight, maxClientHeight);
+ CWinSystemBase::SetWindowResolution(m_nWidth, m_nHeight);
+
+ // center window on desktop
+ m_nLeft = rcWorkArea.left - rcNcArea.left + (maxClientWidth - m_nWidth) / 2;
+ m_nTop = rcWorkArea.top - rcNcArea.top + (maxClientHeight - m_nHeight) / 2;
+ }
+ m_ValidWindowedPosition = true;
+ }
+
+ HWND hWnd = CreateWindowExW(
+ m_windowExStyle,
+ nameW.c_str(),
+ nameW.c_str(),
+ m_windowStyle,
+ m_nLeft,
+ m_nTop,
+ m_nWidth,
+ m_nHeight,
+ nullptr,
+ nullptr,
+ m_hInstance,
+ nullptr
+ );
+
+ if( hWnd == nullptr )
+ {
+ CLog::LogF(LOGERROR, " CreateWindow failed with {}", GetLastError());
+ return false;
+ }
+
+ m_inFocus = true;
+
+ DWORD dwHwndTabletProperty =
+ TABLET_DISABLE_PENBARRELFEEDBACK | // disables UI feedback on pen button down (circle)
+ TABLET_DISABLE_FLICKS; // disables pen flicks (back, forward, drag down, drag up)
+
+ SetProp(hWnd, MICROSOFT_TABLETPENSERVICE_PROPERTY, &dwHwndTabletProperty);
+
+ m_hWnd = hWnd;
+ m_bWindowCreated = true;
+
+ CreateBlankWindows();
+
+ m_state = state;
+ AdjustWindow(true);
+
+ // Show the window
+ ShowWindow( m_hWnd, SW_SHOWDEFAULT );
+ UpdateWindow( m_hWnd );
+
+ // Configure the tray icon.
+ m_trayIcon.cbSize = sizeof(m_trayIcon);
+ m_trayIcon.hWnd = m_hWnd;
+ m_trayIcon.hIcon = m_hIcon;
+ wcsncpy(m_trayIcon.szTip, nameW.c_str(), sizeof(m_trayIcon.szTip) / sizeof(WCHAR));
+ m_trayIcon.uCallbackMessage = TRAY_ICON_NOTIFY;
+ m_trayIcon.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE;
+
+ return true;
+}
+
+bool CWinSystemWin32::CreateBlankWindows()
+{
+ WNDCLASSEX wcex;
+
+ wcex.cbSize = sizeof(WNDCLASSEX);
+ wcex.style= CS_HREDRAW | CS_VREDRAW;
+ wcex.lpfnWndProc= DefWindowProc;
+ wcex.cbClsExtra= 0;
+ wcex.cbWndExtra= 0;
+ wcex.hInstance= nullptr;
+ wcex.hIcon= nullptr;
+ wcex.hCursor= nullptr;
+ wcex.hbrBackground= static_cast<HBRUSH>(CreateSolidBrush(RGB(0, 0, 0)));
+ wcex.lpszMenuName= nullptr;
+ wcex.lpszClassName= L"BlankWindowClass";
+ wcex.hIconSm= nullptr;
+
+ // Now we can go ahead and register our new window class
+ if(!RegisterClassEx(&wcex))
+ {
+ CLog::LogF(LOGERROR, "RegisterClass failed with {}", GetLastError());
+ return false;
+ }
+
+ // We need as many blank windows as there are screens (minus 1)
+ for (size_t i = 0; i < m_displays.size() - 1; i++)
+ {
+ HWND hBlankWindow = CreateWindowEx(WS_EX_TOPMOST, L"BlankWindowClass", L"", WS_POPUP | WS_DISABLED,
+ CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, nullptr, nullptr);
+
+ if (hBlankWindow == nullptr)
+ {
+ CLog::LogF(LOGERROR, "CreateWindowEx failed with {}", GetLastError());
+ return false;
+ }
+
+ m_hBlankWindows.push_back(hBlankWindow);
+ }
+
+ return true;
+}
+
+bool CWinSystemWin32::BlankNonActiveMonitors(bool bBlank)
+{
+ if (m_hBlankWindows.empty())
+ return false;
+
+ if (bBlank == false)
+ {
+ for (unsigned int i=0; i < m_hBlankWindows.size(); i++)
+ ShowWindow(m_hBlankWindows[i], SW_HIDE);
+ return true;
+ }
+
+ // Move a blank window in front of every display, except the current display.
+ for (size_t i = 0, j = 0; i < m_displays.size(); ++i)
+ {
+ MONITOR_DETAILS& details = m_displays[i];
+ if (details.hMonitor == m_hMonitor)
+ continue;
+
+ RECT rBounds = ScreenRect(details.hMonitor);
+ // move and resize the window
+ SetWindowPos(m_hBlankWindows[j], nullptr, rBounds.left, rBounds.top,
+ rBounds.right - rBounds.left, rBounds.bottom - rBounds.top,
+ SWP_NOACTIVATE);
+
+ ShowWindow(m_hBlankWindows[j], SW_SHOW | SW_SHOWNOACTIVATE);
+ j++;
+ }
+
+ if (m_hWnd)
+ SetForegroundWindow(m_hWnd);
+
+ return true;
+}
+
+bool CWinSystemWin32::CenterWindow()
+{
+ RESOLUTION_INFO DesktopRes = CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP);
+
+ m_nLeft = (DesktopRes.iWidth / 2) - (m_nWidth / 2);
+ m_nTop = (DesktopRes.iHeight / 2) - (m_nHeight / 2);
+
+ RECT rc;
+ rc.left = m_nLeft;
+ rc.top = m_nTop;
+ rc.right = rc.left + m_nWidth;
+ rc.bottom = rc.top + m_nHeight;
+ AdjustWindowRect( &rc, WS_OVERLAPPEDWINDOW, false );
+
+ SetWindowPos(m_hWnd, nullptr, rc.left, rc.top, 0, 0, SWP_NOSIZE);
+
+ return true;
+}
+
+bool CWinSystemWin32::ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop)
+{
+ m_nWidth = newWidth;
+ m_nHeight = newHeight;
+
+ if (newLeft > 0)
+ m_nLeft = newLeft;
+
+ if (newTop > 0)
+ m_nTop = newTop;
+
+ AdjustWindow();
+
+ return true;
+}
+
+void CWinSystemWin32::FinishWindowResize(int newWidth, int newHeight)
+{
+ m_nWidth = newWidth;
+ m_nHeight = newHeight;
+}
+
+void CWinSystemWin32::AdjustWindow(bool forceResize)
+{
+ CLog::LogF(LOGDEBUG, "adjusting window if required.");
+
+ HWND windowAfter;
+ RECT rc;
+
+ if (m_state == WINDOW_STATE_FULLSCREEN_WINDOW || m_state == WINDOW_STATE_FULLSCREEN)
+ {
+ windowAfter = HWND_TOP;
+ rc = ScreenRect(m_hMonitor);
+ }
+ else // m_state == WINDOW_STATE_WINDOWED
+ {
+ windowAfter = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_alwaysOnTop ? HWND_TOPMOST : HWND_NOTOPMOST;
+
+ if (!m_ValidWindowedPosition)
+ {
+ const auto settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ const int top = settings->GetInt(SETTING_WINDOW_TOP);
+ const int left = settings->GetInt(SETTING_WINDOW_LEFT);
+ const RECT vsRect = GetVirtualScreenRect();
+
+ // we check that window is inside of virtual screen rect (sum of all monitors)
+ // top 0 left 0 is a special position that centers the window on the screen
+ if ((top != 0 || left != 0) && top >= vsRect.top && top + m_nHeight <= vsRect.bottom &&
+ left >= vsRect.left && left + m_nWidth <= vsRect.right)
+ {
+ // restore previous window position
+ m_nTop = top;
+ m_nLeft = left;
+ }
+ else
+ {
+ // Windowed mode: position and size in settings and most places in Kodi
+ // are for the client part of the window.
+ RECT rcWorkArea = GetScreenWorkArea(m_hMonitor);
+
+ int workAreaWidth = rcWorkArea.right - rcWorkArea.left;
+ int workAreaHeight = rcWorkArea.bottom - rcWorkArea.top;
+
+ RECT rcNcArea = GetNcAreaOffsets(m_windowStyle, false, m_windowExStyle);
+ int maxClientWidth =
+ (rcWorkArea.right - rcNcArea.right) - (rcWorkArea.left - rcNcArea.left);
+ int maxClientHeight =
+ (rcWorkArea.bottom - rcNcArea.bottom) - (rcWorkArea.top - rcNcArea.top);
+
+ m_nWidth = std::min(m_nWidth, maxClientWidth);
+ m_nHeight = std::min(m_nHeight, maxClientHeight);
+ CWinSystemBase::SetWindowResolution(m_nWidth, m_nHeight);
+
+ // center window on desktop
+ m_nLeft = rcWorkArea.left - rcNcArea.left + (maxClientWidth - m_nWidth) / 2;
+ m_nTop = rcWorkArea.top - rcNcArea.top + (maxClientHeight - m_nHeight) / 2;
+ }
+ m_ValidWindowedPosition = true;
+ }
+
+ rc.left = m_nLeft;
+ rc.right = m_nLeft + m_nWidth;
+ rc.top = m_nTop;
+ rc.bottom = m_nTop + m_nHeight;
+
+ HMONITOR hMon = MonitorFromRect(&rc, MONITOR_DEFAULTTONULL);
+ HMONITOR hMon2 = MonitorFromWindow(m_hWnd, MONITOR_DEFAULTTOPRIMARY);
+
+ if (!m_ValidWindowedPosition || hMon == nullptr || hMon != hMon2)
+ {
+ // centering window at desktop
+ RECT newScreenRect = ScreenRect(hMon2);
+ rc.left = m_nLeft = newScreenRect.left + ((newScreenRect.right - newScreenRect.left) / 2) - (m_nWidth / 2);
+ rc.top = m_nTop = newScreenRect.top + ((newScreenRect.bottom - newScreenRect.top) / 2) - (m_nHeight / 2);
+ rc.right = m_nLeft + m_nWidth;
+ rc.bottom = m_nTop + m_nHeight;
+ m_ValidWindowedPosition = true;
+ }
+ AdjustWindowRectEx(&rc, m_windowStyle, false, m_windowExStyle);
+ }
+
+ WINDOWINFO wi;
+ wi.cbSize = sizeof(WINDOWINFO);
+ if (!GetWindowInfo(m_hWnd, &wi))
+ {
+ CLog::LogF(LOGERROR, "GetWindowInfo failed with {}", GetLastError());
+ return;
+ }
+ RECT wr = wi.rcWindow;
+
+ if ( wr.bottom - wr.top == rc.bottom - rc.top
+ && wr.right - wr.left == rc.right - rc.left
+ && (wi.dwStyle & WS_CAPTION) == (m_windowStyle & WS_CAPTION)
+ && !forceResize)
+ {
+ return;
+ }
+
+ //Sets the window style
+ SetLastError(0);
+ SetWindowLongPtr( m_hWnd, GWL_STYLE, m_windowStyle );
+
+ //Sets the window ex style
+ SetLastError(0);
+ SetWindowLongPtr( m_hWnd, GWL_EXSTYLE, m_windowExStyle );
+
+ // resize window
+ CLog::LogF(LOGDEBUG, "resizing due to size change ({},{},{},{}{})->({},{},{},{}{})", wr.left,
+ wr.top, wr.right, wr.bottom, (wi.dwStyle & WS_CAPTION) ? "" : " fullscreen", rc.left,
+ rc.top, rc.right, rc.bottom, (m_windowStyle & WS_CAPTION) ? "" : " fullscreen");
+ SetWindowPos(
+ m_hWnd,
+ windowAfter,
+ rc.left,
+ rc.top,
+ rc.right - rc.left,
+ rc.bottom - rc.top,
+ SWP_SHOWWINDOW | SWP_DRAWFRAME
+ );
+}
+
+void CWinSystemWin32::CenterCursor() const
+{
+ RECT rect;
+ POINT point = {};
+
+ //Gets the client rect, then translates it to screen coordinates
+ //so that SetCursorPos isn't called with relative x and y values
+ GetClientRect(m_hWnd, &rect);
+ ClientToScreen(m_hWnd, &point);
+
+ rect.left += point.x;
+ rect.right += point.x;
+ rect.top += point.y;
+ rect.bottom += point.y;
+
+ int x = rect.left + (rect.right - rect.left) / 2;
+ int y = rect.top + (rect.bottom - rect.top) / 2;
+
+ SetCursorPos(x, y);
+}
+
+bool CWinSystemWin32::SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays)
+{
+ CWinSystemWin32::UpdateStates(fullScreen);
+ WINDOW_STATE state = GetState(fullScreen);
+
+ CLog::LogF(LOGDEBUG, "({}) with size {}x{}, refresh {:f}{}", window_state_names[state],
+ res.iWidth, res.iHeight, res.fRefreshRate,
+ (res.dwFlags & D3DPRESENTFLAG_INTERLACED) ? "i" : "");
+
+ // oldMonitor may be NULL if it's powered off or not available due windows settings
+ MONITOR_DETAILS* oldMonitor = GetDisplayDetails(m_hMonitor);
+ MONITOR_DETAILS* newMonitor = GetDisplayDetails(res.strOutput);
+
+ bool forceChange = false; // resolution/display is changed but window state isn't changed
+ bool changeScreen = false; // display is changed
+ bool stereoChange = IsStereoEnabled() != (CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode() == RENDER_STEREO_MODE_HARDWAREBASED);
+
+ if (m_nWidth != res.iWidth || m_nHeight != res.iHeight || m_fRefreshRate != res.fRefreshRate ||
+ !oldMonitor || oldMonitor->hMonitor != newMonitor->hMonitor || stereoChange ||
+ m_bFirstResChange)
+ {
+ if (!oldMonitor || oldMonitor->hMonitor != newMonitor->hMonitor)
+ changeScreen = true;
+ forceChange = true;
+ }
+
+ if (state == m_state && !forceChange)
+ {
+ m_bBlankOtherDisplay = blankOtherDisplays;
+ BlankNonActiveMonitors(m_bBlankOtherDisplay);
+ return true;
+ }
+
+ // entering to stereo mode, limit resolution to 1080p@23.976
+ if (stereoChange && !IsStereoEnabled() && res.iWidth > 1280)
+ {
+ res = CDisplaySettings::GetInstance().GetResolutionInfo(
+ CResolutionUtils::ChooseBestResolution(24.f / 1.001f, 1920, 1080, true));
+ }
+
+ if (m_state == WINDOW_STATE_WINDOWED)
+ {
+ WINDOWINFO wi = {};
+ wi.cbSize = sizeof(WINDOWINFO);
+ if (GetWindowInfo(m_hWnd, &wi) && wi.rcClient.top > 0)
+ {
+ m_nLeft = wi.rcClient.left;
+ m_nTop = wi.rcClient.top;
+ m_ValidWindowedPosition = true;
+ }
+ }
+
+ m_IsAlteringWindow = true;
+ ReleaseBackBuffer();
+
+ if (changeScreen)
+ {
+ // before we changing display we have to leave exclusive mode on "old" display
+ if (m_state == WINDOW_STATE_FULLSCREEN)
+ SetDeviceFullScreen(false, res);
+
+ // restoring native resolution on "old" display
+ RestoreDesktopResolution(oldMonitor);
+
+ // notify about screen change (it may require recreate rendering device)
+ m_fRefreshRate = res.fRefreshRate; // use desired refresh for driver hook
+ OnScreenChange(newMonitor->hMonitor);
+ }
+
+ m_bFirstResChange = false;
+ m_bFullScreen = fullScreen;
+ m_hMonitor = newMonitor->hMonitor;
+ m_nWidth = res.iWidth;
+ m_nHeight = res.iHeight;
+ m_bBlankOtherDisplay = blankOtherDisplays;
+ m_fRefreshRate = res.fRefreshRate;
+
+ if (state == WINDOW_STATE_FULLSCREEN)
+ {
+ SetForegroundWindowInternal(m_hWnd);
+
+ m_state = state;
+ AdjustWindow(changeScreen);
+
+ // enter in exclusive mode, this will change resolution if we already in
+ SetDeviceFullScreen(true, res);
+ }
+ else if (m_state == WINDOW_STATE_FULLSCREEN || m_state == WINDOW_STATE_FULLSCREEN_WINDOW) // we're in fullscreen state now
+ {
+ // guess we are leaving exclusive mode, this will not an effect if we already not in
+ SetDeviceFullScreen(false, res);
+
+ if (state == WINDOW_STATE_WINDOWED) // go to a windowed state
+ {
+ // need to restore resolution if it was changed to not native
+ // because we do not support resolution change in windowed mode
+ RestoreDesktopResolution(newMonitor);
+ }
+ else if (state == WINDOW_STATE_FULLSCREEN_WINDOW) // enter fullscreen window instead
+ {
+ ChangeResolution(res, stereoChange);
+ }
+
+ m_state = state;
+ AdjustWindow(changeScreen);
+ }
+ else // we're in windowed state now
+ {
+ if (state == WINDOW_STATE_FULLSCREEN_WINDOW)
+ {
+ ChangeResolution(res, stereoChange);
+
+ m_state = state;
+ AdjustWindow(changeScreen);
+ }
+ }
+
+ if (changeScreen)
+ CenterCursor();
+
+ CreateBackBuffer();
+
+ BlankNonActiveMonitors(m_bBlankOtherDisplay);
+ m_IsAlteringWindow = false;
+
+ return true;
+}
+
+bool CWinSystemWin32::DPIChanged(WORD dpi, RECT windowRect) const
+{
+ (void)dpi;
+ RECT resizeRect = windowRect;
+ HMONITOR hMon = MonitorFromRect(&resizeRect, MONITOR_DEFAULTTONULL);
+ if (hMon == nullptr)
+ {
+ hMon = MonitorFromWindow(m_hWnd, MONITOR_DEFAULTTOPRIMARY);
+ }
+
+ if (hMon)
+ {
+ MONITORINFOEX monitorInfo;
+ monitorInfo.cbSize = sizeof(MONITORINFOEX);
+ GetMonitorInfoW(hMon, &monitorInfo);
+
+ if (m_state == WINDOW_STATE_FULLSCREEN_WINDOW ||
+ m_state == WINDOW_STATE_FULLSCREEN)
+ {
+ resizeRect = monitorInfo.rcMonitor; // the whole screen
+ }
+ else
+ {
+ RECT wr = monitorInfo.rcWork; // it excludes task bar
+ long wrWidth = wr.right - wr.left;
+ long wrHeight = wr.bottom - wr.top;
+ long resizeWidth = resizeRect.right - resizeRect.left;
+ long resizeHeight = resizeRect.bottom - resizeRect.top;
+
+ if (resizeWidth > wrWidth)
+ {
+ resizeRect.right = resizeRect.left + wrWidth;
+ }
+
+ // make sure suggested windows size is not taller or wider than working area of new monitor (considers the toolbar)
+ if (resizeHeight > wrHeight)
+ {
+ resizeRect.bottom = resizeRect.top + wrHeight;
+ }
+ }
+ }
+
+ // resize the window to the suggested size. Will generate a WM_SIZE event
+ SetWindowPos(m_hWnd,
+ nullptr,
+ resizeRect.left,
+ resizeRect.top,
+ resizeRect.right - resizeRect.left,
+ resizeRect.bottom - resizeRect.top,
+ SWP_NOZORDER | SWP_NOACTIVATE);
+
+ return true;
+}
+
+void CWinSystemWin32::SetMinimized(bool minimized)
+{
+ const auto advancedSettings = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings();
+
+ if (advancedSettings->m_minimizeToTray)
+ {
+ if (minimized)
+ {
+ Shell_NotifyIcon(NIM_ADD, &m_trayIcon);
+ ShowWindow(m_hWnd, SW_HIDE);
+ }
+ else
+ {
+ Shell_NotifyIcon(NIM_DELETE, &m_trayIcon);
+ ShowWindow(m_hWnd, SW_RESTORE);
+ }
+ }
+
+ m_bMinimized = minimized;
+}
+
+std::vector<std::string> CWinSystemWin32::GetConnectedOutputs()
+{
+ std::vector<std::string> outputs;
+
+ for (auto& display : m_displays)
+ {
+ outputs.emplace_back(KODI::PLATFORM::WINDOWS::FromW(display.MonitorNameW));
+ }
+
+ return outputs;
+}
+
+void CWinSystemWin32::RestoreDesktopResolution(MONITOR_DETAILS* details)
+{
+ if (!details)
+ return;
+
+ RESOLUTION_INFO info;
+ info.iWidth = details->ScreenWidth;
+ info.iHeight = details->ScreenHeight;
+ if ((details->RefreshRate + 1) % 24 == 0 || (details->RefreshRate + 1) % 30 == 0)
+ info.fRefreshRate = static_cast<float>(details->RefreshRate + 1) / 1.001f;
+ else
+ info.fRefreshRate = static_cast<float>(details->RefreshRate);
+ info.strOutput = KODI::PLATFORM::WINDOWS::FromW(details->DeviceNameW);
+ info.dwFlags = details->Interlaced ? D3DPRESENTFLAG_INTERLACED : 0;
+
+ CLog::LogF(LOGDEBUG, "restoring desktop resolution for '{}'.", KODI::PLATFORM::WINDOWS::FromW(details->MonitorNameW));
+ ChangeResolution(info);
+}
+
+MONITOR_DETAILS* CWinSystemWin32::GetDisplayDetails(const std::string& name)
+{
+ using KODI::PLATFORM::WINDOWS::ToW;
+
+ if (!name.empty() && name != "Default")
+ {
+ std::wstring nameW = ToW(name);
+ auto it = std::find_if(m_displays.begin(), m_displays.end(), [&nameW](MONITOR_DETAILS& m)
+ {
+ if (nameW[0] == '\\') // name is device name
+ return m.DeviceNameW == nameW;
+ return m.MonitorNameW == nameW;
+ });
+ if (it != m_displays.end())
+ return &(*it);
+ }
+
+ // fallback to primary
+ auto it = std::find_if(m_displays.begin(), m_displays.end(), [](MONITOR_DETAILS& m)
+ {
+ return m.IsPrimary;
+ });
+ if (it != m_displays.end())
+ return &(*it);
+
+ // nothing found
+ return nullptr;
+}
+
+MONITOR_DETAILS* CWinSystemWin32::GetDisplayDetails(HMONITOR handle)
+{
+ auto it = std::find_if(m_displays.begin(), m_displays.end(), [&handle](MONITOR_DETAILS& m)
+ {
+ return m.hMonitor == handle;
+ });
+ if (it != m_displays.end())
+ return &(*it);
+
+ return nullptr;
+}
+
+RECT CWinSystemWin32::ScreenRect(HMONITOR handle)
+{
+ const MONITOR_DETAILS* details = GetDisplayDetails(handle);
+ if (!details)
+ {
+ CLog::LogF(LOGERROR, "no monitor found for handle");
+ return RECT();
+ }
+
+ DEVMODEW sDevMode = {};
+ sDevMode.dmSize = sizeof(sDevMode);
+ if(!EnumDisplaySettingsW(details->DeviceNameW.c_str(), ENUM_CURRENT_SETTINGS, &sDevMode))
+ CLog::LogF(LOGERROR, " EnumDisplaySettings failed with {}", GetLastError());
+
+ RECT rc;
+ rc.left = sDevMode.dmPosition.x;
+ rc.right = sDevMode.dmPosition.x + sDevMode.dmPelsWidth;
+ rc.top = sDevMode.dmPosition.y;
+ rc.bottom = sDevMode.dmPosition.y + sDevMode.dmPelsHeight;
+
+ return rc;
+}
+
+void CWinSystemWin32::GetConnectedDisplays(std::vector<MONITOR_DETAILS>& outputs)
+{
+ using KODI::PLATFORM::WINDOWS::FromW;
+
+ const POINT ptZero = { 0, 0 };
+ HMONITOR hmPrimary = MonitorFromPoint(ptZero, MONITOR_DEFAULTTOPRIMARY);
+
+ DISPLAY_DEVICEW ddAdapter = {};
+ ddAdapter.cb = sizeof(ddAdapter);
+
+ for (DWORD adapter = 0; EnumDisplayDevicesW(nullptr, adapter, &ddAdapter, 0); ++adapter)
+ {
+ // Exclude displays that are not part of the windows desktop. Using them is too different: no windows,
+ // direct access with GDI CreateDC() or DirectDraw for example. So it may be possible to play video, but GUI?
+ if ((ddAdapter.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER)
+ || !(ddAdapter.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP))
+ continue;
+
+ DISPLAY_DEVICEW ddMon = {};
+ ddMon.cb = sizeof(ddMon);
+ bool foundScreen = false;
+
+ DWORD screen = 0;
+ // Just look for the first active output, we're actually only interested in the information at the adapter level.
+ for (; EnumDisplayDevicesW(ddAdapter.DeviceName, screen, &ddMon, 0); ++screen)
+ {
+ if (ddMon.StateFlags & (DISPLAY_DEVICE_ACTIVE | DISPLAY_DEVICE_ATTACHED))
+ {
+ foundScreen = true;
+ break;
+ }
+ }
+
+ // Remoting returns no screens. Handle with a dummy screen.
+ if (!foundScreen && screen == 0)
+ {
+ lstrcpyW(ddMon.DeviceString, L"Dummy Monitor"); // safe: large static array
+ foundScreen = true;
+ }
+
+ if (foundScreen)
+ {
+ // get information about the display's current position and display mode
+ DEVMODEW dm = {};
+ dm.dmSize = sizeof(dm);
+ if (EnumDisplaySettingsExW(ddAdapter.DeviceName, ENUM_CURRENT_SETTINGS, &dm, 0) == FALSE)
+ EnumDisplaySettingsExW(ddAdapter.DeviceName, ENUM_REGISTRY_SETTINGS, &dm, 0);
+
+ POINT pt = { dm.dmPosition.x, dm.dmPosition.y };
+ HMONITOR hm = MonitorFromPoint(pt, MONITOR_DEFAULTTONULL);
+
+ MONITOR_DETAILS md = {};
+ uint8_t num = 1;
+ do
+ {
+ // `Monitor #N`
+ md.MonitorNameW = std::wstring(ddMon.DeviceString) + L" #" + std::to_wstring(num++);
+ }
+ while(std::any_of(outputs.begin(), outputs.end(), [&](MONITOR_DETAILS& m) { return m.MonitorNameW == md.MonitorNameW; }));
+
+ md.CardNameW = ddAdapter.DeviceString;
+ md.DeviceNameW = ddAdapter.DeviceName;
+ md.ScreenWidth = dm.dmPelsWidth;
+ md.ScreenHeight = dm.dmPelsHeight;
+ md.hMonitor = hm;
+ md.RefreshRate = dm.dmDisplayFrequency;
+ md.Bpp = dm.dmBitsPerPel;
+ md.Interlaced = (dm.dmDisplayFlags & DM_INTERLACED) ? true : false;
+
+ MONITORINFO mi = {};
+ mi.cbSize = sizeof(mi);
+ if (GetMonitorInfoW(hm, &mi) && (mi.dwFlags & MONITORINFOF_PRIMARY))
+ md.IsPrimary = true;
+
+ outputs.push_back(md);
+ }
+ }
+}
+
+bool CWinSystemWin32::ChangeResolution(const RESOLUTION_INFO& res, bool forceChange /*= false*/)
+{
+ using KODI::PLATFORM::WINDOWS::ToW;
+ std::wstring outputW = ToW(res.strOutput);
+
+ DEVMODEW sDevMode = {};
+ sDevMode.dmSize = sizeof(sDevMode);
+
+ // If we can't read the current resolution or any detail of the resolution is different than res
+ if (!EnumDisplaySettingsW(outputW.c_str(), ENUM_CURRENT_SETTINGS, &sDevMode) ||
+ sDevMode.dmPelsWidth != res.iWidth || sDevMode.dmPelsHeight != res.iHeight ||
+ sDevMode.dmDisplayFrequency != static_cast<int>(res.fRefreshRate) ||
+ ((sDevMode.dmDisplayFlags & DM_INTERLACED) && !(res.dwFlags & D3DPRESENTFLAG_INTERLACED)) ||
+ (!(sDevMode.dmDisplayFlags & DM_INTERLACED) && (res.dwFlags & D3DPRESENTFLAG_INTERLACED))
+ || forceChange)
+ {
+ ZeroMemory(&sDevMode, sizeof(sDevMode));
+ sDevMode.dmSize = sizeof(sDevMode);
+ sDevMode.dmDriverExtra = 0;
+ sDevMode.dmPelsWidth = res.iWidth;
+ sDevMode.dmPelsHeight = res.iHeight;
+ sDevMode.dmDisplayFrequency = static_cast<int>(res.fRefreshRate);
+ sDevMode.dmDisplayFlags = (res.dwFlags & D3DPRESENTFLAG_INTERLACED) ? DM_INTERLACED : 0;
+ sDevMode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY | DM_DISPLAYFLAGS;
+
+ LONG rc;
+ bool bResChanged = false;
+
+ // Windows 8 refresh rate workaround for 24.0, 48.0 and 60.0 Hz
+ if ( CSysInfo::IsWindowsVersionAtLeast(CSysInfo::WindowsVersionWin8)
+ && (res.fRefreshRate == 24.0 || res.fRefreshRate == 48.0 || res.fRefreshRate == 60.0))
+ {
+ CLog::LogF(LOGDEBUG, "Using Windows 8+ workaround for refresh rate {} Hz",
+ static_cast<int>(res.fRefreshRate));
+
+ // Get current resolution stored in registry
+ DEVMODEW sDevModeRegistry = {};
+ sDevModeRegistry.dmSize = sizeof(sDevModeRegistry);
+ if (EnumDisplaySettingsW(outputW.c_str(), ENUM_REGISTRY_SETTINGS, &sDevModeRegistry))
+ {
+ // Set requested mode in registry without actually changing resolution
+ rc = ChangeDisplaySettingsExW(outputW.c_str(), &sDevMode, nullptr, CDS_UPDATEREGISTRY | CDS_NORESET, nullptr);
+ if (rc == DISP_CHANGE_SUCCESSFUL)
+ {
+ // Change resolution based on registry setting
+ rc = ChangeDisplaySettingsExW(outputW.c_str(), nullptr, nullptr, CDS_FULLSCREEN, nullptr);
+ if (rc == DISP_CHANGE_SUCCESSFUL)
+ bResChanged = true;
+ else
+ CLog::LogF(
+ LOGERROR,
+ "ChangeDisplaySettingsEx (W8+ change resolution) failed with {}, using fallback",
+ rc);
+
+ // Restore registry with original values
+ sDevModeRegistry.dmSize = sizeof(sDevModeRegistry);
+ sDevModeRegistry.dmDriverExtra = 0;
+ sDevModeRegistry.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY | DM_DISPLAYFLAGS;
+ rc = ChangeDisplaySettingsExW(outputW.c_str(), &sDevModeRegistry, nullptr, CDS_UPDATEREGISTRY | CDS_NORESET, nullptr);
+ if (rc != DISP_CHANGE_SUCCESSFUL)
+ CLog::LogF(LOGERROR, "ChangeDisplaySettingsEx (W8+ restore registry) failed with {}",
+ rc);
+ }
+ else
+ CLog::LogF(LOGERROR,
+ "ChangeDisplaySettingsEx (W8+ set registry) failed with {}, using fallback",
+ rc);
+ }
+ else
+ CLog::LogF(LOGERROR, "Unable to retrieve registry settings for Windows 8+ workaround, using fallback");
+ }
+
+ // Standard resolution change/fallback for Windows 8+ workaround
+ if (!bResChanged)
+ {
+ // CDS_FULLSCREEN is for temporary fullscreen mode and prevents icons and windows from moving
+ // to fit within the new dimensions of the desktop
+ rc = ChangeDisplaySettingsExW(outputW.c_str(), &sDevMode, nullptr, CDS_FULLSCREEN, nullptr);
+ if (rc == DISP_CHANGE_SUCCESSFUL)
+ bResChanged = true;
+ else
+ CLog::LogF(LOGERROR, "ChangeDisplaySettingsEx failed with {}", rc);
+ }
+
+ if (bResChanged)
+ ResolutionChanged();
+
+ return bResChanged;
+ }
+
+ // nothing to do, return success
+ return true;
+}
+
+void CWinSystemWin32::UpdateResolutions()
+{
+ using KODI::PLATFORM::WINDOWS::FromW;
+
+ m_displays.clear();
+
+ CWinSystemBase::UpdateResolutions();
+ GetConnectedDisplays(m_displays);
+
+ MONITOR_DETAILS* details = GetDisplayDetails(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_VIDEOSCREEN_MONITOR));
+ if (!details)
+ return;
+
+ float refreshRate;
+ int w = details->ScreenWidth;
+ int h = details->ScreenHeight;
+ if ((details->RefreshRate + 1) % 24 == 0 || (details->RefreshRate + 1) % 30 == 0)
+ refreshRate = static_cast<float>(details->RefreshRate + 1) / 1.001f;
+ else
+ refreshRate = static_cast<float>(details->RefreshRate);
+
+ std::string strOutput = FromW(details->DeviceNameW);
+ std::string monitorName = FromW(details->MonitorNameW);
+
+ uint32_t dwFlags = details->Interlaced ? D3DPRESENTFLAG_INTERLACED : 0;
+
+ RESOLUTION_INFO& info = CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP);
+ UpdateDesktopResolution(info, monitorName, w, h, refreshRate, dwFlags);
+ info.strOutput = strOutput;
+
+ CLog::Log(LOGINFO, "Primary mode: {}", info.strMode);
+
+ // erase previous stored modes
+ CDisplaySettings::GetInstance().ClearCustomResolutions();
+
+ for(int mode = 0;; mode++)
+ {
+ DEVMODEW devmode = {};
+ devmode.dmSize = sizeof(devmode);
+ if(EnumDisplaySettingsW(details->DeviceNameW.c_str(), mode, &devmode) == 0)
+ break;
+ if(devmode.dmBitsPerPel != 32)
+ continue;
+
+ float refresh;
+ if ((devmode.dmDisplayFrequency + 1) % 24 == 0 || (devmode.dmDisplayFrequency + 1) % 30 == 0)
+ refresh = static_cast<float>(devmode.dmDisplayFrequency + 1) / 1.001f;
+ else
+ refresh = static_cast<float>(devmode.dmDisplayFrequency);
+ dwFlags = (devmode.dmDisplayFlags & DM_INTERLACED) ? D3DPRESENTFLAG_INTERLACED : 0;
+
+ RESOLUTION_INFO res;
+ res.iWidth = devmode.dmPelsWidth;
+ res.iHeight = devmode.dmPelsHeight;
+ res.bFullScreen = true;
+ res.dwFlags = dwFlags;
+ res.fRefreshRate = refresh;
+ res.fPixelRatio = 1.0f;
+ res.iScreenWidth = res.iWidth;
+ res.iScreenHeight = res.iHeight;
+ res.iSubtitles = res.iHeight;
+ res.strMode = StringUtils::Format("{}: {}x{} @ {:.2f}Hz", monitorName, res.iWidth, res.iHeight,
+ res.fRefreshRate);
+ GetGfxContext().ResetOverscan(res);
+ res.strOutput = strOutput;
+
+ if (AddResolution(res))
+ CLog::Log(LOGINFO, "Additional mode: {}", res.strMode);
+ }
+
+ CDisplaySettings::GetInstance().ApplyCalibrations();
+}
+
+bool CWinSystemWin32::AddResolution(const RESOLUTION_INFO &res)
+{
+ for (unsigned int i = RES_CUSTOM; i < CDisplaySettings::GetInstance().ResolutionInfoSize(); i++)
+ {
+ RESOLUTION_INFO& info = CDisplaySettings::GetInstance().GetResolutionInfo(i);
+ if (info.iWidth == res.iWidth
+ && info.iHeight == res.iHeight
+ && info.iScreenWidth == res.iScreenWidth
+ && info.iScreenHeight == res.iScreenHeight
+ && info.fRefreshRate == res.fRefreshRate
+ && info.dwFlags == res.dwFlags)
+ return false; // already have this resolution
+ }
+
+ CDisplaySettings::GetInstance().AddResolutionInfo(res);
+ return true;
+}
+
+void CWinSystemWin32::ShowOSMouse(bool show)
+{
+ static int counter = 0;
+ if ((counter < 0 && show) || (counter >= 0 && !show))
+ counter = ShowCursor(show);
+}
+
+bool CWinSystemWin32::Minimize()
+{
+ ShowWindow(m_hWnd, SW_MINIMIZE);
+ return true;
+}
+bool CWinSystemWin32::Restore()
+{
+ ShowWindow(m_hWnd, SW_RESTORE);
+ return true;
+}
+bool CWinSystemWin32::Hide()
+{
+ ShowWindow(m_hWnd, SW_HIDE);
+ return true;
+}
+bool CWinSystemWin32::Show(bool raise)
+{
+ HWND windowAfter = HWND_BOTTOM;
+ if (raise)
+ {
+ if (m_bFullScreen)
+ windowAfter = HWND_TOP;
+ else
+ windowAfter = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_alwaysOnTop ? HWND_TOPMOST : HWND_NOTOPMOST;
+ }
+
+ SetWindowPos(m_hWnd, windowAfter, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_ASYNCWINDOWPOS);
+ UpdateWindow(m_hWnd);
+
+ if (raise)
+ {
+ SetForegroundWindow(m_hWnd);
+ SetFocus(m_hWnd);
+ }
+ return true;
+}
+
+void CWinSystemWin32::Register(IDispResource *resource)
+{
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ m_resources.push_back(resource);
+}
+
+void CWinSystemWin32::Unregister(IDispResource* resource)
+{
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ std::vector<IDispResource*>::iterator i = find(m_resources.begin(), m_resources.end(), resource);
+ if (i != m_resources.end())
+ m_resources.erase(i);
+}
+
+void CWinSystemWin32::OnDisplayLost()
+{
+ CLog::LogF(LOGDEBUG, "notify display lost event");
+
+ {
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ for (std::vector<IDispResource *>::iterator i = m_resources.begin(); i != m_resources.end(); ++i)
+ (*i)->OnLostDisplay();
+ }
+}
+
+void CWinSystemWin32::OnDisplayReset()
+{
+ if (!m_delayDispReset)
+ {
+ CLog::LogF(LOGDEBUG, "notify display reset event");
+ std::unique_lock<CCriticalSection> lock(m_resourceSection);
+ for (std::vector<IDispResource *>::iterator i = m_resources.begin(); i != m_resources.end(); ++i)
+ (*i)->OnResetDisplay();
+ }
+}
+
+void CWinSystemWin32::OnDisplayBack()
+{
+ auto delay =
+ std::chrono::milliseconds(CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
+ "videoscreen.delayrefreshchange") *
+ 100);
+ if (delay > 0ms)
+ {
+ m_delayDispReset = true;
+ m_dispResetTimer.Set(delay);
+ }
+ OnDisplayReset();
+}
+
+void CWinSystemWin32::ResolutionChanged()
+{
+ OnDisplayLost();
+ OnDisplayBack();
+}
+
+void CWinSystemWin32::SetForegroundWindowInternal(HWND hWnd)
+{
+ if (!IsWindow(hWnd)) return;
+
+ // if the window isn't focused, bring it to front or SetFullScreen will fail
+ BYTE keyState[256] = {};
+ // to unlock SetForegroundWindow we need to imitate Alt pressing
+ if (GetKeyboardState(reinterpret_cast<LPBYTE>(&keyState)) && !(keyState[VK_MENU] & 0x80))
+ keybd_event(VK_MENU, 0, KEYEVENTF_EXTENDEDKEY | 0, 0);
+
+ BOOL res = SetForegroundWindow(hWnd);
+
+ if (GetKeyboardState(reinterpret_cast<LPBYTE>(&keyState)) && !(keyState[VK_MENU] & 0x80))
+ keybd_event(VK_MENU, 0, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0);
+
+ if (!res)
+ {
+ //relation time of SetForegroundWindow lock
+ DWORD lockTimeOut = 0;
+ HWND hCurrWnd = GetForegroundWindow();
+ DWORD dwThisTID = GetCurrentThreadId(),
+ dwCurrTID = GetWindowThreadProcessId(hCurrWnd, nullptr);
+
+ // we need to bypass some limitations from Microsoft
+ if (dwThisTID != dwCurrTID)
+ {
+ AttachThreadInput(dwThisTID, dwCurrTID, TRUE);
+ SystemParametersInfo(SPI_GETFOREGROUNDLOCKTIMEOUT, 0, &lockTimeOut, 0);
+ SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, nullptr, SPIF_SENDWININICHANGE | SPIF_UPDATEINIFILE);
+ AllowSetForegroundWindow(ASFW_ANY);
+ }
+
+ SetForegroundWindow(hWnd);
+
+ if (dwThisTID != dwCurrTID)
+ {
+ SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, &lockTimeOut, SPIF_SENDWININICHANGE | SPIF_UPDATEINIFILE);
+ AttachThreadInput(dwThisTID, dwCurrTID, FALSE);
+ }
+ }
+}
+
+std::unique_ptr<CVideoSync> CWinSystemWin32::GetVideoSync(void *clock)
+{
+ std::unique_ptr<CVideoSync> pVSync(new CVideoSyncD3D(clock));
+ return pVSync;
+}
+
+std::string CWinSystemWin32::GetClipboardText()
+{
+ std::wstring unicode_text;
+ std::string utf8_text;
+
+ if (OpenClipboard(nullptr))
+ {
+ HGLOBAL hglb = GetClipboardData(CF_UNICODETEXT);
+ if (hglb != nullptr)
+ {
+ LPWSTR lpwstr = static_cast<LPWSTR>(GlobalLock(hglb));
+ if (lpwstr != nullptr)
+ {
+ unicode_text = lpwstr;
+ GlobalUnlock(hglb);
+ }
+ }
+ CloseClipboard();
+ }
+
+ return KODI::PLATFORM::WINDOWS::FromW(unicode_text);
+}
+
+bool CWinSystemWin32::UseLimitedColor()
+{
+ return CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOSCREEN_LIMITEDRANGE);
+}
+
+void CWinSystemWin32::NotifyAppFocusChange(bool bGaining)
+{
+ if (m_state == WINDOW_STATE_FULLSCREEN && !m_IsAlteringWindow)
+ {
+ m_IsAlteringWindow = true;
+ ReleaseBackBuffer();
+
+ if (bGaining)
+ SetWindowPos(m_hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOREDRAW);
+
+ RESOLUTION_INFO res = {};
+ const RESOLUTION resolution = CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution();
+ if (bGaining && resolution > RES_INVALID)
+ res = CDisplaySettings::GetInstance().GetResolutionInfo(resolution);
+
+ SetDeviceFullScreen(bGaining, res);
+
+ if (!bGaining)
+ SetWindowPos(m_hWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOREDRAW);
+
+ CreateBackBuffer();
+ m_IsAlteringWindow = false;
+ }
+ m_inFocus = bGaining;
+}
+
+void CWinSystemWin32::UpdateStates(bool fullScreen)
+{
+ m_fullscreenState = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOSCREEN_FAKEFULLSCREEN)
+ ? WINDOW_FULLSCREEN_STATE_FULLSCREEN_WINDOW
+ : WINDOW_FULLSCREEN_STATE_FULLSCREEN;
+ m_windowState = WINDOW_WINDOW_STATE_WINDOWED; // currently only this allowed
+
+ // set the appropriate window style
+ if (fullScreen)
+ {
+ m_windowStyle = FULLSCREEN_WINDOW_STYLE;
+ m_windowExStyle = FULLSCREEN_WINDOW_EX_STYLE;
+ }
+ else
+ {
+ m_windowStyle = WINDOWED_STYLE;
+ m_windowExStyle = WINDOWED_EX_STYLE;
+ }
+}
+
+WINDOW_STATE CWinSystemWin32::GetState(bool fullScreen) const
+{
+ return static_cast<WINDOW_STATE>(fullScreen ? m_fullscreenState : m_windowState);
+}
+
+bool CWinSystemWin32::MessagePump()
+{
+ return m_winEvents->MessagePump();
+}
+
+void CWinSystemWin32::SetTogglingHDR(bool toggling)
+{
+ if (toggling)
+ SetTimer(m_hWnd, ID_TIMER_HDR, 6000U, nullptr);
+
+ m_IsTogglingHDR = toggling;
+}
+
+RECT CWinSystemWin32::GetVirtualScreenRect()
+{
+ RECT rect = {};
+ rect.left = GetSystemMetrics(SM_XVIRTUALSCREEN);
+ rect.right = GetSystemMetrics(SM_CXVIRTUALSCREEN) + rect.left;
+ rect.top = GetSystemMetrics(SM_YVIRTUALSCREEN);
+ rect.bottom = GetSystemMetrics(SM_CYVIRTUALSCREEN) + rect.top;
+
+ return rect;
+}
+
+int CWinSystemWin32::GetGuiSdrPeakLuminance() const
+{
+ const auto settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+
+ return settings->GetInt(CSettings::SETTING_VIDEOSCREEN_GUISDRPEAKLUMINANCE);
+}
+
+RECT CWinSystemWin32::GetScreenWorkArea(HMONITOR handle) const
+{
+ MONITORINFO monitorInfo{};
+ monitorInfo.cbSize = sizeof(MONITORINFO);
+ if (!GetMonitorInfoW(handle, &monitorInfo))
+ {
+ CLog::LogF(LOGERROR, "GetMonitorInfoW failed with {}", GetLastError());
+ return RECT();
+ }
+ return monitorInfo.rcWork;
+}
+
+RECT CWinSystemWin32::GetNcAreaOffsets(DWORD dwStyle, BOOL bMenu, DWORD dwExStyle) const
+{
+ RECT rcNcArea{};
+ SetRectEmpty(&rcNcArea);
+
+ if (!AdjustWindowRectEx(&rcNcArea, dwStyle, false, dwExStyle))
+ {
+ CLog::LogF(LOGERROR, "AdjustWindowRectEx failed with {}", GetLastError());
+ return RECT();
+ }
+ return rcNcArea;
+}
diff --git a/xbmc/windowing/windows/WinSystemWin32.h b/xbmc/windowing/windows/WinSystemWin32.h
new file mode 100644
index 0000000..0573295
--- /dev/null
+++ b/xbmc/windowing/windows/WinSystemWin32.h
@@ -0,0 +1,215 @@
+/*
+ * 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/CriticalSection.h"
+#include "threads/SystemClock.h"
+#include "windowing/WinSystem.h"
+
+#include <shellapi.h>
+#include <vector>
+
+static const DWORD WINDOWED_STYLE = WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN;
+static const DWORD WINDOWED_EX_STYLE = NULL;
+static const DWORD FULLSCREEN_WINDOW_STYLE = WS_POPUP | WS_SYSMENU | WS_CLIPCHILDREN;
+static const DWORD FULLSCREEN_WINDOW_EX_STYLE = WS_EX_APPWINDOW;
+static const UINT ID_TIMER_HDR = 34U;
+
+/* Controls the way the window appears and behaves. */
+enum WINDOW_STATE
+{
+ WINDOW_STATE_FULLSCREEN = 1, // Exclusive fullscreen
+ WINDOW_STATE_FULLSCREEN_WINDOW, // Non-exclusive fullscreen window
+ WINDOW_STATE_WINDOWED, //Movable window with border
+ WINDOW_STATE_BORDERLESS //Non-movable window with no border
+};
+
+static const char* window_state_names[] =
+{
+ "unknown",
+ "true fullscreen",
+ "windowed fullscreen",
+ "windowed",
+ "borderless"
+};
+
+/* WINDOW_STATE restricted to fullscreen modes. */
+enum WINDOW_FULLSCREEN_STATE
+{
+ WINDOW_FULLSCREEN_STATE_FULLSCREEN = WINDOW_STATE_FULLSCREEN,
+ WINDOW_FULLSCREEN_STATE_FULLSCREEN_WINDOW = WINDOW_STATE_FULLSCREEN_WINDOW
+};
+
+/* WINDOW_STATE restricted to windowed modes. */
+enum WINDOW_WINDOW_STATE
+{
+ WINDOW_WINDOW_STATE_WINDOWED = WINDOW_STATE_WINDOWED,
+ WINDOW_WINDOW_STATE_BORDERLESS = WINDOW_STATE_BORDERLESS
+};
+
+struct MONITOR_DETAILS
+{
+ int ScreenWidth;
+ int ScreenHeight;
+ int RefreshRate;
+ int Bpp;
+ int DisplayId;
+ bool Interlaced;
+ bool IsPrimary;
+
+ HMONITOR hMonitor;
+ std::wstring MonitorNameW;
+ std::wstring CardNameW;
+ std::wstring DeviceNameW;
+};
+
+class CIRServerSuite;
+
+class CWinSystemWin32 : public CWinSystemBase
+{
+public:
+ CWinSystemWin32();
+ virtual ~CWinSystemWin32();
+
+ // CWinSystemBase overrides
+ bool InitWindowSystem() override;
+ bool DestroyWindowSystem() override;
+ bool ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop) override;
+ void FinishWindowResize(int newWidth, int newHeight) override;
+ void UpdateResolutions() override;
+ bool CenterWindow() override;
+ virtual void NotifyAppFocusChange(bool bGaining) override;
+ void ShowOSMouse(bool show) override;
+ bool HasInertialGestures() override { return true; }//if win32 has touchscreen - it uses the win32 gesture api for inertial scrolling
+ bool Minimize() override;
+ bool Restore() override;
+ bool Hide() override;
+ bool Show(bool raise = true) override;
+ std::string GetClipboardText() override;
+ bool UseLimitedColor() override;
+
+ // videosync
+ std::unique_ptr<CVideoSync> GetVideoSync(void *clock) override;
+
+ bool SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) override;
+
+ std::vector<std::string> GetConnectedOutputs() override;
+
+ // CWinSystemWin32
+ HWND GetHwnd() const { return m_hWnd; }
+ bool IsAlteringWindow() const { return m_IsAlteringWindow; }
+ void SetAlteringWindow(bool altering) { m_IsAlteringWindow = altering; }
+ bool IsTogglingHDR() const { return m_IsTogglingHDR; }
+ void SetTogglingHDR(bool toggling);
+ virtual bool DPIChanged(WORD dpi, RECT windowRect) const;
+ bool IsMinimized() const { return m_bMinimized; }
+ void SetMinimized(bool minimized);
+ int GetGuiSdrPeakLuminance() const;
+
+ // touchscreen support
+ typedef BOOL(WINAPI *pGetGestureInfo)(HGESTUREINFO, PGESTUREINFO);
+ typedef BOOL(WINAPI *pSetGestureConfig)(HWND, DWORD, UINT, PGESTURECONFIG, UINT);
+ typedef BOOL(WINAPI *pCloseGestureInfoHandle)(HGESTUREINFO);
+ typedef BOOL(WINAPI *pEnableNonClientDpiScaling)(HWND);
+ pGetGestureInfo PtrGetGestureInfo;
+ pSetGestureConfig PtrSetGestureConfig;
+ pCloseGestureInfoHandle PtrCloseGestureInfoHandle;
+ pEnableNonClientDpiScaling PtrEnableNonClientDpiScaling;
+
+ void SetSizeMoveMode(bool mode) { m_bSizeMoveEnabled = mode; }
+ bool IsInSizeMoveMode() const { return m_bSizeMoveEnabled; }
+
+ // winevents override
+ bool MessagePump() override;
+
+protected:
+ bool CreateNewWindow(const std::string& name, bool fullScreen, RESOLUTION_INFO& res) override = 0;
+ virtual void UpdateStates(bool fullScreen);
+ WINDOW_STATE GetState(bool fullScreen) const;
+ virtual void SetDeviceFullScreen(bool fullScreen, RESOLUTION_INFO& res) = 0;
+ virtual void ReleaseBackBuffer() = 0;
+ virtual void CreateBackBuffer() = 0;
+ virtual void ResizeDeviceBuffers() = 0;
+ virtual bool IsStereoEnabled() = 0;
+ virtual void OnScreenChange(HMONITOR monitor) = 0;
+ virtual void AdjustWindow(bool forceResize = false);
+ void CenterCursor() const;
+
+ virtual void Register(IDispResource *resource);
+ virtual void Unregister(IDispResource *resource);
+
+ virtual bool ChangeResolution(const RESOLUTION_INFO& res, bool forceChange = false);
+ virtual bool CreateBlankWindows();
+ virtual bool BlankNonActiveMonitors(bool bBlank);
+ MONITOR_DETAILS* GetDisplayDetails(const std::string& name);
+ MONITOR_DETAILS* GetDisplayDetails(HMONITOR handle);
+ void RestoreDesktopResolution(MONITOR_DETAILS* details);
+ RECT ScreenRect(HMONITOR handle);
+ void GetConnectedDisplays(std::vector<MONITOR_DETAILS>& outputs);
+
+
+ /*!
+ \brief Adds a resolution to the list of resolutions if we don't already have it
+ \param res resolution to add.
+ */
+ static bool AddResolution(const RESOLUTION_INFO &res);
+
+ void OnDisplayLost();
+ void OnDisplayReset();
+ void OnDisplayBack();
+ void ResolutionChanged();
+ static void SetForegroundWindowInternal(HWND hWnd);
+ static RECT GetVirtualScreenRect();
+ /*!
+ * Retrieve the work area of the screen (exclude task bar and other occlusions)
+ */
+ RECT GetScreenWorkArea(HMONITOR handle) const;
+ /*!
+ * Retrieve size of the title bar and borders
+ * Add to coordinates to convert client coordinates to window coordinates
+ * Substract from coordinates to convert from window coordinates to client coordinates
+ */
+ RECT GetNcAreaOffsets(DWORD dwStyle, BOOL bMenu, DWORD dwExStyle) const;
+
+ HWND m_hWnd;
+ HMONITOR m_hMonitor;
+
+ std::vector<HWND> m_hBlankWindows;
+ HINSTANCE m_hInstance;
+ HICON m_hIcon;
+ bool m_ValidWindowedPosition;
+ bool m_IsAlteringWindow;
+ bool m_IsTogglingHDR{false};
+
+ CCriticalSection m_resourceSection;
+ std::vector<IDispResource*> m_resources;
+ bool m_delayDispReset;
+ XbmcThreads::EndTime<> m_dispResetTimer;
+
+ WINDOW_STATE m_state; // the state of the window
+ WINDOW_FULLSCREEN_STATE m_fullscreenState; // the state of the window when in fullscreen
+ WINDOW_WINDOW_STATE m_windowState; // the state of the window when in windowed
+ DWORD m_windowStyle; // the style of the window
+ DWORD m_windowExStyle; // the ex style of the window
+ bool m_inFocus;
+ bool m_bMinimized;
+ bool m_bSizeMoveEnabled = false;
+ bool m_bFirstResChange = true;
+ std::unique_ptr<CIRServerSuite> m_irss;
+ std::vector<MONITOR_DETAILS> m_displays;
+
+ NOTIFYICONDATA m_trayIcon = {};
+
+ static const char* SETTING_WINDOW_TOP;
+ static const char* SETTING_WINDOW_LEFT;
+};
+
+extern HWND g_hWnd;
+
diff --git a/xbmc/windowing/windows/WinSystemWin32DX.cpp b/xbmc/windowing/windows/WinSystemWin32DX.cpp
new file mode 100644
index 0000000..6d568fc
--- /dev/null
+++ b/xbmc/windowing/windows/WinSystemWin32DX.cpp
@@ -0,0 +1,447 @@
+/*
+ * 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 "WinSystemWin32DX.h"
+
+#include "commons/ilog.h"
+#include "rendering/dx/RenderContext.h"
+#include "settings/DisplaySettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/SystemInfo.h"
+#include "utils/XTimeUtils.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+#include "windowing/WindowSystemFactory.h"
+
+#include "platform/win32/CharsetConverter.h"
+#include "platform/win32/WIN32Util.h"
+
+#ifndef _M_X64
+#include "utils/SystemInfo.h"
+#endif
+#if _DEBUG
+#pragma comment(lib, "detoursd.lib")
+#else
+#pragma comment(lib, "detours.lib")
+#endif
+#pragma comment(lib, "dxgi.lib")
+#include <windows.h>
+#include <winnt.h>
+#include <winternl.h>
+#pragma warning(disable: 4091)
+#include <d3d10umddi.h>
+#pragma warning(default: 4091)
+#include <detours.h>
+
+using KODI::PLATFORM::WINDOWS::FromW;
+
+using namespace std::chrono_literals;
+
+// User Mode Driver hooks definitions
+void APIENTRY HookCreateResource(D3D10DDI_HDEVICE hDevice, const D3D10DDIARG_CREATERESOURCE* pResource, D3D10DDI_HRESOURCE hResource, D3D10DDI_HRTRESOURCE hRtResource);
+HRESULT APIENTRY HookCreateDevice(D3D10DDI_HADAPTER hAdapter, D3D10DDIARG_CREATEDEVICE* pCreateData);
+HRESULT APIENTRY HookOpenAdapter10_2(D3D10DDIARG_OPENADAPTER *pOpenData);
+static PFND3D10DDI_OPENADAPTER s_fnOpenAdapter10_2{ nullptr };
+static PFND3D10DDI_CREATEDEVICE s_fnCreateDeviceOrig{ nullptr };
+static PFND3D10DDI_CREATERESOURCE s_fnCreateResourceOrig{ nullptr };
+
+void CWinSystemWin32DX::Register()
+{
+ KODI::WINDOWING::CWindowSystemFactory::RegisterWindowSystem(CreateWinSystem);
+}
+
+std::unique_ptr<CWinSystemBase> CWinSystemWin32DX::CreateWinSystem()
+{
+ return std::make_unique<CWinSystemWin32DX>();
+}
+
+CWinSystemWin32DX::CWinSystemWin32DX() : CRenderSystemDX()
+ , m_hDriverModule(nullptr)
+{
+}
+
+CWinSystemWin32DX::~CWinSystemWin32DX()
+{
+}
+
+void CWinSystemWin32DX::PresentRenderImpl(bool rendered)
+{
+ if (rendered)
+ m_deviceResources->Present();
+
+ if (m_delayDispReset && m_dispResetTimer.IsTimePast())
+ {
+ m_delayDispReset = false;
+ OnDisplayReset();
+ }
+
+ if (!rendered)
+ KODI::TIME::Sleep(40ms);
+}
+
+bool CWinSystemWin32DX::CreateNewWindow(const std::string& name, bool fullScreen, RESOLUTION_INFO& res)
+{
+ const MONITOR_DETAILS* monitor = GetDisplayDetails(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_VIDEOSCREEN_MONITOR));
+ if (!monitor)
+ return false;
+
+ m_hMonitor = monitor->hMonitor;
+ m_deviceResources = DX::DeviceResources::Get();
+ // setting monitor before creating window for proper hooking into a driver
+ m_deviceResources->SetMonitor(m_hMonitor);
+
+ return CWinSystemWin32::CreateNewWindow(name, fullScreen, res) && m_deviceResources->HasValidDevice();
+}
+
+void CWinSystemWin32DX::SetWindow(HWND hWnd) const
+{
+ m_deviceResources->SetWindow(hWnd);
+}
+
+bool CWinSystemWin32DX::DestroyRenderSystem()
+{
+ CRenderSystemDX::DestroyRenderSystem();
+
+ m_deviceResources->Release();
+ m_deviceResources.reset();
+ return true;
+}
+
+void CWinSystemWin32DX::SetDeviceFullScreen(bool fullScreen, RESOLUTION_INFO& res)
+{
+ if (m_deviceResources->SetFullScreen(fullScreen, res))
+ {
+ ResolutionChanged();
+ }
+}
+
+bool CWinSystemWin32DX::ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop)
+{
+ CWinSystemWin32::ResizeWindow(newWidth, newHeight, newLeft, newTop);
+ CRenderSystemDX::OnResize();
+
+ return true;
+}
+
+void CWinSystemWin32DX::OnMove(int x, int y)
+{
+ // do not handle moving at window creation because MonitorFromWindow
+ // returns default system monitor in case of m_hWnd is null
+ if (!m_hWnd)
+ return;
+
+ HMONITOR newMonitor = MonitorFromWindow(m_hWnd, MONITOR_DEFAULTTONEAREST);
+ if (newMonitor != m_hMonitor)
+ {
+ MONITOR_DETAILS* details = GetDisplayDetails(newMonitor);
+
+ if (!details)
+ return;
+
+ CDisplaySettings::GetInstance().SetMonitor(KODI::PLATFORM::WINDOWS::FromW(details->MonitorNameW));
+ m_deviceResources->SetMonitor(newMonitor);
+ m_hMonitor = newMonitor;
+ }
+
+ // Save window position if not fullscreen
+ if (!IsFullScreen() && (m_nLeft != x || m_nTop != y))
+ {
+ m_nLeft = x;
+ m_nTop = y;
+ const auto settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ settings->SetInt(SETTING_WINDOW_LEFT, x);
+ settings->SetInt(SETTING_WINDOW_TOP, y);
+ settings->Save();
+ }
+}
+
+bool CWinSystemWin32DX::DPIChanged(WORD dpi, RECT windowRect) const
+{
+ // on Win10 FCU the OS keeps window size exactly the same size as it was
+ if (CSysInfo::IsWindowsVersionAtLeast(CSysInfo::WindowsVersionWin10_1709))
+ return true;
+
+ m_deviceResources->SetDpi(dpi);
+ if (!IsAlteringWindow())
+ return __super::DPIChanged(dpi, windowRect);
+
+ return true;
+}
+
+void CWinSystemWin32DX::ReleaseBackBuffer()
+{
+ m_deviceResources->ReleaseBackBuffer();
+}
+
+void CWinSystemWin32DX::CreateBackBuffer()
+{
+ m_deviceResources->CreateBackBuffer();
+}
+
+void CWinSystemWin32DX::ResizeDeviceBuffers()
+{
+ m_deviceResources->ResizeBuffers();
+}
+
+bool CWinSystemWin32DX::IsStereoEnabled()
+{
+ return m_deviceResources->IsStereoEnabled();
+}
+
+void CWinSystemWin32DX::OnScreenChange(HMONITOR monitor)
+{
+ m_deviceResources->SetMonitor(monitor);
+}
+
+bool CWinSystemWin32DX::ChangeResolution(const RESOLUTION_INFO &res, bool forceChange)
+{
+ bool changed = CWinSystemWin32::ChangeResolution(res, forceChange);
+ // this is a try to fix FCU issue after changing resolution
+ if (m_deviceResources && changed)
+ m_deviceResources->ResizeBuffers();
+ return changed;
+}
+
+void CWinSystemWin32DX::OnResize(int width, int height)
+{
+ if (!m_IsAlteringWindow)
+ ReleaseBackBuffer();
+
+ m_deviceResources->SetLogicalSize(static_cast<float>(width), static_cast<float>(height));
+
+ if (!m_IsAlteringWindow)
+ CreateBackBuffer();
+}
+
+bool CWinSystemWin32DX::SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays)
+{
+ bool const result = CWinSystemWin32::SetFullScreen(fullScreen, res, blankOtherDisplays);
+ CRenderSystemDX::OnResize();
+ return result;
+}
+
+void CWinSystemWin32DX::UninitHooks()
+{
+ // uninstall
+ if (!s_fnOpenAdapter10_2)
+ return;
+
+ DetourTransactionBegin();
+ DetourUpdateThread(GetCurrentThread());
+ DetourDetach(reinterpret_cast<void**>(&s_fnOpenAdapter10_2), HookOpenAdapter10_2);
+ DetourTransactionCommit();
+ if (m_hDriverModule)
+ {
+ FreeLibrary(m_hDriverModule);
+ m_hDriverModule = nullptr;
+ }
+}
+
+void CWinSystemWin32DX::InitHooks(IDXGIOutput* pOutput)
+{
+ DXGI_OUTPUT_DESC outputDesc;
+ if (!pOutput || FAILED(pOutput->GetDesc(&outputDesc)))
+ return;
+
+ DISPLAY_DEVICEW displayDevice;
+ displayDevice.cb = sizeof(DISPLAY_DEVICEW);
+ DWORD adapter = 0;
+ bool deviceFound = false;
+
+ // delete exiting hooks.
+ if (s_fnOpenAdapter10_2)
+ UninitHooks();
+
+ // enum devices to find matched
+ while (EnumDisplayDevicesW(nullptr, adapter, &displayDevice, 0))
+ {
+ if (wcscmp(displayDevice.DeviceName, outputDesc.DeviceName) == 0)
+ {
+ deviceFound = true;
+ break;
+ }
+ adapter++;
+ }
+ if (!deviceFound)
+ return;
+
+ CLog::LogF(LOGDEBUG, "Hooking into UserModeDriver on device {}. ",
+ FromW(displayDevice.DeviceKey));
+ const wchar_t* keyName =
+#ifndef _M_X64
+ // on x64 system and x32 build use UserModeDriverNameWow key
+ CSysInfo::GetKernelBitness() == 64 ? keyName = L"UserModeDriverNameWow" :
+#endif // !_WIN64
+ L"UserModeDriverName";
+
+ DWORD dwType = REG_MULTI_SZ;
+ HKEY hKey = nullptr;
+ wchar_t value[1024];
+ DWORD valueLength = sizeof(value);
+ LSTATUS lstat;
+
+ // to void \Registry\Machine at the beginning, we use shifted pointer at 18
+ if (ERROR_SUCCESS == (lstat = RegOpenKeyExW(HKEY_LOCAL_MACHINE, displayDevice.DeviceKey + 18, 0, KEY_READ, &hKey))
+ && ERROR_SUCCESS == (lstat = RegQueryValueExW(hKey, keyName, nullptr, &dwType, (LPBYTE)&value, &valueLength)))
+ {
+ // 1. registry value has a list of drivers for each API with the following format: dx9\0dx10\0dx11\0dx12\0\0
+ // 2. we split the value by \0
+ std::vector<std::wstring> drivers;
+ const wchar_t* pValue = value;
+ while (*pValue)
+ {
+ drivers.push_back(std::wstring(pValue));
+ pValue += drivers.back().size() + 1;
+ }
+ // no entries in the registry
+ if (drivers.empty())
+ return;
+ // 3. we take only first three values (dx12 driver isn't needed if it exists ofc)
+ if (drivers.size() > 3)
+ drivers = std::vector<std::wstring>(drivers.begin(), drivers.begin() + 3);
+ // 4. and then iterate with reverse order to start iterate with the best candidate for d3d11 driver
+ for (auto it = drivers.rbegin(); it != drivers.rend(); ++it)
+ {
+ m_hDriverModule = LoadLibraryW(it->c_str());
+ if (m_hDriverModule != nullptr)
+ {
+ s_fnOpenAdapter10_2 = reinterpret_cast<PFND3D10DDI_OPENADAPTER>(GetProcAddress(m_hDriverModule, "OpenAdapter10_2"));
+ if (s_fnOpenAdapter10_2 != nullptr)
+ {
+ DetourTransactionBegin();
+ DetourUpdateThread(GetCurrentThread());
+ DetourAttach(reinterpret_cast<void**>(&s_fnOpenAdapter10_2), HookOpenAdapter10_2);
+ if (NO_ERROR == DetourTransactionCommit())
+ // install and activate hook into a driver
+ {
+ CLog::LogF(LOGDEBUG, "D3D11 hook installed and activated.");
+ break;
+ }
+ else
+ {
+ CLog::Log(LOGDEBUG, __FUNCTION__": Unable to install and activate D3D11 hook.");
+ s_fnOpenAdapter10_2 = nullptr;
+ FreeLibrary(m_hDriverModule);
+ m_hDriverModule = nullptr;
+ }
+ }
+ }
+ }
+ }
+
+ if (lstat != ERROR_SUCCESS)
+ CLog::LogF(LOGDEBUG, "error open registry key with error {}.", lstat);
+
+ if (hKey != nullptr)
+ RegCloseKey(hKey);
+}
+
+void CWinSystemWin32DX::FixRefreshRateIfNecessary(const D3D10DDIARG_CREATERESOURCE* pResource) const
+{
+ if (pResource && pResource->pPrimaryDesc)
+ {
+ float refreshRate = RATIONAL_TO_FLOAT(pResource->pPrimaryDesc->ModeDesc.RefreshRate);
+ if (refreshRate > 10.0f && refreshRate < 300.0f)
+ {
+ // interlaced
+ if (pResource->pPrimaryDesc->ModeDesc.ScanlineOrdering > DXGI_DDI_MODE_SCANLINE_ORDER_PROGRESSIVE)
+ refreshRate /= 2;
+
+ uint32_t refreshNum, refreshDen;
+ DX::GetRefreshRatio(static_cast<uint32_t>(floor(m_fRefreshRate)), &refreshNum, &refreshDen);
+ float diff = fabs(refreshRate - static_cast<float>(refreshNum) / static_cast<float>(refreshDen)) / refreshRate;
+ CLog::LogF(LOGDEBUG,
+ "refreshRate: {:0.4f}, desired: {:0.4f}, deviation: {:.5f}, fixRequired: {}, {}",
+ refreshRate, m_fRefreshRate, diff, (diff > 0.0005 && diff < 0.1) ? "yes" : "no",
+ pResource->pPrimaryDesc->Flags);
+ if (diff > 0.0005 && diff < 0.1)
+ {
+ pResource->pPrimaryDesc->ModeDesc.RefreshRate.Numerator = refreshNum;
+ pResource->pPrimaryDesc->ModeDesc.RefreshRate.Denominator = refreshDen;
+ if (pResource->pPrimaryDesc->ModeDesc.ScanlineOrdering > DXGI_DDI_MODE_SCANLINE_ORDER_PROGRESSIVE)
+ pResource->pPrimaryDesc->ModeDesc.RefreshRate.Numerator *= 2;
+ CLog::LogF(LOGDEBUG, "refreshRate fix applied -> {:0.3f}",
+ RATIONAL_TO_FLOAT(pResource->pPrimaryDesc->ModeDesc.RefreshRate));
+ }
+ }
+ }
+}
+
+void APIENTRY HookCreateResource(D3D10DDI_HDEVICE hDevice, const D3D10DDIARG_CREATERESOURCE* pResource, D3D10DDI_HRESOURCE hResource, D3D10DDI_HRTRESOURCE hRtResource)
+{
+ if (pResource && pResource->pPrimaryDesc)
+ {
+ DX::Windowing()->FixRefreshRateIfNecessary(pResource);
+ }
+ s_fnCreateResourceOrig(hDevice, pResource, hResource, hRtResource);
+}
+
+HRESULT APIENTRY HookCreateDevice(D3D10DDI_HADAPTER hAdapter, D3D10DDIARG_CREATEDEVICE* pCreateData)
+{
+ HRESULT hr = s_fnCreateDeviceOrig(hAdapter, pCreateData);
+ if (pCreateData->pDeviceFuncs->pfnCreateResource)
+ {
+ CLog::LogF(LOGDEBUG, "hook into pCreateData->pDeviceFuncs->pfnCreateResource");
+ s_fnCreateResourceOrig = pCreateData->pDeviceFuncs->pfnCreateResource;
+ pCreateData->pDeviceFuncs->pfnCreateResource = HookCreateResource;
+ }
+ return hr;
+}
+
+HRESULT APIENTRY HookOpenAdapter10_2(D3D10DDIARG_OPENADAPTER *pOpenData)
+{
+ HRESULT hr = s_fnOpenAdapter10_2(pOpenData);
+ if (pOpenData->pAdapterFuncs->pfnCreateDevice)
+ {
+ CLog::LogF(LOGDEBUG, "hook into pOpenData->pAdapterFuncs->pfnCreateDevice");
+ s_fnCreateDeviceOrig = pOpenData->pAdapterFuncs->pfnCreateDevice;
+ pOpenData->pAdapterFuncs->pfnCreateDevice = HookCreateDevice;
+ }
+ return hr;
+}
+
+bool CWinSystemWin32DX::IsHDRDisplay()
+{
+ return (CWIN32Util::GetWindowsHDRStatus() != HDR_STATUS::HDR_UNSUPPORTED);
+}
+
+HDR_STATUS CWinSystemWin32DX::GetOSHDRStatus()
+{
+ return CWIN32Util::GetWindowsHDRStatus();
+}
+
+HDR_STATUS CWinSystemWin32DX::ToggleHDR()
+{
+ return m_deviceResources->ToggleHDR();
+}
+
+bool CWinSystemWin32DX::IsHDROutput() const
+{
+ return m_deviceResources->IsHDROutput();
+}
+
+bool CWinSystemWin32DX::IsTransferPQ() const
+{
+ return m_deviceResources->IsTransferPQ();
+}
+
+void CWinSystemWin32DX::SetHdrMetaData(DXGI_HDR_METADATA_HDR10& hdr10) const
+{
+ m_deviceResources->SetHdrMetaData(hdr10);
+}
+
+void CWinSystemWin32DX::SetHdrColorSpace(const DXGI_COLOR_SPACE_TYPE colorSpace) const
+{
+ m_deviceResources->SetHdrColorSpace(colorSpace);
+}
+
+DEBUG_INFO_RENDER CWinSystemWin32DX::GetDebugInfo()
+{
+ return m_deviceResources->GetDebugInfo();
+}
diff --git a/xbmc/windowing/windows/WinSystemWin32DX.h b/xbmc/windowing/windows/WinSystemWin32DX.h
new file mode 100644
index 0000000..d3673de
--- /dev/null
+++ b/xbmc/windowing/windows/WinSystemWin32DX.h
@@ -0,0 +1,96 @@
+/*
+ * 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 "HDRStatus.h"
+#include "rendering/dx/RenderSystemDX.h"
+#include "windowing/windows/WinSystemWin32.h"
+
+struct D3D10DDIARG_CREATERESOURCE;
+
+class CWinSystemWin32DX : public CWinSystemWin32, public CRenderSystemDX
+{
+ friend interface DX::IDeviceNotify;
+public:
+ CWinSystemWin32DX();
+ ~CWinSystemWin32DX();
+
+ static void Register();
+ static std::unique_ptr<CWinSystemBase> CreateWinSystem();
+
+ // Implementation of CWinSystemBase via CWinSystemWin32
+ 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;
+ bool SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) override;
+ void PresentRenderImpl(bool rendered) override;
+ bool DPIChanged(WORD dpi, RECT windowRect) const override;
+ void SetWindow(HWND hWnd) const;
+ bool DestroyRenderSystem() override;
+ void* GetHWContext() override { return m_deviceResources->GetD3DContext(); }
+
+ void UninitHooks();
+ void InitHooks(IDXGIOutput* pOutput);
+
+ void OnMove(int x, int y) override;
+ void OnResize(int width, int height);
+
+ /*!
+ \brief Register as a dependent of the DirectX Render System
+ Resources should call this on construction if they're dependent on the Render System
+ for survival. Any resources that registers will get callbacks on loss and reset of
+ device. In addition, callbacks for destruction and creation of the device are also called,
+ where any resources dependent on the DirectX device should be destroyed and recreated.
+ \sa Unregister, ID3DResource
+ */
+ void Register(ID3DResource *resource) const
+ {
+ m_deviceResources->Register(resource);
+ };
+ /*!
+ \brief Unregister as a dependent of the DirectX Render System
+ Resources should call this on destruction if they're a dependent on the Render System
+ \sa Register, ID3DResource
+ */
+ void Unregister(ID3DResource *resource) const
+ {
+ m_deviceResources->Unregister(resource);
+ };
+
+ void Register(IDispResource* resource) override { CWinSystemWin32::Register(resource); }
+ void Unregister(IDispResource* resource) override { CWinSystemWin32::Unregister(resource); }
+
+ void FixRefreshRateIfNecessary(const D3D10DDIARG_CREATERESOURCE* pResource) const;
+
+ // HDR OS/display override
+ bool IsHDRDisplay() override;
+ HDR_STATUS ToggleHDR() override;
+ HDR_STATUS GetOSHDRStatus() override;
+
+ // HDR support
+ bool IsHDROutput() const;
+ bool IsTransferPQ() const;
+ void SetHdrMetaData(DXGI_HDR_METADATA_HDR10& hdr10) const;
+ void SetHdrColorSpace(const DXGI_COLOR_SPACE_TYPE colorSpace) const;
+
+ // Get debug info from swapchain
+ DEBUG_INFO_RENDER GetDebugInfo() override;
+
+protected:
+ void SetDeviceFullScreen(bool fullScreen, RESOLUTION_INFO& res) override;
+ void ReleaseBackBuffer() override;
+ void CreateBackBuffer() override;
+ void ResizeDeviceBuffers() override;
+ bool IsStereoEnabled() override;
+ void OnScreenChange(HMONITOR monitor) override;
+ bool ChangeResolution(const RESOLUTION_INFO& res, bool forceChange = false) override;
+
+ HMODULE m_hDriverModule;
+};
+