/*
            KeyboardNotify plugin v1.5 for Miranda IM
            _________________________________________

  Copyright (C) 2002,2003  Martin �berg
  Copyright (C) 2004	   Std
  Copyright (C) 2005,2006  TioDuke (tioduke@yahoo.ca)


  This program 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.
*/

#include "Common.h"

#define NCONVERS_BLINKID ((HANDLE)123456) //nconvers' random identifier used to flash an icon for "incoming message" on contact list

#ifndef SPI_GETSCREENSAVERRUNNING
#define SPI_GETSCREENSAVERRUNNING 114
#endif

#ifndef WM_XBUTTONDBLCLK
#define WM_XBUTTONDBLCLK 0x020D
#endif
#ifndef WM_NCXBUTTONDBLCLK
#define WM_NCXBUTTONDBLCLK 0x00AD
#endif


HINSTANCE hInst;

int hLangpack;

DWORD IDThread = 0;
HANDLE hThread = NULL;
HANDLE hFlashEvent;
HANDLE hExitEvent;

HANDLE hModulesLoaded = NULL;
HANDLE hMsgEventHook = NULL;
HANDLE hOptionsInitialize = NULL;
HANDLE hEnableService = NULL;
HANDLE hDisableService = NULL;
HANDLE hStartBlinkService = NULL;
HANDLE hEventsOpenedService = NULL;
HANDLE hFlashingEventService = NULL;
HANDLE hNormalizeSequenceService = NULL;

HHOOK hMirandaMouseHook = NULL;
HHOOK hMirandaKeyBoardHook = NULL;
HHOOK hMirandaWndProcHook = NULL;
UINT hReminderTimer = 0;

HHOOK hMouseHook = NULL;
HHOOK hKeyBoardHook = NULL;
BYTE bEmulateKeypresses = 0;
DWORD dwLastInput = 0;
POINT lastGlobalMousePos = {0, 0};

static BOOL (WINAPI * MyGetLastInputInfo)(PLASTINPUTINFO);


BYTE bFlashOnMsg;
BYTE bFlashOnURL;
BYTE bFlashOnFile;
BYTE bFlashOnOther;
BYTE bFullScreenMode;
BYTE bScreenSaverRunning;
BYTE bWorkstationLocked;
BYTE bProcessesAreRunning;
BYTE bWorkstationActive;
BYTE bFlashIfMsgOpen;
BYTE bFlashIfMsgWinNotTop;
BYTE bFlashIfMsgOlder;
WORD wSecondsOlder;
BYTE bFlashUntil;
WORD wBlinksNumber;
BYTE bMirandaOrWindows;
WORD wStatusMap;
WORD wReminderCheck;
BYTE bFlashLed[3];
BYTE bFlashEffect;
BYTE bSequenceOrder;
WORD wCustomTheme;
WORD wStartDelay;
BYTE bFlashSpeed;
BYTE bOverride;
BYTE bTrillianLedsMsg;
BYTE bTrillianLedsURL;
BYTE bTrillianLedsFile;
BYTE bTrillianLedsOther;

PROTOCOL_LIST ProtoList = {0, NULL};
PROCESS_LIST ProcessList = {0, NULL};

double dWinVer;
BOOL bWindowsNT;

int nWaitDelay;
unsigned int nExternCount = 0;
BOOL bFlashingEnabled = TRUE;
BOOL bReminderDisabled = FALSE;

char *szMetaProto = NULL;
BYTE bMetaProtoEnabled = 0;


PLUGININFOEX pluginInfo={
	sizeof(PLUGININFOEX),
	__PLUGIN_NAME,
	PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM),
	__DESCRIPTION,
	__AUTHOR,
	__AUTHOREMAIL,
	__COPYRIGHT,
	__AUTHORWEB,
	UNICODE_AWARE,
	//{119D7288-2050-448D-9900-D86AC70426BF}
	{0x119d7288, 0x2050, 0x448d, {0x99, 0x00, 0xd8, 0x6a, 0xc7, 0x04, 0x26, 0xbf}}
};



int InitializeOptions(WPARAM,LPARAM);
void LoadSettings(void);
int HookWindowsHooks(void);
int UnhookWindowsHooks(void);
static LRESULT CALLBACK MouseHookFunction(int, WPARAM, LPARAM);
static LRESULT CALLBACK KeyBoardHookFunction(int, WPARAM, LPARAM);
static LRESULT CALLBACK MirandaMouseHookFunction(int, WPARAM, LPARAM);
static LRESULT CALLBACK MirandaKeyBoardHookFunction(int, WPARAM, LPARAM);
static LRESULT CALLBACK MirandaWndProcHookFunction(int, WPARAM, LPARAM);
BOOL CheckMsgWnd(HANDLE, BOOL *);


BOOL isMetaContactsSubContact(HANDLE hMetaContact, HANDLE hContact)
{
	char *szProto = GetContactProto(hMetaContact);
	if (szProto && !strcmp(szMetaProto, szProto)) { // Safety check
		int i = db_get_dw(hContact, szMetaProto, "ContactNumber", -1);
		if (i >= 0 && hContact == (HANDLE)CallService(MS_MC_GETSUBCONTACT, (WPARAM)hMetaContact, i))
			return TRUE;
	}
	return FALSE;
}


BOOL checkOpenWindow(HANDLE hContact)
{
	BOOL found, focus;

	if (bFlashIfMsgOpen && !bFlashIfMsgWinNotTop)
		return TRUE;

	found = CheckMsgWnd(hContact, &focus);
	if (!found && szMetaProto && bMetaProtoEnabled) {
		HANDLE hMetaContact = (HANDLE)db_get_dw(hContact, szMetaProto, "Handle", 0);
		if (hMetaContact && isMetaContactsSubContact(hMetaContact, hContact))
			found = CheckMsgWnd(hMetaContact, &focus);
	}
	if (!found)
		return TRUE;

	if (bFlashIfMsgOpen && !focus)
		return TRUE;

	return FALSE;
}


