/*
 * synergy -- mouse and keyboard sharing utility
 * Copyright (C) 2012 Synergy Si Ltd.
 * Copyright (C) 2002 Chris Schoeneman
 * 
 * This package is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * found in the file COPYING that should have accompanied this file.
 * 
 * This package is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "platform/MSWindowsScreenSaver.h"

#include "platform/MSWindowsScreen.h"
#include "mt/Thread.h"
#include "arch/Arch.h"
#include "arch/win32/ArchMiscWindows.h"
#include "base/Log.h"
#include "base/TMethodJob.h"

#include <malloc.h>
#include <tchar.h>

#if !defined(SPI_GETSCREENSAVERRUNNING)
#define SPI_GETSCREENSAVERRUNNING 114
#endif

static const TCHAR* g_isSecureNT = "ScreenSaverIsSecure";
static const TCHAR* g_isSecure9x = "ScreenSaveUsePassword";
static const TCHAR* const g_pathScreenSaverIsSecure[] = {
	"Control Panel",
	"Desktop",
	NULL
};

//
// MSWindowsScreenSaver
//

MSWindowsScreenSaver::MSWindowsScreenSaver() :
	m_wasSecure(false),
	m_wasSecureAnInt(false),
	m_process(NULL),
	m_watch(NULL),
	m_threadID(0),
	m_active(false)
{
	// check if screen saver is enabled
	SystemParametersInfo(SPI_GETSCREENSAVEACTIVE, 0, &m_wasEnabled, 0);
}

MSWindowsScreenSaver::~MSWindowsScreenSaver()
{
	unwatchProcess();
}

bool
MSWindowsScreenSaver::checkStarted(UINT msg, WPARAM wParam, LPARAM lParam)
{
	// if already started then say it didn't just start
	if (m_active) {
		return false;
	}

	// 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);

	// set parameters common to all screen saver handling
	m_threadID = GetCurrentThreadId();
	m_msg      = msg;
	m_wParam   = wParam;
	m_lParam   = lParam;

	// on the windows nt family we wait for the desktop to
	// change until it's neither the Screen-Saver desktop
	// nor a desktop we can't open (the login desktop).
	// since windows will send the request-to-start-screen-
	// saver message even when the screen saver is disabled
	// we first check that the screen saver is indeed active
	// before watching for it to stop.
	if (!isActive()) {
		LOG((CLOG_DEBUG2 "can't open screen saver desktop"));
		return false;
	}

	watchDesktop();
	return true;
}

void
MSWindowsScreenSaver::enable()
{
	SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, m_wasEnabled, 0, 0);

	// restore password protection
	if (m_wasSecure) {
		setSecure(true, m_wasSecureAnInt);
	}

	// restore display power down
	ArchMiscWindows::removeBusyState(ArchMiscWindows::kDISPLAY);
}

void
MSWindowsScreenSaver::disable()
{
	SystemParametersInfo(SPI_GETSCREENSAVEACTIVE, 0, &m_wasEnabled, 0);
	SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, FALSE, 0, 0);

	// disable password protected screensaver
	m_wasSecure = isSecure(&m_wasSecureAnInt);
	if (m_wasSecure) {
		setSecure(false, m_wasSecureAnInt);
	}

	// disable display power down
	ArchMiscWindows::addBusyState(ArchMiscWindows::kDISPLAY);
}

void
MSWindowsScreenSaver::activate()
{
	// don't activate if already active
	if (!isActive()) {
		// activate
		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);
		}

		// restore power save when screen saver activates
		ArchMiscWindows::removeBusyState(ArchMiscWindows::kDISPLAY);
	}
}

void
MSWindowsScreenSaver::deactivate()
{
	bool killed = false;

	// NT runs screen saver in another desktop
	HDESK desktop = OpenDesktop("Screen-saver", 0, FALSE,
							DESKTOP_READOBJECTS | DESKTOP_WRITEOBJECTS);
	if (desktop != NULL) {
		EnumDesktopWindows(desktop,
							&MSWindowsScreenSaver::killScreenSaverFunc,
							reinterpret_cast<LPARAM>(&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) {
			// win2k may use a different class
			hwnd = FindWindow("Default Screen Saver", 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);

	// disable display power down
	ArchMiscWindows::removeBusyState(ArchMiscWindows::kDISPLAY);
}

bool
MSWindowsScreenSaver::isActive() const
{
	BOOL running;
	SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, &running, 0);
	return (running != FALSE);
}

BOOL CALLBACK
MSWindowsScreenSaver::killScreenSaverFunc(HWND hwnd, LPARAM arg)
{
	if (IsWindowVisible(hwnd)) {
		HINSTANCE instance = (HINSTANCE)GetWindowLongPtr(hwnd, GWLP_HINSTANCE);
		if (instance != MSWindowsScreen::getWindowInstance()) {
			PostMessage(hwnd, WM_CLOSE, 0, 0);
			*reinterpret_cast<bool*>(arg) = true;
		}
	}
	return TRUE;
}

void
MSWindowsScreenSaver::watchDesktop()
{
	// stop watching previous process/desktop
	unwatchProcess();

	// watch desktop in another thread
	LOG((CLOG_DEBUG "watching screen saver desktop"));
	m_active = true;
	m_watch  = new Thread(new TMethodJob<MSWindowsScreenSaver>(this,
								&MSWindowsScreenSaver::watchDesktopThread));
}

void
MSWindowsScreenSaver::watchProcess(HANDLE process)
{
	// stop watching previous process/desktop
	unwatchProcess();

	// watch new process in another thread
	if (process != NULL) {
		LOG((CLOG_DEBUG "watching screen saver process"));
		m_process = process;
		m_active  = true;
		m_watch   = new Thread(new TMethodJob<MSWindowsScreenSaver>(this,
								&MSWindowsScreenSaver::watchProcessThread));
	}
}

void
MSWindowsScreenSaver::unwatchProcess()
{
	if (m_watch != NULL) {
		LOG((CLOG_DEBUG "stopped watching screen saver process/desktop"));
		m_watch->cancel();
		m_watch->wait();
		delete m_watch;
		m_watch  = NULL;
		m_active = false;
	}
	if (m_process != NULL) {
		CloseHandle(m_process);
		m_process = NULL;
	}
}

void
MSWindowsScreenSaver::watchDesktopThread(void*)
{
	DWORD reserved = 0;
	TCHAR* name    = NULL;

	for (;;) {
		// wait a bit
		ARCH->sleep(0.2);

		BOOL running;
		SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, &running, 0);
		if (running) {
			continue;
		}

		// send screen saver deactivation message
		m_active = false;
		PostThreadMessage(m_threadID, m_msg, m_wParam, m_lParam);
		return;
	}
}

void
MSWindowsScreenSaver::watchProcessThread(void*)
{
	for (;;) {
		Thread::testCancel();
		if (WaitForSingleObject(m_process, 50) == WAIT_OBJECT_0) {
			// process terminated
			LOG((CLOG_DEBUG "screen saver died"));

			// send screen saver deactivation message
			m_active = false;
			PostThreadMessage(m_threadID, m_msg, m_wParam, m_lParam);
			return;
		}
	}
}

void
MSWindowsScreenSaver::setSecure(bool secure, bool saveSecureAsInt)
{
	HKEY hkey =
		ArchMiscWindows::addKey(HKEY_CURRENT_USER, g_pathScreenSaverIsSecure);
	if (hkey == NULL) {
		return;
	}

	if (saveSecureAsInt) {
		ArchMiscWindows::setValue(hkey, g_isSecureNT, secure ? 1 : 0);
	}
	else {
		ArchMiscWindows::setValue(hkey, g_isSecureNT, secure ? "1" : "0");
	}

	ArchMiscWindows::closeKey(hkey);
}

bool
MSWindowsScreenSaver::isSecure(bool* wasSecureFlagAnInt) const
{
	// get the password protection setting key
	HKEY hkey =
		ArchMiscWindows::openKey(HKEY_CURRENT_USER, g_pathScreenSaverIsSecure);
	if (hkey == NULL) {
		return false;
	}

	// get the value.  the value may be an int or a string, depending
	// on the version of windows.
	bool result;
	switch (ArchMiscWindows::typeOfValue(hkey, g_isSecureNT)) {
	default:
		result = false;
		break;

	case ArchMiscWindows::kUINT: {
		DWORD value =
			ArchMiscWindows::readValueInt(hkey, g_isSecureNT);
		*wasSecureFlagAnInt = true;
		result = (value != 0);
		break;
	}

	case ArchMiscWindows::kSTRING: {
		std::string value =
			ArchMiscWindows::readValueString(hkey, g_isSecureNT);
		*wasSecureFlagAnInt = false;
		result = (value != "0");
		break;
	}
	}

	ArchMiscWindows::closeKey(hkey);
	return result;
}