//  TODO: add Copyright?
//
//  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.

// This is the source for the low-level keyboard hook, which allows intercepting and sending
// special keys (such as ALT,CTRL, ALT+TAB, etc) to the VNCServer side.
// written by Assaf Gordon (Assaf@mazleg.com), 10/9/2003

#define WINVER 0x0400
#define _WIN32_WINNT 0x0400
#include "LowLevelHook.h"
#include "res/resource.h"
#include "common/win32_helpers.h"

HWND LowLevelHook::g_hwndVNCViewer=NULL;
DWORD LowLevelHook::g_VncProcessID=0;
BOOL  LowLevelHook::g_fHookActive=FALSE;
HHOOK LowLevelHook::g_HookID=0;
BOOL  LowLevelHook::g_fGlobalScrollLock=FALSE;

BOOL LowLevelHook::Initialize(HWND hwndMain)
{
        HINSTANCE hInstance = NULL ;

        g_hwndVNCViewer = NULL ;
        g_fHookActive = GetScrollLockState() ;
        g_VncProcessID = 0 ;
        g_HookID = 0 ;
        g_fGlobalScrollLock = FALSE ;

        //Store our window's handle
        g_hwndVNCViewer = hwndMain;
        if (g_hwndVNCViewer==NULL)
                return FALSE;


        //Get the HInstacne of this window
        //(required because LowLevel-Keyboard-Hook must be global, 
        // and need the HMODULE parameter in SetWindowsHookEx)
        hInstance = helper::SafeGetWindowInstance(g_hwndVNCViewer);
        if (hInstance==NULL)
                return FALSE;

        //
        //Store the ProcessID of the VNC window.
        //this will prevent the keyboard hook procedure to interfere
        //with keypressed in other processes' windows
        GetWindowThreadProcessId(g_hwndVNCViewer,&g_VncProcessID);

        //Try to set the hook procedure
        g_HookID = SetWindowsHookEx(WH_KEYBOARD_LL,VncLowLevelKbHookProc,hInstance,0);
        if (g_HookID==0) {

                DWORD dw = GetLastError();

                //TODO:
                //Analyze why the error occured (might be because we're under Win98/95/ME?)
                return FALSE ;
        }
        
        return TRUE;
}

BOOL LowLevelHook::Release()
{
        if (g_HookID!=0) {
                return UnhookWindowsHookEx(g_HookID);
        }
        return FALSE;
}

void LowLevelHook::Enable_Disable(bool state)
{
        g_fHookActive=state;
}

BOOL LowLevelHook::GetScrollLockState() 
{
  BYTE keyState[256];
  GetKeyboardState((LPBYTE)&keyState);
  return (keyState[VK_SCROLL] & 1);
}

BOOL LowLevelHook::HaveFocus() 
{
// Check if mouse is over inputwindow
	POINT point;
	GetCursorPos(&point);
	HWND testhwnd=WindowFromPoint(point);
    if (testhwnd==g_hwndVNCViewer) 
	{
		if (GetFocus()==testhwnd)
		{
			return true;
		}
	}
	return false;
}