BOOL IsSaverOnNT4()
{
	HDESK hd = OpenDesktop(_T("screen-saver"), 0, FALSE, MAXIMUM_ALLOWED);

	if(hd == NULL)
		return GetLastError()==ERROR_ACCESS_DENIED;

	CloseDesktop(hd);
	return TRUE;
}


BOOL isScreenSaverRunning()
{
	BOOL screenSaverIsRunning=FALSE;

	if (bWindowsNT && dWinVer < 5) return IsSaverOnNT4();

	SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, &screenSaverIsRunning, FALSE);
	return screenSaverIsRunning;
}


/* this function is from the original idle module */
BOOL isWorkstationLocked()
{
	HDESK hd;
	char buf[MAX_PATH];

	if (!bWindowsNT) return FALSE;

	hd = OpenInputDesktop(0, FALSE, MAXIMUM_ALLOWED); /* if it fails then the workstation is prolly locked anyway */
	if (hd == NULL) return TRUE;
	GetUserObjectInformation(hd, UOI_NAME, buf, sizeof(buf), NULL); /* if we got it (hmm,) get a name */
	CloseDesktop(hd);
	return strcmp(buf, "Winlogon")==0;
}


BOOL isFullScreen()
{
	int w = GetSystemMetrics(SM_CXSCREEN);
	int h = GetSystemMetrics(SM_CYSCREEN);

	HWND hWnd = 0;
	while (hWnd = FindWindowEx(NULL, hWnd, NULL, NULL)) {
		RECT WindowRect;

		if (!(GetWindowLongPtr(hWnd, GWL_EXSTYLE) & WS_EX_TOPMOST))
			continue;

		GetWindowRect(hWnd, &WindowRect);
		if ((w != (WindowRect.right - WindowRect.left)) || (h != (WindowRect.bottom - WindowRect.top)))
			continue;

		return TRUE;
	}

	return FALSE;
}


BOOL checkNotifyOptions()
{
	BOOL fullScreenMode, screenSaverIsRunning, workstationIsLocked, processesRunning;

	screenSaverIsRunning = isScreenSaverRunning();
	if (screenSaverIsRunning && bScreenSaverRunning)
			return TRUE;

	workstationIsLocked = isWorkstationLocked();
	if (workstationIsLocked && bWorkstationLocked)
			return TRUE;

	fullScreenMode = isFullScreen() && !screenSaverIsRunning;
	if (fullScreenMode && bFullScreenMode)
			return TRUE;

	processesRunning = areThereProcessesRunning();
	if (processesRunning && bProcessesAreRunning)
			return TRUE;

	return (!fullScreenMode && !screenSaverIsRunning && !workstationIsLocked && !processesRunning && bWorkstationActive);
}


BOOL isStatusEnabled(int status)
{
	switch (status) {
		case ID_STATUS_OFFLINE:		return wStatusMap & MAP_OFFLINE;
		case ID_STATUS_ONLINE:		return wStatusMap & MAP_ONLINE;
		case ID_STATUS_AWAY:		return wStatusMap & MAP_AWAY;
		case ID_STATUS_NA:			return wStatusMap & MAP_NA;
		case ID_STATUS_OCCUPIED:	return wStatusMap & MAP_OCCUPIED;
		case ID_STATUS_DND:			return wStatusMap & MAP_DND;
		case ID_STATUS_FREECHAT:	return wStatusMap & MAP_FREECHAT;
		case ID_STATUS_INVISIBLE:	return wStatusMap & MAP_INVISIBLE;
		case ID_STATUS_ONTHEPHONE:	return wStatusMap & MAP_ONTHEPHONE;
		case ID_STATUS_OUTTOLUNCH:	return wStatusMap & MAP_OUTTOLUNCH;
		default:					return FALSE;
	}
}


BOOL checkGlobalStatus()
{
	return isStatusEnabled(CallService(MS_CLIST_GETSTATUSMODE, 0, 0));
}


BOOL checkGlobalXstatus()
{
	int protosSupporting=0, status=0;

	for (int i=0; i < ProtoList.protoCount; i++) {
		if ( !ProtoList.protoInfo[i].enabled || !ProtoList.protoInfo[i].xstatus.count)
			continue;

		protosSupporting++;
		// Retrieve xstatus for protocol
		CUSTOM_STATUS xstatus = { sizeof(CUSTOM_STATUS) };
		xstatus.flags = CSSF_MASK_STATUS;
		xstatus.status = &status;
		CallProtoService(ProtoList.protoInfo[i].szProto, PS_GETCUSTOMSTATUSEX, 0, (LPARAM)&xstatus);

		if (ProtoList.protoInfo[i].xstatus.enabled[status])
			return TRUE;
	}

	return protosSupporting == 0;
}


DBEVENTINFO createMsgEventInfo(HANDLE hContact)
{
	DBEVENTINFO einfo = {0};

	einfo.cbSize = sizeof(einfo);
	einfo.eventType = EVENTTYPE_MESSAGE;
	einfo.szModule = GetContactProto(hContact);

	return einfo;
}


DBEVENTINFO readEventInfo(HANDLE hDbEvent, HANDLE hContact)
{
	if (hDbEvent == NCONVERS_BLINKID) // we need to handle nconvers' blink event
		return createMsgEventInfo(hContact);

	DBEVENTINFO einfo = { sizeof(einfo) };
	db_event_get(hDbEvent, &einfo);
	return einfo;
}


BOOL checkIgnore(HANDLE hContact, WORD eventType)
{
	return !IsIgnored(hContact, eventType);
}


BOOL checkProtocol(char *szProto)
{
	if (!szProto)
		return FALSE;

	for(int i=0; i < ProtoList.protoCount; i++)
		if (ProtoList.protoInfo[i].szProto && !strcmp(ProtoList.protoInfo[i].szProto, szProto))
			return ProtoList.protoInfo[i].enabled;

	return FALSE;
}


