/* $Id: $ */
/** @file
 *
 * spy1 - the program.
 *
 * Copyright (c) 1997-2006 knut st. osmundsen <bird@anduin.net>
 *
 * GPL
 *
 */

/*******************************************************************************
*   Header Files                                                               *
*******************************************************************************/
#define INCL_GPI
#define INCL_DOS
#define INCL_WIN
#define INCL_ERRORS
#include <os2.h>
#include <string.h>
#include <stdio.h>
#include <stdint.h>
#include <ctype.h>
#include "spy1.h"


/**
 * Get the name of a virtual key.
 * @returns the key name (readonly).
 * @param   ulVirtualKey    The virtual key.
 */
const char *GetVirtualKeyName(ULONG ulVirtualKey)
{
    switch (ulVirtualKey) {
#define CASE(vk) case vk: return #vk
        CASE(VK_BUTTON1);
        CASE(VK_BUTTON2);
        CASE(VK_BUTTON3);
        CASE(VK_BREAK);
        CASE(VK_BACKSPACE);
        CASE(VK_TAB);
        CASE(VK_BACKTAB);
        CASE(VK_NEWLINE);
        CASE(VK_SHIFT);
        CASE(VK_CTRL);
        CASE(VK_ALT);
        CASE(VK_ALTGRAF);
        CASE(VK_PAUSE);
        CASE(VK_CAPSLOCK);
        CASE(VK_ESC);
        CASE(VK_SPACE);
        CASE(VK_PAGEUP);
        CASE(VK_PAGEDOWN);
        CASE(VK_END);
        CASE(VK_HOME);
        CASE(VK_LEFT);
        CASE(VK_UP);
        CASE(VK_RIGHT);
        CASE(VK_DOWN);
        CASE(VK_PRINTSCRN);
        CASE(VK_INSERT);
        CASE(VK_DELETE);
        CASE(VK_SCRLLOCK);
        CASE(VK_NUMLOCK);
        CASE(VK_ENTER);
        CASE(VK_SYSRQ);
        CASE(VK_F1);
        CASE(VK_F2);
        CASE(VK_F3);
        CASE(VK_F4);
        CASE(VK_F5);
        CASE(VK_F6);
        CASE(VK_F7);
        CASE(VK_F8);
        CASE(VK_F9);
        CASE(VK_F10);
        CASE(VK_F11);
        CASE(VK_F12);
        CASE(VK_F13);
        CASE(VK_F14);
        CASE(VK_F15);
        CASE(VK_F16);
        CASE(VK_F17);
        CASE(VK_F18);
        CASE(VK_F19);
        CASE(VK_F20);
        CASE(VK_F21);
        CASE(VK_F22);
        CASE(VK_F23);
        CASE(VK_F24);
        CASE(VK_ENDDRAG);
        CASE(VK_CLEAR);
        CASE(VK_EREOF);
        CASE(VK_PA1);
        CASE(VK_ATTN);
        CASE(VK_CRSEL);
        CASE(VK_EXSEL);
        CASE(VK_COPY);
        CASE(VK_BLK1);
        CASE(VK_BLK2);
#undef CASE
        default:
            return "";
    }
}

/**
 * Prints the given KC flags.
 * @param   fKC     The KC_* flags to print.
 */
void PrintKC(USHORT fKC)
{
#define PRINT_KC(kc) if (fKC & kc) printf(" " #kc);
    PRINT_KC(KC_CHAR);
    PRINT_KC(KC_VIRTUALKEY);
    PRINT_KC(KC_SCANCODE);
    PRINT_KC(KC_SHIFT);
    PRINT_KC(KC_CTRL);
    PRINT_KC(KC_ALT);
    PRINT_KC(KC_KEYUP);
    PRINT_KC(KC_PREVDOWN);
    PRINT_KC(KC_LONEKEY);
    PRINT_KC(KC_DEADKEY);
    PRINT_KC(KC_COMPOSITE);
    PRINT_KC(KC_INVALIDCOMP);
    PRINT_KC(KC_TOGGLE);
    PRINT_KC(KC_INVALIDCHAR);
    PRINT_KC(KC_DBCSRSRVD1);
    PRINT_KC(KC_DBCSRSRVD2);
#undef PRINT_KC
}


/**
 * Prints the given KDD flags.
 * @param   fKDD    The KDD_* flags to print.
 */
