/*
 * Copyright (c) 2000-2009 TeamDev Ltd. All rights reserved.
 * TeamDev PROPRIETARY and CONFIDENTIAL.
 * Use is subject to license terms.
 */
import com.jniwrapper.*;
import com.jniwrapper.win32.FunctionName;
import com.jniwrapper.win32.Handle;
import com.jniwrapper.win32.MessageLoopThread;
import com.jniwrapper.win32.system.Module;
import com.jniwrapper.win32.ui.User32;

/**
 * This sample demonstrates a keyboad events traking by adding global LowLevel Keyboard hook.
 *
 * @author Serge Piletsky
 */
public class LowLevelKeyboardHookSample {
    static final FunctionName FUNCTION_SET_WINDOWS_HOOK = new FunctionName("SetWindowsHookEx");
    static final String FUNCTION_CALL_NEXT_HOOK = "CallNextHookEx";
    static final String FUNCTION_UNHOOK_WINDOWS_HOOK = "UnhookWindowsHookEx";

    static final int WH_KEYBOARD_LL = 13;

    private Handle keyboardHookHandle = new Handle();
    private MessageLoopThread _dispatchThread;

    public LowLevelKeyboardHookSample() {
    }

    /**
     * Class KBDLLHOOKSTRUCT represents KBDLLHOOKSTRUCT native structure
     */
    static class KBDLLHOOKSTRUCT extends Structure {
        UInt32 vkCode = new UInt32();
        UInt32 scanCode = new UInt32();
        UInt32 flags = new UInt32();
        UInt32 time = new UInt32();
        Pointer.Void dwExtraInfo = new Pointer.Void();

        public KBDLLHOOKSTRUCT() {
            init(new Parameter[]{vkCode, scanCode, flags, time, dwExtraInfo});
        }


        public Object clone() {
            KBDLLHOOKSTRUCT clone = new KBDLLHOOKSTRUCT();
            clone.initFrom(this);
            return clone;
        }
    }

    // callback proc
    static class CallbackProc extends Callback {
        private Int code = new Int();
        private UInt32 wParam = new UInt32();
        private UInt32 lParam = new UInt32();
        private UInt32 lResult = new UInt32();

        public CallbackProc() {
            init(new Parameter[]{code, wParam, lParam}, lResult);
        }

        public void callback() {
            Pointer.Void structureHandle = new Pointer.Void(lParam.getValue());

            KBDLLHOOKSTRUCT ref = new KBDLLHOOKSTRUCT();
            Pointer structurePointer = new Pointer(ref);
            structureHandle.castTo(structurePointer);

            System.out.println("Keyboard event: vkCode = " + ref.vkCode + ", scanCode = " + ref.scanCode);
        }
    }

    public void start() {
        _dispatchThread = new MessageLoopThread("HookDispatchThread");
        _dispatchThread.doStart();

        // install a hook in the dispatch thread
        try {
            _dispatchThread.doInvokeAndWait(new Runnable() {
                public void run() {
                    final User32 user32 = User32.getInstance();
                    final Function setWindowsHook = user32.getFunction(FUNCTION_SET_WINDOWS_HOOK.toString());
                    UInt32 threadID = new UInt32(0);

                    CallbackProc trackingCallback = new CallbackProc();
                    keyboardHookHandle = new Handle();
                    final Module currentModule = Module.getCurrent();
                    setWindowsHook.invoke(keyboardHookHandle, new Parameter[] {
                            new Int(WH_KEYBOARD_LL),
                            trackingCallback,
                            currentModule,
                            threadID
                    });
                }
            });
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void stop() {
        _dispatchThread.doInvokeLater(new Runnable() {
            public void run() {
                final User32 user32 = User32.getInstance();
                final Function unhook = user32.getFunction(FUNCTION_UNHOOK_WINDOWS_HOOK);
                unhook.invoke(null, keyboardHookHandle);
            }
        });
        _dispatchThread.doStop();

    }

    public static void main(String[] args) throws Exception {
        LowLevelKeyboardHookSample sample = new LowLevelKeyboardHookSample();
        sample.start();
        try {
            System.out.println("Press 'Enter' to terminate the sample.");
            System.in.read();
        }
        finally {
            sample.stop();
        }
    }
}