BOOL metaCheckProtocol(char *szProto, HANDLE hContact, WORD eventType)
{
	HANDLE hSubContact=NULL;

	if (szMetaProto && bMetaProtoEnabled && szProto && !strcmp(szMetaProto, szProto))
		if (hSubContact = (HANDLE)CallService(MS_MC_GETMOSTONLINECONTACT, (WPARAM)hContact, 0))
			szProto = GetContactProto(hSubContact);

	return checkProtocol(szProto) && checkIgnore(hSubContact?hSubContact:hContact, eventType);
}


BOOL checkUnopenEvents()
{
	int nIndex;
	CLISTEVENT *pCLEvent;

	if (nExternCount && bFlashOnOther)
		return TRUE;

	for (nIndex = 0; pCLEvent = (CLISTEVENT*)CallService(MS_CLIST_GETEVENT, -1, nIndex); nIndex++) {
		DBEVENTINFO einfo = readEventInfo(pCLEvent->hDbEvent, pCLEvent->hContact);

		if ((einfo.eventType == EVENTTYPE_MESSAGE && bFlashOnMsg)  ||
		    (einfo.eventType == EVENTTYPE_URL     && bFlashOnURL)  ||
		    (einfo.eventType == EVENTTYPE_FILE    && bFlashOnFile) ||
		    (einfo.eventType != EVENTTYPE_MESSAGE && einfo.eventType != EVENTTYPE_URL && einfo.eventType != EVENTTYPE_FILE && bFlashOnOther))

			if (metaCheckProtocol(einfo.szModule, pCLEvent->hContact, einfo.eventType))
				return TRUE;
	}

	return FALSE;
}


static void FlashThreadFunction()
{
	BOOL bEvent = FALSE;
	DWORD dwEventStarted, dwFlashStarted;
	BYTE data, unchangedLeds;

	while (TRUE) {
		unchangedLeds = (BYTE)(LedState(VK_PAUSE) * !bFlashLed[2] + ((LedState(VK_NUMLOCK) * !bFlashLed[0])<<1) + ((LedState(VK_CAPITAL) * !bFlashLed[1])<<2));
		GetAsyncKeyState(VK_PAUSE); // empty Pause/Break's keystroke buffer

		// Start flashing
		while(bEvent && bFlashingEnabled)
		{
			// Let's give the user the opportunity of finishing flashing manually :)
			if (GetAsyncKeyState(VK_PAUSE) & 1)
				break;

			if ((bFlashUntil & UNTIL_NBLINKS) && GetTickCount() > (dwFlashStarted + wBlinksNumber * 1000))
				break;
			if (bFlashUntil & UNTIL_REATTENDED) {
				if (bMirandaOrWindows == ACTIVE_WINDOWS && MyGetLastInputInfo && !bEmulateKeypresses) {
					LASTINPUTINFO lii;
					ZeroMemory(&lii, sizeof(lii));
					lii.cbSize = sizeof(lii);
					MyGetLastInputInfo(&lii);
					dwLastInput = lii.dwTime;
				}
				if (dwLastInput > dwEventStarted)
					break;
			}
			if ((bFlashUntil & UNTIL_EVENTSOPEN) && !checkUnopenEvents())
				break;
			if ((bFlashUntil & UNTIL_CONDITIONS) && (!checkNotifyOptions() || !checkGlobalStatus() || !checkGlobalXstatus()))
				break;

			data = getBlinkingLeds();
			ToggleKeyboardLights((BYTE)(data|unchangedLeds));

			// Wait for exit event
			if (WaitForSingleObject(hExitEvent, nWaitDelay) == WAIT_OBJECT_0)
				return;
		}
		RestoreLEDState();

		setFlashingSequence();
		bReminderDisabled = FALSE;

		// Wait for new event
		{
			DWORD dwEvent;
			HANDLE Objects[2];
			Objects[0] = hFlashEvent;
			Objects[1] = hExitEvent;
			dwEvent = WaitForMultipleObjects(2, Objects, FALSE, INFINITE);
			if ((dwEvent - WAIT_OBJECT_0) == 1)
				return;
		}

		bEvent = TRUE;
		bReminderDisabled = TRUE;
		dwEventStarted = GetTickCount();
		// Wait StartDelay seconds
		if (wStartDelay > 0)
			Sleep(wStartDelay * 1000);
		dwFlashStarted = GetTickCount();

	}

}


BOOL checkMsgTimestamp(HANDLE hEventCurrent, DWORD timestampCurrent)
{
	if (!bFlashIfMsgOlder)
		return TRUE;

	for (HANDLE hEvent = db_event_prev(hEventCurrent); hEvent; hEvent = db_event_prev(hEvent)) {
		DBEVENTINFO einfo = { sizeof(einfo) };
		db_event_get(hEvent, &einfo);
		if ((einfo.timestamp + wSecondsOlder) <= timestampCurrent)
			return TRUE;
		if (einfo.eventType == EVENTTYPE_MESSAGE)
			return FALSE;
	}

	return TRUE;
}


BOOL contactCheckProtocol(char *szProto, HANDLE hContact, WORD eventType)
{
	if (szMetaProto && bMetaProtoEnabled && hContact) {
		HANDLE hMetaContact = (HANDLE)db_get_dw(hContact, szMetaProto, "Handle", 0);
		if (hMetaContact && isMetaContactsSubContact(hMetaContact, hContact))
			return FALSE;
	}

	return (metaCheckProtocol(szProto, hContact, eventType));
}


BOOL checkStatus(char *szProto)
{
	if (!szProto)
		return checkGlobalStatus();

	return isStatusEnabled(CallProtoService(szProto, PS_GETSTATUS, 0, 0));
}


