//  Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
//
//  This file is part of the VNC system.
//
//  The VNC system is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This program 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, write to the Free Software
//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
//  USA.
//
// If the source code for the VNC system is not available from the place 
// whence you received this file, check http://www.uk.research.att.com/vnc or contact
// the authors on vnc@uk.research.att.com for information on obtaining it.


// Flasher.cpp: implementation of the Flasher class.

#include "stdhdrs.h"
#include "vncviewer.h"
#include "Flasher.h"
#include "Exception.h"
#include "common/win32_helpers.h"

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

#define FLASHER_CLASS_NAME "VNCviewer Flasher"
#define FLASHFONTHEIGHT 80

extern char sz_G1[64];
extern char sz_G2[64];
extern char sz_G3[64];

Flasher::Flasher(int port)
{
	// Create a dummy window.  We don't use it for anything except
	// receiving socket events, so a seperate listening thread would
	// probably be easier!

	WNDCLASSEX wndclass;

	wndclass.cbSize			= sizeof(wndclass);
	wndclass.style			= CS_HREDRAW | CS_VREDRAW;
	wndclass.lpfnWndProc	= Flasher::WndProc;
	wndclass.cbClsExtra		= 0;
	wndclass.cbWndExtra		= 0;
	wndclass.hInstance		= pApp->m_instance;
	wndclass.hIcon			= LoadIcon(NULL, IDI_APPLICATION);
	wndclass.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wndclass.hbrBackground	= (HBRUSH) GetStockObject(WHITE_BRUSH);
	wndclass.lpszMenuName	= (const char *) NULL;
	wndclass.lpszClassName	= FLASHER_CLASS_NAME;
	wndclass.hIconSm		= LoadIcon(NULL, IDI_APPLICATION);

	RegisterClassEx(&wndclass);

	m_hwnd = CreateWindow(FLASHER_CLASS_NAME,
				FLASHER_CLASS_NAME,
				WS_OVERLAPPEDWINDOW,
				CW_USEDEFAULT,
				CW_USEDEFAULT,
				200, 200,
				NULL,
				NULL,
				pApp->m_instance,
				NULL);
	
	// record which client created this window
	helper::SafeSetWindowUserData(m_hwnd, (LONG)this);
	// Select a font for displaying user name
	LOGFONT lf;
	memset(&lf, 0, sizeof(lf));
	lf.lfHeight = FLASHFONTHEIGHT;
	lf.lfWeight = FW_BOLD;
	lf.lfItalic = 1;
	lf.lfPitchAndFamily = VARIABLE_PITCH | FF_SWISS;
	lf.lfFaceName[0] = '\0';
	m_hfont = CreateFontIndirect(&lf);
	if (m_hfont == NULL) {
		vnclog.Print(1, _T("FAILED TO SELECT FLASHER FONT!\n"));
	}

	// Create a listening socket
    struct sockaddr_in addr;

    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = INADDR_ANY;

    m_sock = socket(AF_INET, SOCK_STREAM, 0);
	if (!m_sock) throw WarningException(sz_G1);
    
	try {
		int one = 1, res = 0;

		// If we use the SO_REUSEADDR option then you can run multiple daemons
		// because the bind doesn't return an error.  Only one gets the accept,
		// but when that process dies it hands back to another.  This may or may
		// not be desirable.  We don't use it.

		//int res = setsockopt(m_sock, SOL_SOCKET, SO_REUSEADDR, (const char *) &one, sizeof(one));
		//if (res == SOCKET_ERROR) 
		//	throw WarningException("Error setting Flasher socket options");
		
		res = bind(m_sock, (struct sockaddr *)&addr, sizeof(addr));
		if (res == SOCKET_ERROR)
			throw WarningException(sz_G2);
		
		res = listen(m_sock, 5);
		if (res == SOCKET_ERROR)
			throw WarningException(sz_G3);
	} catch (...) {
		closesocket(m_sock);
		m_sock = 0;
		throw;
	}
	
	// Send a message to specified window on an incoming connection
	WSAAsyncSelect (m_sock,  m_hwnd,  WM_SOCKEVENT, FD_ACCEPT | FD_CLOSE);
}

// convert a lower-case ASCII char to a value 0-255
inline int scalechar(char c) {
	return ( ((c - 'a')+4) & 0x1f ) * 255 / 0x1f;
}

// We use this on each screen saver window running
BOOL CALLBACK KillScreenSaverFunc(HWND hwnd, LPARAM lParam)
{
	char buffer[256];

	// - ONLY try to close Screen-saver windows!!!
	if ((GetClassName(hwnd, buffer, 256) != 0) &&
		(strcmp(buffer, "WindowsScreenSaverClass") == 0))
		PostMessage(hwnd, WM_CLOSE, 0, 0);
	return TRUE;
}

