diff --git a/platform/CMSWindowsScreenSaver.cpp b/platform/CMSWindowsScreenSaver.cpp new file mode 100755 index 00000000..2c4b3760 --- /dev/null +++ b/platform/CMSWindowsScreenSaver.cpp @@ -0,0 +1,298 @@ +#include "CMSWindowsScreenSaver.h" +#include "CThread.h" +#include "CLog.h" +#include "TMethodJob.h" + +#if !defined(SPI_GETSCREENSAVERRUNNING) +#define SPI_GETSCREENSAVERRUNNING 114 +#endif + +// +// CMSWindowsScreenSaver +// + +CMSWindowsScreenSaver::CMSWindowsScreenSaver() : + m_process(NULL), + m_threadID(0), + m_watch(NULL) +{ + // detect OS + m_is95Family = false; + m_is95 = false; + m_isNT = false; + OSVERSIONINFO info; + info.dwOSVersionInfoSize = sizeof(info); + if (GetVersionEx(&info)) { + m_is95Family = (info.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS); + if (info.dwPlatformId == VER_PLATFORM_WIN32_NT && + info.dwMajorVersion <= 4) { + m_isNT = true; + } + else if (info.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS && + info.dwMajorVersion == 4 && + info.dwMinorVersion == 0) { + m_is95 = true; + } + } + + // check if screen saver is enabled + SystemParametersInfo(SPI_GETSCREENSAVEACTIVE, 0, &m_wasEnabled, 0); +} + +CMSWindowsScreenSaver::~CMSWindowsScreenSaver() +{ + unwatchProcess(); +} + +bool +CMSWindowsScreenSaver::checkStarted(UINT msg, WPARAM wParam, LPARAM lParam) +{ + // screen saver may have started. look for it and get + // the process. if we can't find it then assume it + // didn't really start. we wait a moment before + // looking to give the screen saver a chance to start. + // this shouldn't be a problem since we only get here + // if the screen saver wants to kick in, meaning that + // the system is idle or the user deliberately started + // the screen saver. + Sleep(250); + HWND hwnd = findScreenSaver(); + if (hwnd == NULL) { + // didn't start + log((CLOG_DEBUG "can't find screen saver window")); + return false; + } + + // get the process + DWORD processID; + GetWindowThreadProcessId(hwnd, &processID); + HANDLE process = OpenProcess(SYNCHRONIZE, FALSE, processID); + if (process == NULL) { + // didn't start + log((CLOG_DEBUG "can't open screen saver process")); + return false; + } + + // watch for the process to exit + m_threadID = GetCurrentThreadId(); + m_msg = msg; + m_wParam = wParam; + m_lParam = lParam; + watchProcess(process); + + return true; +} + +void +CMSWindowsScreenSaver::enable() +{ + SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, m_wasEnabled, 0, 0); +} + +void +CMSWindowsScreenSaver::disable() +{ + SystemParametersInfo(SPI_GETSCREENSAVEACTIVE, 0, &m_wasEnabled, 0); + SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, FALSE, 0, 0); +} + +void +CMSWindowsScreenSaver::activate() +{ + // don't activate if already active + if (!isActive()) { + HWND hwnd = GetForegroundWindow(); + if (hwnd != NULL) { + PostMessage(hwnd, WM_SYSCOMMAND, SC_SCREENSAVE, 0); + } + else { + // no foreground window. pretend we got the event instead. + DefWindowProc(NULL, WM_SYSCOMMAND, SC_SCREENSAVE, 0); + } + } +} + +void +CMSWindowsScreenSaver::deactivate() +{ + bool killed = false; + if (!m_is95Family) { + // NT runs screen saver in another desktop + HDESK desktop = OpenDesktop("screen-saver", 0, FALSE, + DESKTOP_READOBJECTS | DESKTOP_WRITEOBJECTS); + if (desktop != NULL) { + EnumDesktopWindows(desktop, + &CMSWindowsScreenSaver::killScreenSaverFunc, + reinterpret_cast(&killed)); + CloseDesktop(desktop); + } + } + + // if above failed or wasn't tried, try the windows 95 way + if (!killed) { + // find screen saver window and close it + HWND hwnd = FindWindow("WindowsScreenSaverClass", NULL); + if (hwnd != NULL) { + PostMessage(hwnd, WM_CLOSE, 0, 0); + } + } + + // force timer to restart + SystemParametersInfo(SPI_GETSCREENSAVEACTIVE, 0, &m_wasEnabled, 0); + SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, + !m_wasEnabled, 0, SPIF_SENDWININICHANGE); + SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, + m_wasEnabled, 0, SPIF_SENDWININICHANGE); +} + +bool +CMSWindowsScreenSaver::isActive() const +{ + if (m_is95) { + return (FindWindow("WindowsScreenSaverClass", NULL) != NULL); + } + else if (m_isNT) { + // screen saver runs on a separate desktop + HDESK desktop = OpenDesktop("screen-saver", 0, FALSE, MAXIMUM_ALLOWED); + if (desktop == NULL && GetLastError() != ERROR_ACCESS_DENIED) { + // desktop doesn't exist so screen saver is not running + return false; + } + + // desktop exists. this should indicate that the screen saver + // is running but an OS bug can cause a valid handle to be + // returned even if the screen saver isn't running (Q230117). + // we'll try to enumerate the windows on the desktop and, if + // there are any, we assume the screen saver is running. (note + // that if we don't have permission to enumerate then we'll + // assume that the screen saver is not running.) that'd be + // easy enough except there's another OS bug (Q198590) that can + // cause EnumDesktopWindows() to enumerate the windows of + // another desktop if the requested desktop has no windows. to + // work around that we have to verify that the enumerated + // windows are, in fact, on the expected desktop. + CFindScreenSaverInfo info; + info.m_desktop = desktop; + info.m_window = NULL; + EnumDesktopWindows(desktop, + &CMSWindowsScreenSaver::findScreenSaverFunc, + reinterpret_cast(&info)); + + // done with desktop + CloseDesktop(desktop); + + // screen saver is running if a window was found + return (info.m_window != NULL); + } + else { + BOOL running; + SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, &running, 0); + return (running != FALSE); + } +} + +BOOL CALLBACK +CMSWindowsScreenSaver::findScreenSaverFunc(HWND hwnd, LPARAM arg) +{ + CFindScreenSaverInfo* info = reinterpret_cast(arg); + + if (info->m_desktop != NULL) { + DWORD threadID = GetWindowThreadProcessId(hwnd, NULL); + HDESK desktop = GetThreadDesktop(threadID); + if (desktop != NULL && desktop != info->m_desktop) { + // stop enumerating -- wrong desktop + return FALSE; + } + } + + // found a window + info->m_window = hwnd; + + // don't need to enumerate further + return FALSE; +} + +BOOL CALLBACK +CMSWindowsScreenSaver::killScreenSaverFunc(HWND hwnd, LPARAM arg) +{ + if (IsWindowVisible(hwnd)) { + PostMessage(hwnd, WM_CLOSE, 0, 0); + *reinterpret_cast(arg) = true; + } + return TRUE; +} + +HWND +CMSWindowsScreenSaver::findScreenSaver() +{ + if (!m_is95Family) { + // screen saver runs on a separate desktop + HDESK desktop = OpenDesktop("screen-saver", 0, FALSE, MAXIMUM_ALLOWED); + if (desktop != NULL) { + // search + CFindScreenSaverInfo info; + info.m_desktop = desktop; + info.m_window = NULL; + EnumDesktopWindows(desktop, + &CMSWindowsScreenSaver::findScreenSaverFunc, + reinterpret_cast(&info)); + + // done with desktop + CloseDesktop(desktop); + + // return window if found + if (info.m_window != NULL) { + return info.m_window; + } + } + } + + // try windows 95 way + return FindWindow("WindowsScreenSaverClass", NULL); +} + +void +CMSWindowsScreenSaver::watchProcess(HANDLE process) +{ + // stop watching previous process + unwatchProcess(); + + // watch new process in another thread + if (process != NULL) { + log((CLOG_DEBUG "watching screen saver process")); + m_process = process; + m_watch = new CThread(new TMethodJob(this, + &CMSWindowsScreenSaver::watchProcessThread)); + } +} + +void +CMSWindowsScreenSaver::unwatchProcess() +{ + if (m_watch != NULL) { + log((CLOG_DEBUG "stopped watching screen saver process")); + m_watch->cancel(); + m_watch->wait(); + delete m_watch; + m_watch = NULL; + } + if (m_process != NULL) { + CloseHandle(m_process); + m_process = NULL; + } +} + +void +CMSWindowsScreenSaver::watchProcessThread(void*) +{ + for (;;) { + CThread::testCancel(); + if (WaitForSingleObject(m_process, 50) == WAIT_OBJECT_0) { + // process terminated. send screen saver deactivation + // message. + log((CLOG_DEBUG "screen saver died")); + PostThreadMessage(m_threadID, m_msg, m_wParam, m_lParam); + return; + } + } +} diff --git a/platform/CMSWindowsScreenSaver.h b/platform/CMSWindowsScreenSaver.h new file mode 100755 index 00000000..df4a342b --- /dev/null +++ b/platform/CMSWindowsScreenSaver.h @@ -0,0 +1,59 @@ +#ifndef CMSWINDOWSSCREENSAVER_H +#define CMSWINDOWSSCREENSAVER_H + +#include "IScreenSaver.h" +#define WIN32_LEAN_AND_MEAN +#include + +class CThread; + +class CMSWindowsScreenSaver : public IScreenSaver { +public: + CMSWindowsScreenSaver(); + virtual ~CMSWindowsScreenSaver(); + + // manipulators + + // check if the screen saver really started. returns false if it + // hasn't, true otherwise. when the screen saver stops msg will + // be posted to the current thread's message queue with the given + // parameters. + bool checkStarted(UINT msg, WPARAM, LPARAM); + + // IScreenSaver overrides + virtual void enable(); + virtual void disable(); + virtual void activate(); + virtual void deactivate(); + virtual bool isActive() const; + +private: + class CFindScreenSaverInfo { + public: + HDESK m_desktop; + HWND m_window; + }; + + static BOOL CALLBACK findScreenSaverFunc(HWND hwnd, LPARAM lParam); + static BOOL CALLBACK killScreenSaverFunc(HWND hwnd, LPARAM lParam); + + HWND findScreenSaver(); + void watchProcess(HANDLE process); + void unwatchProcess(); + void watchProcessThread(void*); + +private: + bool m_is95Family; + bool m_is95; + bool m_isNT; + BOOL m_wasEnabled; + + HANDLE m_process; + CThread* m_watch; + DWORD m_threadID; + UINT m_msg; + WPARAM m_wParam; + LPARAM m_lParam; +}; + +#endif