BOOL checkXstatus(char *szProto)
{
	int status=0;
	CUSTOM_STATUS xstatus={0};

	if (!szProto)
		return checkGlobalXstatus();

	for(int i=0; i < ProtoList.protoCount; i++)
		if (ProtoList.protoInfo[i].szProto && !strcmp(ProtoList.protoInfo[i].szProto, szProto)) {
			if (!ProtoList.protoInfo[i].xstatus.count) return TRUE;

			// Retrieve xstatus for protocol
			xstatus.cbSize = sizeof(CUSTOM_STATUS);
			xstatus.flags = CSSF_MASK_STATUS;
			xstatus.status = &status;
			CallProtoService(ProtoList.protoInfo[i].szProto, PS_GETCUSTOMSTATUSEX, 0, (LPARAM)&xstatus);

			return ProtoList.protoInfo[i].xstatus.enabled[status];
		}

	return TRUE;
}


// 'Pings' the FlashThread to keep the LEDs flashing.
static int PluginMessageEventHook(WPARAM wParam, LPARAM lParam)
{
	HANDLE hContact = (HANDLE)wParam;
	HANDLE hEvent = (HANDLE)lParam;

	//get DBEVENTINFO without pBlob
	DBEVENTINFO einfo = { sizeof(einfo) };
	db_event_get(hEvent, &einfo);
	if (!(einfo.flags & DBEF_SENT))
		if ((einfo.eventType == EVENTTYPE_MESSAGE && bFlashOnMsg && checkOpenWindow(hContact) && checkMsgTimestamp(hEvent, einfo.timestamp)) ||
		    (einfo.eventType == EVENTTYPE_URL     && bFlashOnURL)  ||
		    (einfo.eventType == EVENTTYPE_FILE    && bFlashOnFile) ||
		    (einfo.eventType != EVENTTYPE_MESSAGE && einfo.eventType != EVENTTYPE_URL && einfo.eventType != EVENTTYPE_FILE && bFlashOnOther)) {

			if (contactCheckProtocol(einfo.szModule, hContact, einfo.eventType) && checkNotifyOptions() && checkStatus(einfo.szModule) && checkXstatus(einfo.szModule))

				SetEvent(hFlashEvent);
		}

	return 0;
}


// **
// ** Checks for pending events. If it finds any, it pings the FlashThread to keep the LEDs flashing.
// **

static VOID CALLBACK ReminderTimer(HWND hwnd, UINT message, UINT_PTR idEvent, DWORD dwTime)
{
	int nIndex;
	CLISTEVENT *pCLEvent;

	if (!bReminderDisabled && nExternCount && bFlashOnOther) {
		SetEvent(hFlashEvent);
		return;
	}

	for (nIndex = 0; !bReminderDisabled && (pCLEvent = (CLISTEVENT*)CallService(MS_CLIST_GETEVENT, -1, nIndex)); nIndex++) {
		DBEVENTINFO einfo = readEventInfo(pCLEvent->hDbEvent, pCLEvent->hContact);

		if ((einfo.eventType == EVENTTYPE_MESSAGE && bFlashOnMsg)  ||
		    (einfo.eventType == EVENTTYPE_URL     && bFlashOnURL)  ||
		    (einfo.eventType == EVENTTYPE_FILE    && bFlashOnFile) ||
		    (einfo.eventType != EVENTTYPE_MESSAGE && einfo.eventType != EVENTTYPE_URL && einfo.eventType != EVENTTYPE_FILE && bFlashOnOther))

			if (metaCheckProtocol(einfo.szModule, pCLEvent->hContact, einfo.eventType) && checkNotifyOptions() && checkStatus(einfo.szModule) && checkXstatus(einfo.szModule)) {

				SetEvent(hFlashEvent);
				return;
			}
	}

}


// Support for third-party plugins and mBot's scripts
static INT_PTR EnableService(WPARAM wParam, LPARAM lParam)
{
	bFlashingEnabled = TRUE;
	return 0;
}

static INT_PTR DisableService(WPARAM wParam, LPARAM lParam)
{
	bFlashingEnabled = FALSE;
	return 0;
}

static INT_PTR StartBlinkService(WPARAM wParam, LPARAM lParam)
{
	nExternCount += (unsigned int)wParam;
	if (bFlashOnOther && checkNotifyOptions() && checkGlobalStatus() && checkGlobalXstatus()) {
		if (lParam)
			useExternSequence((TCHAR *)lParam);
		SetEvent(hFlashEvent);
	}

	return 0;
}

static INT_PTR EventsWereOpenedService(WPARAM wParam, LPARAM lParam)
{
	if ((unsigned int)wParam > nExternCount)
		nExternCount = 0;
	else
		nExternCount -= (unsigned int)wParam;

	return 0;
}


static INT_PTR IsFlashingActiveService(WPARAM wParam, LPARAM lParam)
{
	if (!bReminderDisabled)
		return 0;

	return (int)getCurrentSequenceString();
}


INT_PTR NormalizeSequenceService(WPARAM wParam, LPARAM lParam)
{
	TCHAR strAux[MAX_PATH+1], *strIn = (TCHAR *)lParam;

	mir_sntprintf(strAux, MAX_PATH, _T("%s"), strIn);
	mir_sntprintf(strIn, MAX_PATH, _T("%s"), normalizeCustomString(strAux));

	return (int)strIn;
}


// Support for Trigger plugin
static void ForceEventsWereOpenedThread(void *eventMaxSeconds)
{
	Sleep(((WORD)eventMaxSeconds) * 1000);
	CallService(MS_KBDNOTIFY_EVENTSOPENED, 1, 0);
}


void StartBlinkAction(char *flashSequence, WORD eventMaxSeconds)
{
	DWORD threadID = 0;

	if (eventMaxSeconds)
		CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ForceEventsWereOpenedThread, (void *)eventMaxSeconds, 0, &threadID);

	CallService(MS_KBDNOTIFY_STARTBLINK, 1, (LPARAM)flashSequence);
}