// Process window messages
LRESULT CALLBACK Flasher::WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) {
	// This is a static method, so we don't know which instantiation we're 
	// dealing with. We use Allen Hadden's (ahadden@taratec.com) suggestion 
	// from a newsgroup to get the pseudo-this.
	Flasher *_this = helper::SafeGetWindowUserData<Flasher>(hwnd);
	switch (iMsg) {

	case WM_CREATE:
		return 0;

	case WM_SOCKEVENT:
		{
			assert(HIWORD(lParam) == 0);

			// A new socket created by accept might send messages to
			// this procedure. We can ignore them.
			if(wParam != _this->m_sock) {
				return 0;
			}

			switch(lParam) {
			case FD_ACCEPT:
				{
					SOCKET hNewSock;
					char username[256];
					hNewSock = accept(_this->m_sock, NULL, NULL);
					// make it blocking
					WSAAsyncSelect(hNewSock, hwnd, 0, 0);
					u_long blk = 0;
					int res = ioctlsocket(hNewSock, FIONBIO, &blk);
					assert(res == 0);

					CloseScreenSaver();

					// Se if the server's sending a user name
					int namelen = recv(hNewSock, username, 250, 0);
					if (namelen >= 0) 
						username[namelen] = 0;
					vnclog.Print(2, _T("Flash for '%s'\n"), username);

					closesocket(hNewSock);


					// flash
					// Get a DC for the root window
					HDC hrootdc = ::GetDC(NULL);

					HBRUSH holdbrush = (HBRUSH) SelectObject(hrootdc, 
						(HBRUSH) GetStockObject(BLACK_BRUSH));
					
					// Find the size.
					RECT rect;
					GetClipBox(hrootdc, &rect);
					int barwidth = (rect.right - rect.left) / 10;
					int barheight = max( 
						(rect.bottom - rect.top) / 10,
						FLASHFONTHEIGHT);
					HFONT oldfont = (HFONT) SelectObject(hrootdc, _this->m_hfont);

					// Flash the screen
					::Beep(440,50);
										
					Rectangle(hrootdc, rect.left, rect.top, 
								  rect.right, barheight);
					Rectangle(hrootdc, rect.left,  rect.bottom-barheight, 
								  rect.right, rect.bottom);
					Rectangle(hrootdc, rect.left,  rect.top+barheight, 
								  barwidth, rect.bottom-barheight);
					Rectangle(hrootdc, rect.right-barwidth, rect.top+barheight, 
								  rect.right, rect.bottom-barheight);

					RECT topbar;
					SetRect(&topbar, rect.left, rect.top, 
								  rect.right, barheight);
					int i = 0;
					if (namelen > 0) {
						int oldmode = SetBkMode(hrootdc, TRANSPARENT);
						COLORREF oldcolor = SetTextColor(hrootdc, 
							RGB(scalechar(username[0])/(i+1), scalechar(username[1])/(i+1), scalechar(username[2])/(i+1)));
						DrawText(hrootdc, username, -1, &topbar, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
						SetTextColor(hrootdc, oldcolor);
						SetBkMode(hrootdc, oldmode);
					}
					GdiFlush();
					SelectObject(hrootdc, holdbrush);
					::Sleep(1000);
						
					SelectObject(hrootdc, oldfont);
					InvalidateRect(0, &rect, TRUE);

					::ReleaseDC(NULL, hrootdc);
					break;
				}
			case FD_CLOSE:
				vnclog.Print(2, _T("Flasher connection closed\n"));
				DestroyWindow(hwnd);
				break;
			}
			
			return 0;
		}
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}
	
	return DefWindowProc(hwnd, iMsg, wParam, lParam);
}

void Flasher::CloseScreenSaver() {
	// Which OS are we on?
	OSVERSIONINFO ovi;
	ovi.dwOSVersionInfoSize = sizeof(ovi);
	if (!GetVersionEx(&ovi)) return;

	switch (ovi.dwPlatformId) {
	case VER_PLATFORM_WIN32_WINDOWS:
		{
			// Windows 95
			HWND hsswnd = FindWindow ("WindowsScreenSaverClass", NULL);
			if (hsswnd != NULL)
				PostMessage(hsswnd, WM_CLOSE, 0, 0); 
			break;
		} 
	case VER_PLATFORM_WIN32_NT:
		{
			// Windows NT
			HDESK hdesk = OpenDesktop(
				TEXT("Screen-saver"),                       
				0,	FALSE,	DESKTOP_READOBJECTS | DESKTOP_WRITEOBJECTS);
			if (hdesk) {
                if (EnumDesktopWindows(hdesk, (WNDENUMPROC) KillScreenSaverFunc, 0)) {
				    CloseDesktop(hdesk);
                }
                Sleep(1000);
			}
		}
	}
}

Flasher::~Flasher()
{
	shutdown(m_sock, SD_BOTH);
	closesocket(m_sock);
	if (m_hfont != NULL) DeleteObject(m_hfont);
}

