[Windows] Fix driver version detection of AMD RDNA+ GPU on Windows 10
[xbmc.git] / xbmc / windowing / windows / WinSystemWin32.cpp
blob74bacc0f0a8877f16945a136821d87b52117ffdd
1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "WinSystemWin32.h"
11 #include "ServiceBroker.h"
12 #include "VideoSyncD3D.h"
13 #include "WIN32Util.h"
14 #include "WinEventsWin32.h"
15 #include "application/Application.h"
16 #include "cores/AudioEngine/AESinkFactory.h"
17 #include "cores/AudioEngine/Sinks/AESinkDirectSound.h"
18 #include "cores/AudioEngine/Sinks/AESinkWASAPI.h"
19 #include "filesystem/File.h"
20 #include "filesystem/SpecialProtocol.h"
21 #include "messaging/ApplicationMessenger.h"
22 #include "platform/Environment.h"
23 #include "rendering/dx/ScreenshotSurfaceWindows.h"
24 #include "resource.h"
25 #include "settings/AdvancedSettings.h"
26 #include "settings/DisplaySettings.h"
27 #include "settings/Settings.h"
28 #include "settings/SettingsComponent.h"
29 #include "utils/StringUtils.h"
30 #include "utils/SystemInfo.h"
31 #include "utils/log.h"
32 #include "windowing/GraphicContext.h"
33 #include "windowing/windows/Win32DPMSSupport.h"
35 #include "platform/win32/CharsetConverter.h"
36 #include "platform/win32/input/IRServerSuite.h"
38 #include <algorithm>
39 #include <cmath>
40 #include <mutex>
42 #include <tpcshrd.h>
44 using namespace std::chrono_literals;
46 const char* CWinSystemWin32::SETTING_WINDOW_TOP = "window.top";
47 const char* CWinSystemWin32::SETTING_WINDOW_LEFT = "window.left";
49 CWinSystemWin32::CWinSystemWin32()
50 : CWinSystemBase()
51 , m_hWnd(nullptr)
52 , m_hMonitor(nullptr)
53 , m_hInstance(nullptr)
54 , m_hIcon(nullptr)
55 , m_ValidWindowedPosition(false)
56 , m_IsAlteringWindow(false)
57 , m_delayDispReset(false)
58 , m_state(WINDOW_STATE_WINDOWED)
59 , m_fullscreenState(WINDOW_FULLSCREEN_STATE_FULLSCREEN_WINDOW)
60 , m_windowState(WINDOW_WINDOW_STATE_WINDOWED)
61 , m_windowStyle(WINDOWED_STYLE)
62 , m_windowExStyle(WINDOWED_EX_STYLE)
63 , m_inFocus(false)
64 , m_bMinimized(false)
66 std::string cacert = CEnvironment::getenv("SSL_CERT_FILE");
67 if (cacert.empty() || !XFILE::CFile::Exists(cacert))
69 cacert = CSpecialProtocol::TranslatePath("special://xbmc/system/certs/cacert.pem");
70 if (XFILE::CFile::Exists(cacert))
71 CEnvironment::setenv("SSL_CERT_FILE", cacert.c_str(), 1);
74 m_winEvents.reset(new CWinEventsWin32());
75 AE::CAESinkFactory::ClearSinks();
76 CAESinkDirectSound::Register();
77 CAESinkWASAPI::Register();
78 CScreenshotSurfaceWindows::Register();
80 if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_bScanIRServer)
82 m_irss.reset(new CIRServerSuite());
83 m_irss->Initialize();
85 m_dpms = std::make_shared<CWin32DPMSSupport>();
88 CWinSystemWin32::~CWinSystemWin32()
90 if (m_hIcon)
92 DestroyIcon(m_hIcon);
93 m_hIcon = nullptr;
97 bool CWinSystemWin32::InitWindowSystem()
99 if(!CWinSystemBase::InitWindowSystem())
100 return false;
102 return true;
105 bool CWinSystemWin32::DestroyWindowSystem()
107 if (m_hMonitor)
109 MONITOR_DETAILS* details = GetDisplayDetails(m_hMonitor);
110 if (details)
111 RestoreDesktopResolution(details);
113 return true;
116 bool CWinSystemWin32::CreateNewWindow(const std::string& name, bool fullScreen, RESOLUTION_INFO& res)
118 using KODI::PLATFORM::WINDOWS::ToW;
119 auto nameW = ToW(name);
121 m_hInstance = static_cast<HINSTANCE>(GetModuleHandle(nullptr));
122 if(m_hInstance == nullptr)
123 CLog::LogF(LOGDEBUG, " GetModuleHandle failed with {}", GetLastError());
125 UpdateStates(fullScreen);
126 // initialize the state
127 WINDOW_STATE state = GetState(fullScreen);
129 m_nWidth = res.iWidth;
130 m_nHeight = res.iHeight;
131 m_bFullScreen = fullScreen;
132 m_fRefreshRate = res.fRefreshRate;
134 m_hIcon = LoadIcon(m_hInstance, MAKEINTRESOURCE(IDI_MAIN_ICON));
136 // Register the windows class
137 WNDCLASSEX wndClass = {};
138 wndClass.cbSize = sizeof(wndClass);
139 wndClass.style = CS_HREDRAW | CS_VREDRAW;
140 wndClass.lpfnWndProc = CWinEventsWin32::WndProc;
141 wndClass.cbClsExtra = 0;
142 wndClass.cbWndExtra = 0;
143 wndClass.hInstance = m_hInstance;
144 wndClass.hIcon = m_hIcon;
145 wndClass.hCursor = LoadCursor(nullptr, IDC_ARROW );
146 wndClass.hbrBackground = static_cast<HBRUSH>(GetStockObject(BLACK_BRUSH));
147 wndClass.lpszMenuName = nullptr;
148 wndClass.lpszClassName = nameW.c_str();
150 if( !RegisterClassExW( &wndClass ) )
152 CLog::LogF(LOGERROR, " RegisterClassExW failed with {}", GetLastError());
153 return false;
156 // put the window at desired display
157 RECT screenRect = ScreenRect(m_hMonitor);
158 m_nLeft = screenRect.left;
159 m_nTop = screenRect.top;
161 if (state == WINDOW_STATE_WINDOWED)
163 const auto settings = CServiceBroker::GetSettingsComponent()->GetSettings();
164 const int top = settings->GetInt(SETTING_WINDOW_TOP);
165 const int left = settings->GetInt(SETTING_WINDOW_LEFT);
166 const RECT vsRect = GetVirtualScreenRect();
168 // we check that window is inside of virtual screen rect (sum of all monitors)
169 // top 0 left 0 is a special position that centers the window on the screen
170 if ((top != 0 || left != 0) && top >= vsRect.top && top + m_nHeight <= vsRect.bottom &&
171 left >= vsRect.left && left + m_nWidth <= vsRect.right)
173 // restore previous window position
174 m_nLeft = left;
175 m_nTop = top;
177 else
179 // Windowed mode: position and size in settings and most places in Kodi
180 // are for the client part of the window.
181 RECT rcWorkArea = GetScreenWorkArea(m_hMonitor);
183 RECT rcNcArea = GetNcAreaOffsets(m_windowStyle, false, m_windowExStyle);
184 int maxClientWidth = (rcWorkArea.right - rcNcArea.right) - (rcWorkArea.left - rcNcArea.left);
185 int maxClientHeight = (rcWorkArea.bottom - rcNcArea.bottom) - (rcWorkArea.top - rcNcArea.top);
187 m_nWidth = std::min(m_nWidth, maxClientWidth);
188 m_nHeight = std::min(m_nHeight, maxClientHeight);
189 CWinSystemBase::SetWindowResolution(m_nWidth, m_nHeight);
191 // center window on desktop
192 m_nLeft = rcWorkArea.left - rcNcArea.left + (maxClientWidth - m_nWidth) / 2;
193 m_nTop = rcWorkArea.top - rcNcArea.top + (maxClientHeight - m_nHeight) / 2;
195 m_ValidWindowedPosition = true;
198 HWND hWnd = CreateWindowExW(
199 m_windowExStyle,
200 nameW.c_str(),
201 nameW.c_str(),
202 m_windowStyle,
203 m_nLeft,
204 m_nTop,
205 m_nWidth,
206 m_nHeight,
207 nullptr,
208 nullptr,
209 m_hInstance,
210 nullptr
213 if( hWnd == nullptr )
215 CLog::LogF(LOGERROR, " CreateWindow failed with {}", GetLastError());
216 return false;
219 m_inFocus = true;
221 DWORD dwHwndTabletProperty =
222 TABLET_DISABLE_PENBARRELFEEDBACK | // disables UI feedback on pen button down (circle)
223 TABLET_DISABLE_FLICKS; // disables pen flicks (back, forward, drag down, drag up)
225 SetProp(hWnd, MICROSOFT_TABLETPENSERVICE_PROPERTY, &dwHwndTabletProperty);
227 m_hWnd = hWnd;
228 m_bWindowCreated = true;
230 CreateBlankWindows();
232 m_state = state;
233 AdjustWindow(true);
235 // Show the window
236 ShowWindow( m_hWnd, SW_SHOWDEFAULT );
237 UpdateWindow( m_hWnd );
239 // Configure the tray icon.
240 m_trayIcon.cbSize = sizeof(m_trayIcon);
241 m_trayIcon.hWnd = m_hWnd;
242 m_trayIcon.hIcon = m_hIcon;
243 wcsncpy(m_trayIcon.szTip, nameW.c_str(), sizeof(m_trayIcon.szTip) / sizeof(WCHAR));
244 m_trayIcon.uCallbackMessage = TRAY_ICON_NOTIFY;
245 m_trayIcon.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE;
247 return true;
250 bool CWinSystemWin32::CreateBlankWindows()
252 WNDCLASSEX wcex;
254 wcex.cbSize = sizeof(WNDCLASSEX);
255 wcex.style= CS_HREDRAW | CS_VREDRAW;
256 wcex.lpfnWndProc= DefWindowProc;
257 wcex.cbClsExtra= 0;
258 wcex.cbWndExtra= 0;
259 wcex.hInstance= nullptr;
260 wcex.hIcon= nullptr;
261 wcex.hCursor= nullptr;
262 wcex.hbrBackground= static_cast<HBRUSH>(CreateSolidBrush(RGB(0, 0, 0)));
263 wcex.lpszMenuName= nullptr;
264 wcex.lpszClassName= L"BlankWindowClass";
265 wcex.hIconSm= nullptr;
267 // Now we can go ahead and register our new window class
268 if(!RegisterClassEx(&wcex))
270 CLog::LogF(LOGERROR, "RegisterClass failed with {}", GetLastError());
271 return false;
274 // We need as many blank windows as there are screens (minus 1)
275 for (size_t i = 0; i < m_displays.size() - 1; i++)
277 HWND hBlankWindow = CreateWindowEx(WS_EX_TOPMOST, L"BlankWindowClass", L"", WS_POPUP | WS_DISABLED,
278 CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, nullptr, nullptr);
280 if (hBlankWindow == nullptr)
282 CLog::LogF(LOGERROR, "CreateWindowEx failed with {}", GetLastError());
283 return false;
286 m_hBlankWindows.push_back(hBlankWindow);
289 return true;
292 bool CWinSystemWin32::BlankNonActiveMonitors(bool bBlank)
294 if (m_hBlankWindows.empty())
295 return false;
297 if (bBlank == false)
299 for (unsigned int i=0; i < m_hBlankWindows.size(); i++)
300 ShowWindow(m_hBlankWindows[i], SW_HIDE);
301 return true;
304 // Move a blank window in front of every display, except the current display.
305 for (size_t i = 0, j = 0; i < m_displays.size(); ++i)
307 MONITOR_DETAILS& details = m_displays[i];
308 if (details.hMonitor == m_hMonitor)
309 continue;
311 RECT rBounds = ScreenRect(details.hMonitor);
312 // move and resize the window
313 SetWindowPos(m_hBlankWindows[j], nullptr, rBounds.left, rBounds.top,
314 rBounds.right - rBounds.left, rBounds.bottom - rBounds.top,
315 SWP_NOACTIVATE);
317 ShowWindow(m_hBlankWindows[j], SW_SHOW | SW_SHOWNOACTIVATE);
318 j++;
321 if (m_hWnd)
322 SetForegroundWindow(m_hWnd);
324 return true;
327 bool CWinSystemWin32::CenterWindow()
329 RESOLUTION_INFO DesktopRes = CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP);
331 m_nLeft = (DesktopRes.iWidth / 2) - (m_nWidth / 2);
332 m_nTop = (DesktopRes.iHeight / 2) - (m_nHeight / 2);
334 RECT rc;
335 rc.left = m_nLeft;
336 rc.top = m_nTop;
337 rc.right = rc.left + m_nWidth;
338 rc.bottom = rc.top + m_nHeight;
339 AdjustWindowRect( &rc, WS_OVERLAPPEDWINDOW, false );
341 SetWindowPos(m_hWnd, nullptr, rc.left, rc.top, 0, 0, SWP_NOSIZE);
343 return true;
346 bool CWinSystemWin32::ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop)
348 m_nWidth = newWidth;
349 m_nHeight = newHeight;
351 if (newLeft > 0)
352 m_nLeft = newLeft;
354 if (newTop > 0)
355 m_nTop = newTop;
357 AdjustWindow();
359 return true;
362 void CWinSystemWin32::FinishWindowResize(int newWidth, int newHeight)
364 m_nWidth = newWidth;
365 m_nHeight = newHeight;
368 void CWinSystemWin32::ForceFullScreen(const RESOLUTION_INFO& resInfo)
370 ResizeWindow(resInfo.iScreenWidth, resInfo.iScreenHeight, 0, 0);
373 void CWinSystemWin32::AdjustWindow(bool forceResize)
375 CLog::LogF(LOGDEBUG, "adjusting window if required.");
377 HWND windowAfter;
378 RECT rc;
380 if (m_state == WINDOW_STATE_FULLSCREEN_WINDOW || m_state == WINDOW_STATE_FULLSCREEN)
382 windowAfter = HWND_TOP;
383 rc = ScreenRect(m_hMonitor);
385 else // m_state == WINDOW_STATE_WINDOWED
387 windowAfter = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_alwaysOnTop ? HWND_TOPMOST : HWND_NOTOPMOST;
389 if (!m_ValidWindowedPosition)
391 const auto settings = CServiceBroker::GetSettingsComponent()->GetSettings();
392 const int top = settings->GetInt(SETTING_WINDOW_TOP);
393 const int left = settings->GetInt(SETTING_WINDOW_LEFT);
394 const RECT vsRect = GetVirtualScreenRect();
396 // we check that window is inside of virtual screen rect (sum of all monitors)
397 // top 0 left 0 is a special position that centers the window on the screen
398 if ((top != 0 || left != 0) && top >= vsRect.top && top + m_nHeight <= vsRect.bottom &&
399 left >= vsRect.left && left + m_nWidth <= vsRect.right)
401 // restore previous window position
402 m_nTop = top;
403 m_nLeft = left;
405 else
407 // Windowed mode: position and size in settings and most places in Kodi
408 // are for the client part of the window.
409 RECT rcWorkArea = GetScreenWorkArea(m_hMonitor);
411 RECT rcNcArea = GetNcAreaOffsets(m_windowStyle, false, m_windowExStyle);
412 int maxClientWidth =
413 (rcWorkArea.right - rcNcArea.right) - (rcWorkArea.left - rcNcArea.left);
414 int maxClientHeight =
415 (rcWorkArea.bottom - rcNcArea.bottom) - (rcWorkArea.top - rcNcArea.top);
417 m_nWidth = std::min(m_nWidth, maxClientWidth);
418 m_nHeight = std::min(m_nHeight, maxClientHeight);
419 CWinSystemBase::SetWindowResolution(m_nWidth, m_nHeight);
421 // center window on desktop
422 m_nLeft = rcWorkArea.left - rcNcArea.left + (maxClientWidth - m_nWidth) / 2;
423 m_nTop = rcWorkArea.top - rcNcArea.top + (maxClientHeight - m_nHeight) / 2;
425 m_ValidWindowedPosition = true;
428 rc.left = m_nLeft;
429 rc.right = m_nLeft + m_nWidth;
430 rc.top = m_nTop;
431 rc.bottom = m_nTop + m_nHeight;
433 HMONITOR hMon = MonitorFromRect(&rc, MONITOR_DEFAULTTONULL);
434 HMONITOR hMon2 = MonitorFromWindow(m_hWnd, MONITOR_DEFAULTTOPRIMARY);
436 if (!m_ValidWindowedPosition || hMon == nullptr || hMon != hMon2)
438 // centering window at desktop
439 RECT newScreenRect = ScreenRect(hMon2);
440 rc.left = m_nLeft = newScreenRect.left + ((newScreenRect.right - newScreenRect.left) / 2) - (m_nWidth / 2);
441 rc.top = m_nTop = newScreenRect.top + ((newScreenRect.bottom - newScreenRect.top) / 2) - (m_nHeight / 2);
442 rc.right = m_nLeft + m_nWidth;
443 rc.bottom = m_nTop + m_nHeight;
444 m_ValidWindowedPosition = true;
446 AdjustWindowRectEx(&rc, m_windowStyle, false, m_windowExStyle);
449 WINDOWINFO wi;
450 wi.cbSize = sizeof(WINDOWINFO);
451 if (!GetWindowInfo(m_hWnd, &wi))
453 CLog::LogF(LOGERROR, "GetWindowInfo failed with {}", GetLastError());
454 return;
456 RECT wr = wi.rcWindow;
458 if ( wr.bottom - wr.top == rc.bottom - rc.top
459 && wr.right - wr.left == rc.right - rc.left
460 && (wi.dwStyle & WS_CAPTION) == (m_windowStyle & WS_CAPTION)
461 && !forceResize)
463 return;
466 //Sets the window style
467 SetLastError(0);
468 SetWindowLongPtr( m_hWnd, GWL_STYLE, m_windowStyle );
470 //Sets the window ex style
471 SetLastError(0);
472 SetWindowLongPtr( m_hWnd, GWL_EXSTYLE, m_windowExStyle );
474 // resize window
475 CLog::LogF(LOGDEBUG, "resizing due to size change ({},{},{},{}{})->({},{},{},{}{})", wr.left,
476 wr.top, wr.right, wr.bottom, (wi.dwStyle & WS_CAPTION) ? "" : " fullscreen", rc.left,
477 rc.top, rc.right, rc.bottom, (m_windowStyle & WS_CAPTION) ? "" : " fullscreen");
478 SetWindowPos(
479 m_hWnd,
480 windowAfter,
481 rc.left,
482 rc.top,
483 rc.right - rc.left,
484 rc.bottom - rc.top,
485 SWP_SHOWWINDOW | SWP_DRAWFRAME
489 void CWinSystemWin32::CenterCursor() const
491 RECT rect;
492 POINT point = {};
494 //Gets the client rect, then translates it to screen coordinates
495 //so that SetCursorPos isn't called with relative x and y values
496 GetClientRect(m_hWnd, &rect);
497 ClientToScreen(m_hWnd, &point);
499 rect.left += point.x;
500 rect.right += point.x;
501 rect.top += point.y;
502 rect.bottom += point.y;
504 int x = rect.left + (rect.right - rect.left) / 2;
505 int y = rect.top + (rect.bottom - rect.top) / 2;
507 SetCursorPos(x, y);
510 bool CWinSystemWin32::SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays)
512 // Initialisation not finished, pretend the function succeeded
513 if (!m_hWnd)
514 return true;
516 CWinSystemWin32::UpdateStates(fullScreen);
517 WINDOW_STATE state = GetState(fullScreen);
519 CLog::LogF(LOGDEBUG, "({}) with size {}x{}, refresh {:f}{}", window_state_names[state],
520 res.iWidth, res.iHeight, res.fRefreshRate,
521 (res.dwFlags & D3DPRESENTFLAG_INTERLACED) ? "i" : "");
523 // oldMonitor may be NULL if it's powered off or not available due windows settings
524 MONITOR_DETAILS* oldMonitor = GetDisplayDetails(m_hMonitor);
525 MONITOR_DETAILS* newMonitor = GetDisplayDetails(res.strOutput);
527 bool forceChange = false; // resolution/display is changed but window state isn't changed
528 bool changeScreen = false; // display is changed
529 bool stereoChange = IsStereoEnabled() != (CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode() == RENDER_STEREO_MODE_HARDWAREBASED);
531 if (m_nWidth != res.iWidth || m_nHeight != res.iHeight || m_fRefreshRate != res.fRefreshRate ||
532 !oldMonitor || oldMonitor->hMonitor != newMonitor->hMonitor || stereoChange ||
533 m_bFirstResChange)
535 if (!oldMonitor || oldMonitor->hMonitor != newMonitor->hMonitor)
536 changeScreen = true;
537 forceChange = true;
540 if (state == m_state && !forceChange)
542 m_bBlankOtherDisplay = blankOtherDisplays;
543 BlankNonActiveMonitors(m_bBlankOtherDisplay);
544 return true;
547 // entering to stereo mode, limit resolution to 1080p@23.976
548 if (stereoChange && !IsStereoEnabled() && res.iWidth > 1280)
550 res = CDisplaySettings::GetInstance().GetResolutionInfo(
551 CResolutionUtils::ChooseBestResolution(24.f / 1.001f, 1920, 1080, true));
554 if (m_state == WINDOW_STATE_WINDOWED)
556 WINDOWINFO wi = {};
557 wi.cbSize = sizeof(WINDOWINFO);
558 if (GetWindowInfo(m_hWnd, &wi) && wi.rcClient.top > 0)
560 m_nLeft = wi.rcClient.left;
561 m_nTop = wi.rcClient.top;
562 m_ValidWindowedPosition = true;
566 m_IsAlteringWindow = true;
567 ReleaseBackBuffer();
569 if (changeScreen)
571 // before we changing display we have to leave exclusive mode on "old" display
572 if (m_state == WINDOW_STATE_FULLSCREEN)
573 SetDeviceFullScreen(false, res);
575 // restoring native resolution on "old" display
576 RestoreDesktopResolution(oldMonitor);
578 // notify about screen change (it may require recreate rendering device)
579 m_fRefreshRate = res.fRefreshRate; // use desired refresh for driver hook
580 OnScreenChange(newMonitor->hMonitor);
583 m_bFirstResChange = false;
584 m_bFullScreen = fullScreen;
585 m_hMonitor = newMonitor->hMonitor;
586 m_nWidth = res.iWidth;
587 m_nHeight = res.iHeight;
588 m_bBlankOtherDisplay = blankOtherDisplays;
589 m_fRefreshRate = res.fRefreshRate;
591 if (state == WINDOW_STATE_FULLSCREEN)
593 SetForegroundWindowInternal(m_hWnd);
595 m_state = state;
596 AdjustWindow(changeScreen);
598 // enter in exclusive mode, this will change resolution if we already in
599 SetDeviceFullScreen(true, res);
601 else if (m_state == WINDOW_STATE_FULLSCREEN || m_state == WINDOW_STATE_FULLSCREEN_WINDOW) // we're in fullscreen state now
603 // guess we are leaving exclusive mode, this will not an effect if we already not in
604 SetDeviceFullScreen(false, res);
606 if (state == WINDOW_STATE_WINDOWED) // go to a windowed state
608 // need to restore resolution if it was changed to not native
609 // because we do not support resolution change in windowed mode
610 RestoreDesktopResolution(newMonitor);
612 else if (state == WINDOW_STATE_FULLSCREEN_WINDOW) // enter fullscreen window instead
614 ChangeResolution(res, stereoChange);
617 m_state = state;
618 AdjustWindow(changeScreen);
620 else // we're in windowed state now
622 if (state == WINDOW_STATE_FULLSCREEN_WINDOW)
624 ChangeResolution(res, stereoChange);
626 m_state = state;
627 AdjustWindow(changeScreen);
631 if (changeScreen)
632 CenterCursor();
634 CreateBackBuffer();
636 BlankNonActiveMonitors(m_bBlankOtherDisplay);
637 m_IsAlteringWindow = false;
639 return true;
642 bool CWinSystemWin32::DPIChanged(WORD dpi, RECT windowRect) const
644 (void)dpi;
645 RECT resizeRect = windowRect;
646 HMONITOR hMon = MonitorFromRect(&resizeRect, MONITOR_DEFAULTTONULL);
647 if (hMon == nullptr)
649 hMon = MonitorFromWindow(m_hWnd, MONITOR_DEFAULTTOPRIMARY);
652 if (hMon)
654 MONITORINFOEX monitorInfo;
655 monitorInfo.cbSize = sizeof(MONITORINFOEX);
656 GetMonitorInfoW(hMon, &monitorInfo);
658 if (m_state == WINDOW_STATE_FULLSCREEN_WINDOW ||
659 m_state == WINDOW_STATE_FULLSCREEN)
661 resizeRect = monitorInfo.rcMonitor; // the whole screen
663 else
665 RECT wr = monitorInfo.rcWork; // it excludes task bar
666 long wrWidth = wr.right - wr.left;
667 long wrHeight = wr.bottom - wr.top;
668 long resizeWidth = resizeRect.right - resizeRect.left;
669 long resizeHeight = resizeRect.bottom - resizeRect.top;
671 if (resizeWidth > wrWidth)
673 resizeRect.right = resizeRect.left + wrWidth;
676 // make sure suggested windows size is not taller or wider than working area of new monitor (considers the toolbar)
677 if (resizeHeight > wrHeight)
679 resizeRect.bottom = resizeRect.top + wrHeight;
684 // resize the window to the suggested size. Will generate a WM_SIZE event
685 SetWindowPos(m_hWnd,
686 nullptr,
687 resizeRect.left,
688 resizeRect.top,
689 resizeRect.right - resizeRect.left,
690 resizeRect.bottom - resizeRect.top,
691 SWP_NOZORDER | SWP_NOACTIVATE);
693 return true;
696 void CWinSystemWin32::SetMinimized(bool minimized)
698 const auto advancedSettings = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings();
700 if (advancedSettings->m_minimizeToTray)
702 if (minimized)
704 Shell_NotifyIcon(NIM_ADD, &m_trayIcon);
705 ShowWindow(m_hWnd, SW_HIDE);
707 else
709 Shell_NotifyIcon(NIM_DELETE, &m_trayIcon);
710 ShowWindow(m_hWnd, SW_RESTORE);
714 m_bMinimized = minimized;
717 std::vector<std::string> CWinSystemWin32::GetConnectedOutputs()
719 std::vector<std::string> outputs;
721 for (auto& display : m_displays)
723 outputs.emplace_back(KODI::PLATFORM::WINDOWS::FromW(display.MonitorNameW));
726 return outputs;
729 void CWinSystemWin32::RestoreDesktopResolution(MONITOR_DETAILS* details)
731 if (!details)
732 return;
734 RESOLUTION_INFO info;
735 info.iWidth = details->ScreenWidth;
736 info.iHeight = details->ScreenHeight;
737 if ((details->RefreshRate + 1) % 24 == 0 || (details->RefreshRate + 1) % 30 == 0)
738 info.fRefreshRate = static_cast<float>(details->RefreshRate + 1) / 1.001f;
739 else
740 info.fRefreshRate = static_cast<float>(details->RefreshRate);
741 info.strOutput = KODI::PLATFORM::WINDOWS::FromW(details->DeviceNameW);
742 info.dwFlags = details->Interlaced ? D3DPRESENTFLAG_INTERLACED : 0;
744 CLog::LogF(LOGDEBUG, "restoring desktop resolution for '{}'.", KODI::PLATFORM::WINDOWS::FromW(details->MonitorNameW));
745 ChangeResolution(info);
748 MONITOR_DETAILS* CWinSystemWin32::GetDisplayDetails(const std::string& name)
750 using KODI::PLATFORM::WINDOWS::ToW;
752 if (!name.empty() && name != "Default")
754 std::wstring nameW = ToW(name);
755 auto it = std::find_if(m_displays.begin(), m_displays.end(), [&nameW](MONITOR_DETAILS& m)
757 if (nameW[0] == '\\') // name is device name
758 return m.DeviceNameW == nameW;
759 else if (m.MonitorNameW == nameW)
760 return true;
761 else if (m.DeviceStringW == nameW)
762 return true;
763 return false;
765 if (it != m_displays.end())
766 return &(*it);
769 // fallback to primary
770 auto it = std::find_if(m_displays.begin(), m_displays.end(), [](MONITOR_DETAILS& m)
772 return m.IsPrimary;
774 if (it != m_displays.end())
775 return &(*it);
777 // nothing found
778 return nullptr;
781 MONITOR_DETAILS* CWinSystemWin32::GetDisplayDetails(HMONITOR handle)
783 auto it = std::find_if(m_displays.begin(), m_displays.end(), [&handle](MONITOR_DETAILS& m)
785 return m.hMonitor == handle;
787 if (it != m_displays.end())
788 return &(*it);
790 return nullptr;
793 RECT CWinSystemWin32::ScreenRect(HMONITOR handle)
795 const MONITOR_DETAILS* details = GetDisplayDetails(handle);
796 if (!details)
798 CLog::LogF(LOGERROR, "no monitor found for handle");
799 return RECT();
802 DEVMODEW sDevMode = {};
803 sDevMode.dmSize = sizeof(sDevMode);
804 if(!EnumDisplaySettingsW(details->DeviceNameW.c_str(), ENUM_CURRENT_SETTINGS, &sDevMode))
805 CLog::LogF(LOGERROR, " EnumDisplaySettings failed with {}", GetLastError());
807 RECT rc;
808 rc.left = sDevMode.dmPosition.x;
809 rc.right = sDevMode.dmPosition.x + sDevMode.dmPelsWidth;
810 rc.top = sDevMode.dmPosition.y;
811 rc.bottom = sDevMode.dmPosition.y + sDevMode.dmPelsHeight;
813 return rc;
816 void CWinSystemWin32::GetConnectedDisplays(std::vector<MONITOR_DETAILS>& outputs)
818 using KODI::PLATFORM::WINDOWS::FromW;
820 DISPLAY_DEVICEW ddAdapter = {};
821 ddAdapter.cb = sizeof(ddAdapter);
823 for (DWORD adapter = 0; EnumDisplayDevicesW(nullptr, adapter, &ddAdapter, 0); ++adapter)
825 // Exclude displays that are not part of the windows desktop. Using them is too different: no windows,
826 // direct access with GDI CreateDC() or DirectDraw for example. So it may be possible to play video, but GUI?
827 if ((ddAdapter.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER)
828 || !(ddAdapter.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP))
829 continue;
831 DISPLAY_DEVICEW ddMon = {};
832 ddMon.cb = sizeof(ddMon);
833 bool foundScreen = false;
835 DWORD screen = 0;
836 // Just look for the first active output, we're actually only interested in the information at the adapter level.
837 for (; EnumDisplayDevicesW(ddAdapter.DeviceName, screen, &ddMon, 0); ++screen)
839 if (ddMon.StateFlags & (DISPLAY_DEVICE_ACTIVE | DISPLAY_DEVICE_ATTACHED))
841 foundScreen = true;
842 break;
846 // Remoting returns no screens. Handle with a dummy screen.
847 if (!foundScreen && screen == 0)
849 lstrcpyW(ddMon.DeviceString, L"Dummy Monitor"); // safe: large static array
850 foundScreen = true;
853 if (foundScreen)
855 // get information about the display's current position and display mode
856 DEVMODEW dm = {};
857 dm.dmSize = sizeof(dm);
858 if (EnumDisplaySettingsExW(ddAdapter.DeviceName, ENUM_CURRENT_SETTINGS, &dm, 0) == FALSE)
859 EnumDisplaySettingsExW(ddAdapter.DeviceName, ENUM_REGISTRY_SETTINGS, &dm, 0);
861 POINT pt = { dm.dmPosition.x, dm.dmPosition.y };
862 HMONITOR hm = MonitorFromPoint(pt, MONITOR_DEFAULTTONULL);
864 MONITOR_DETAILS md = {};
865 uint8_t num = 1;
868 // `Monitor #N`
869 md.DeviceStringW = std::wstring(ddMon.DeviceString) + L" #" + std::to_wstring(num++);
870 } while (std::any_of(outputs.begin(), outputs.end(), [&](MONITOR_DETAILS& m) {
871 return m.DeviceStringW == md.DeviceStringW;
872 }));
874 std::wstring displayName = CWIN32Util::GetDisplayFriendlyName(ddAdapter.DeviceName);
875 if (displayName.empty())
876 displayName = std::wstring(ddMon.DeviceString);
877 num = 1;
880 // `Pretty Monitor Name #N`
881 md.MonitorNameW = displayName + L" #" + std::to_wstring(num++);
882 } while (std::any_of(outputs.begin(), outputs.end(),
883 [&](MONITOR_DETAILS& m) { return m.MonitorNameW == md.MonitorNameW; }));
885 md.CardNameW = ddAdapter.DeviceString;
886 md.DeviceNameW = ddAdapter.DeviceName;
887 md.ScreenWidth = dm.dmPelsWidth;
888 md.ScreenHeight = dm.dmPelsHeight;
889 md.hMonitor = hm;
890 md.RefreshRate = dm.dmDisplayFrequency;
891 md.Bpp = dm.dmBitsPerPel;
892 md.Interlaced = (dm.dmDisplayFlags & DM_INTERLACED) ? true : false;
894 MONITORINFO mi = {};
895 mi.cbSize = sizeof(mi);
896 if (GetMonitorInfoW(hm, &mi) && (mi.dwFlags & MONITORINFOF_PRIMARY))
897 md.IsPrimary = true;
899 outputs.push_back(md);
904 bool CWinSystemWin32::ChangeResolution(const RESOLUTION_INFO& res, bool forceChange /*= false*/)
906 using KODI::PLATFORM::WINDOWS::ToW;
907 std::wstring outputW = ToW(res.strOutput);
909 DEVMODEW sDevMode = {};
910 sDevMode.dmSize = sizeof(sDevMode);
912 // If we can't read the current resolution or any detail of the resolution is different than res
913 if (!EnumDisplaySettingsW(outputW.c_str(), ENUM_CURRENT_SETTINGS, &sDevMode) ||
914 sDevMode.dmPelsWidth != res.iWidth || sDevMode.dmPelsHeight != res.iHeight ||
915 sDevMode.dmDisplayFrequency != static_cast<int>(res.fRefreshRate) ||
916 ((sDevMode.dmDisplayFlags & DM_INTERLACED) && !(res.dwFlags & D3DPRESENTFLAG_INTERLACED)) ||
917 (!(sDevMode.dmDisplayFlags & DM_INTERLACED) && (res.dwFlags & D3DPRESENTFLAG_INTERLACED))
918 || forceChange)
920 ZeroMemory(&sDevMode, sizeof(sDevMode));
921 sDevMode.dmSize = sizeof(sDevMode);
922 sDevMode.dmDriverExtra = 0;
923 sDevMode.dmPelsWidth = res.iWidth;
924 sDevMode.dmPelsHeight = res.iHeight;
925 sDevMode.dmDisplayFrequency = static_cast<int>(res.fRefreshRate);
926 sDevMode.dmDisplayFlags = (res.dwFlags & D3DPRESENTFLAG_INTERLACED) ? DM_INTERLACED : 0;
927 sDevMode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY | DM_DISPLAYFLAGS;
929 LONG rc;
930 bool bResChanged = false;
932 // Windows 8 refresh rate workaround for 24.0, 48.0 and 60.0 Hz
933 if (res.fRefreshRate == 24.0 || res.fRefreshRate == 48.0 || res.fRefreshRate == 60.0)
935 CLog::LogF(LOGDEBUG, "Using Windows 8+ workaround for refresh rate {} Hz",
936 static_cast<int>(res.fRefreshRate));
938 // Get current resolution stored in registry
939 DEVMODEW sDevModeRegistry = {};
940 sDevModeRegistry.dmSize = sizeof(sDevModeRegistry);
941 if (EnumDisplaySettingsW(outputW.c_str(), ENUM_REGISTRY_SETTINGS, &sDevModeRegistry))
943 // Set requested mode in registry without actually changing resolution
944 rc = ChangeDisplaySettingsExW(outputW.c_str(), &sDevMode, nullptr, CDS_UPDATEREGISTRY | CDS_NORESET, nullptr);
945 if (rc == DISP_CHANGE_SUCCESSFUL)
947 // Change resolution based on registry setting
948 rc = ChangeDisplaySettingsExW(outputW.c_str(), nullptr, nullptr, CDS_FULLSCREEN, nullptr);
949 if (rc == DISP_CHANGE_SUCCESSFUL)
950 bResChanged = true;
951 else
952 CLog::LogF(
953 LOGERROR,
954 "ChangeDisplaySettingsEx (W8+ change resolution) failed with {}, using fallback",
955 rc);
957 // Restore registry with original values
958 sDevModeRegistry.dmSize = sizeof(sDevModeRegistry);
959 sDevModeRegistry.dmDriverExtra = 0;
960 sDevModeRegistry.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY | DM_DISPLAYFLAGS;
961 rc = ChangeDisplaySettingsExW(outputW.c_str(), &sDevModeRegistry, nullptr, CDS_UPDATEREGISTRY | CDS_NORESET, nullptr);
962 if (rc != DISP_CHANGE_SUCCESSFUL)
963 CLog::LogF(LOGERROR, "ChangeDisplaySettingsEx (W8+ restore registry) failed with {}",
964 rc);
966 else
967 CLog::LogF(LOGERROR,
968 "ChangeDisplaySettingsEx (W8+ set registry) failed with {}, using fallback",
969 rc);
971 else
972 CLog::LogF(LOGERROR, "Unable to retrieve registry settings for Windows 8+ workaround, using fallback");
975 // Standard resolution change/fallback for Windows 8+ workaround
976 if (!bResChanged)
978 // CDS_FULLSCREEN is for temporary fullscreen mode and prevents icons and windows from moving
979 // to fit within the new dimensions of the desktop
980 rc = ChangeDisplaySettingsExW(outputW.c_str(), &sDevMode, nullptr, CDS_FULLSCREEN, nullptr);
981 if (rc == DISP_CHANGE_SUCCESSFUL)
982 bResChanged = true;
983 else
984 CLog::LogF(LOGERROR, "ChangeDisplaySettingsEx failed with {}", rc);
987 if (bResChanged)
988 ResolutionChanged();
990 return bResChanged;
993 // nothing to do, return success
994 return true;
997 void CWinSystemWin32::UpdateResolutions()
999 using KODI::PLATFORM::WINDOWS::FromW;
1001 m_displays.clear();
1003 CWinSystemBase::UpdateResolutions();
1004 GetConnectedDisplays(m_displays);
1006 const auto settings = CServiceBroker::GetSettingsComponent()->GetSettings();
1007 std::string settingsMonitor = settings->GetString(CSettings::SETTING_VIDEOSCREEN_MONITOR);
1008 MONITOR_DETAILS* details = GetDisplayDetails(settingsMonitor);
1009 if (!details)
1010 return;
1011 // Migrate the setting to EDID-based name for connected and recognized screens.
1012 // Other screens may be temporarily turned off, ignore so they're used once available again
1013 if (settingsMonitor != FromW(details->MonitorNameW) &&
1014 (settingsMonitor == FromW(details->DeviceStringW) ||
1015 settingsMonitor == FromW(details->DeviceNameW)))
1016 CDisplaySettings::GetInstance().SetMonitor(FromW(details->MonitorNameW));
1018 float refreshRate;
1019 int w = details->ScreenWidth;
1020 int h = details->ScreenHeight;
1021 if ((details->RefreshRate + 1) % 24 == 0 || (details->RefreshRate + 1) % 30 == 0)
1022 refreshRate = static_cast<float>(details->RefreshRate + 1) / 1.001f;
1023 else
1024 refreshRate = static_cast<float>(details->RefreshRate);
1026 std::string strOutput = FromW(details->DeviceNameW);
1027 std::string monitorName = FromW(details->MonitorNameW);
1029 uint32_t dwFlags = details->Interlaced ? D3DPRESENTFLAG_INTERLACED : 0;
1031 RESOLUTION_INFO& info = CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP);
1032 UpdateDesktopResolution(info, monitorName, w, h, refreshRate, dwFlags);
1033 info.strOutput = strOutput;
1035 CLog::Log(LOGINFO, "Primary mode: {}", info.strMode);
1037 // erase previous stored modes
1038 CDisplaySettings::GetInstance().ClearCustomResolutions();
1040 for(int mode = 0;; mode++)
1042 DEVMODEW devmode = {};
1043 devmode.dmSize = sizeof(devmode);
1044 if(EnumDisplaySettingsW(details->DeviceNameW.c_str(), mode, &devmode) == 0)
1045 break;
1046 if(devmode.dmBitsPerPel != 32)
1047 continue;
1049 float refresh;
1050 if ((devmode.dmDisplayFrequency + 1) % 24 == 0 || (devmode.dmDisplayFrequency + 1) % 30 == 0)
1051 refresh = static_cast<float>(devmode.dmDisplayFrequency + 1) / 1.001f;
1052 else
1053 refresh = static_cast<float>(devmode.dmDisplayFrequency);
1054 dwFlags = (devmode.dmDisplayFlags & DM_INTERLACED) ? D3DPRESENTFLAG_INTERLACED : 0;
1056 RESOLUTION_INFO res;
1057 res.iWidth = devmode.dmPelsWidth;
1058 res.iHeight = devmode.dmPelsHeight;
1059 res.bFullScreen = true;
1060 res.dwFlags = dwFlags;
1061 res.fRefreshRate = refresh;
1062 res.fPixelRatio = 1.0f;
1063 res.iScreenWidth = res.iWidth;
1064 res.iScreenHeight = res.iHeight;
1065 res.iSubtitles = res.iHeight;
1066 res.strMode = StringUtils::Format("{}: {}x{} @ {:.2f}Hz", monitorName, res.iWidth, res.iHeight,
1067 res.fRefreshRate);
1068 GetGfxContext().ResetOverscan(res);
1069 res.strOutput = strOutput;
1071 if (AddResolution(res))
1072 CLog::Log(LOGINFO, "Additional mode: {}", res.strMode);
1075 CDisplaySettings::GetInstance().ApplyCalibrations();
1078 bool CWinSystemWin32::AddResolution(const RESOLUTION_INFO &res)
1080 for (unsigned int i = RES_CUSTOM; i < CDisplaySettings::GetInstance().ResolutionInfoSize(); i++)
1082 RESOLUTION_INFO& info = CDisplaySettings::GetInstance().GetResolutionInfo(i);
1083 if (info.iWidth == res.iWidth
1084 && info.iHeight == res.iHeight
1085 && info.iScreenWidth == res.iScreenWidth
1086 && info.iScreenHeight == res.iScreenHeight
1087 && info.fRefreshRate == res.fRefreshRate
1088 && info.dwFlags == res.dwFlags)
1089 return false; // already have this resolution
1092 CDisplaySettings::GetInstance().AddResolutionInfo(res);
1093 return true;
1096 void CWinSystemWin32::ShowOSMouse(bool show)
1098 static int counter = 0;
1099 if ((counter < 0 && show) || (counter >= 0 && !show))
1100 counter = ShowCursor(show);
1103 bool CWinSystemWin32::Minimize()
1105 ShowWindow(m_hWnd, SW_MINIMIZE);
1106 return true;
1108 bool CWinSystemWin32::Restore()
1110 ShowWindow(m_hWnd, SW_RESTORE);
1111 return true;
1113 bool CWinSystemWin32::Hide()
1115 ShowWindow(m_hWnd, SW_HIDE);
1116 return true;
1118 bool CWinSystemWin32::Show(bool raise)
1120 HWND windowAfter = HWND_BOTTOM;
1121 if (raise)
1123 if (m_bFullScreen)
1124 windowAfter = HWND_TOP;
1125 else
1126 windowAfter = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_alwaysOnTop ? HWND_TOPMOST : HWND_NOTOPMOST;
1129 SetWindowPos(m_hWnd, windowAfter, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_ASYNCWINDOWPOS);
1130 UpdateWindow(m_hWnd);
1132 if (raise)
1134 SetForegroundWindow(m_hWnd);
1135 SetFocus(m_hWnd);
1137 return true;
1140 void CWinSystemWin32::Register(IDispResource *resource)
1142 std::unique_lock<CCriticalSection> lock(m_resourceSection);
1143 m_resources.push_back(resource);
1146 void CWinSystemWin32::Unregister(IDispResource* resource)
1148 std::unique_lock<CCriticalSection> lock(m_resourceSection);
1149 std::vector<IDispResource*>::iterator i = find(m_resources.begin(), m_resources.end(), resource);
1150 if (i != m_resources.end())
1151 m_resources.erase(i);
1154 void CWinSystemWin32::OnDisplayLost()
1156 CLog::LogF(LOGDEBUG, "notify display lost event");
1159 std::unique_lock<CCriticalSection> lock(m_resourceSection);
1160 for (std::vector<IDispResource *>::iterator i = m_resources.begin(); i != m_resources.end(); ++i)
1161 (*i)->OnLostDisplay();
1165 void CWinSystemWin32::OnDisplayReset()
1167 if (!m_delayDispReset)
1169 CLog::LogF(LOGDEBUG, "notify display reset event");
1170 std::unique_lock<CCriticalSection> lock(m_resourceSection);
1171 for (std::vector<IDispResource *>::iterator i = m_resources.begin(); i != m_resources.end(); ++i)
1172 (*i)->OnResetDisplay();
1176 void CWinSystemWin32::OnDisplayBack()
1178 auto delay =
1179 std::chrono::milliseconds(CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
1180 "videoscreen.delayrefreshchange") *
1181 100);
1182 if (delay > 0ms)
1184 m_delayDispReset = true;
1185 m_dispResetTimer.Set(delay);
1187 OnDisplayReset();
1190 void CWinSystemWin32::ResolutionChanged()
1192 OnDisplayLost();
1193 OnDisplayBack();
1196 void CWinSystemWin32::SetForegroundWindowInternal(HWND hWnd)
1198 if (!IsWindow(hWnd)) return;
1200 // if the window isn't focused, bring it to front or SetFullScreen will fail
1201 BYTE keyState[256] = {};
1202 // to unlock SetForegroundWindow we need to imitate Alt pressing
1203 if (GetKeyboardState(reinterpret_cast<LPBYTE>(&keyState)) && !(keyState[VK_MENU] & 0x80))
1204 keybd_event(VK_MENU, 0, KEYEVENTF_EXTENDEDKEY | 0, 0);
1206 BOOL res = SetForegroundWindow(hWnd);
1208 if (GetKeyboardState(reinterpret_cast<LPBYTE>(&keyState)) && !(keyState[VK_MENU] & 0x80))
1209 keybd_event(VK_MENU, 0, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0);
1211 if (!res)
1213 //relation time of SetForegroundWindow lock
1214 DWORD lockTimeOut = 0;
1215 HWND hCurrWnd = GetForegroundWindow();
1216 DWORD dwThisTID = GetCurrentThreadId(),
1217 dwCurrTID = GetWindowThreadProcessId(hCurrWnd, nullptr);
1219 // we need to bypass some limitations from Microsoft
1220 if (dwThisTID != dwCurrTID)
1222 AttachThreadInput(dwThisTID, dwCurrTID, TRUE);
1223 SystemParametersInfo(SPI_GETFOREGROUNDLOCKTIMEOUT, 0, &lockTimeOut, 0);
1224 SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, nullptr, SPIF_SENDWININICHANGE | SPIF_UPDATEINIFILE);
1225 AllowSetForegroundWindow(ASFW_ANY);
1228 SetForegroundWindow(hWnd);
1230 if (dwThisTID != dwCurrTID)
1232 SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, &lockTimeOut, SPIF_SENDWININICHANGE | SPIF_UPDATEINIFILE);
1233 AttachThreadInput(dwThisTID, dwCurrTID, FALSE);
1238 std::unique_ptr<CVideoSync> CWinSystemWin32::GetVideoSync(CVideoReferenceClock* clock)
1240 std::unique_ptr<CVideoSync> pVSync(new CVideoSyncD3D(clock));
1241 return pVSync;
1244 std::string CWinSystemWin32::GetClipboardText()
1246 std::wstring unicode_text;
1247 std::string utf8_text;
1249 if (OpenClipboard(nullptr))
1251 HGLOBAL hglb = GetClipboardData(CF_UNICODETEXT);
1252 if (hglb != nullptr)
1254 LPWSTR lpwstr = static_cast<LPWSTR>(GlobalLock(hglb));
1255 if (lpwstr != nullptr)
1257 unicode_text = lpwstr;
1258 GlobalUnlock(hglb);
1261 CloseClipboard();
1264 return KODI::PLATFORM::WINDOWS::FromW(unicode_text);
1267 bool CWinSystemWin32::UseLimitedColor()
1269 return CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOSCREEN_LIMITEDRANGE);
1272 void CWinSystemWin32::NotifyAppFocusChange(bool bGaining)
1274 if (m_state == WINDOW_STATE_FULLSCREEN && !m_IsAlteringWindow)
1276 m_IsAlteringWindow = true;
1277 ReleaseBackBuffer();
1279 if (bGaining)
1280 SetWindowPos(m_hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOREDRAW);
1282 RESOLUTION_INFO res = {};
1283 const RESOLUTION resolution = CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution();
1284 if (bGaining && resolution > RES_INVALID)
1285 res = CDisplaySettings::GetInstance().GetResolutionInfo(resolution);
1287 SetDeviceFullScreen(bGaining, res);
1289 if (!bGaining)
1290 SetWindowPos(m_hWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOREDRAW);
1292 CreateBackBuffer();
1293 m_IsAlteringWindow = false;
1295 m_inFocus = bGaining;
1298 void CWinSystemWin32::UpdateStates(bool fullScreen)
1300 m_fullscreenState = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOSCREEN_FAKEFULLSCREEN)
1301 ? WINDOW_FULLSCREEN_STATE_FULLSCREEN_WINDOW
1302 : WINDOW_FULLSCREEN_STATE_FULLSCREEN;
1303 m_windowState = WINDOW_WINDOW_STATE_WINDOWED; // currently only this allowed
1305 // set the appropriate window style
1306 if (fullScreen)
1308 m_windowStyle = FULLSCREEN_WINDOW_STYLE;
1309 m_windowExStyle = FULLSCREEN_WINDOW_EX_STYLE;
1311 else
1313 m_windowStyle = WINDOWED_STYLE;
1314 m_windowExStyle = WINDOWED_EX_STYLE;
1318 WINDOW_STATE CWinSystemWin32::GetState(bool fullScreen) const
1320 return static_cast<WINDOW_STATE>(fullScreen ? m_fullscreenState : m_windowState);
1323 bool CWinSystemWin32::MessagePump()
1325 return m_winEvents->MessagePump();
1328 void CWinSystemWin32::SetTogglingHDR(bool toggling)
1330 if (toggling)
1331 SetTimer(m_hWnd, ID_TIMER_HDR, 6000U, nullptr);
1333 m_IsTogglingHDR = toggling;
1336 RECT CWinSystemWin32::GetVirtualScreenRect()
1338 RECT rect = {};
1339 rect.left = GetSystemMetrics(SM_XVIRTUALSCREEN);
1340 rect.right = GetSystemMetrics(SM_CXVIRTUALSCREEN) + rect.left;
1341 rect.top = GetSystemMetrics(SM_YVIRTUALSCREEN);
1342 rect.bottom = GetSystemMetrics(SM_CYVIRTUALSCREEN) + rect.top;
1344 return rect;
1348 * \brief Max luminance for GUI SDR content in HDR mode.
1349 * \return Max luminance in nits, lower than 10000.
1351 float CWinSystemWin32::GetGuiSdrPeakLuminance() const
1353 const auto settings = CServiceBroker::GetSettingsComponent()->GetSettings();
1355 // use cached system value as this is called for each frame
1356 if (settings->GetBool(CSettings::SETTING_VIDEOSCREEN_USESYSTEMSDRPEAKLUMINANCE) &&
1357 m_validSystemSdrPeakLuminance)
1358 return m_systemSdrPeakLuminance;
1360 // Max nits for 100% UI setting = 1000 nits, < 10000 nits, min 80 nits for 0%
1361 const int guiSdrPeak = settings->GetInt(CSettings::SETTING_VIDEOSCREEN_GUISDRPEAKLUMINANCE);
1362 return (80.0f * std::pow(std::exp(1.0f), 0.025257f * guiSdrPeak));
1366 * \brief Test support of the OS for a SDR max luminance in HDR mode setting
1367 * \return true when the OS supports that setting, false otherwise
1369 bool CWinSystemWin32::HasSystemSdrPeakLuminance()
1371 MONITOR_DETAILS* md = GetDisplayDetails(m_hMonitor);
1372 if (md)
1373 return CWIN32Util::GetSystemSdrWhiteLevel(md->DeviceNameW, nullptr);
1375 return false;
1379 * \brief Cache the system HDR/SDR balance for use during rendering, instead of querying the API
1380 for each frame.
1382 void CWinSystemWin32::CacheSystemSdrPeakLuminance()
1384 m_validSystemSdrPeakLuminance = false;
1386 MONITOR_DETAILS* md = GetDisplayDetails(m_hMonitor);
1387 if (md)
1389 m_validSystemSdrPeakLuminance =
1390 CWIN32Util::GetSystemSdrWhiteLevel(md->DeviceNameW, &m_systemSdrPeakLuminance);
1394 RECT CWinSystemWin32::GetScreenWorkArea(HMONITOR handle) const
1396 MONITORINFO monitorInfo{};
1397 monitorInfo.cbSize = sizeof(MONITORINFO);
1398 if (!GetMonitorInfoW(handle, &monitorInfo))
1400 CLog::LogF(LOGERROR, "GetMonitorInfoW failed with {}", GetLastError());
1401 return RECT();
1403 return monitorInfo.rcWork;
1406 RECT CWinSystemWin32::GetNcAreaOffsets(DWORD dwStyle, BOOL bMenu, DWORD dwExStyle) const
1408 RECT rcNcArea{};
1409 SetRectEmpty(&rcNcArea);
1411 if (!AdjustWindowRectEx(&rcNcArea, dwStyle, false, dwExStyle))
1413 CLog::LogF(LOGERROR, "AdjustWindowRectEx failed with {}", GetLastError());
1414 return RECT();
1416 return rcNcArea;