diff --git a/CMakeLists.txt b/CMakeLists.txt index a11924a..2bb79bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,11 +3,41 @@ project(StenoByte_Prototype C) set(CMAKE_C_STANDARD 23) -find_package(PkgConfig REQUIRED) -pkg_search_module(LIBEVDEV REQUIRED libevdev) - -add_executable(StenoByte_Prototype main.c - StenoByte_Helper.c - StenoByte_Helper.h) -target_include_directories(StenoByte_Prototype PRIVATE ${LIBEVDEV_INCLUDE_DIRS}) -target_link_libraries(StenoByte_Prototype PRIVATE ${LIBEVDEV_LIBRARIES}) \ No newline at end of file +# Linux Variant +if (LINUX) + # Finds Libevdev Library + find_package(PkgConfig REQUIRED) + pkg_search_module(LIBEVDEV REQUIRED libevdev) + + # Prints Location of Libevdev Library + message(STATUS "libevdev include dirs: ${LIBEVDEV_INCLUDE_DIRS}") + message(STATUS "libevdev libraries: ${LIBEVDEV_LIBRARIES}") + + ## StenoByte Library for Linux Library + add_library(StenoByte_Library STATIC + includes/StenoByte_Helper_for_Linux.c + includes/StenoByte_Core.c) + target_include_directories(StenoByte_Library PRIVATE + ${LIBEVDEV_INCLUDE_DIRS} + includes) + target_link_libraries(StenoByte_Library PRIVATE ${LIBEVDEV_LIBRARIES}) + +# MacOS Variant +elseif (APPLE) + message(FATAL_ERROR "Unfortunately StenoByte is not yet compatible with MacOS, + but support for MacOS is in the works!") + +# Windows Variant +elseif (WIN32) + message(FATAL_ERROR "Unfortunately StenoByte is not yet compatible with Windows, + but support for Windows is in the works!") + +# Throws an error if system is incompatible +else () + message(FATAL_ERROR "Unfortunately your system is not compatible with StenoByte") +endif () + +# Main App +add_executable(StenoByte_Prototype main.c) +target_include_directories(StenoByte_Prototype PRIVATE ${LIBEVDEV_INCLUDE_DIRS} StenoByte_Library) +target_link_libraries(StenoByte_Prototype PRIVATE ${LIBEVDEV_LIBRARIES} StenoByte_Library) \ No newline at end of file diff --git a/README.md b/README.md index 29d6f1d..2097f66 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,13 @@ A stenotype inspired terminal app for typing out bytes quickly. This program is designed to allow you to build up a byte using eight keys from your keyboard, with each key -corresponding to a bit in a byte (which is made up of eight bits). When you press & hold down those keys, their +corresponding to a bit in a byte (which is made up of eight bits). When you press and hold down those keys, their associated bits are set to 1. When the keys are released or are not being pushed, their associated bits are set to 0. -When you are ready to turn those bits into a byte, press the Space Bar and the application will convert those bits into +When you are ready to turn those bits into a byte, press the Space Bar, and the application will convert those bits into a byte and display it as a decimal. -This application is currently designed to work in Linux. +This application is currently designed to work on Linux. For the time-being, this app requires elevated privileges (e.g. `sudo`) to run. @@ -40,6 +40,11 @@ sudo apt -y install build-essential cmake git libevdev-dev sudo pacman -S gcc cmake git libevdev ```` +#### Install Dependencies on Alpine +```shell +apk add --update build-base 'cmake>3.30' libevdev libevdev-dev gdb +``` + ### Step 2: Clone this repository Run: ```shell @@ -80,5 +85,5 @@ maintaining this repository. ## License Copyright (C) 2025 Asami De Almeida -Released under the Apache License 2.0 License. See the following pages for more information: -* [Apache License 2.0](LICENSE) +Released under the Apache Licence 2.0 Licence. See the following pages for more information: +* [Apache Licence 2.0](LICENSE) diff --git a/dockerfiles/README.md b/dockerfiles/README.md new file mode 100644 index 0000000..8f80b70 --- /dev/null +++ b/dockerfiles/README.md @@ -0,0 +1,6 @@ +# Dockerfiles + +This directory will contain directories of Dockerfiles used for building a local Linux sandbox environment. +This is useful for developing for Linux while using a Mac or Windows machine. + +If you are using CLion, visit [this tutorial](https://www.jetbrains.com/help/clion/clion-toolchains-in-docker.html) to learn how to connect a Docker image to your project for development. \ No newline at end of file diff --git a/dockerfiles/alpine-dev-test/Dockerfile b/dockerfiles/alpine-dev-test/Dockerfile new file mode 100644 index 0000000..27dd2e8 --- /dev/null +++ b/dockerfiles/alpine-dev-test/Dockerfile @@ -0,0 +1,3 @@ +FROM alpine:latest + +RUN apk add --update build-base 'cmake>3.30' libevdev libevdev-dev gdb diff --git a/includes/StenoByte_Core.c b/includes/StenoByte_Core.c new file mode 100644 index 0000000..1d4121b --- /dev/null +++ b/includes/StenoByte_Core.c @@ -0,0 +1,117 @@ +/** + StenoByte: a stenotype inspired keyboard app for typing out bytes. + + StenoByte_Core.c is the source file for implementing resources related to processing the Bits into a Byte and + for producing the result as an output. + + This file is intended to be Operating System agnostic and to be compiled for any Operating System. + + Copyright 2025 Asami De Almeida + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#include "StenoByte_Core.h" + +// Arrays & Variables +// Bit Array that contains the bits that forms a byte +bool bit_arr[BITS_ARR_SIZE] = {0, 0, 0, 0, 0, 0, 0, 0}; // ordered from b0 to b7 during initialisation +char keys_arr[BITS_ARR_SIZE] = {';', 'L', 'K', 'J', 'F', 'D', 'S', 'A'}; // ';' = b0, 'L' = b1, ... 'A' = b7 +u_int8_t subvalues_arr[BITS_ARR_SIZE]; +bool ready_to_compute_byte = false; // the state for whether to convert the bit array into a byte and process it + +u_int8_t current_byte = 0x00; // The byte last computed from the bit array + + +// Methods & Functions + +/* + * Generates the Byte based on the bits in the array + */ +void compute_byte() { + current_byte = 0x00; // Resets the Byte to zero + for (int i = 0; i < BITS_ARR_SIZE; i++) { + current_byte = current_byte ^ bit_arr[i] << i; + } + ready_to_compute_byte = false; +} + +/* + * Sets up the sub-values array for labelling + */ +void setup_subvalues_array() { + for (int i = BITS_ARR_SIZE; i >= 0; i--) { + const u_int8_t val = 0; + subvalues_arr[i] = val ^ 1 << i; + } +} + +/* + * Gets Byte Summary as a String (an array of chars) + * Assumes msg has a minimum length of 35. Assumes value of current_byte will not exceed 255. + */ +void get_byte_summary(char* msg) { + sprintf(msg + strlen(msg), "Last Computed Byte as decimal: %d\n", current_byte); // Prints between 33 and 35 chars +} + +/* + * Prints the Byte Summary + */ +void print_byte_summary() { + char msg[35]; + get_byte_summary(msg); + printf("%s", msg); +} + +/* + * Prints the current state of the Bit Array + * TODO: The repeated for loops could probably be simplified into a dedicated method + */ +void print_bit_arr_summary() { + char msg[654] = ""; + + sprintf(msg + strlen(msg), "\nBits in Array:\n"); // Prints 16 chars + sprintf(msg + strlen(msg), "\tBit Value:\t| "); // Prints 14 chars + for (int i = 7; i >= 0; i--) { // Repeats 8 times + sprintf(msg + strlen(msg), "\t%d\t|", bit_arr[i]); // Prints between 4 and 6 chars + } + sprintf(msg + strlen(msg), "\n"); // Prints 1 char + + for (int i=0; i<24+16*BITS_ARR_SIZE; i++) { // Repeats 24+(16*8) times, which is 152 + sprintf(msg + strlen(msg), "-"); // Prints 1 char + } + + sprintf(msg + strlen(msg), "\n\tSub-Value:\t|"); // Prints 14 chars + for (int i = 7; i >= 0; i--) { // Repeats 8 times + sprintf(msg + strlen(msg), "\t[%d]\t|", subvalues_arr[i]); // Prints between 6 and 8 chars + } + + sprintf(msg + strlen(msg), "\n\tBit Index:\t|"); // Prints 14 chars + for (int i = 7; i >= 0; i--) { // Repeats 8 times + sprintf(msg + strlen(msg), "\t[b%d]\t|", i); // Prints 7 chars + } + sprintf(msg + strlen(msg), "\n\tKey:\t\t|"); // Prints 9 times + + for (int i = 7; i >= 0; i--) { // Repeats 8 times + sprintf(msg + strlen(msg), "\t[%c]\t|", keys_arr[i]); // Prints 6 chars + } + sprintf(msg + strlen(msg), "\n"); // Prints 1 char + get_byte_summary(msg); // Prints between 33 and 35 chars + sprintf(msg + strlen(msg), "\nPress & Hold the keys corresponding to the bits in the" + " byte you would like to set to 1."); // Prints 88 chars + sprintf(msg + strlen(msg), "\nBits will be 0 if keys are not pressed."); // Prints 40 chars + sprintf(msg + strlen(msg), "\nPress SPACE BAR to compute Byte\t\t|\t" + "Press ESC to exit\n"); // Prints 53 chars + + printf("%s", msg); +} \ No newline at end of file diff --git a/StenoByte_Helper.h b/includes/StenoByte_Core.h similarity index 64% rename from StenoByte_Helper.h rename to includes/StenoByte_Core.h index 89ab1f6..650faf2 100644 --- a/StenoByte_Helper.h +++ b/includes/StenoByte_Core.h @@ -1,7 +1,10 @@ /** StenoByte: a stenotype inspired keyboard app for typing out bytes. - Copyright 2025 Asami De Almeida + StenoByte_Core.h is the header file for defining resources related to processing the Bits into a Byte and + for producing the result as an output. + + Copyright 2025 Asami De Almeida Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,57 +19,34 @@ limitations under the License. */ -#ifndef STENOBYTE_HELPER_H -#define STENOBYTE_HELPER_H - -#include -#include -#include -#include -#include -#include -#include +#ifndef STENOBYTE_CORE_H +#define STENOBYTE_CORE_H -// Key Press States -#define EV_KEY_RELEASED 0 -#define EV_KEY_PRESSED 1 -#define EV_KEY_REPEATED 2 +#include "StenoByte_Helper.h" // Number of Bits in the Bit Array (should be 8) # define BITS_ARR_SIZE 8 -extern struct termios original_terminal_settings; // Termios Struct to store original terminal settings - // Arrays & Variables // Bit Array that contains the bits that forms a byte extern bool bit_arr[BITS_ARR_SIZE]; // ordered from b0 to b7 during initialisation extern char keys_arr[BITS_ARR_SIZE]; // ';' = b0, 'L' = b1, ... 'A' = b7 extern u_int8_t subvalues_arr[BITS_ARR_SIZE]; extern bool ready_to_compute_byte; // the state for whether to convert the bit array into a byte and process it - extern u_int8_t current_byte; // The byte last computed from the bit array -// Methods & Functions - -bool is_valid_key(int key_code); - -void update_bit_arr(int key_code, bool new_state); +// Externally Declared Methods & Functions (expected to be declared & defined StenoByte_Helper files) +extern void update_bit_arr(int key_code, bool new_state); +extern int setup_stenobyte(); +extern void run_stenobyte(); +extern void end_stenobyte(); +// Methods & Functions void compute_byte(); - void setup_subvalues_array(); - -void print_event_summary(const struct input_event* current_event); - +void get_byte_summary(char* msg); void print_byte_summary(); - void print_bit_arr_summary(); -void process_key_presses(const struct input_event* current_event); - -void disable_echo(); - -void restore_terminal(); - -#endif //STENOBYTE_HELPER_H +#endif //STENOBYTE_CORE_H diff --git a/includes/StenoByte_Helper.h b/includes/StenoByte_Helper.h new file mode 100644 index 0000000..aa3fa5d --- /dev/null +++ b/includes/StenoByte_Helper.h @@ -0,0 +1,66 @@ +/** + StenoByte: a stenotype inspired keyboard app for typing out bytes. + + StenoByte_Helper.h is the header file for defining resources for Keyboard Event Reading for a range of + Operating Systems. + + TODO: Implement conditional declarations that will be dependent on the target OS. + + Copyright 2025 Asami De Almeida + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#ifndef STENOBYTE_HELPER_H +#define STENOBYTE_HELPER_H + +#include +#include +#include +#include +#include +#include +#include +#include + +// Key Press States +#define EV_KEY_RELEASED 0 +#define EV_KEY_PRESSED 1 +#define EV_KEY_REPEATED 2 + +// Externally Declared Structs and Variables (expected to be declared and implemented in dependencies) +extern struct libevdev *keyboard_device; // Struct to store the evdev device +extern struct termios original_terminal_settings; // Termios Struct to store original terminal settings +extern const int event_file_device; + + +// Externally Declared Methods & Functions +// (expected to be declared & defined in dependent libraries or in StenoByte_Core files) +extern void setup_subvalues_array(); // StenoByte_Core.h/c +extern void compute_byte(); +extern void print_bit_arr_summary(); +extern void update_bit_arr(int key_code, bool new_state); + + +// Methods & Functions +int setup_stenobyte(); +void update_bit_arr(int key_code, bool new_state); +void run_stenobyte(); +void end_stenobyte(); +void process_key_presses(const struct input_event* current_event); +bool is_valid_key(int key_code); +void print_event_summary(const struct input_event* current_event); +void disable_echo(); +void restore_terminal(); + +#endif //STENOBYTE_HELPER_H diff --git a/StenoByte_Helper.c b/includes/StenoByte_Helper_for_Linux.c similarity index 58% rename from StenoByte_Helper.c rename to includes/StenoByte_Helper_for_Linux.c index c81429c..281fe3a 100644 --- a/StenoByte_Helper.c +++ b/includes/StenoByte_Helper_for_Linux.c @@ -1,6 +1,10 @@ /** StenoByte: a stenotype inspired keyboard app for typing out bytes. + StenoByte_Helper.c is the source file for implementing the Reading & Processing of Keyboard Events. + + This file is intended to be modular and to be used when building for Linux Operating Systems. + Copyright 2025 Asami De Almeida Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,36 +21,48 @@ */ #include "StenoByte_Helper.h" +#include "StenoByte_Core.h" +// Struct to store the evdev device +struct libevdev *keyboard_device = nullptr; struct termios original_terminal_settings; // Termios Struct to store original terminal settings +const int event_file_device; -// Arrays & Variables -// Bit Array that contains the bits that forms a byte -bool bit_arr[BITS_ARR_SIZE] = {0, 0, 0, 0, 0, 0, 0, 0}; // ordered from b0 to b7 during initialisation -char keys_arr[BITS_ARR_SIZE] = {';', 'L', 'K', 'J', 'F', 'D', 'S', 'A'}; // ';' = b0, 'L' = b1, ... 'A' = b7 -u_int8_t subvalues_arr[BITS_ARR_SIZE]; -bool ready_to_compute_byte = false; // the state for whether to convert the bit array into a byte and process it - -u_int8_t current_byte = 0x00; // The byte last computed from the bit array /* - * Checks whether a valid key is pressed + * Sets up the application and configures the devices to read from + * + * Returns 0 if there were no errors, 1 if there were errors */ -bool is_valid_key(const int key_code) { - switch (key_code) { - case KEY_A: - case KEY_S: - case KEY_D: - case KEY_F: - case KEY_J: - case KEY_K: - case KEY_L: - case KEY_SEMICOLON: - case KEY_SPACE: - return true; - default: - return false; +int setup_stenobyte() { + printf("Starting StenoType...\n"); + + setup_subvalues_array(); + + const int event_file_device = open("/dev/input/event3", O_RDONLY | O_NONBLOCK); // Change to the correct device + + // Opens the keyboard event file (usually event3) in Read-Only and Non-Blocking Modes + // Reports an error if something went wrong + + if (event_file_device < 0) { + perror("Failed to open device"); + return 1; } + + // Disables printing inputs to the terminal + disable_echo(); + + // Initialises the evdev device (stored in libevdev struct named "keyboard_device") + // Reports an error if evdev initialisation failed + if (libevdev_new_from_fd(event_file_device, &keyboard_device) < 0) { + perror("Failed to init libevdev"); + return 1; + } + + // Prints evdev device name + printf("Input device name: %s\n", libevdev_get_name(keyboard_device)); + printf("Press ESC to exit\n"); + return 0; } /* @@ -88,28 +104,75 @@ void update_bit_arr(const int key_code, const bool new_state) { } /* - * Generates the Byte based on the bits in the array + * Runs the loop that constantly checks the keyboard events and performs the associated actions */ -void compute_byte() { - current_byte = 0x00; // Resets the Byte to zero - for (int i = 0; i < BITS_ARR_SIZE; i++) { - current_byte = current_byte ^ bit_arr[i] << i; +void run_stenobyte() { + struct input_event current_event; // The current event struct + + print_bit_arr_summary(); // Initial Print Summary + + // Infinitely loops by default + while (1) { + // Gets the next event + const int next_event_result_code = libevdev_next_event(keyboard_device, + LIBEVDEV_READ_FLAG_NORMAL, ¤t_event); + + // Ignores Non-Success Read Events + if (next_event_result_code != LIBEVDEV_READ_STATUS_SUCCESS) { + continue; + } + + // Ensures a Key Event Type Occurred, ignores otherwise + if (current_event.type != EV_KEY) { + continue; + } + + // If the ESC Key is pushed, then exit the app + if (current_event.code == KEY_ESC) { + printf("ESC pressed\nExiting...\n"); + break; + } + + process_key_presses(¤t_event); + print_bit_arr_summary(); + + if (ready_to_compute_byte) { + compute_byte(); + } + + usleep(1000); // Small delay } - ready_to_compute_byte = false; +} + +void end_stenobyte() { + // Frees up resources before application ends + libevdev_free(keyboard_device); + restore_terminal(); // Restores printing inputs to the terminal + close(event_file_device); } /* - * Sets up the sub-values array for labelling + * Checks whether a valid key is pressed */ -void setup_subvalues_array() { - for (int i = BITS_ARR_SIZE; i >= 0; i--) { - u_int8_t val = 0; - subvalues_arr[i] = val ^ 1 << i; +bool is_valid_key(const int key_code) { + switch (key_code) { + case KEY_A: + case KEY_S: + case KEY_D: + case KEY_F: + case KEY_J: + case KEY_K: + case KEY_L: + case KEY_SEMICOLON: + case KEY_SPACE: + return true; + default: + return false; } } /* - * Prints the event summary of a key press * + * Prints the event summary of a key press. Used for debugging. */ void print_event_summary(const struct input_event* current_event) { if (current_event == NULL) { @@ -126,47 +189,6 @@ void print_event_summary(const struct input_event* current_event) { current_event->code); // Prints the ID for the key that is affected } -void print_byte_summary() { - printf("Last Computed Byte as decimal: %d\n", current_byte); -} - -/* - * Prints the current state of the Bit Array - * TODO: The repeated for loops could probably be simplified into a dedicated method - */ -void print_bit_arr_summary() { - printf("\nBits in Array:\n"); - printf("\tBit Value:\t| "); - for (int i = 7; i >= 0; i--) { - printf("\t%d\t|", bit_arr[i]); - } - printf("\n"); - - for (int i=0; i<24+16*BITS_ARR_SIZE; i++) { - printf("-"); - } - - printf("\n\tSub-Value:\t|"); - for (int i = 7; i >= 0; i--) { - printf("\t[%d]\t|", subvalues_arr[i]); - } - - printf("\n\tBit Index:\t|"); - for (int i = 7; i >= 0; i--) { - printf("\t[b%d]\t|", i); - } - printf("\n\tKey:\t\t|"); - - for (int i = 7; i >= 0; i--) { - printf("\t[%c]\t|", keys_arr[i]); - } - printf("\n"); - print_byte_summary(); - printf("\nPress & Hold the keys corresponding to the bits in the byte you would like to set to 1."); - printf("\nBits will be 0 if keys are not pressed."); - printf("\nPress SPACE BAR to compute Byte\t\t|\tPress ESC to exit\n"); -} - /* * Processes the key events, such as key presses, key releases, and key repeats (when the key is held down) */ @@ -176,7 +198,7 @@ void process_key_presses(const struct input_event* current_event) { return; } - // Exits method if event type is not related to a key event + // Exits method if the event_type variable is not related to a key event if (current_event->type != EV_KEY) { return; } @@ -186,8 +208,8 @@ void process_key_presses(const struct input_event* current_event) { return; } - // Sets the bit value in array to zero if the associated key is released - // Then exits method + // Sets the bit value in the array to zero if the associated key is released + // Then exits the method if (current_event->value == EV_KEY_RELEASED) { update_bit_arr(current_event->code, false); return; @@ -214,8 +236,8 @@ void disable_echo() { } /* - * Restores original terminal settings prior to this program running + * Restores original terminal settings before this program running */ void restore_terminal() { tcsetattr(STDIN_FILENO, TCSANOW, &original_terminal_settings); // restore settings -} \ No newline at end of file +} diff --git a/main.c b/main.c index cea7c89..953185e 100644 --- a/main.c +++ b/main.c @@ -16,78 +16,20 @@ limitations under the License. */ -#include "StenoByte_Helper.h" +#include "includes/StenoByte_Core.h" int main() { - printf("Starting StenoType...\n"); - // Struct to store the evdev device - struct libevdev *keyboard_device = nullptr; - - setup_subvalues_array(); - - // Opens the keyboard event file (usually event3) in Read Only and Non-Blocking Modes - // Reports an error if something went wrong - const int event_file_device = open("/dev/input/event3", O_RDONLY | O_NONBLOCK); // Change to correct device - if (event_file_device < 0) { - perror("Failed to open device"); - return 1; - } - - // Disables printing inputs to the terminal - disable_echo(); - - // Initialises the evdev device (stored in libevdev struct named "keyboard_device") - // Reports an error if evdev initialisation failed - if (libevdev_new_from_fd(event_file_device, &keyboard_device) < 0) { - perror("Failed to init libevdev"); - return 1; + // Performs setup; exits app if there was an error while setting up + const int setup_result = setup_stenobyte(); + if (setup_result != 0) { + return setup_result; } - // Prints evdev device name - printf("Input device name: %s\n", libevdev_get_name(keyboard_device)); - printf("Press ESC to exit\n"); - - - struct input_event current_event; // The current event struct - - print_bit_arr_summary(); // Initial Print Summary - - // Infinitely loops by default - while (1) { - // Gets the next event - const int next_event_result_code = libevdev_next_event(keyboard_device, - LIBEVDEV_READ_FLAG_NORMAL, ¤t_event); - - // Ignores Non-Success Read Events - if (next_event_result_code != LIBEVDEV_READ_STATUS_SUCCESS) { - continue; - } - - // Ensures a Key Event Type Occurred, ignores otherwise - if (current_event.type != EV_KEY) { - continue; - } - - // If ESC Key is pushed, then exit app - if (current_event.code == KEY_ESC) { - printf("ESC pressed\nExiting...\n"); - break; - } - - process_key_presses(¤t_event); - print_bit_arr_summary(); - - if (ready_to_compute_byte) { - compute_byte(); - } - - usleep(1000); // Small delay - } + // Runs the loop + run_stenobyte(); - // Frees up resources before application ends - libevdev_free(keyboard_device); - restore_terminal(); // Restores printing inputs to the terminal - close(event_file_device); + // Frees up Memory Safely + end_stenobyte(); return 0; }