diff --git a/code/logic/CMakeLists.txt b/code/logic/CMakeLists.txt index 8832d9f..b34e2ff 100644 --- a/code/logic/CMakeLists.txt +++ b/code/logic/CMakeLists.txt @@ -13,6 +13,7 @@ set(TEST_CODE output.c soap.c stream.c + keyboard.c ) # Create the library target diff --git a/code/logic/fossil/io/framework.h b/code/logic/fossil/io/framework.h index 397642d..3e59ce5 100644 --- a/code/logic/fossil/io/framework.h +++ b/code/logic/fossil/io/framework.h @@ -15,6 +15,7 @@ #define FOSSIL_IO_FRAMEWORK_H // Include the necessary headers +#include "keyboard.h" #include "output.h" #include "input.h" #include "error.h" diff --git a/code/logic/fossil/io/keyboard.h b/code/logic/fossil/io/keyboard.h new file mode 100644 index 0000000..4a68595 --- /dev/null +++ b/code/logic/fossil/io/keyboard.h @@ -0,0 +1,145 @@ +/* + * ----------------------------------------------------------------------------- + * Project: Fossil Logic + * + * This file is part of the Fossil Logic project, which aims to develop high- + * performance, cross-platform applications and libraries. The code contained + * herein is subject to the terms and conditions defined in the project license. + * + * Author: Michael Gene Brockus (Dreamer) + * + * Copyright (C) 2024 Fossil Logic. All rights reserved. + * ----------------------------------------------------------------------------- + */ +#ifndef FOSSIL_IO_KEYBOARD_H +#define FOSSIL_IO_KEYBOARD_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Define a keyboard event structure +typedef struct { + int shift; // 1 if Shift is pressed, 0 otherwise + int ctrl; // 1 if Ctrl is pressed, 0 otherwise + int alt; // 1 if Alt is pressed, 0 otherwise + char key; // The character of the key pressed +} fossil_io_keyboard_event_t; + +// Define a callback type for key events +typedef void (*fossil_io_keyboard_callback_t)(fossil_io_keyboard_event_t event); + +/** + * Initialize the keyboard library. + * Sets up any platform-specific configurations. + */ +void fossil_io_keyboard_init(void); + +/** + * Shut down the keyboard library. + * Cleans up any platform-specific configurations. + */ +void fossil_io_keyboard_shutdown(void); + +/** + * Clear all keybindings from the library. + */ +void fossil_io_keyboard_clear_bindings(void); + +/** + * Register a keybinding with the library. + * + * @param event The keyboard event to bind to. + * @param callback The callback function to call when the event occurs. + */ +void fossil_io_keyboard_register_binding(fossil_io_keyboard_event_t event, fossil_io_keyboard_callback_t callback); + +/** + * Unregister a keybinding with the library. + * + * @param event The keyboard event to unbind. + */ +void fossil_io_keyboard_unregister_binding(fossil_io_keyboard_event_t event); + +/** + * Poll for keyboard events and trigger any registered callbacks. + * This function should be called in the main loop of the application. + */ +void fossil_io_keyboard_poll_events(void); + +#ifdef __cplusplus +} + +/** + * C++ wrapper for the SOAP API. + */ +namespace fossil { + + /** + * Namespace for I/O operations. + */ + namespace io { + /** + * Class for interacting with the keyboard. + */ + class keyboard { + public: + /** + * Initialize the keyboard library. + * Sets up any platform-specific configurations. + */ + static void init() { + fossil_io_keyboard_init(); + } + + /** + * Shut down the keyboard library. + * Cleans up any platform-specific configurations. + */ + static void shutdown() { + fossil_io_keyboard_shutdown(); + } + + /** + * Clear all keybindings from the library. + */ + static void clear_bindings() { + fossil_io_keyboard_clear_bindings(); + } + + /** + * Register a keybinding with the library. + * + * @param event The keyboard event to bind to. + * @param callback The callback function to call when the event occurs. + */ + static void register_binding(fossil_io_keyboard_event_t event, fossil_io_keyboard_callback_t callback) { + fossil_io_keyboard_register_binding(event, callback); + } + + /** + * Unregister a keybinding with the library. + * + * @param event The keyboard event to unbind. + */ + static void unregister_binding(fossil_io_keyboard_event_t event) { + fossil_io_keyboard_unregister_binding(event); + } + + /** + * Poll for keyboard events and trigger any registered callbacks. + * This function should be called in the main loop of the application. + */ + static void poll_events() { + fossil_io_keyboard_poll_events(); + } + }; + } +} + +#endif + +#endif /* FOSSIL_IO_FRAMEWORK_H */ diff --git a/code/logic/keyboard.c b/code/logic/keyboard.c new file mode 100644 index 0000000..339b0fa --- /dev/null +++ b/code/logic/keyboard.c @@ -0,0 +1,179 @@ +/* + * ----------------------------------------------------------------------------- + * Project: Fossil Logic + * + * This file is part of the Fossil Logic project, which aims to develop high- + * performance, cross-platform applications and libraries. The code contained + * herein is subject to the terms and conditions defined in the project license. + * + * Author: Michael Gene Brockus (Dreamer) + * + * Copyright (C) 2024 Fossil Logic. All rights reserved. + * ----------------------------------------------------------------------------- + */ +#include "fossil/io/keyboard.h" +#include +#include +#include + +#if defined(_WIN32) || defined(_WIN64) + #include + #include +#else + #include + #include + #include + #include // For fd_set, FD_ZERO, FD_SET, select + #include // For struct timeval + #include // For system types +#endif + +#define MAX_KEYBINDS 256 + +typedef struct { + fossil_io_keyboard_event_t event; + fossil_io_keyboard_callback_t callback; +} fossil_io_keyboard_binding_t; + +typedef struct { + fossil_io_keyboard_binding_t bindings[MAX_KEYBINDS]; + size_t count; +} fossil_io_keyboard_manager_t; + +static fossil_io_keyboard_manager_t keyboard_manager = { .count = 0 }; + +#if defined(_WIN32) || defined(_WIN64) + +static int fossil_io_keyboard_is_key_pressed(void) { + return _kbhit(); +} + +static fossil_io_keyboard_event_t fossil_io_keyboard_get_event(void) { + fossil_io_keyboard_event_t event = {0}; + int key = _getch(); + + // Check for extended keys (e.g., arrow keys, function keys) + if (key == 0 || key == 224) { + key = _getch(); // Fetch the actual key code + } + + // Check modifiers + event.shift = GetKeyState(VK_SHIFT) & 0x8000; + event.ctrl = GetKeyState(VK_CONTROL) & 0x8000; + event.alt = GetKeyState(VK_MENU) & 0x8000; + event.key = (char)key; + + return event; +} + +#else +static struct termios old_termios, new_termios; + +static void fossil_io_keyboard_enable_raw_mode(void) { + tcgetattr(STDIN_FILENO, &old_termios); + new_termios = old_termios; + new_termios.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &new_termios); +} + +static void fossil_io_keyboard_disable_raw_mode(void) { + tcsetattr(STDIN_FILENO, TCSANOW, &old_termios); +} + +static int fossil_io_keyboard_is_key_pressed(void) { + struct timeval timeout = {0L, 0L}; + fd_set fds; + FD_ZERO(&fds); + FD_SET(STDIN_FILENO, &fds); + return select(STDIN_FILENO + 1, &fds, NULL, NULL, &timeout) > 0; +} + +static fossil_io_keyboard_event_t fossil_io_keyboard_get_event(void) { + fossil_io_keyboard_event_t event = {0}; + char c; + read(STDIN_FILENO, &c, 1); + + event.key = c; + + // Modifier keys (POSIX doesn't have direct access to key states; approximation) + if (c == 27) { // Alt key (ESC sequence) + event.alt = 1; + read(STDIN_FILENO, &c, 1); + event.key = c; + } else if (c < 32) { // Ctrl key (control characters) + event.ctrl = 1; + event.key = c + 96; // Map to lowercase ASCII + } else if (c == 127) { // Backspace key with Ctrl + event.ctrl = 1; + event.key = 8; + } + + return event; +} +#endif + +void fossil_io_keyboard_init(void) { +#if defined(_WIN32) || defined(_WIN64) + // Windows doesn't require explicit setup for raw mode. +#else + fossil_io_keyboard_enable_raw_mode(); + atexit(fossil_io_keyboard_disable_raw_mode); +#endif +} + +void fossil_io_keyboard_shutdown(void) { +#if defined(_WIN32) || defined(_WIN64) + // Windows doesn't require explicit cleanup for raw mode. +#else + fossil_io_keyboard_disable_raw_mode(); +#endif +} + +void fossil_io_keyboard_clear_bindings(void) { + keyboard_manager.count = 0; +} + +void fossil_io_keyboard_register_binding(fossil_io_keyboard_event_t event, fossil_io_keyboard_callback_t callback) { + if (keyboard_manager.count < MAX_KEYBINDS) { + keyboard_manager.bindings[keyboard_manager.count].event = event; + keyboard_manager.bindings[keyboard_manager.count].callback = callback; + keyboard_manager.count++; + } else { + fprintf(stderr, "Max keybindings reached.\n"); + } +} + +void fossil_io_keyboard_unregister_binding(fossil_io_keyboard_event_t event) { + for (size_t i = 0; i < keyboard_manager.count; ++i) { + if (keyboard_manager.bindings[i].event.key == event.key && + keyboard_manager.bindings[i].event.shift == event.shift && + keyboard_manager.bindings[i].event.ctrl == event.ctrl && + keyboard_manager.bindings[i].event.alt == event.alt) { + for (size_t j = i; j < keyboard_manager.count - 1; ++j) { + keyboard_manager.bindings[j] = keyboard_manager.bindings[j + 1]; + } + keyboard_manager.count--; + return; + } + } + fprintf(stderr, "No matching keybinding to unregister.\n"); +} + +void fossil_io_keyboard_poll_events(void) { + if (fossil_io_keyboard_is_key_pressed()) { + fossil_io_keyboard_event_t event = fossil_io_keyboard_get_event(); + + for (size_t i = 0; i < keyboard_manager.count; ++i) { + fossil_io_keyboard_binding_t *binding = &keyboard_manager.bindings[i]; + if (binding->event.key == event.key && + binding->event.shift == event.shift && + binding->event.ctrl == event.ctrl && + binding->event.alt == event.alt) { + if (binding->callback) { + binding->callback(event); + } + break; + } + } + } +} diff --git a/code/logic/meson.build b/code/logic/meson.build index 8fb21d6..8bda5cb 100644 --- a/code/logic/meson.build +++ b/code/logic/meson.build @@ -2,7 +2,7 @@ dir = include_directories('.') fossil_io_lib = library('fossil-io', - files('input.c', 'output.c', 'error.c', 'soap.c', 'stream.c'), + files('input.c', 'output.c', 'error.c', 'soap.c', 'stream.c', 'keyboard.c'), install: true, include_directories: dir) diff --git a/code/tests/cases/test_keyboard.c b/code/tests/cases/test_keyboard.c new file mode 100644 index 0000000..e8af898 --- /dev/null +++ b/code/tests/cases/test_keyboard.c @@ -0,0 +1,87 @@ +/* + * ----------------------------------------------------------------------------- + * Project: Fossil Logic + * + * This file is part of the Fossil Logic project, which aims to develop high- + * performance, cross-platform applications and libraries. The code contained + * herein is subject to the terms and conditions defined in the project license. + * + * Author: Michael Gene Brockus (Dreamer) + * + * Copyright (C) 2024 Fossil Logic. All rights reserved. + * ----------------------------------------------------------------------------- + */ +#include + +#include "fossil/io/framework.h" + +// * * * * * * * * * * * * * * * * * * * * * * * * +// * Fossil Logic Test Utilites +// * * * * * * * * * * * * * * * * * * * * * * * * +// Setup steps for things like test fixtures and +// mock objects are set here. +// * * * * * * * * * * * * * * * * * * * * * * * * + +// Define the test suite and add test cases +FOSSIL_TEST_SUITE(c_keyboard_suite); + +// Setup function for the test suite +FOSSIL_SETUP(c_keyboard_suite) { + // Setup code here +} + +// Teardown function for the test suite +FOSSIL_TEARDOWN(c_keyboard_suite) { + // Teardown code here +} + +// * * * * * * * * * * * * * * * * * * * * * * * * +// * Fossil Logic Test Cases +// * * * * * * * * * * * * * * * * * * * * * * * * +// The test cases below are provided as samples, inspired +// by the Meson build system's approach of using test cases +// as samples for library usage. +// * * * * * * * * * * * * * * * * * * * * * * * * + +FOSSIL_TEST_CASE(c_test_keyboard_register_unregister_binding) { + fossil_io_keyboard_event_t event = { .key = 'a', .shift = 0, .ctrl = 0, .alt = 0 }; + fossil_io_keyboard_callback_t callback = (fossil_io_keyboard_callback_t)1; // Assuming a valid callback function + + fossil_io_keyboard_register_binding(event, callback); + ASSUME_NOT_CNULL(callback); // Assumption on pointer + + fossil_io_keyboard_unregister_binding(event); + ASSUME_NOT_CNULL(callback); // Assumption on pointer +} + +FOSSIL_TEST_CASE(c_test_keyboard_clear_bindings) { + fossil_io_keyboard_event_t event = { .key = 'a', .shift = 0, .ctrl = 0, .alt = 0 }; + fossil_io_keyboard_callback_t callback = (fossil_io_keyboard_callback_t)1; // Assuming a valid callback function + + fossil_io_keyboard_register_binding(event, callback); + fossil_io_keyboard_clear_bindings(); + ASSUME_NOT_CNULL(callback); // Assumption on pointer +} + +FOSSIL_TEST_CASE(c_test_keyboard_poll_events) { + fossil_io_keyboard_event_t event = { .key = 'a', .shift = 0, .ctrl = 0, .alt = 0 }; + fossil_io_keyboard_callback_t callback = (fossil_io_keyboard_callback_t)1; // Assuming a valid callback function + + fossil_io_keyboard_register_binding(event, callback); + fossil_io_keyboard_poll_events(); + ASSUME_NOT_CNULL(callback); // Assumption on pointer + fossil_io_keyboard_unregister_binding(event); +} + + +// * * * * * * * * * * * * * * * * * * * * * * * * +// * Fossil Logic Test Pool +// * * * * * * * * * * * * * * * * * * * * * * * * + +FOSSIL_TEST_GROUP(c_keyboard_tests) { + FOSSIL_TEST_ADD(c_keyboard_suite, c_test_keyboard_register_unregister_binding); + FOSSIL_TEST_ADD(c_keyboard_suite, c_test_keyboard_clear_bindings); + FOSSIL_TEST_ADD(c_keyboard_suite, c_test_keyboard_poll_events); + + FOSSIL_TEST_REGISTER(c_keyboard_suite); +} diff --git a/code/tests/cases/test_keyboard.cpp b/code/tests/cases/test_keyboard.cpp new file mode 100644 index 0000000..ab3a02a --- /dev/null +++ b/code/tests/cases/test_keyboard.cpp @@ -0,0 +1,127 @@ +/* + * ----------------------------------------------------------------------------- + * Project: Fossil Logic + * + * This file is part of the Fossil Logic project, which aims to develop high- + * performance, cross-platform applications and libraries. The code contained + * herein is subject to the terms and conditions defined in the project license. + * + * Author: Michael Gene Brockus (Dreamer) + * + * Copyright (C) 2024 Fossil Logic. All rights reserved. + * ----------------------------------------------------------------------------- + */ +#include + +#include "fossil/io/framework.h" + +// * * * * * * * * * * * * * * * * * * * * * * * * +// * Fossil Logic Test Utilites +// * * * * * * * * * * * * * * * * * * * * * * * * +// Setup steps for things like test fixtures and +// mock objects are set here. +// * * * * * * * * * * * * * * * * * * * * * * * * + +// Define the test suite and add test cases +FOSSIL_TEST_SUITE(cpp_keyboard_suite); + +// Setup function for the test suite +FOSSIL_SETUP(cpp_keyboard_suite) { + // Setup code here +} + +// Teardown function for the test suite +FOSSIL_TEARDOWN(cpp_keyboard_suite) { + // Teardown code here +} + +// * * * * * * * * * * * * * * * * * * * * * * * * +// * Fossil Logic Test Cases +// * * * * * * * * * * * * * * * * * * * * * * * * +// The test cases below are provided as samples, inspired +// by the Meson build system's approach of using test cases +// as samples for library usage. +// * * * * * * * * * * * * * * * * * * * * * * * * + +FOSSIL_TEST_CASE(cpp_test_keyboard_register_unregister_binding) { + fossil_io_keyboard_event_t event = { 'a', 0, 0, 0 }; + fossil_io_keyboard_callback_t callback = (fossil_io_keyboard_callback_t)1; // Assuming a valid callback function + + fossil_io_keyboard_register_binding(event, callback); + ASSUME_NOT_CNULL(callback); // Assumption on pointer + + fossil_io_keyboard_unregister_binding(event); + ASSUME_NOT_CNULL(callback); // Assumption on pointer +} + +FOSSIL_TEST_CASE(cpp_test_keyboard_clear_bindings) { + fossil_io_keyboard_event_t event = { 'a', 0, 0, 0 }; + fossil_io_keyboard_callback_t callback = (fossil_io_keyboard_callback_t)1; // Assuming a valid callback function + + fossil_io_keyboard_register_binding(event, callback); + fossil_io_keyboard_clear_bindings(); + ASSUME_NOT_CNULL(callback); // Assumption on pointer +} + +FOSSIL_TEST_CASE(cpp_test_keyboard_poll_events) { + fossil_io_keyboard_event_t event = { 'a', 0, 0, 0 }; + fossil_io_keyboard_callback_t callback = (fossil_io_keyboard_callback_t)1; // Assuming a valid callback function + + fossil_io_keyboard_register_binding(event, callback); + fossil_io_keyboard_poll_events(); + ASSUME_NOT_CNULL(callback); // Assumption on pointer + fossil_io_keyboard_unregister_binding(event); +} + +FOSSIL_TEST_CASE(cpp_test_keyboard_class_register_unregister_binding) { + fossil::io::keyboard::init(); + fossil_io_keyboard_event_t event = { 'a', 0, 0, 0 }; + fossil_io_keyboard_callback_t callback = (fossil_io_keyboard_callback_t)1; // Assuming a valid callback function + + fossil::io::keyboard::register_binding(event, callback); + ASSUME_NOT_CNULL(callback); // Assumption on pointer + + fossil::io::keyboard::unregister_binding(event); + ASSUME_NOT_CNULL(callback); // Assumption on pointer + fossil::io::keyboard::shutdown(); +} + +FOSSIL_TEST_CASE(cpp_test_keyboard_class_clear_bindings) { + fossil::io::keyboard::init(); + fossil_io_keyboard_event_t event = { 'a', 0, 0, 0 }; + fossil_io_keyboard_callback_t callback = (fossil_io_keyboard_callback_t)1; // Assuming a valid callback function + + fossil::io::keyboard::register_binding(event, callback); + fossil::io::keyboard::clear_bindings(); + ASSUME_NOT_CNULL(callback); // Assumption on pointer + fossil::io::keyboard::shutdown(); +} + +FOSSIL_TEST_CASE(cpp_test_keyboard_class_poll_events) { + fossil::io::keyboard::init(); + fossil_io_keyboard_event_t event = { 'a', 0, 0, 0 }; + fossil_io_keyboard_callback_t callback = (fossil_io_keyboard_callback_t)1; // Assuming a valid callback function + + fossil::io::keyboard::register_binding(event, callback); + fossil::io::keyboard::poll_events(); + ASSUME_NOT_CNULL(callback); // Assumption on pointer + fossil::io::keyboard::unregister_binding(event); + fossil::io::keyboard::shutdown(); +} + + +// * * * * * * * * * * * * * * * * * * * * * * * * +// * Fossil Logic Test Pool +// * * * * * * * * * * * * * * * * * * * * * * * * + +FOSSIL_TEST_GROUP(cpp_keyboard_tests) { + FOSSIL_TEST_ADD(cpp_keyboard_suite, cpp_test_keyboard_register_unregister_binding); + FOSSIL_TEST_ADD(cpp_keyboard_suite, cpp_test_keyboard_clear_bindings); + FOSSIL_TEST_ADD(cpp_keyboard_suite, cpp_test_keyboard_poll_events); + + FOSSIL_TEST_ADD(cpp_keyboard_suite, cpp_test_keyboard_class_register_unregister_binding); + FOSSIL_TEST_ADD(cpp_keyboard_suite, cpp_test_keyboard_class_clear_bindings); + FOSSIL_TEST_ADD(cpp_keyboard_suite, cpp_test_keyboard_class_poll_events); + + FOSSIL_TEST_REGISTER(cpp_keyboard_suite); +} diff --git a/code/tests/meson.build b/code/tests/meson.build index 1c47043..e5b105c 100644 --- a/code/tests/meson.build +++ b/code/tests/meson.build @@ -2,7 +2,7 @@ if get_option('with_test').enabled() run_command(['python3', 'tools' / 'generate-runner.py'], check: true) test_c = ['unit_runner.c'] - test_cases = ['stream', 'soap', 'input', 'error'] + test_cases = ['stream', 'soap', 'input', 'error', 'keyboard'] foreach cases : test_cases test_c += ['cases' / 'test_' + cases + '.c']