void createProcessList(void)
{
	DBVARIANT dbv;
	unsigned int i, count;

	count = (unsigned int)db_get_w(NULL, KEYBDMODULE, "processcount", 0);

	ProcessList.count = 0;
	ProcessList.szFileName = (TCHAR **)malloc(count * sizeof(TCHAR *));
	if (ProcessList.szFileName) {
		for(i=0; i < count; i++)
			if (db_get_ts(NULL, KEYBDMODULE, fmtDBSettingName("process%d", i), &dbv))
				ProcessList.szFileName[i] = NULL;
			else {
				ProcessList.szFileName[i] = (TCHAR *)malloc(wcslen(dbv.ptszVal) + 1);
				if (ProcessList.szFileName[i])
					wcscpy(ProcessList.szFileName[i], dbv.ptszVal);
				db_free(&dbv);
			}
		ProcessList.count = count;
	}

}


void destroyProcessList(void)
{
	unsigned int i, count;

	count = ProcessList.count;

	ProcessList.count = 0;
	for(i=0; i < count; i++)
		if (ProcessList.szFileName[i])
			free(ProcessList.szFileName[i]);

	if (ProcessList.szFileName)
		free(ProcessList.szFileName);
	ProcessList.szFileName = NULL;
}


void LoadSettings(void)
{
	bFlashOnMsg = db_get_b(NULL, KEYBDMODULE, "onmsg", DEF_SETTING_ONMSG);
	bFlashOnURL = db_get_b(NULL, KEYBDMODULE, "onurl", DEF_SETTING_ONURL);
	bFlashOnFile = db_get_b(NULL, KEYBDMODULE, "onfile", DEF_SETTING_ONFILE);
	bFlashOnOther = db_get_b(NULL, KEYBDMODULE, "onother", DEF_SETTING_OTHER);
	bFullScreenMode = db_get_b(NULL, KEYBDMODULE, "fscreenmode", DEF_SETTING_FSCREEN);
	bScreenSaverRunning = db_get_b(NULL, KEYBDMODULE, "ssaverrunning", DEF_SETTING_SSAVER);
	bWorkstationLocked = (bWindowsNT ? db_get_b(NULL, KEYBDMODULE, "wstationlocked", DEF_SETTING_LOCKED):0);
	bProcessesAreRunning = db_get_b(NULL, KEYBDMODULE, "procsrunning", DEF_SETTING_PROCS);
	bWorkstationActive = db_get_b(NULL, KEYBDMODULE, "wstationactive", DEF_SETTING_ACTIVE);
	bFlashIfMsgOpen = db_get_b(NULL, KEYBDMODULE, "ifmsgopen", DEF_SETTING_IFMSGOPEN);
	bFlashIfMsgWinNotTop = db_get_b(NULL, KEYBDMODULE, "ifmsgnottop", DEF_SETTING_IFMSGNOTTOP);
	bFlashIfMsgOlder = db_get_b(NULL, KEYBDMODULE, "ifmsgolder", DEF_SETTING_IFMSGOLDER);
	wSecondsOlder = db_get_w(NULL, KEYBDMODULE, "secsolder", DEF_SETTING_SECSOLDER);
	bFlashUntil = db_get_b(NULL, KEYBDMODULE, "funtil", DEF_SETTING_FLASHUNTIL);
	wBlinksNumber = db_get_w(NULL, KEYBDMODULE, "nblinks", DEF_SETTING_NBLINKS);
	bMirandaOrWindows = db_get_b(NULL, KEYBDMODULE, "mirorwin", DEF_SETTING_MIRORWIN);
	wStatusMap = db_get_w(NULL, KEYBDMODULE, "status", DEF_SETTING_STATUS);
	wReminderCheck = db_get_w(NULL, KEYBDMODULE, "remcheck", DEF_SETTING_CHECKTIME);
	bFlashLed[0] = !!db_get_b(NULL, KEYBDMODULE, "fnum", DEF_SETTING_FLASHNUM);
	bFlashLed[1] = !!db_get_b(NULL, KEYBDMODULE, "fcaps", DEF_SETTING_FLASHCAPS);
	bFlashLed[2] = !!db_get_b(NULL, KEYBDMODULE, "fscroll", DEF_SETTING_FLASHSCROLL);
	bFlashEffect = db_get_b(NULL, KEYBDMODULE, "feffect", DEF_SETTING_FLASHEFFECT);
	bSequenceOrder = db_get_b(NULL, KEYBDMODULE, "order", DEF_SETTING_SEQORDER);
	wCustomTheme = db_get_w(NULL, KEYBDMODULE, "custom", DEF_SETTING_CUSTOMTHEME);
	bTrillianLedsMsg = db_get_b(NULL, KEYBDMODULE, "ledsmsg", DEF_SETTING_LEDSMSG);
	bTrillianLedsURL = db_get_b(NULL, KEYBDMODULE, "ledsurl", DEF_SETTING_LEDSURL);
	bTrillianLedsFile = db_get_b(NULL, KEYBDMODULE, "ledsfile", DEF_SETTING_LEDSFILE);
	bTrillianLedsOther = db_get_b(NULL, KEYBDMODULE, "ledsother", DEF_SETTING_LEDSOTHER);
	wStartDelay = db_get_w(NULL, KEYBDMODULE, "sdelay", DEF_SETTING_STARTDELAY);
	bFlashSpeed = db_get_b(NULL, KEYBDMODULE, "speed", DEF_SETTING_FLASHSPEED);
	switch (bFlashSpeed) {
		case 0:	 nWaitDelay = 1500; break;
		case 1:  nWaitDelay = 0750; break;
		case 2:  nWaitDelay = 0250; break;
		case 3:  nWaitDelay = 0150; break;
		case 4:  nWaitDelay = 0100; break;
		default: nWaitDelay = 0050; break;
	}
	setFlashingSequence();
	bEmulateKeypresses = db_get_b(NULL, KEYBDMODULE, "keypresses", DEF_SETTING_KEYPRESSES);
	bOverride = db_get_b(NULL, KEYBDMODULE, "override", DEF_SETTING_OVERRIDE);
	// Create hidden settings (for test button) if needed
	if (db_get_b(NULL, KEYBDMODULE, "testnum", -1) == -1)
		db_set_b(NULL, KEYBDMODULE, "testnum", DEF_SETTING_TESTNUM);
	if (db_get_b(NULL, KEYBDMODULE, "testsecs", -1) == -1)
		db_set_b(NULL, KEYBDMODULE, "testsecs", DEF_SETTING_TESTSECS);
	for(int i=0; i < ProtoList.protoCount; i++)
		if (ProtoList.protoInfo[i].visible) {
			unsigned int j;
			ProtoList.protoInfo[i].enabled = db_get_b(NULL, KEYBDMODULE, ProtoList.protoInfo[i].szProto, DEF_SETTING_PROTOCOL);
			for(j=0; j < ProtoList.protoInfo[i].xstatus.count; j++)
				ProtoList.protoInfo[i].xstatus.enabled[j] = db_get_b(NULL, KEYBDMODULE, fmtDBSettingName("%sxstatus%d", ProtoList.protoInfo[i].szProto, j), DEF_SETTING_XSTATUS);
		}

	if (szMetaProto)
		bMetaProtoEnabled = db_get_b(NULL, szMetaProto, "Enabled", 1);

	destroyProcessList();
	createProcessList();
	UnhookWindowsHooks();
	HookWindowsHooks();
}


