diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
commit | c04dcc2e7d834218ef2d4194331e383402495ae1 (patch) | |
tree | 7333e38d10d75386e60f336b80c2443c1166031d /xbmc/powermanagement | |
parent | Initial commit. (diff) | |
download | kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip |
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xbmc/powermanagement')
-rw-r--r-- | xbmc/powermanagement/CMakeLists.txt | 14 | ||||
-rw-r--r-- | xbmc/powermanagement/DPMSSupport.cpp | 44 | ||||
-rw-r--r-- | xbmc/powermanagement/DPMSSupport.h | 56 | ||||
-rw-r--r-- | xbmc/powermanagement/IPowerSyscall.cpp | 24 | ||||
-rw-r--r-- | xbmc/powermanagement/IPowerSyscall.h | 111 | ||||
-rw-r--r-- | xbmc/powermanagement/PowerManager.cpp | 315 | ||||
-rw-r--r-- | xbmc/powermanagement/PowerManager.h | 69 | ||||
-rw-r--r-- | xbmc/powermanagement/PowerTypes.h | 21 | ||||
-rw-r--r-- | xbmc/powermanagement/WinIdleTimer.h | 18 |
9 files changed, 672 insertions, 0 deletions
diff --git a/xbmc/powermanagement/CMakeLists.txt b/xbmc/powermanagement/CMakeLists.txt new file mode 100644 index 0000000..a1953d4 --- /dev/null +++ b/xbmc/powermanagement/CMakeLists.txt @@ -0,0 +1,14 @@ +set(SOURCES DPMSSupport.cpp + IPowerSyscall.cpp + PowerManager.cpp) + +set(HEADERS DPMSSupport.h + IPowerSyscall.h + PowerManager.h + PowerTypes.h) + +if(CORE_SYSTEM_NAME MATCHES windows) + list(APPEND HEADERS WinIdleTimer.h) +endif() + +core_add_library(powermanagement) diff --git a/xbmc/powermanagement/DPMSSupport.cpp b/xbmc/powermanagement/DPMSSupport.cpp new file mode 100644 index 0000000..077117d --- /dev/null +++ b/xbmc/powermanagement/DPMSSupport.cpp @@ -0,0 +1,44 @@ +/* + * 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 "DPMSSupport.h" + +#include "ServiceBroker.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "settings/lib/Setting.h" +#include "utils/log.h" + +#include <array> +#include <string> + +CDPMSSupport::CDPMSSupport() +{ + auto settingsComponent = CServiceBroker::GetSettingsComponent(); + if (settingsComponent) + { + auto settings = settingsComponent->GetSettings(); + if (settings) + { + auto setting = settings->GetSetting(CSettings::SETTING_POWERMANAGEMENT_DISPLAYSOFF); + if (setting) + setting->SetRequirementsMet(true); + } + } +} + +bool CDPMSSupport::IsModeSupported(PowerSavingMode mode) const +{ + for (const auto& supportedModes : m_supportedModes) + { + if (supportedModes == mode) + return true; + } + + return false; +} diff --git a/xbmc/powermanagement/DPMSSupport.h b/xbmc/powermanagement/DPMSSupport.h new file mode 100644 index 0000000..cf10b4c --- /dev/null +++ b/xbmc/powermanagement/DPMSSupport.h @@ -0,0 +1,56 @@ +/* + * 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. + */ + +#pragma once + +#include <vector> + +// This class encapsulates support for monitor power-saving features (DPMS). +// An instance is connected to a Surface, provides information on which +// power-saving features are available for that screen, and it is able to +// turn power-saving on an off. +// Note that SDL turns off DPMS timeouts at the beginning of the application. +class CDPMSSupport +{ +public: + // All known DPMS power-saving modes, on any platform. + enum PowerSavingMode + { + STANDBY, + SUSPEND, + OFF, + NUM_MODES, + }; + + CDPMSSupport(); + virtual ~CDPMSSupport() = default; + + // Whether power-saving is supported on this screen. + bool IsSupported() const { return !m_supportedModes.empty(); } + + // Which power-saving modes are supported, in the order of preference (i.e. + // the first mode should be the best choice). + const std::vector<PowerSavingMode>& GetSupportedModes() const + { + return m_supportedModes; + } + + // Whether a given mode is supported. + bool IsModeSupported(PowerSavingMode mode) const; + + // Turns on the specified power-saving mode, which must be valid + // and supported. Returns false if this failed. + virtual bool EnablePowerSaving(PowerSavingMode mode) = 0; + + // Turns off power-saving mode. You should only call this if the display + // is currently in a power-saving mode, to avoid visual artifacts. + virtual bool DisablePowerSaving() = 0; + +protected: + std::vector<PowerSavingMode> m_supportedModes; +}; diff --git a/xbmc/powermanagement/IPowerSyscall.cpp b/xbmc/powermanagement/IPowerSyscall.cpp new file mode 100644 index 0000000..623337f --- /dev/null +++ b/xbmc/powermanagement/IPowerSyscall.cpp @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "IPowerSyscall.h" + +CreatePowerSyscallFunc IPowerSyscall::m_createFunc = nullptr; + +IPowerSyscall* IPowerSyscall::CreateInstance() +{ + if (m_createFunc) + return m_createFunc(); + + return nullptr; +} + +void IPowerSyscall::RegisterPowerSyscall(CreatePowerSyscallFunc createFunc) +{ + m_createFunc = createFunc; +}
\ No newline at end of file diff --git a/xbmc/powermanagement/IPowerSyscall.h b/xbmc/powermanagement/IPowerSyscall.h new file mode 100644 index 0000000..f54186e --- /dev/null +++ b/xbmc/powermanagement/IPowerSyscall.h @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +class IPowerEventsCallback +{ +public: + virtual ~IPowerEventsCallback() = default; + + virtual void OnSleep() = 0; + virtual void OnWake() = 0; + + virtual void OnLowBattery() = 0; +}; + +class IPowerSyscall; +using CreatePowerSyscallFunc = IPowerSyscall* (*)(); + +class IPowerSyscall +{ +public: + /**\brief Called by power manager to create platform power system adapter + * + * This method used to create platform specified power system adapter + */ + static IPowerSyscall* CreateInstance(); + static void RegisterPowerSyscall(CreatePowerSyscallFunc createFunc); + + virtual ~IPowerSyscall() = default; + virtual bool Powerdown() = 0; + virtual bool Suspend() = 0; + virtual bool Hibernate() = 0; + virtual bool Reboot() = 0; + +// Might need to be membervariables instead for speed + virtual bool CanPowerdown() = 0; + virtual bool CanSuspend() = 0; + virtual bool CanHibernate() = 0; + virtual bool CanReboot() = 0; + + virtual int CountPowerFeatures() = 0; + +// Battery related functions + virtual int BatteryLevel() = 0; + + /*! + \brief Pump power related events back to xbmc. + + PumpPowerEvents is called from Application Thread and the platform implementation may signal + power related events back to xbmc through the callback. + + return true if an event occurred and false if not. + + \param callback the callback to signal to + */ + virtual bool PumpPowerEvents(IPowerEventsCallback *callback) = 0; + + static const int MAX_COUNT_POWER_FEATURES = 4; + +private: + static CreatePowerSyscallFunc m_createFunc; +}; + +class CAbstractPowerSyscall : public IPowerSyscall +{ +public: + int CountPowerFeatures() override + { + return (CanPowerdown() ? 1 : 0) + + (CanSuspend() ? 1 : 0) + + (CanHibernate() ? 1 : 0) + + (CanReboot() ? 1 : 0); + } +}; + +class CPowerSyscallWithoutEvents : public CAbstractPowerSyscall +{ +public: + CPowerSyscallWithoutEvents() { m_OnResume = false; m_OnSuspend = false; } + + bool Suspend() override { m_OnSuspend = true; return false; } + bool Hibernate() override { m_OnSuspend = true; return false; } + + bool PumpPowerEvents(IPowerEventsCallback *callback) override + { + if (m_OnSuspend) + { + callback->OnSleep(); + m_OnSuspend = false; + m_OnResume = true; + return true; + } + else if (m_OnResume) + { + callback->OnWake(); + m_OnResume = false; + return true; + } + else + return false; + } +private: + bool m_OnResume; + bool m_OnSuspend; +}; diff --git a/xbmc/powermanagement/PowerManager.cpp b/xbmc/powermanagement/PowerManager.cpp new file mode 100644 index 0000000..3c30817 --- /dev/null +++ b/xbmc/powermanagement/PowerManager.cpp @@ -0,0 +1,315 @@ +/* + * 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 "PowerManager.h" + +#include "FileItem.h" +#include "PowerTypes.h" +#include "ServiceBroker.h" +#include "application/AppParams.h" +#include "application/Application.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPlayer.h" +#include "application/ApplicationPowerHandling.h" +#include "application/ApplicationStackHelper.h" +#include "cores/AudioEngine/Interfaces/AE.h" +#include "dialogs/GUIDialogBusyNoCancel.h" +#include "dialogs/GUIDialogKaiToast.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "interfaces/AnnouncementManager.h" +#include "network/Network.h" +#include "pvr/PVRManager.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "settings/lib/Setting.h" +#include "settings/lib/SettingDefinitions.h" +#include "settings/lib/SettingsManager.h" +#include "utils/log.h" +#include "weather/WeatherManager.h" + +#include <list> +#include <memory> + +#if defined(TARGET_WINDOWS_DESKTOP) +extern HWND g_hWnd; +#endif + +CPowerManager::CPowerManager() : m_settings(CServiceBroker::GetSettingsComponent()->GetSettings()) +{ + m_settings->GetSettingsManager()->RegisterSettingOptionsFiller("shutdownstates", SettingOptionsShutdownStatesFiller); +} + +CPowerManager::~CPowerManager() = default; + +void CPowerManager::Initialize() +{ + m_instance.reset(IPowerSyscall::CreateInstance()); +} + +void CPowerManager::SetDefaults() +{ + auto setting = m_settings->GetSetting(CSettings::SETTING_POWERMANAGEMENT_SHUTDOWNSTATE); + if (!setting) + { + CLog::Log(LOGERROR, "Failed to load setting for: {}", + CSettings::SETTING_POWERMANAGEMENT_SHUTDOWNSTATE); + return; + } + + int defaultShutdown = m_settings->GetInt(CSettings::SETTING_POWERMANAGEMENT_SHUTDOWNSTATE); + + switch (defaultShutdown) + { + case POWERSTATE_QUIT: + case POWERSTATE_MINIMIZE: + // assume we can shutdown if --standalone is passed + if (CServiceBroker::GetAppParams()->IsStandAlone()) + defaultShutdown = POWERSTATE_SHUTDOWN; + break; + case POWERSTATE_HIBERNATE: + if (!CServiceBroker::GetPowerManager().CanHibernate()) + { + if (CServiceBroker::GetPowerManager().CanSuspend()) + defaultShutdown = POWERSTATE_SUSPEND; + else + defaultShutdown = CServiceBroker::GetPowerManager().CanPowerdown() ? POWERSTATE_SHUTDOWN : POWERSTATE_QUIT; + } + break; + case POWERSTATE_SUSPEND: + if (!CServiceBroker::GetPowerManager().CanSuspend()) + { + if (CServiceBroker::GetPowerManager().CanHibernate()) + defaultShutdown = POWERSTATE_HIBERNATE; + else + defaultShutdown = CServiceBroker::GetPowerManager().CanPowerdown() ? POWERSTATE_SHUTDOWN : POWERSTATE_QUIT; + } + break; + case POWERSTATE_SHUTDOWN: + if (!CServiceBroker::GetPowerManager().CanPowerdown()) + { + if (CServiceBroker::GetPowerManager().CanSuspend()) + defaultShutdown = POWERSTATE_SUSPEND; + else + defaultShutdown = CServiceBroker::GetPowerManager().CanHibernate() ? POWERSTATE_HIBERNATE : POWERSTATE_QUIT; + } + break; + } + + std::static_pointer_cast<CSettingInt>(setting)->SetDefault(defaultShutdown); +} + +bool CPowerManager::Powerdown() +{ + if (CanPowerdown() && m_instance->Powerdown()) + { + CGUIDialogBusyNoCancel* dialog = + CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogBusyNoCancel>( + WINDOW_DIALOG_BUSY_NOCANCEL); + if (dialog) + dialog->Open(); + + return true; + } + + return false; +} + +bool CPowerManager::Suspend() +{ + return (CanSuspend() && m_instance->Suspend()); +} + +bool CPowerManager::Hibernate() +{ + return (CanHibernate() && m_instance->Hibernate()); +} + +bool CPowerManager::Reboot() +{ + bool success = CanReboot() ? m_instance->Reboot() : false; + + if (success) + { + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::System, "OnRestart"); + + CGUIDialogBusyNoCancel* dialog = + CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogBusyNoCancel>( + WINDOW_DIALOG_BUSY_NOCANCEL); + if (dialog) + dialog->Open(); + } + + return success; +} + +bool CPowerManager::CanPowerdown() +{ + return m_instance ? m_instance->CanPowerdown() : false; +} +bool CPowerManager::CanSuspend() +{ + return m_instance ? m_instance->CanSuspend() : false; +} +bool CPowerManager::CanHibernate() +{ + return m_instance ? m_instance->CanHibernate() : false; +} +bool CPowerManager::CanReboot() +{ + return m_instance ? m_instance->CanReboot() : false; +} +int CPowerManager::BatteryLevel() +{ + return m_instance ? m_instance->BatteryLevel() : 0; +} +void CPowerManager::ProcessEvents() +{ + if (!m_instance) + return; + + static int nesting = 0; + + if (++nesting == 1) + m_instance->PumpPowerEvents(this); + + nesting--; +} + +void CPowerManager::OnSleep() +{ + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::System, "OnSleep"); + + CGUIDialogBusyNoCancel* dialog = + CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogBusyNoCancel>( + WINDOW_DIALOG_BUSY_NOCANCEL); + if (dialog) + dialog->Open(); + + CLog::Log(LOGINFO, "{}: Running sleep jobs", __FUNCTION__); + + StorePlayerState(); + + g_application.StopPlaying(); + CServiceBroker::GetPVRManager().OnSleep(); + auto& components = CServiceBroker::GetAppComponents(); + const auto appPower = components.GetComponent<CApplicationPowerHandling>(); + appPower->StopShutdownTimer(); + appPower->StopScreenSaverTimer(); + g_application.CloseNetworkShares(); + CServiceBroker::GetActiveAE()->Suspend(); +} + +void CPowerManager::OnWake() +{ + CLog::Log(LOGINFO, "{}: Running resume jobs", __FUNCTION__); + + CServiceBroker::GetNetwork().WaitForNet(); + + // reset out timers + auto& components = CServiceBroker::GetAppComponents(); + const auto appPower = components.GetComponent<CApplicationPowerHandling>(); + appPower->ResetShutdownTimers(); + + CGUIDialogBusyNoCancel* dialog = + CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogBusyNoCancel>( + WINDOW_DIALOG_BUSY_NOCANCEL); + if (dialog) + dialog->Close(true); // force close. no closing animation, sound etc at this early stage + +#if defined(TARGET_DARWIN_OSX) || defined(TARGET_WINDOWS) + if (CServiceBroker::GetWinSystem()->IsFullScreen()) + { +#if defined(TARGET_WINDOWS_DESKTOP) + ShowWindow(g_hWnd, SW_RESTORE); + SetForegroundWindow(g_hWnd); +#endif + } + appPower->ResetScreenSaver(); +#endif + + CServiceBroker::GetActiveAE()->Resume(); + g_application.UpdateLibraries(); + CServiceBroker::GetWeatherManager().Refresh(); + CServiceBroker::GetPVRManager().OnWake(); + RestorePlayerState(); + + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::System, "OnWake"); +} + +void CPowerManager::OnLowBattery() +{ + CLog::Log(LOGINFO, "{}: Running low battery jobs", __FUNCTION__); + + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Warning, g_localizeStrings.Get(13050), ""); + + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::System, "OnLowBattery"); +} + +void CPowerManager::StorePlayerState() +{ + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (appPlayer->IsPlaying()) + { + m_lastUsedPlayer = appPlayer->GetCurrentPlayer(); + m_lastPlayedFileItem.reset(new CFileItem(g_application.CurrentFileItem())); + // set the actual offset instead of store and load it from database + m_lastPlayedFileItem->SetStartOffset(appPlayer->GetTime()); + // in case of regular stack, correct the start offset by adding current part start time + const auto stackHelper = components.GetComponent<CApplicationStackHelper>(); + if (stackHelper->IsPlayingRegularStack()) + m_lastPlayedFileItem->SetStartOffset(m_lastPlayedFileItem->GetStartOffset() + + stackHelper->GetCurrentStackPartStartTimeMs()); + // in case of iso stack, keep track of part number + m_lastPlayedFileItem->m_lStartPartNumber = + stackHelper->IsPlayingISOStack() ? stackHelper->GetCurrentPartNumber() + 1 : 1; + // for iso and iso stacks, keep track of playerstate + m_lastPlayedFileItem->SetProperty("savedplayerstate", appPlayer->GetPlayerState()); + CLog::Log(LOGDEBUG, + "CPowerManager::StorePlayerState - store last played item (startOffset: {} ms)", + m_lastPlayedFileItem->GetStartOffset()); + } + else + { + m_lastUsedPlayer.clear(); + m_lastPlayedFileItem.reset(); + } +} + +void CPowerManager::RestorePlayerState() +{ + if (!m_lastPlayedFileItem) + return; + + CLog::Log(LOGDEBUG, + "CPowerManager::RestorePlayerState - resume last played item (startOffset: {} ms)", + m_lastPlayedFileItem->GetStartOffset()); + g_application.PlayFile(*m_lastPlayedFileItem, m_lastUsedPlayer); +} + +void CPowerManager::SettingOptionsShutdownStatesFiller(const SettingConstPtr& setting, + std::vector<IntegerSettingOption>& list, + int& current, + void* data) +{ + if (CServiceBroker::GetPowerManager().CanPowerdown()) + list.emplace_back(g_localizeStrings.Get(13005), POWERSTATE_SHUTDOWN); + if (CServiceBroker::GetPowerManager().CanHibernate()) + list.emplace_back(g_localizeStrings.Get(13010), POWERSTATE_HIBERNATE); + if (CServiceBroker::GetPowerManager().CanSuspend()) + list.emplace_back(g_localizeStrings.Get(13011), POWERSTATE_SUSPEND); + if (!CServiceBroker::GetAppParams()->IsStandAlone()) + { + list.emplace_back(g_localizeStrings.Get(13009), POWERSTATE_QUIT); +#if !defined(TARGET_DARWIN_EMBEDDED) + list.emplace_back(g_localizeStrings.Get(13014), POWERSTATE_MINIMIZE); +#endif + } +} diff --git a/xbmc/powermanagement/PowerManager.h b/xbmc/powermanagement/PowerManager.h new file mode 100644 index 0000000..319be9d --- /dev/null +++ b/xbmc/powermanagement/PowerManager.h @@ -0,0 +1,69 @@ +/* + * 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 "IPowerSyscall.h" + +#include <memory> +#include <string> +#include <utility> +#include <vector> + +class CFileItem; +class CSetting; +class CSettings; + +struct IntegerSettingOption; + +// This class will wrap and handle PowerSyscalls. +// It will handle and decide if syscalls are needed. +class CPowerManager : public IPowerEventsCallback +{ +public: + CPowerManager(); + ~CPowerManager() override; + + void Initialize(); + void SetDefaults(); + + bool Powerdown(); + bool Suspend(); + bool Hibernate(); + bool Reboot(); + + bool CanPowerdown(); + bool CanSuspend(); + bool CanHibernate(); + bool CanReboot(); + + int BatteryLevel(); + + void ProcessEvents(); + + static void SettingOptionsShutdownStatesFiller(const std::shared_ptr<const CSetting>& setting, + std::vector<IntegerSettingOption>& list, + int& current, + void* data); + + IPowerSyscall* GetPowerSyscall() const { return m_instance.get(); } + +private: + void OnSleep() override; + void OnWake() override; + void OnLowBattery() override; + void RestorePlayerState(); + void StorePlayerState(); + + // Construction parameters + std::shared_ptr<CSettings> m_settings; + + std::unique_ptr<IPowerSyscall> m_instance; + std::unique_ptr<CFileItem> m_lastPlayedFileItem; + std::string m_lastUsedPlayer; +}; diff --git a/xbmc/powermanagement/PowerTypes.h b/xbmc/powermanagement/PowerTypes.h new file mode 100644 index 0000000..625d00e --- /dev/null +++ b/xbmc/powermanagement/PowerTypes.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +enum PowerState +{ + POWERSTATE_QUIT = 0, + POWERSTATE_SHUTDOWN, + POWERSTATE_HIBERNATE, + POWERSTATE_SUSPEND, + POWERSTATE_REBOOT, + POWERSTATE_MINIMIZE, + POWERSTATE_NONE, + POWERSTATE_ASK +}; diff --git a/xbmc/powermanagement/WinIdleTimer.h b/xbmc/powermanagement/WinIdleTimer.h new file mode 100644 index 0000000..d7a6f8f --- /dev/null +++ b/xbmc/powermanagement/WinIdleTimer.h @@ -0,0 +1,18 @@ +/* + * 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 "utils/Stopwatch.h" + +class CWinIdleTimer : public CStopWatch +{ +public: + void StartZero(); +}; + |