void PrintKDD(USHORT fKDD)
{
    /* action */
    switch (fKDD & KDD_ACTIONFIELD) {
#define PRINT_KDD(kdd) case kdd: printf(" " #kdd); break
        PRINT_KDD(KDD_PUTINKIB);
        PRINT_KDD(KDD_ACK);
        PRINT_KDD(KDD_PREFIXKEY);
        PRINT_KDD(KDD_OVERRUN);
        PRINT_KDD(KDD_RESEND);
        PRINT_KDD(KDD_REBOOTKEY);
        PRINT_KDD(KDD_DUMPKEY);
        PRINT_KDD(KDD_SHIFTKEY);
        PRINT_KDD(KDD_PAUSEKEY);
        PRINT_KDD(KDD_PSEUDOPAUSE);
        PRINT_KDD(KDD_WAKEUPKEY);
        PRINT_KDD(KDD_BADACCENT);
        PRINT_KDD(KDD_HOTKEY);
        PRINT_KDD(KDD_ACCENTKEY);
        PRINT_KDD(KDD_BREAKKEY);
        PRINT_KDD(KDD_PSEUDOBREAK);
        PRINT_KDD(KDD_PRTSCKEY);
        PRINT_KDD(KDD_PRTECHOKEY);
        PRINT_KDD(KDD_PSEUDOPRECH);
        PRINT_KDD(KDD_STATUSCHG);
        PRINT_KDD(KDD_WRITTENKEY);
        PRINT_KDD(KDD_UNDEFINED);
#undef PRINT_KDD
    }
    /* the flags */
#define PRINT_KDD(kdd) if (fKDD & kdd) printf(" " #kdd);
    PRINT_KDD(KDD_ACCENTED);
    PRINT_KDD(KDD_MULTIMAKE);
    PRINT_KDD(KDD_SECONDARY);
    PRINT_KDD(KDD_BREAK);
    PRINT_KDD(KDD_EXTENDEDKEY);
    PRINT_KDD(KDD_KC_LONEKEY);
    PRINT_KDD(KDD_KC_PREVDOWN);
    PRINT_KDD(KDD_KC_KEYUP);
    PRINT_KDD(KDD_KC_ALT);
    PRINT_KDD(KDD_KC_CTRL);
    PRINT_KDD(KDD_KC_SHIFT);
#undef PRINT_KDD
}

/**
 * Decods the message (if known to us).
 *
 * @param   pQmsg       The message.
 */
void PrintMsg(const QMSG *pQmsg)
{
    switch (pQmsg->msg) {
        case WM_VIOCHAR:
#if 1
            if (SHORT1FROMMP(pQmsg->mp1) & KC_PREVDOWN) {
                break;
            }
#endif
            printf("%09ld: ", pQmsg->time);
            printf("WM_VIOCHAR: fKC=%04x rep=%02x scan=%02x ",
                   SHORT1FROMMP(pQmsg->mp1), CHAR3FROMMP(pQmsg->mp1), CHAR4FROMMP(pQmsg->mp1));
            printf("xlch=%02x(%c) xlscan=%02x fKDD=%04x",
                   CHAR1FROMMP(pQmsg->mp2), isprint(CHAR1FROMMP(pQmsg->mp2)) ? CHAR1FROMMP(pQmsg->mp2) : '.',
                   CHAR2FROMMP(pQmsg->mp2), SHORT2FROMMP(pQmsg->mp2));
            PrintKC(SHORT1FROMMP(pQmsg->mp1));
            PrintKDD(SHORT2FROMMP(pQmsg->mp2));
            printf("\n");
            break;

        case WM_CHAR:
#if 1
            if (SHORT1FROMMP(pQmsg->mp1) & KC_PREVDOWN) {
                break;
            }
#endif
            printf("%09ld: ", pQmsg->time);
            printf("   WM_CHAR: fKC=%04x rep=%02x scan=%02x ",
                   SHORT1FROMMP(pQmsg->mp1), CHAR3FROMMP(pQmsg->mp1), CHAR4FROMMP(pQmsg->mp1));
            printf("ch=%04x(%c)             vk=%04x",
                   SHORT1FROMMP(pQmsg->mp2), isprint(SHORT1FROMMP(pQmsg->mp2)) && (SHORT1FROMMP(pQmsg->mp1) & KC_CHAR) ? SHORT1FROMMP(pQmsg->mp2) : '.',
                   SHORT2FROMMP(pQmsg->mp2));
            if (SHORT1FROMMP(pQmsg->mp1) & KC_VIRTUALKEY) {
                printf("(%s)", GetVirtualKeyName(SHORT2FROMMP(pQmsg->mp2)));
            }
            PrintKC(SHORT1FROMMP(pQmsg->mp1));
            printf("\n");
            break;
    }
}


