From c04dcc2e7d834218ef2d4194331e383402495ae1 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 10 Apr 2024 20:07:22 +0200 Subject: Adding upstream version 2:20.4+dfsg. Signed-off-by: Daniel Baumann --- xbmc/windowing/windows/CMakeLists.txt | 14 + xbmc/windowing/windows/VideoSyncD3D.cpp | 137 +++ xbmc/windowing/windows/VideoSyncD3D.h | 37 + xbmc/windowing/windows/Win32DPMSSupport.cpp | 52 + xbmc/windowing/windows/Win32DPMSSupport.h | 20 + xbmc/windowing/windows/WinEventsWin32.cpp | 1074 +++++++++++++++++++++ xbmc/windowing/windows/WinEventsWin32.h | 32 + xbmc/windowing/windows/WinKeyMap.h | 181 ++++ xbmc/windowing/windows/WinSystemWin32.cpp | 1368 +++++++++++++++++++++++++++ xbmc/windowing/windows/WinSystemWin32.h | 215 +++++ xbmc/windowing/windows/WinSystemWin32DX.cpp | 447 +++++++++ xbmc/windowing/windows/WinSystemWin32DX.h | 96 ++ 12 files changed, 3673 insertions(+) create mode 100644 xbmc/windowing/windows/CMakeLists.txt create mode 100644 xbmc/windowing/windows/VideoSyncD3D.cpp create mode 100644 xbmc/windowing/windows/VideoSyncD3D.h create mode 100644 xbmc/windowing/windows/Win32DPMSSupport.cpp create mode 100644 xbmc/windowing/windows/Win32DPMSSupport.h create mode 100644 xbmc/windowing/windows/WinEventsWin32.cpp create mode 100644 xbmc/windowing/windows/WinEventsWin32.h create mode 100644 xbmc/windowing/windows/WinKeyMap.h create mode 100644 xbmc/windowing/windows/WinSystemWin32.cpp create mode 100644 xbmc/windowing/windows/WinSystemWin32.h create mode 100644 xbmc/windowing/windows/WinSystemWin32DX.cpp create mode 100644 xbmc/windowing/windows/WinSystemWin32DX.h (limited to 'xbmc/windowing/windows') 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 + +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 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 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(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(2)) == 0; + case STANDBY: + // Set display to low power + return SendMessage(DX::Windowing()->GetHwnd(), WM_SYSCOMMAND, SC_MONITORPOWER, static_cast(1)) == 0; + default: + return true; + } +} + +bool CWin32DPMSSupport::DisablePowerSaving() +{ + // Turn display on + return SendMessage(DX::Windowing()->GetHwnd(), WM_SYSCOMMAND, SC_MONITORPOWER, static_cast(-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 +#include + +#include +#include + +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(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(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(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 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(vkey - VK_NUMPAD0 + '0'); + } + else if (ToUnicode(static_cast(vkey), scancode, keystate, + reinterpret_cast(wchars.data()), wchars.size(), 0) > 0) + { + keysym->unicode = wchars[0]; + } + } + + // Set the modifier bitmap + + uint16_t mod = static_cast(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(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 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(); + 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(); + 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((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(); + 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(point.x); + newEvent.button.y = static_cast(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(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(); + 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(); + 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(); + 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(); + 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(); + 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(); + 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(); + 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(wParam), static_cast(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(); + 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(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(point.x), static_cast(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(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(point.x); + m_touchPointer.current.y = static_cast(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(point.x), static_cast(point.y)); + } + break; + + case GID_END: + CGenericTouchActionHandler::GetInstance().OnTouchGestureEnd(static_cast(point.x), static_cast(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(point.x), static_cast(point.y), + -static_cast(ROTATE_ANGLE_DEGREE(gi.ullArguments))); + } + break; + + case GID_ZOOM: + { + if (gi.dwFlags == GF_BEGIN) + { + m_originalZoomDistance = static_cast(LODWORD(gi.ullArguments)); + break; + } + + // avoid division by 0 + if (m_originalZoomDistance == 0) + break; + + CGenericTouchActionHandler::GetInstance().OnZoomPinch(static_cast(point.x), static_cast(point.y), + static_cast(LODWORD(gi.ullArguments)) / static_cast(m_originalZoomDistance)); + } + break; + + case GID_TWOFINGERTAP: + CGenericTouchActionHandler::GetInstance().OnTap(static_cast(point.x), static_cast(point.y), 2); + break; + + case GID_PRESSANDTAP: + default: + // You have encountered an unknown gesture + break; + } + if(DX::Windowing()->PtrCloseGestureInfoHandle) + DX::Windowing()->PtrCloseGestureInfoHandle(reinterpret_cast(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 + +namespace KODI +{ +namespace WINDOWING +{ +namespace WINDOWS +{ + +static std::array 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 +#include + +#include + +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(); +} + +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(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(GetProcAddress(hUser32, "GetGestureInfo")); + PtrSetGestureConfig = reinterpret_cast(GetProcAddress(hUser32, "SetGestureConfig")); + PtrCloseGestureInfoHandle = reinterpret_cast(GetProcAddress(hUser32, "CloseGestureInfoHandle")); + // if available, enable automatic DPI scaling of the non-client area portions of the window. + PtrEnableNonClientDpiScaling = reinterpret_cast(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(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(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 CWinSystemWin32::GetConnectedOutputs() +{ + std::vector 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(details->RefreshRate + 1) / 1.001f; + else + info.fRefreshRate = static_cast(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& 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(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(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(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(details->RefreshRate + 1) / 1.001f; + else + refreshRate = static_cast(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(devmode.dmDisplayFrequency + 1) / 1.001f; + else + refresh = static_cast(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 lock(m_resourceSection); + m_resources.push_back(resource); +} + +void CWinSystemWin32::Unregister(IDispResource* resource) +{ + std::unique_lock lock(m_resourceSection); + std::vector::iterator i = find(m_resources.begin(), m_resources.end(), resource); + if (i != m_resources.end()) + m_resources.erase(i); +} + +void CWinSystemWin32::OnDisplayLost() +{ + CLog::LogF(LOGDEBUG, "notify display lost event"); + + { + std::unique_lock lock(m_resourceSection); + for (std::vector::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 lock(m_resourceSection); + for (std::vector::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(&keyState)) && !(keyState[VK_MENU] & 0x80)) + keybd_event(VK_MENU, 0, KEYEVENTF_EXTENDEDKEY | 0, 0); + + BOOL res = SetForegroundWindow(hWnd); + + if (GetKeyboardState(reinterpret_cast(&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 CWinSystemWin32::GetVideoSync(void *clock) +{ + std::unique_ptr 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(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(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 +#include + +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 GetVideoSync(void *clock) override; + + bool SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) override; + + std::vector 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& 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 m_hBlankWindows; + HINSTANCE m_hInstance; + HICON m_hIcon; + bool m_ValidWindowedPosition; + bool m_IsAlteringWindow; + bool m_IsTogglingHDR{false}; + + CCriticalSection m_resourceSection; + std::vector 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 m_irss; + std::vector 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 +#include +#include +#pragma warning(disable: 4091) +#include +#pragma warning(default: 4091) +#include + +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 CWinSystemWin32DX::CreateWinSystem() +{ + return std::make_unique(); +} + +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(width), static_cast(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(&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 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(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(GetProcAddress(m_hDriverModule, "OpenAdapter10_2")); + if (s_fnOpenAdapter10_2 != nullptr) + { + DetourTransactionBegin(); + DetourUpdateThread(GetCurrentThread()); + DetourAttach(reinterpret_cast(&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(floor(m_fRefreshRate)), &refreshNum, &refreshDen); + float diff = fabs(refreshRate - static_cast(refreshNum) / static_cast(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 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; +}; + -- cgit v1.2.3