void GetWindowsVersion(void)
{
	OSVERSIONINFOEX osvi;
	BOOL bOsVersionInfoEx;

	ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
	osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
	if (!(bOsVersionInfoEx = GetVersionEx((OSVERSIONINFO *) &osvi))) {
		osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
		if (!GetVersionEx((OSVERSIONINFO *)&osvi))
			osvi.dwPlatformId = VER_PLATFORM_WIN32_WINDOWS;
	}
	bWindowsNT = osvi.dwPlatformId==VER_PLATFORM_WIN32_NT;
	dWinVer = osvi.dwMajorVersion + osvi.dwMinorVersion / 10.0;
}


void updateXstatusProto(PROTOCOL_INFO *protoInfo)
{
	if (!ProtoServiceExists(protoInfo->szProto, PS_GETCUSTOMSTATUSEX))
		return;

	// Retrieve xstatus.count
	CUSTOM_STATUS xstatus = { sizeof(xstatus) };
	xstatus.flags = CSSF_STATUSES_COUNT;
	xstatus.wParam = &(protoInfo->xstatus.count);
	CallProtoService(protoInfo->szProto, PS_GETCUSTOMSTATUSEX, 0, (LPARAM)&xstatus);
	(protoInfo->xstatus.count)++;	// Don't forget about xstatus=0 (None)

	// Alloc and initiailize xstatus.enabled array
	protoInfo->xstatus.enabled = (BOOL *)malloc(protoInfo->xstatus.count * sizeof(BOOL));
	if (!protoInfo->xstatus.enabled)
		protoInfo->xstatus.count = 0;
	else
		for(unsigned i=0; i < protoInfo->xstatus.count; i++)
			protoInfo->xstatus.enabled[i] = FALSE;
}


void createProtocolList(void)
{
	PROTOACCOUNT **proto;

	if (ServiceExists(MS_MC_GETPROTOCOLNAME))
		szMetaProto = (char *)CallService(MS_MC_GETPROTOCOLNAME, 0, 0);

	ProtoEnumAccounts(&ProtoList.protoCount, &proto);
	ProtoList.protoInfo = (PROTOCOL_INFO *)malloc(ProtoList.protoCount * sizeof(PROTOCOL_INFO));
	if (!ProtoList.protoInfo) {
		ProtoList.protoCount = 0;
		return;
	}

	for(int i=0; i < ProtoList.protoCount; i++) {
		ProtoList.protoInfo[i].xstatus.count = 0;
		ProtoList.protoInfo[i].xstatus.enabled = NULL;
		ProtoList.protoInfo[i].szProto = (char *)malloc(strlen(proto[i]->szModuleName) + 1);
		if (!ProtoList.protoInfo[i].szProto) {
			ProtoList.protoInfo[i].enabled = FALSE;
			ProtoList.protoInfo[i].visible = FALSE;
		}
		else {
			strcpy(ProtoList.protoInfo[i].szProto, proto[i]->szModuleName);
			ProtoList.protoInfo[i].enabled = FALSE;
			if (szMetaProto && !strcmp(proto[i]->szModuleName, szMetaProto))
				ProtoList.protoInfo[i].visible = FALSE;
			else {
				ProtoList.protoInfo[i].visible = TRUE;
				updateXstatusProto(&(ProtoList.protoInfo[i]));
			}
		}
	}
}


// We use the profile name to create the first part of each event name
// We do so to avoid problems between different instances of the plugin concurrently running
void createEventPrefix(TCHAR *prefixName, size_t maxLen)
{
	size_t len;
	TCHAR profileName[MAX_PATH+1], *str;

	getAbsoluteProfileName(profileName, MAX_PATH);

	while (str = _tcschr(profileName, _T('\\')))
		*str = _T('/');
	if ((len = _tcslen(profileName)) <= maxLen)
		_tcscpy(prefixName, profileName);
	else {
		str = profileName + len - maxLen / 2;
		mir_sntprintf(prefixName, maxLen / 2, _T("%s"), profileName);
		_tcscat(prefixName, str);
	}
}


// **
// ** Everything below is just Miranda init/uninit stuff
// **


