/*
 * 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.*;
import com.jniwrapper.win32.system.Module;
import com.jniwrapper.win32.ui.User32;
import com.jniwrapper.win32.ui.Wnd;

/**
 * This sample demonstrates keyboard and mouse events tracking by adding global Journal hook.
 *
 * NOTE: Journal hooks are limited under MS Vista, so this sample may not work.
 * For details look at:
 *
 * http://social.msdn.microsoft.com/forums/en-US/windowssecurity/thread/3dc0a67e-c2f0-4e70-8990-3a6947f1155a/
 *
 * @author Vadim Ridosh
 */
public class JournalHookSample
{
    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 Function _setWindowsHook;
    static Function _callNextHookEx;

    static final int WH_JOURNALRECORD = 0;
    static final int HC_ACTION = 0;
    static final int HC_GETNEXT = 1;
    static final int HC_SKIP = 2;

    private static Handle journalHookHandle = new Handle();
    private MessageLoopThread _dispatchThread;

    public JournalHookSample() {
    }

    static class EventMsg extends Structure {
        UInt32 message = new UInt32();
        UInt32 paramL = new UInt32();
        UInt32 paramH = new UInt32();
        UInt32 time = new UInt32();
        Wnd hwnd = new Wnd();

        EventMsg()
        {
            init(new Parameter[] {message, paramL, paramH, time, hwnd});
        }

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

        public UInt32 getMessage()
        {
            return message;
        }

        public UInt32 getParamL()
        {
            return paramL;
        }

        public UInt32 getParamH()
        {
            return paramH;
        }

        public UInt32 getTime()
        {
            return time;
        }

        public Wnd getHwnd()
        {
            return hwnd;
        }
    }

    // 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() {
            if (code.getValue() == HC_ACTION) {
                Pointer.Void structureHandle = new Pointer.Void(lParam.getValue());
                Pointer evMsg = new Pointer(new EventMsg());
                structureHandle.castTo(evMsg);

                EventMsg res = (EventMsg)evMsg.getReferencedObject();
                System.out.println(" msg = " + res.getMessage().getValue());
            } else {
                _callNextHookEx.invoke(this.getReturnValue(),
                        journalHookHandle, code, wParam, lParam);
            }
        }
    }

    public void start() {
        final User32 user32 = User32.getInstance();
        _callNextHookEx = user32.getFunction(FUNCTION_CALL_NEXT_HOOK);
        _setWindowsHook = user32.getFunction(FUNCTION_SET_WINDOWS_HOOK.toString());

        _dispatchThread = new MessageLoopThread("HookDispatchThread");
        _dispatchThread.doStart();

        // install a hook in the dispatch thread
        try {
            _dispatchThread.doInvokeAndWait(new Runnable() {
                public void run() {

                    CallbackProc trackingCallback = new CallbackProc();
                    journalHookHandle = new Handle();
                    final Module currentModule = Module.getCurrent();
                    long res = _setWindowsHook.invoke(journalHookHandle, new Parameter[] {
                            new Int(WH_JOURNALRECORD),
                            trackingCallback,
                            currentModule,
                            new UInt32(0)
                    });
                    if (res != 0) {
                        throw new LastErrorException(res);
                    }
                }
            });
        } 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, journalHookHandle);
            }
        });
        _dispatchThread.doStop();

    }

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