LRESULT CALLBACK LowLevelHook::VncLowLevelKbHookProc(INT nCode, WPARAM wParam, LPARAM lParam)
{
        //if set to TRUE, the key-pressed message will NOT be passed on to windows.
    BOOL fHandled = FALSE;
	//g_fHookActive = HaveFocus() ;
	//Beep(1000,10);

        BOOL fKeyDown = FALSE;

    if (nCode == HC_ACTION) {
        KBDLLHOOKSTRUCT *pkbdllhook = (KBDLLHOOKSTRUCT *)lParam;

                DWORD ProcessID ;

                //Get the process ID of the Active Window
                //(The window with the input focus)
                GetWindowThreadProcessId(GetFocus(),&ProcessID);

                //only if this is "our" process (vncviewer's process)
                //we should intecept the key-presses
                if (ProcessID==g_VncProcessID) {

                        fKeyDown = ( (wParam==WM_KEYDOWN) || (wParam==WM_SYSKEYDOWN) );

                        switch (pkbdllhook->vkCode)
                        {
                                //Print Screen Key 
                                //      Request Full screen Update
                                //      Simulate a "Request Refresh" from the System Menu
                        case VK_SNAPSHOT:
                                if (fKeyDown && g_fHookActive) {
                                        PostMessage(g_hwndVNCViewer,WM_SYSCOMMAND,ID_REQUEST_REFRESH,0);
                                        fHandled = TRUE;
                                }
                                break ;

                                //Pause Key 
                                //      Toggle FullScreen On/Off
                                //      Simulate a "FullScreen" from the System Menu
                        case VK_PAUSE:
                                if (fKeyDown && g_fHookActive) {
                                        PostMessage(g_hwndVNCViewer,WM_SYSCOMMAND,ID_FULLSCREEN,0);
                                        fHandled = TRUE;
                                }
                                break ;


                                //Left or Right CONTROL keys
                                //      Simulate a "Send CONTROL up/down" from the System Menu
                        case VK_LCONTROL:
                        case VK_RCONTROL:
                                if (g_fHookActive) {
                                        if(fKeyDown)
                                                PostMessage(g_hwndVNCViewer,WM_SYSCOMMAND,ID_CONN_CTLDOWN,0);
                                        else
                                                PostMessage(g_hwndVNCViewer,WM_SYSCOMMAND,ID_CONN_CTLUP,0);
                                        fHandled = TRUE;
                                }
                                break;

                                //Either Left or Right ALT keys
                                //      Simulate a "Send ALT up/down" from the System Menu
                        case VK_LMENU:
                        case VK_RMENU:
                                if (g_fHookActive) {
                                        if(fKeyDown)
                                                PostMessage(g_hwndVNCViewer,WM_SYSCOMMAND,ID_CONN_ALTDOWN,0);
                                        else
                                                PostMessage(g_hwndVNCViewer,WM_SYSCOMMAND,ID_CONN_ALTUP,0);
                                        fHandled = TRUE;
                                }
                                break;
                        case VK_LWIN:
                                if (g_fHookActive) {
                                        if(fKeyDown)
                                                PostMessage(g_hwndVNCViewer,WM_SYSCOMMAND,ID_VK_LWINDOWN,0);
                                        else
                                                PostMessage(g_hwndVNCViewer,WM_SYSCOMMAND,ID_VK_LWINUP,0);
                                        fHandled = TRUE;
                                }
                                break;
						case VK_RWIN:
                                if (g_fHookActive) {
                                        if(fKeyDown)
                                                PostMessage(g_hwndVNCViewer,WM_SYSCOMMAND,ID_VK_RWINDOWN,0);
                                        else
                                                PostMessage(g_hwndVNCViewer,WM_SYSCOMMAND,ID_VK_RWINUP,0);
                                        fHandled = TRUE;
                                }
                                break;
						case VK_APPS:
                                if (g_fHookActive) {
                                        if(fKeyDown)
                                                PostMessage(g_hwndVNCViewer,WM_SYSCOMMAND,ID_VK_APPSDOWN,0);
                                        else
                                                PostMessage(g_hwndVNCViewer,WM_SYSCOMMAND,ID_VK_APPSUP,0);
                                        fHandled = TRUE;
                                }
                                break; 




                                //Scroll Lock = Turn the whole thing on or off
                                //This is a little tricky hack:
                                //Windows sets the scroll-lock LED on or off when the user PRESSes the scroll-lock key.
                                //We'll check the LED state when the user RELEASEs the key, so the LED is already set.
                                //If the LED/ScrollLock is ON, we'll activate the special key interception.
                        case VK_SCROLL:
                                if (!fKeyDown) {
                                        g_fHookActive = GetScrollLockState(); 
                                }
                                break;


                                //SPACEBAR = When key interception is Active, no special handling is required for 'spacebar'.
                                //But when key interception is turned off, I want ALT+SPACE to open the VNCViewer's System Menu.
                        case VK_SPACE:
                                if (!g_fHookActive) {
                                        if (pkbdllhook->flags & LLKHF_ALTDOWN) {
                                                if(!fKeyDown)
                                                        PostMessage(g_hwndVNCViewer,WM_SYSCOMMAND,0xF100,0x20); 

                                                fHandled = TRUE;
                                        }
                                }
                                break ;


                                
                                //TAB = If the user presses ALT+TAB, we must block the TAB key (fHandled=TRUE),
                                //Otherwise windows (on the VNCViewer's side) will switch to another application.
                                //But because we block the TAB key, the 'ClientConnection' window won't know to send
                                //a TAB key to the VNCServer. so we simulate a TAB key pressed. 
                                //(The ALT key down was already sent to the VNCServer when the user pressed ALT)
                        case VK_TAB:
                                if (g_fHookActive) {
                                        if (pkbdllhook->flags & LLKHF_ALTDOWN) {
                                                if(fKeyDown)
                                                        PostMessage(g_hwndVNCViewer,WM_KEYDOWN,VK_TAB,0);

                                                /* Implementation Note:
                                                   Don't send the Key-UP event, it confuses Windows on the server side.
                                                   (It makes Windows switch TWO applications at a time). 

                                                   This way it works OK on servers running Win98, Win2K, WinXP and Linux+IceWM.
                                                   Should test it with more servers to make sure it's OK.
                                                */
                                                fHandled = TRUE;
                                        }
                                }
                                break;

                                //ESCAPE = ALT+ESC is also a way to switch application, so we block the ESCAPE key,
                                //Otherwise windows (on the VNCViewer's side) will switch to another application.
                                //Transmitting the ALT+ESCAPE combination to a VNCServer running Windows doesn't work
                                //very well, so for now, we'll just block the ALT+ESCAPE combination.
                                //(CTRL+ESC work OK, BTW)
                case VK_ESCAPE:
                                if (g_fHookActive) {
                                        if (pkbdllhook->flags & LLKHF_ALTDOWN) {
                                                fHandled = TRUE;
                                        }
                                }
                                break;


                        } //switch(pkbdllhook->vkCode)

                } // if (ProcessID == g_VncProcesID)

        } // if (nCode==HT_ACTION)

        //Call the next hook, if we didn't handle this message
    return (fHandled ? TRUE : CallNextHookEx(g_HookID, nCode, wParam, lParam));
}