static int ModulesLoaded(WPARAM wParam, LPARAM lParam)
{
	TCHAR eventPrefix[MAX_PATH+1], eventName[MAX_PATH+1];

	LoadProcsLibrary();
	if (bWindowsNT && dWinVer >= 5)
		MyGetLastInputInfo = (BOOL (WINAPI *)(PLASTINPUTINFO)) GetProcAddress(GetModuleHandle(_T("user32")), "GetLastInputInfo");
	else
		MyGetLastInputInfo = NULL;

	createProtocolList();
	LoadSettings();

	// Create some synchronisation objects
	createEventPrefix(eventPrefix, MAX_PATH - 11);
	mir_sntprintf(eventName, SIZEOF(eventName), _T("%s/FlashEvent"), eventPrefix);
	hFlashEvent = CreateEvent(NULL, FALSE, FALSE, eventName);
	mir_sntprintf(eventName, SIZEOF(eventName), _T("%s/ExitEvent"), eventPrefix);
	hExitEvent = CreateEvent(NULL, FALSE, FALSE, eventName);

	hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)FlashThreadFunction, NULL, 0, &IDThread);

	hMsgEventHook = HookEvent(ME_DB_EVENT_ADDED, PluginMessageEventHook);
	hOptionsInitialize = HookEvent(ME_OPT_INITIALISE, InitializeOptions);
	hEnableService = CreateServiceFunction(MS_KBDNOTIFY_ENABLE, EnableService);
	hDisableService = CreateServiceFunction(MS_KBDNOTIFY_DISABLE, DisableService);
	hStartBlinkService = CreateServiceFunction(MS_KBDNOTIFY_STARTBLINK, StartBlinkService);
	hEventsOpenedService = CreateServiceFunction(MS_KBDNOTIFY_EVENTSOPENED, EventsWereOpenedService);
	hFlashingEventService = CreateServiceFunction(MS_KBDNOTIFY_FLASHINGACTIVE, IsFlashingActiveService);
	hNormalizeSequenceService = CreateServiceFunction(MS_KBDNOTIFY_NORMALSEQUENCE, NormalizeSequenceService);

	if (ServiceExists("DBEditorpp/RegisterSingleModule"))
		CallService("DBEditorpp/RegisterSingleModule", (WPARAM)KEYBDMODULE, 0);
	
	return 0;
}



BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
	hInst = hinstDLL;
	return TRUE;
}

extern "C" __declspec(dllexport) PLUGININFOEX* MirandaPluginInfoEx(DWORD mirandaVersion)
{
	return &pluginInfo;
}

extern "C" __declspec(dllexport) int Load(void)
{
	mir_getLP(&pluginInfo);

	GetWindowsVersion();
	OpenKeyboardDevice();
	hModulesLoaded = HookEvent(ME_SYSTEM_MODULESLOADED, ModulesLoaded);

	return 0;
}



void destroyProtocolList(void)
{
	for(int i=0; i < ProtoList.protoCount; i++) {
		if (ProtoList.protoInfo[i].szProto)
			free(ProtoList.protoInfo[i].szProto);
		if (ProtoList.protoInfo[i].xstatus.enabled)
			free(ProtoList.protoInfo[i].xstatus.enabled);
	}

	ProtoList.protoCount = 0;
	if (ProtoList.protoInfo)
		free(ProtoList.protoInfo);
}


extern "C" __declspec(dllexport) int Unload(void)
{
	UnhookWindowsHooks();
	if (hModulesLoaded)
		UnhookEvent(hModulesLoaded);
	if (hMsgEventHook)
		UnhookEvent(hMsgEventHook);
	if (hOptionsInitialize)
		UnhookEvent(hOptionsInitialize);
	if (hEnableService)
		DestroyServiceFunction(hEnableService);
	if (hDisableService)
		DestroyServiceFunction(hDisableService);
	if (hStartBlinkService)
		DestroyServiceFunction(hStartBlinkService);
	if (hEventsOpenedService)
		DestroyServiceFunction(hEventsOpenedService);
	if (hFlashingEventService)
		DestroyServiceFunction(hFlashingEventService);
	if (hNormalizeSequenceService)
		DestroyServiceFunction(hNormalizeSequenceService);

	// Wait for thread to exit
	SetEvent(hExitEvent);
	WaitForSingleObject(hThread, INFINITE);

	RestoreLEDState();
	CloseKeyboardDevice();

	UnloadProcsLibrary();
	destroyProcessList();
	destroyProtocolList();

	return 0;
}


// ========================== Windows hooks ==========================
int HookWindowsHooks()
{
	if (wReminderCheck)
		hReminderTimer = SetTimer(NULL,0, wReminderCheck * 60000, ReminderTimer);

	if (bFlashUntil & UNTIL_REATTENDED)
		switch (bMirandaOrWindows) {
			case ACTIVE_WINDOWS:
				if (!MyGetLastInputInfo || bEmulateKeypresses) {
					if (hMouseHook == NULL)
						hMouseHook = SetWindowsHookEx(WH_MOUSE, MouseHookFunction, 0, GetCurrentThreadId());
					if (hKeyBoardHook == NULL)
						hKeyBoardHook = SetWindowsHookEx(WH_KEYBOARD, KeyBoardHookFunction, 0, GetCurrentThreadId());
				}
				break;
			case ACTIVE_MIRANDA:
				if (hMirandaMouseHook == NULL)
					hMirandaMouseHook = SetWindowsHookEx(WH_MOUSE, MirandaMouseHookFunction, NULL, GetCurrentThreadId());
				if (hMirandaKeyBoardHook == NULL)
					hMirandaKeyBoardHook = SetWindowsHookEx(WH_KEYBOARD, MirandaKeyBoardHookFunction, NULL, GetCurrentThreadId());
				if (hMirandaWndProcHook == NULL)
					hMirandaWndProcHook = SetWindowsHookEx(WH_CALLWNDPROC, MirandaWndProcHookFunction, NULL, GetCurrentThreadId());
		}

	return 0;
}

int UnhookWindowsHooks()
{
	if (hReminderTimer)
		KillTimer(NULL, hReminderTimer);
	if (hMouseHook)
		UnhookWindowsHookEx(hMouseHook);
	if (hKeyBoardHook)
		UnhookWindowsHookEx(hKeyBoardHook);
	if (hMirandaMouseHook)
		UnhookWindowsHookEx(hMirandaMouseHook);
	if (hMirandaKeyBoardHook)
		UnhookWindowsHookEx(hMirandaKeyBoardHook);
	if (hMirandaWndProcHook)
		UnhookWindowsHookEx(hMirandaWndProcHook);

	hReminderTimer = 0;
	hMouseHook = hKeyBoardHook = hMirandaMouseHook = hMirandaKeyBoardHook = hMirandaWndProcHook = NULL;

	return 0;
}

