From 1b1a786a8019f293e9f8a92f5e893deed397c9bd Mon Sep 17 00:00:00 2001 From: e2dk4r <43293320+e2dk4r@users.noreply.github.com> Date: Thu, 24 Oct 2024 21:08:59 +0300 Subject: [PATCH] src: parse command line arguments - Add parsing timeout Spec from nushell's duration type https://www.nushell.sh/book/types_of_data.html#durations - Add parsing maximum gamepad count - Add help --- src/main.c | 172 ++++++++++++++++++++++++---- src/text.h | 325 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 473 insertions(+), 24 deletions(-) create mode 100644 src/text.h diff --git a/src/main.c b/src/main.c index b6cca2b..79df898 100644 --- a/src/main.c +++ b/src/main.c @@ -24,12 +24,12 @@ #endif #if GAMEPAD_IDLE_INHIBIT_DEBUG -#define assert(x) \ +#define debug_assert(x) \ if (!(x)) { \ __builtin_debugtrap(); \ } #else -#define assert(x) +#define debug_assert(x) #endif #define runtime_assert(x) \ @@ -87,6 +87,13 @@ typedef double f64; #define GAMEPAD_ERROR_WAYLAND_EXTENSION 52 #define GAMEPAD_ERROR_WAYLAND_FD 53 +#define GAMEPAD_ERROR_ARGUMENT_MISSING 9000 +#define GAMEPAD_ERROR_ARGUMENT_UNKNOWN 9001 +#define GAMEPAD_ERROR_ARGUMENT_MUST_BE_POSITIVE_NUMBER 9002 +#define GAMEPAD_ERROR_ARGUMENT_MUST_BE_GRATER_THAN_0 9003 +#define GAMEPAD_ERROR_ARGUMENT_MUST_BE_GRATER 9004 +#define GAMEPAD_ERROR_ARGUMENT_MUST_BE_LESS 9005 + #if GAMEPAD_IDLE_INHIBIT_DEBUG #define debug(str) write(STDERR_FILENO, "d: " str, 3 + sizeof(str) - 1) #else @@ -216,7 +223,7 @@ mem_chunk_pop(struct memory_chunk *chunk, void *block) static void * mem_push(struct memory_block *mem, u64 size) { - assert(mem->used + size <= mem->total); + debug_assert(mem->used + size <= mem->total); void *result = mem->block + mem->used; mem->used += size; return result; @@ -265,21 +272,137 @@ wl_registry_global_remove(void *data, struct wl_registry *wl_registry, u32 name) static const struct wl_registry_listener wl_registry_listener = {.global = wl_registry_global, .global_remove = wl_registry_global_remove}; + +#include "text.h" + int -main(void) +main(int argc, char *argv[]) { int error_code = 0; struct wl_context context = {}; - // TODO: Read timeout from command line arguments + struct duration timeout = (struct duration){.ns = 30 * 1e9L}; #if GAMEPAD_IDLE_INHIBIT_DEBUG - u64 timeout = 3; -#else - u64 timeout = 30; + timeout = (struct duration){.ns = 3 * 1e9L}; #endif - // TODO: make maxSupportedControllers configurable - u32 maxSupportedControllers = 4; + u32 maxGamepadCount = 4; + + // parse commandline arguments + for (u64 argumentIndex = 1; argumentIndex < argc; argumentIndex++) { + struct string argument = StringFromZeroTerminated((u8 *)argv[argumentIndex]); + +#define ARGUMENT_STRING(variableName, zeroTerminatedString) \ + static struct string variableName = { \ + .data = (u8 *)zeroTerminatedString, \ + .len = sizeof(zeroTerminatedString) - 1, \ + } + ARGUMENT_STRING(argumentTimeoutString, "--timeout"); + ARGUMENT_STRING(argumentMaxGamepadCountString, "--max-gamepad-count"); + ARGUMENT_STRING(argumentHelpShortString, "-h"); + ARGUMENT_STRING(argumentHelpString, "--help"); +#undef ARGUMENT_STRING + + // --timeout + if (IsStringEqual(&argument, &argumentTimeoutString)) { + b8 isArgumentWasLast = argumentIndex + 1 == argc; + if (isArgumentWasLast) { + fatal("timeout value is missing\n"); + return GAMEPAD_ERROR_ARGUMENT_MISSING; + } + + argumentIndex++; + struct string timeoutString = StringFromZeroTerminated((u8 *)argv[argumentIndex]); + struct duration parsed; + struct duration oneSecond = (struct duration){.ns = 1 * 1e9L}; + struct duration oneDay = (struct duration){.ns = 60 * 60 * 24 * 1 * 1e9L}; + + if (!ParseDuration(&timeoutString, &parsed)) { + fatal("timeout must be positive number\n"); + return GAMEPAD_ERROR_ARGUMENT_MUST_BE_POSITIVE_NUMBER; + } else if (IsDurationLessThan(&parsed, &oneSecond)) { + fatal("timeout must be bigger or equal than 1 second\n"); + return GAMEPAD_ERROR_ARGUMENT_MUST_BE_GRATER; + } else if (IsDurationGraterThan(&parsed, &oneDay)) { + fatal("timeout must be less or equal 1 day\n"); + return GAMEPAD_ERROR_ARGUMENT_MUST_BE_LESS; + } + + timeout = parsed; + } + + // --max-gamepad-count + else if (IsStringEqual(&argument, &argumentMaxGamepadCountString)) { + b8 isArgumentWasLast = argumentIndex + 1 == argc; + if (isArgumentWasLast) { + fatal("max-gamepad-count value is missing\n"); + return GAMEPAD_ERROR_ARGUMENT_MISSING; + } + + argumentIndex++; + struct string maxGamepadCountString = StringFromZeroTerminated((u8 *)argv[argumentIndex]); + if (!ParseU64(&maxGamepadCountString, (u64 *)&maxGamepadCount)) { + fatal("max-gamepad-count must be positive number\n"); + return GAMEPAD_ERROR_ARGUMENT_MUST_BE_POSITIVE_NUMBER; + } else if (maxGamepadCount == 0) { + fatal("max-gamepad-count must be bigger than 0\n"); + return GAMEPAD_ERROR_ARGUMENT_MUST_BE_GRATER_THAN_0; + } else if (maxGamepadCount > 256) { + fatal("max-gamepad-count must be less than 256\n"); + return GAMEPAD_ERROR_ARGUMENT_MUST_BE_LESS; + } + } + + // -h, --help + else if (IsStringEqual(&argument, &argumentHelpShortString) || IsStringEqual(&argument, &argumentHelpString)) { + static struct string helpString = { +#define HELP_STRING_TEXT \ + "NAME: " \ + "\n" \ + " gamepad_idle_inhibit - prevent idling wayland on controllers button presses" \ + "\n\n" \ + "SYNOPSIS: " \ + "\n" \ + " gamepad_idle_inhibit [OPTION]..." \ + "\n\n" \ + "DESCIPTION: " \ + "\n" \ + " -t, --timeout [1sec,1day]\n" \ + " How much time need to elapse to idle.\n" \ + " | Duration | Length |\n" \ + " |----------|-------------|\n" \ + " | ns | nanosecond |\n" \ + " | us | microsecond |\n" \ + " | ms | millisecond |\n" \ + " | sec | second |\n" \ + " | min | minute |\n" \ + " | hr | hour |\n" \ + " | day | day |\n" \ + " | wk | week |\n" \ + " Default is 30sec." \ + "\n\n" \ + " --max-gamepad-count [1-256]\n" \ + " How many gamepads need to be tracked.\n" \ + " Default is 4." \ + "\n\n" + .data = (u8 *)HELP_STRING_TEXT, + .len = sizeof(HELP_STRING_TEXT) - 1, +#undef HELP_STRING_TEXT + }; + write(STDOUT_FILENO, helpString.data, helpString.len); + return 0; + } + + // unknown argument + else { + write(STDERR_FILENO, "e: Unknown '", 12); + write(STDERR_FILENO, argument.data, argument.len); + write(STDERR_FILENO, "' argument\n", 11); + return GAMEPAD_ERROR_ARGUMENT_UNKNOWN; + } + } + + // TODO: handle SIGINT /* wayland */ context.wl_display = wl_display_connect(0); @@ -309,7 +432,9 @@ main(void) /* memory */ struct memory_block memory_block = {}; + // TODO: tune total used memory according to arguments memory_block.total = 1 * KILOBYTES; + // TODO: check stack memory is enough memory_block.block = alloca(memory_block.total); bzero(memory_block.block, memory_block.total); // mmap(0, (size_t)memory_block.total, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); @@ -319,14 +444,13 @@ main(void) goto wayland_exit; } - struct gamepad *gamepads = mem_push(&memory_block, maxSupportedControllers * sizeof(*gamepads)); + struct gamepad *gamepads = mem_push(&memory_block, maxGamepadCount * sizeof(*gamepads)); - struct memory_chunk *MemoryForDeviceOpenEvents = - mem_push_chunk(&memory_block, sizeof(struct op), maxSupportedControllers); + struct memory_chunk *MemoryForDeviceOpenEvents = mem_push_chunk(&memory_block, sizeof(struct op), maxGamepadCount); struct memory_chunk *MemoryForJoystickPollEvents = - mem_push_chunk(&memory_block, sizeof(struct op_joystick_poll), maxSupportedControllers); + mem_push_chunk(&memory_block, sizeof(struct op_joystick_poll), maxGamepadCount); struct memory_chunk *MemoryForJoystickReadEvents = - mem_push_chunk(&memory_block, sizeof(struct op_joystick_read), maxSupportedControllers); + mem_push_chunk(&memory_block, sizeof(struct op_joystick_read), maxGamepadCount); #if GAMEPAD_IDLE_INHIBIT_DEBUG printf("total memory usage (in bytes): %lu\n", memory_block.used); @@ -335,7 +459,7 @@ main(void) /* io_uring */ struct io_uring ring; - if (io_uring_queue_init(maxSupportedControllers + 1 + 1 + 4, &ring, 0)) { + if (io_uring_queue_init(maxGamepadCount + 1 + 1 + 4, &ring, 0)) { error_code = GAMEPAD_ERROR_IO_URING_SETUP; goto wayland_exit; } @@ -471,7 +595,7 @@ main(void) stagedOp.triggerMinimum = triggerAbsInfo.minimum; printf("Input device ID: bus %#x vendor %#x product %#x\n", id.bustype, id.vendor, id.product); - stagedOp.gamepad = GamepadGetNotConnected(gamepads, maxSupportedControllers); + stagedOp.gamepad = GamepadGetNotConnected(gamepads, maxGamepadCount); if (!stagedOp.gamepad) { warning("Maximum number of gamepads connected! So not registering this one.\n"); close(stagedOp.fd); @@ -687,7 +811,7 @@ main(void) stagedOp.triggerMinimum = triggerAbsInfo.minimum; printf("Input device ID: bus %#x vendor %#x product %#x\n", id.bustype, id.vendor, id.product); - stagedOp.gamepad = GamepadGetNotConnected(gamepads, maxSupportedControllers); + stagedOp.gamepad = GamepadGetNotConnected(gamepads, maxGamepadCount); if (!stagedOp.gamepad) { warning("Maximum number of gamepads connected! So not registering this one.\n"); struct io_uring_sqe *sqe = io_uring_get_sqe(&ring); @@ -883,9 +1007,9 @@ main(void) s32 minimum = op_joystick_poll->stickMinimum; s32 range = op_joystick_poll->stickRange; f32 normal = (f32)(event->value - minimum) / (f32)range; - assert(normal >= 0.0f && normal <= 1.0f); + debug_assert(normal >= 0.0f && normal <= 1.0f); f32 unit = 2.0f * normal - 1.0f; - assert(unit >= -1.0f && unit <= 1.0f); + debug_assert(unit >= -1.0f && unit <= 1.0f); if (event->code == ABS_Y || event->code == ABS_RY) unit *= -1; @@ -895,7 +1019,7 @@ main(void) : event->code == ABS_RX ? &gamepad->rsX : event->code == ABS_RY ? &gamepad->rsY : 0; - assert(stick); + debug_assert(stick); *stick = unit; } break; @@ -904,10 +1028,10 @@ main(void) s32 minimum = op_joystick_poll->triggerMinimum; s32 range = op_joystick_poll->triggerRange; f32 normal = (f32)(event->value - minimum) / (f32)range; - assert(normal >= 0.0f && normal <= 1.0f); + debug_assert(normal >= 0.0f && normal <= 1.0f); f32 *trigger = event->code == ABS_Z ? &gamepad->lt : event->code == ABS_RZ ? &gamepad->rt : 0; - assert(trigger); + debug_assert(trigger); *trigger = normal; } break; } @@ -956,7 +1080,7 @@ main(void) /* arm timer again */ sqe = io_uring_get_sqe(&ring); struct __kernel_timespec *ts = &(struct __kernel_timespec){ - .tv_sec = timeout, + .tv_nsec = timeout.ns, }; io_uring_prep_timeout(sqe, ts, 0, 0); io_uring_sqe_set_data(sqe, &idledOp); diff --git a/src/text.h b/src/text.h new file mode 100644 index 0000000..1d3bf26 --- /dev/null +++ b/src/text.h @@ -0,0 +1,325 @@ +#ifndef __TEXT_H__ +#define __TEXT_H__ + +struct string { + u8 *data; + u64 len; +}; + +static inline struct string +StringFromZeroTerminated(u8 *string) +{ + debug_assert(string != 0); + u64 len = strnlen((char *)string, 1024); + return (struct string){ + .data = string, + .len = len, + }; +} + +static inline b8 +IsStringEqual(struct string *left, struct string *right) +{ + if (!left || !right || left->len != right->len) + return 0; + + for (u64 index = 0; index < left->len; index++) { + if (left->data[index] != right->data[index]) + return 0; + } + + return 1; +} + +static inline b8 +IsStringContains(struct string *string, struct string *search) +{ + if (!string || !search || string->len < search->len) + return 0; + + /* TEST CODE + struct string string; + struct string search; + b8 result; + + string = (struct string){.data = (u8 *)"abc def ghi", .len = 11}; + search = (struct string){.data = (u8 *)"abc", .len = 3}; + debug_assert(IsStringContains(&string, &search)); + + search = (struct string){.data = (u8 *)"def", .len = 3}; + debug_assert(IsStringContains(&string, &search)); + + search = (struct string){.data = (u8 *)"ghi", .len = 3}; + debug_assert(IsStringContains(&string, &search)); + + search = (struct string){.data = (u8 *)"ghijkl", .len = 6}; + debug_assert(!IsStringContains(&string, &search)); + + search = (struct string){.data = (u8 *)"jkl", .len = 3}; + debug_assert(!IsStringContains(&string, &search)); + */ + + for (u64 stringIndex = 0; stringIndex < string->len; stringIndex++) { + b8 isFound = 1; + for (u64 searchIndex = 0, substringIndex = stringIndex; searchIndex < search->len; + searchIndex++, substringIndex++) { + b8 isEndOfString = substringIndex == string->len; + if (isEndOfString) { + isFound = 0; + break; + } + + b8 isCharactersNotMatching = string->data[substringIndex] != search->data[searchIndex]; + if (isCharactersNotMatching) { + isFound = 0; + break; + } + } + + if (isFound) + return 1; + } + + return 0; +} + +static inline b8 +IsStringStartsWith(struct string *string, struct string *search) +{ + if (!string || !search || string->len < search->len) + return 0; + + /* TEST CODE + struct string string; + struct string search; + + string = (struct string){.data = (u8 *)"abc def ghi", .len = 11}; + search = (struct string){.data = (u8 *)"abc", .len = 3}; + debug_assert(IsStringStartsWith(&string, &search)); + + search = (struct string){.data = (u8 *)"def", .len = 3}; + debug_assert(!IsStringStartsWith(&string, &search)); + + search = (struct string){.data = (u8 *)"ghi", .len = 3}; + debug_assert(!IsStringStartsWith(&string, &search)); + + search = (struct string){.data = (u8 *)"ghijkl", .len = 6}; + debug_assert(!IsStringStartsWith(&string, &search)); + + search = (struct string){.data = (u8 *)"jkl", .len = 3}; + debug_assert(!IsStringStartsWith(&string, &search)); + */ + + for (u64 searchIndex = 0; searchIndex < search->len; searchIndex++) { + b8 isCharactersNotMatching = string->data[searchIndex] != search->data[searchIndex]; + if (isCharactersNotMatching) + return 0; + } + + return 1; +} + +struct duration { + u64 ns; +}; + +static inline b8 +ParseDuration(struct string *string, struct duration *duration) +{ + if (!string || string->len == 0 || string->len < 3) + return 0; + + /* TEST CODE + struct string string; + struct duration duration; + b8 isParsingSuccessful; + + string = (struct string){.data = (u8 *)"1ns", .len = 3}; + isParsingSuccessful = ParseDuration(&string, &duration); + debug_assert(isParsingSuccessful && duration.ns == 1); + + string = (struct string){.data = (u8 *)"1sec", .len = 4}; + isParsingSuccessful = ParseDuration(&string, &duration); + debug_assert(isParsingSuccessful && duration.ns == 1 * 1e9L); + + string = (struct string){.data = (u8 *)"5sec", .len = 4}; + isParsingSuccessful = ParseDuration(&string, &duration); + debug_assert(isParsingSuccessful && duration.ns == 5 * 1e9L); + + string = (struct string){.data = (u8 *)"5min", .len = 4}; + isParsingSuccessful = ParseDuration(&string, &duration); + debug_assert(isParsingSuccessful && duration.ns == 5 * 1e9L * 60); + + string = (struct string){.data = (u8 *)"5day", .len = 4}; + isParsingSuccessful = ParseDuration(&string, &duration); + debug_assert(isParsingSuccessful && duration.ns == 5 * 1e9L * 60 * 60 * 24); + + string = (struct string){.data = (u8 *)"1hr5min", .len = 7}; + isParsingSuccessful = ParseDuration(&string, &duration); + debug_assert(isParsingSuccessful && duration.ns == (1 * 1e9L * 60 * 60) + (5 * 1e9L * 60)); + + string = (struct string){.data = (u8 *)0, .len = 0}; + isParsingSuccessful = ParseDuration(&string, &duration); + debug_assert(!isParsingSuccessful); + + string = (struct string){.data = (u8 *)"", .len = 0}; + isParsingSuccessful = ParseDuration(&string, &duration); + debug_assert(!isParsingSuccessful); + + string = (struct string){.data = (u8 *)" ", .len = 1}; + isParsingSuccessful = ParseDuration(&string, &duration); + debug_assert(!isParsingSuccessful); + + string = (struct string){.data = (u8 *)"abc", .len = 3}; + isParsingSuccessful = ParseDuration(&string, &duration); + debug_assert(!isParsingSuccessful); + + string = (struct string){.data = (u8 *)"5m5s", .len = 4}; + isParsingSuccessful = ParseDuration(&string, &duration); + debug_assert(!isParsingSuccessful) + */ + + // | Duration | Length | + // |----------|-------------| + // | ns | nanosecond | + // | us | microsecond | + // | ms | millisecond | + // | sec | second | + // | min | minute | + // | hr | hour | + // | day | day | + // | wk | week | + +#define UNIT_STRING(variableName, zeroTerminatedString) \ + static struct string variableName = { \ + .data = (u8 *)zeroTerminatedString, \ + .len = sizeof(zeroTerminatedString) - 1, \ + } + UNIT_STRING(nanosecondUnitString, "ns"); + UNIT_STRING(microsecondUnitString, "us"); + UNIT_STRING(millisocondUnitString, "ms"); + UNIT_STRING(secondUnitString, "sec"); + UNIT_STRING(minuteUnitString, "min"); + UNIT_STRING(hourUnitString, "hr"); + UNIT_STRING(dayUnitString, "day"); + UNIT_STRING(weekUnitString, "wk"); +#undef UNIT_STRING + + b8 isUnitExistsInString = + IsStringContains(string, &nanosecondUnitString) || IsStringContains(string, µsecondUnitString) || + IsStringContains(string, &millisocondUnitString) || IsStringContains(string, &secondUnitString) || + IsStringContains(string, &minuteUnitString) || IsStringContains(string, &hourUnitString) || + IsStringContains(string, &dayUnitString) || IsStringContains(string, &weekUnitString); + if (!isUnitExistsInString) { + return 0; + } + + struct duration parsed = {}; + u64 value = 0; + for (u64 index = 0; index < string->len; index++) { + u8 digitCharacter = string->data[index]; + b8 isDigit = digitCharacter >= '0' && digitCharacter <= '9'; + if (!isDigit) { + // - get unit + struct string unitString = {.data = string->data + index, .len = string->len - index}; + if (/* unit: nanosecond */ IsStringStartsWith(&unitString, &nanosecondUnitString)) { + parsed.ns += value; + index += nanosecondUnitString.len; + } else if (/* unit: microsecond */ IsStringStartsWith(&unitString, µsecondUnitString)) { + parsed.ns += value * 1e3L; + index += microsecondUnitString.len; + } else if (/* unit: millisecond */ IsStringStartsWith(&unitString, &millisocondUnitString)) { + parsed.ns += value * 1e6L; + index += millisocondUnitString.len; + } else if (/* unit: second */ IsStringStartsWith(&unitString, &secondUnitString)) { + parsed.ns += value * 1e9L; + index += secondUnitString.len; + } else if (/* unit: minute */ IsStringStartsWith(&unitString, &minuteUnitString)) { + parsed.ns += value * 1e9L * 60; + index += minuteUnitString.len; + } else if (/* unit: hour */ IsStringStartsWith(&unitString, &hourUnitString)) { + parsed.ns += value * 1e9L * 60 * 60; + index += hourUnitString.len; + } else if (/* unit: day */ IsStringStartsWith(&unitString, &dayUnitString)) { + parsed.ns += value * 1e9L * 60 * 60 * 24; + index += dayUnitString.len; + } else if (/* unit: week */ IsStringStartsWith(&unitString, &weekUnitString)) { + parsed.ns += value * 1e9L * 60 * 60 * 24 * 7; + index += weekUnitString.len; + } else { + // unsupported unit + return 0; + } + + // - reset value + value = 0; + continue; + } + + value *= 10; + u8 digit = digitCharacter - (u8)'0'; + value += digit; + } + + *duration = parsed; + + return 1; +} + +/* TEST CODE + struct duration left; + struct duration right; + + left = (struct duration){.ns = 1 * 1e9L}; + right = (struct duration){.ns = 5 * 1e9L}; + debug_assert(IsDurationLessThen(&left, &right)); + debug_assert(!IsDurationGraterThan(&left, &right)); + + left = (struct duration){.ns = 1 * 1e9L}; + right = (struct duration){.ns = 1 * 1e9L}; + debug_assert(!IsDurationLessThan(&left, &right)); + debug_assert(!IsDurationGraterThan(&left, &right)); + + left = (struct duration){.ns = 5 * 1e9L}; + right = (struct duration){.ns = 1 * 1e9L}; + debug_assert(!IsDurationLessThan(&left, &right)); + debug_assert(IsDurationGraterThan(&left, &right)); + + return 0; + */ +static inline b8 +IsDurationLessThan(struct duration *left, struct duration *right) +{ + return left->ns < right->ns; +} + +static inline b8 +IsDurationGraterThan(struct duration *left, struct duration *right) +{ + return left->ns > right->ns; +} + +static inline b8 +ParseU64(struct string *string, u64 *value) +{ + if (!string) + return 0; + + u64 parsed = 0; + for (u64 index = 0; index < string->len; index++) { + u8 digitCharacter = string->data[index]; + b8 isDigit = digitCharacter >= '0' && digitCharacter <= '9'; + if (!isDigit) { + return 1; + } + + parsed *= 10; + u8 digit = digitCharacter - (u8)'0'; + parsed += digit; + } + + *value = parsed; + return 1; +} + +#endif /* __TEXT_H__ */