/**
 * Prints the message.
 * @param   pEvent      The message to print.
 */
void PrintEvent(PSPY1EVENT pEvent)
{
#if 0
    /* first line, raw data */
    printf("p%04lx t%02lx", pEvent->pid, pEvent->tid);
    switch (pEvent->ulHook) {
        case HK_JOURNALRECORD:  printf(" JR"); break;
        case HK_INPUT:          printf(" IN"); break;
        default:                printf(" %ld\n", pEvent->ulHook); break;
    }
    switch (pEvent->enmType) {
        case SPY1EVENTTYPE_MSG_REMOVED: printf(" RM"); break;
        case SPY1EVENTTYPE_MSG_PEEK:    printf(" PK"); break;
        default:                        printf(" invalid msg (%d)\n", pEvent->enmType); return;
    }
#endif
    switch (pEvent->enmType) {
        case SPY1EVENTTYPE_MSG_REMOVED:
#if 0
        case SPY1EVENTTYPE_MSG_PEEK:
            printf(" time=%08lx hwnd=%08lx msg=%04lx mp1=%08x mp2=%08x (%ld,%ld)\n",
                   pEvent->u.Msg.time,
                   pEvent->u.Msg.hwnd,
                   pEvent->u.Msg.msg,
                   (uintptr_t)pEvent->u.Msg.mp1,
                   (uintptr_t)pEvent->u.Msg.mp2,
                   pEvent->u.Msg.ptl.x,
                   pEvent->u.Msg.ptl.y);
#endif
            PrintMsg(&pEvent->u.Msg);
            break;
        default: break; /* pedantic gcc */
    }
}

/**
 * Check if it's an event we'll ignore.
 *
 * @returns TRUE if we should ignore the event, otherwise FALSE.
 * @param   pEvent      The event in question.
 */
BOOL IgnoreEvent(PSPY1EVENT pEvent)
{
    return pEvent->enmType == SPY1EVENTTYPE_MSG_REMOVED
        && (    pEvent->u.Msg.msg == WM_TIMER
            ||  pEvent->u.Msg.msg == WM_PAINT
            ||  pEvent->u.Msg.msg >= WM_USER
            ||  (pEvent->u.Msg.msg >= WM_SEM1 && pEvent->u.Msg.msg <= WM_SEM4)
#if 1
            ||  (pEvent->u.Msg.msg >= WM_MOUSEFIRST && pEvent->u.Msg.msg <= WM_MOUSELAST)
            ||  (pEvent->u.Msg.msg >= WM_EXTMOUSEFIRST && pEvent->u.Msg.msg <= WM_MOUSETRANSLATELAST)
#endif
#if 1
            ||  pEvent->u.Msg.msg == WM_JOURNALNOTIFY
#endif
           );
}


int main(void)
{
	HAB hab;
	HMQ hmq;
    PPIB pPib;
    PTIB pTib;
    int rc;

    /*
     * The PM prologue.
     */
    DosGetInfoBlocks(&pTib, &pPib);
    pPib->pib_ultype = 3;

	hab = WinInitialize(0);
    hmq = WinCreateMsgQueue(hab, 0);

    /*
     * Install the hooks.
     */
    rc = SetHooks(hab);
    if (rc == NO_ERROR) {
        /*
         * The event printing loop.
         */
        for (;;) {
            SPY1EVENT Event;
            rc = GetEvent(&Event, SEM_INDEFINITE_WAIT);
            if (rc) {
                printf("GetEvent failed, rc=%d\n", rc);
            }
            if (!IgnoreEvent(&Event)) {
                PrintEvent(&Event);
            }
        }

        /* cleanup */
        ReleaseHooks(hab);
    } else {
        printf("InstallHooks failed with rc=%#x\n", rc);
    }

    /*
     * The PM epilogue.
     */
	WinDestroyMsgQueue(hmq);
	WinTerminate(hab);
	return NO_ERROR;
}