static LRESULT CALLBACK MouseHookFunction(int code, WPARAM wParam, LPARAM lParam)
{
	if (code >= 0) {
		/* This should handle all mouse buttons ... */
		if ((wParam >= WM_NCLBUTTONDOWN && wParam <= WM_NCXBUTTONDBLCLK && wParam != 0x00AA) || (wParam >= WM_LBUTTONDOWN && wParam <= WM_XBUTTONDBLCLK))
			dwLastInput = GetTickCount();
		/* ... and here it is either mouse move, hover, leave or something unexpected */
		else {
			PMOUSEHOOKSTRUCT mouseInfo = (PMOUSEHOOKSTRUCT)lParam;
			POINT pt = mouseInfo->pt;
			if (pt.x!=lastGlobalMousePos.x || pt.y!=lastGlobalMousePos.y) {
				lastGlobalMousePos = pt;
				dwLastInput = GetTickCount();
			}
		}
	}

	return CallNextHookEx(hMouseHook, code, wParam, lParam);
}

static LRESULT CALLBACK KeyBoardHookFunction(int code, WPARAM wParam, LPARAM lParam)
{
	if (code >= 0 && (!bEmulateKeypresses || (bEmulateKeypresses && wParam != VK_NUMLOCK && wParam != VK_CAPITAL && wParam != VK_SCROLL)))
		dwLastInput = GetTickCount();

	return CallNextHookEx(hKeyBoardHook, code, wParam, lParam);
}

static LRESULT CALLBACK MirandaMouseHookFunction(int code, WPARAM wParam, LPARAM lParam)
{
	static POINT lastMousePos = {0, 0};

	if (code >= 0) {
		/* Movement mouse messages are for some reason incoming in inactive/background window too, that is not input */
		DWORD pid;
		GetWindowThreadProcessId(GetForegroundWindow(), &pid);
		if(pid == GetCurrentProcessId()) {
			/* This should handle all mouse buttons ... */
			if ((wParam >= WM_NCLBUTTONDOWN && wParam <= WM_NCXBUTTONDBLCLK && wParam != 0x00AA) || (wParam >= WM_LBUTTONDOWN && wParam <= WM_XBUTTONDBLCLK))
 				dwLastInput = GetTickCount();
			/* ... and here it is either mouse move, hover, leave or something unexpected */
			else {
				PMOUSEHOOKSTRUCT mouseInfo = (PMOUSEHOOKSTRUCT)lParam;
				POINT pt = mouseInfo->pt;
				if (pt.x!=lastMousePos.x || pt.y!=lastMousePos.y) {
					lastMousePos = pt;
					dwLastInput = GetTickCount();
				}
			}
		}
	}

	return CallNextHookEx(hMirandaMouseHook, code, wParam, lParam);
}

static LRESULT CALLBACK MirandaKeyBoardHookFunction(int code, WPARAM wParam, LPARAM lParam) {

	if (code >= 0 && (!bEmulateKeypresses || (bEmulateKeypresses && wParam != VK_NUMLOCK && wParam != VK_CAPITAL && wParam != VK_SCROLL)))
		dwLastInput = GetTickCount();

	return CallNextHookEx(hMirandaKeyBoardHook, code, wParam, lParam);
}

static LRESULT CALLBACK MirandaWndProcHookFunction(int code, WPARAM wParam, LPARAM lParam) {

	if (code >= 0) {
		/* WM_ACTIVATEAPP with nonzero wParam means someone brought miranda to foreground, that equals to input */
		PCWPSTRUCT cwpInfo = (PCWPSTRUCT)lParam;
		if(cwpInfo->message == WM_ACTIVATEAPP && cwpInfo->wParam)
			dwLastInput = GetTickCount();
	}

 	return CallNextHookEx(hMirandaWndProcHook, code, wParam, lParam);
}

BOOL CheckMsgWnd(HANDLE hContact, BOOL *focus)
{
	if (ServiceExists(MS_MSG_GETWINDOWDATA)) {	// use the new message API
		MessageWindowData mwd;
		MessageWindowInputData mwid;
		mwid.cbSize = sizeof(MessageWindowInputData);
		mwid.hContact = hContact;
		mwid.uFlags = MSG_WINDOW_UFLAG_MSG_BOTH;
		mwd.cbSize = sizeof(MessageWindowData);
		mwd.hContact = hContact;
		if (!CallService(MS_MSG_GETWINDOWDATA, (WPARAM)&mwid, (LPARAM)&mwd) && mwd.hwndWindow) {
			*focus = mwd.uState & MSG_WINDOW_STATE_FOCUS;
			return TRUE;
		}
	}

	*focus = FALSE;
	return FALSE;
}


void countUnopenEvents(int *msgCount, int *fileCount, int *urlCount, int *otherCount)
{
	int nIndex;
	CLISTEVENT *pCLEvent;

	for (nIndex = 0; pCLEvent = (CLISTEVENT*)CallService(MS_CLIST_GETEVENT, -1, nIndex); nIndex++) {
		DBEVENTINFO einfo = readEventInfo(pCLEvent->hDbEvent, pCLEvent->hContact);

		if (metaCheckProtocol(einfo.szModule, pCLEvent->hContact, einfo.eventType))
			switch (einfo.eventType) {
				case EVENTTYPE_MESSAGE:
					if (bFlashOnMsg)
						(*msgCount)++;
					break;
				case EVENTTYPE_URL:
					if (bFlashOnURL)
						(*urlCount)++;
					break;
				case EVENTTYPE_FILE:
					if (bFlashOnFile)
						(*fileCount)++;
					break;
				default:
					if (bFlashOnOther)
						(*otherCount)++;
			}
	}
	if (bFlashOnOther)
		(*otherCount) += nExternCount;
}