#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <windows.h>
#define VOLUME 0.15
#include "board/boards.h"
#define BOARD(str, buf) { .name = str, .wav = buf },
typedef struct {
char *name;
unsigned char* wav;
} Board;
Board boards[] = {
BOARDS
};
int const boards_n = sizeof(boards) / sizeof(boards[0]);
void sound_init(void)
{
DWORD channel_volume = VOLUME * 0xFFFF;
waveOutSetVolume(NULL, (channel_volume << 16) | channel_volume);
}
void sound_play(unsigned char *buffer)
{
PlaySound((const char *) buffer, NULL, SND_MEMORY | SND_ASYNC | SND_NODEFAULT);
}
Board *get_board(char *board_name)
{
for (int i = 0; i < boards_n; ++i) {
if (strcmp(boards[i].name, board_name) == 0)
return &boards[i];
}
return NULL;
}
HHOOK keyboard_hook_windows;
void keyboard_unhook(void)
{
fprintf(stderr, "Cleaning up keyboard hook...\n");
if (!UnhookWindowsHookEx(keyboard_hook_windows))
printf("WARN: Windows keyboard hook could not be cleaned up! Error code: %lu\n", GetLastError());
}
void do_exit(int code)
{
keyboard_unhook();
fprintf(stderr, "Goodbye.\n");
exit(code);
}
void on_clean_exit(void) { do_exit(0); }
Board *board = NULL;
void keyboard_on_down(void)
{
if (board == NULL)
return;
sound_play(board->wav);
}
void keyboard_on_up(void)
{
// if (board == NULL)
// return;
}
/* https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms644985(v=vs.85) */
LRESULT CALLBACK keyboard_windows_callback(int nCode, WPARAM wParam, LPARAM lParam)
{
/* Needed to prevent repeat fires */
static DWORD prev_vk = 0;
/* Do not handle unless nCode >= 0, pass to next hook right away */
if (nCode >= 0) {
BOOL down = wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN;
KBDLLHOOKSTRUCT *hook_struct = (KBDLLHOOKSTRUCT *) lParam;
DWORD vk = hook_struct->vkCode;
if (down && vk != prev_vk) {
keyboard_on_down();
prev_vk = vk;
} else {
keyboard_on_up();
/* Seems like repeat strokes are 0x0 or 0x1, and 'real' ones have 0x80 flag??
Not really sure how to handle this one, can't find many docs...
Saw this though: https://github.com/pyglet/pyglet/blob/838d004d68fcc5c3ce83b733e3d088fad0643859/pyglet/window/win32/__init__.py#L794= */
if (hook_struct->flags & 0x80)
prev_vk = 0;
}
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
void keyboard_hook(void)
{
fprintf(stderr, "Setting up keyboard hook...\n");
keyboard_hook_windows = SetWindowsHookEx(WH_KEYBOARD_LL, keyboard_windows_callback, NULL, 0);
if (keyboard_hook_windows == NULL) {
printf("ERROR: Could not set up Windows keyboard hook.\n");
do_exit(1);
}
}
int main(int argc, char **argv)
{
if (argc < 2 || (board = get_board(argv[1])) == NULL) {
printf("Please provide a valid board name.\n");
printf("Valid boards: ");
for (int i = 0; i < boards_n; ++i) {
printf("%s%s", boards[i].name, (i == boards_n - 1) ? "\n" : ", ");
}
return 1;
}
sound_init();
keyboard_hook();
atexit(on_clean_exit);
fprintf(stderr, "Hooks set up, welcome to Clak!\n");
MSG msg;
BOOL status;
while ((status = GetMessage(&msg, NULL, 0, 0))) {
if (status == -1) {
// error case
printf("ERROR: Windows error, code %lu\n", GetLastError());
exit(1);
}
}
return 0;
}