From bf7fefcc731371c9716eaed4177ee44447bcc2c1 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Thu, 31 Oct 2024 18:19:26 +0300 Subject: [PATCH 1/3] feat: FuriThread stdin --- .../unit_tests/tests/furi/furi_stdio_test.c | 104 ++++++++++++++++++ .../debug/unit_tests/tests/furi/furi_test.c | 8 ++ furi/core/string.h | 8 +- furi/core/thread.c | 64 ++++++++++- furi/core/thread.h | 50 +++++++++ lib/print/SConscript | 20 ++-- lib/print/wrappers.c | 47 +++++++- lib/print/wrappers.h | 6 + targets/f7/api_symbols.csv | 11 +- 9 files changed, 298 insertions(+), 20 deletions(-) create mode 100644 applications/debug/unit_tests/tests/furi/furi_stdio_test.c diff --git a/applications/debug/unit_tests/tests/furi/furi_stdio_test.c b/applications/debug/unit_tests/tests/furi/furi_stdio_test.c new file mode 100644 index 00000000000..a42973900a4 --- /dev/null +++ b/applications/debug/unit_tests/tests/furi/furi_stdio_test.c @@ -0,0 +1,104 @@ +#include +#include +#include +#include "../test.h" // IWYU pragma: keep + +#define TAG "StdioTest" + +// stdin + +static char mock_in[256]; +static size_t mock_in_len, mock_in_pos; + +static void set_mock_in(const char* str) { + size_t len = strlen(str); + strcpy(mock_in, str); + mock_in_len = len; + mock_in_pos = 0; +} + +static size_t mock_in_cb(char* buffer, size_t size, FuriWait wait) { + UNUSED(wait); + size_t remaining = mock_in_len - mock_in_pos; + size = MIN(remaining, size); + memcpy(buffer, mock_in + mock_in_pos, size); + mock_in_pos += size; + return size; +} + +void test_stdin(void) { + FuriThreadStdinReadCallback in_cb = furi_thread_get_stdin_callback(); + furi_thread_set_stdin_callback(mock_in_cb); + char buf[256]; + + // plain in + set_mock_in("Hello, World!\n"); + fgets(buf, sizeof(buf), stdin); + mu_assert_string_eq("Hello, World!\n", buf); + mu_assert_int_eq(EOF, getchar()); + + // ungetc + ungetc('i', stdin); + ungetc('H', stdin); + fgets(buf, sizeof(buf), stdin); + mu_assert_string_eq("Hi", buf); + mu_assert_int_eq(EOF, getchar()); + + // ungetc + plain in + set_mock_in(" World"); + ungetc('i', stdin); + ungetc('H', stdin); + fgets(buf, sizeof(buf), stdin); + mu_assert_string_eq("Hi World", buf); + mu_assert_int_eq(EOF, getchar()); + + // partial plain in + set_mock_in("Hello, World!\n"); + fgets(buf, strlen("Hello") + 1, stdin); + mu_assert_string_eq("Hello", buf); + mu_assert_int_eq(',', getchar()); + fgets(buf, sizeof(buf), stdin); + mu_assert_string_eq(" World!\n", buf); + + furi_thread_set_stdin_callback(in_cb); +} + +// stdout + +static FuriString* mock_out; +FuriThreadStdoutWriteCallback original_out_cb; + +static void mock_out_cb(const char* data, size_t size) { + // there's no furi_string_cat_strn :( + for(size_t i = 0; i < size; i++) { + furi_string_push_back(mock_out, data[i]); + } +} + +static void assert_and_clear_mock_out(const char* expected) { + // return the original stdout callback for the duration of the check + // if the check fails, we don't want the error to end up in our buffer, + // we want to be able to see it! + furi_thread_set_stdout_callback(original_out_cb); + mu_assert_string_eq(expected, furi_string_get_cstr(mock_out)); + furi_thread_set_stdout_callback(mock_out_cb); + + furi_string_reset(mock_out); +} + +void test_stdout(void) { + original_out_cb = furi_thread_get_stdout_callback(); + furi_thread_set_stdout_callback(mock_out_cb); + mock_out = furi_string_alloc(); + + puts("Hello, World!"); + assert_and_clear_mock_out("Hello, World!\n"); + + printf("He"); + printf("llo!"); + fflush(stdout); + assert_and_clear_mock_out("Hello!"); + + furi_string_free(mock_out); + furi_thread_set_stdout_callback(original_out_cb); +} diff --git a/applications/debug/unit_tests/tests/furi/furi_test.c b/applications/debug/unit_tests/tests/furi/furi_test.c index 2a76d5184c4..722718ac52d 100644 --- a/applications/debug/unit_tests/tests/furi/furi_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_test.c @@ -9,6 +9,8 @@ void test_furi_pubsub(void); void test_furi_memmgr(void); void test_furi_event_loop(void); void test_errno_saving(void); +void test_stdin(void); +void test_stdout(void); static int foo = 0; @@ -47,6 +49,11 @@ MU_TEST(mu_test_errno_saving) { test_errno_saving(); } +MU_TEST(mu_test_stdio) { + test_stdin(); + test_stdout(); +} + MU_TEST_SUITE(test_suite) { MU_SUITE_CONFIGURE(&test_setup, &test_teardown); MU_RUN_TEST(test_check); @@ -56,6 +63,7 @@ MU_TEST_SUITE(test_suite) { MU_RUN_TEST(mu_test_furi_pubsub); MU_RUN_TEST(mu_test_furi_memmgr); MU_RUN_TEST(mu_test_furi_event_loop); + MU_RUN_TEST(mu_test_stdio); MU_RUN_TEST(mu_test_errno_saving); } diff --git a/furi/core/string.h b/furi/core/string.h index 84b8c6a2405..0d407356bc6 100644 --- a/furi/core/string.h +++ b/furi/core/string.h @@ -129,12 +129,12 @@ void furi_string_swap(FuriString* string_1, FuriString* string_2); /** Move string_2 content to string_1. * - * Set the string to the other one, and destroy the other one. + * Copy data from one string to another and destroy the source. * - * @param string_1 The FuriString instance 1 - * @param string_2 The FuriString instance 2 + * @param destination The destination FuriString + * @param source The source FuriString */ -void furi_string_move(FuriString* string_1, FuriString* string_2); +void furi_string_move(FuriString* destination, FuriString* source); /** Compute a hash for the string. * diff --git a/furi/core/thread.c b/furi/core/thread.c index fd576ea72bb..12b9e61887c 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -23,12 +23,15 @@ #define THREAD_MAX_STACK_SIZE (UINT16_MAX * sizeof(StackType_t)) -typedef struct FuriThreadStdout FuriThreadStdout; - -struct FuriThreadStdout { +typedef struct { FuriThreadStdoutWriteCallback write_callback; FuriString* buffer; -}; +} FuriThreadStdout; + +typedef struct { + FuriThreadStdinReadCallback read_callback; + FuriString* unread_buffer; // output.buffer = furi_string_alloc(); + thread->input.unread_buffer = furi_string_alloc(); FuriThread* parent = NULL; if(xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) { @@ -245,6 +250,7 @@ void furi_thread_free(FuriThread* thread) { } furi_string_free(thread->output.buffer); + furi_string_free(thread->input.unread_buffer); free(thread); } @@ -717,6 +723,15 @@ static size_t __furi_thread_stdout_write(FuriThread* thread, const char* data, s return size; } +static size_t + __furi_thread_stdin_read(FuriThread* thread, char* data, size_t size, FuriWait timeout) { + if(thread->input.read_callback != NULL) { + return thread->input.read_callback(data, size, timeout); + } else { + return 0; + } +} + static int32_t __furi_thread_stdout_flush(FuriThread* thread) { FuriString* buffer = thread->output.buffer; size_t size = furi_string_size(buffer); @@ -727,6 +742,18 @@ static int32_t __furi_thread_stdout_flush(FuriThread* thread) { return 0; } +FuriThreadStdoutWriteCallback furi_thread_get_stdout_callback(void) { + FuriThread* thread = furi_thread_get_current(); + furi_check(thread); + return thread->output.write_callback; +} + +FuriThreadStdinReadCallback furi_thread_get_stdin_callback(void) { + FuriThread* thread = furi_thread_get_current(); + furi_check(thread); + return thread->input.read_callback; +} + void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback) { FuriThread* thread = furi_thread_get_current(); furi_check(thread); @@ -734,10 +761,10 @@ void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback) { thread->output.write_callback = callback; } -FuriThreadStdoutWriteCallback furi_thread_get_stdout_callback(void) { +void furi_thread_set_stdin_callback(FuriThreadStdinReadCallback callback) { FuriThread* thread = furi_thread_get_current(); furi_check(thread); - return thread->output.write_callback; + thread->input.read_callback = callback; } size_t furi_thread_stdout_write(const char* data, size_t size) { @@ -772,6 +799,31 @@ int32_t furi_thread_stdout_flush(void) { return __furi_thread_stdout_flush(thread); } +size_t furi_thread_stdin_read(char* buffer, size_t size, FuriWait timeout) { + FuriThread* thread = furi_thread_get_current(); + furi_check(thread); + + size_t from_buffer = MIN(furi_string_size(thread->input.unread_buffer), size); + size_t from_input = size - from_buffer; + size_t from_input_actual = + __furi_thread_stdin_read(thread, buffer + from_buffer, from_input, timeout); + memcpy(buffer, furi_string_get_cstr(thread->input.unread_buffer), from_buffer); + furi_string_right(thread->input.unread_buffer, from_buffer); + + return from_buffer + from_input_actual; +} + +void furi_thread_stdin_unread(char* buffer, size_t size) { + FuriThread* thread = furi_thread_get_current(); + furi_check(thread); + + FuriString* new_buf = furi_string_alloc(); // there's no furi_string_alloc_set_strn :( + furi_string_set_strn(new_buf, buffer, size); + furi_string_cat(new_buf, thread->input.unread_buffer); + furi_string_free(thread->input.unread_buffer); + thread->input.unread_buffer = new_buf; +} + void furi_thread_suspend(FuriThreadId thread_id) { furi_check(thread_id); diff --git a/furi/core/thread.h b/furi/core/thread.h index ed7aa4553b0..219d839ee17 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -77,6 +77,18 @@ typedef int32_t (*FuriThreadCallback)(void* context); */ typedef void (*FuriThreadStdoutWriteCallback)(const char* data, size_t size); +/** + * @brief Standard input callback function pointer type + * + * The function to be used as a standard input callback MUST follow this signature. + * + * @param[out] buffer buffer to read data into + * @param[in] size maximum number of bytes to read into the buffer + * @param[in] timeout how long to wait for (in ticks) before giving up + * @returns number of bytes that was actually read into the buffer + */ +typedef size_t (*FuriThreadStdinReadCallback)(char* buffer, size_t size, FuriWait timeout); + /** * @brief State change callback function pointer type. * @@ -468,13 +480,28 @@ uint32_t furi_thread_get_stack_space(FuriThreadId thread_id); */ FuriThreadStdoutWriteCallback furi_thread_get_stdout_callback(void); +/** + * @brief Get the standard input callback for the current thead. + * + * @return pointer to the standard in callback function + */ +FuriThreadStdinReadCallback furi_thread_get_stdin_callback(void); + /** Set standard output callback for the current thread. * * @param[in] callback pointer to the callback function or NULL to clear */ void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback); +/** Set standard input callback for the current thread. + * + * @param[in] callback pointer to the callback function or NULL to clear + */ +void furi_thread_set_stdin_callback(FuriThreadStdinReadCallback callback); + /** Write data to buffered standard output. + * + * @note You can also use the standard C `putc`, `puts`, `printf` and friends. * * @param[in] data pointer to the data to be written * @param[in] size data size in bytes @@ -489,6 +516,29 @@ size_t furi_thread_stdout_write(const char* data, size_t size); */ int32_t furi_thread_stdout_flush(void); +/** Read data from the standard input + * + * @note You can also use the standard C `getc`, `gets` and friends. + * + * @param[in] buffer pointer to the buffer to read data into + * @param[in] size how many bytes to read into the buffer + * @param[in] timeout how long to wait for (in ticks) before giving up + * @return number of bytes that was actually read + */ +size_t furi_thread_stdin_read(char* buffer, size_t size, FuriWait timeout); + +/** Puts data back into the standard input buffer + * + * `furi_thread_stdin_read` will return the bytes in the same order that they + * were supplied to this function. + * + * @note You can also use the standard C `ungetc`. + * + * @param[in] buffer pointer to the buffer to get data from + * @param[in] size how many bytes to read from the buffer + */ +void furi_thread_stdin_unread(char* buffer, size_t size); + /** * @brief Suspend a thread. * diff --git a/lib/print/SConscript b/lib/print/SConscript index 07be8d890e1..90028cf06a1 100644 --- a/lib/print/SConscript +++ b/lib/print/SConscript @@ -44,18 +44,24 @@ wrapped_fn_list = [ "vsiprintf", "vsniprintf", # - # Scanf is not implemented 4 now + # standard input + # + "fgetc", + "getc", + "getchar", + "fgets", + "ungetc", + # + # standard input, but unimplemented + # + "gets", + # + # scanf, not implemented for now # # "fscanf", # "scanf", # "sscanf", # "vsprintf", - # "fgetc", - # "fgets", - # "getc", - # "getchar", - # "gets", - # "ungetc", # "vfscanf", # "vscanf", # "vsscanf", diff --git a/lib/print/wrappers.c b/lib/print/wrappers.c index c8d72d19285..18df92def8c 100644 --- a/lib/print/wrappers.c +++ b/lib/print/wrappers.c @@ -51,11 +51,54 @@ int __wrap_snprintf(char* str, size_t size, const char* format, ...) { } int __wrap_fflush(FILE* stream) { - UNUSED(stream); - furi_thread_stdout_flush(); + if(stream == stdout) furi_thread_stdout_flush(); return 0; } +int __wrap_fgetc(FILE* stream) { + if(stream != stdin) return EOF; + char c; + if(furi_thread_stdin_read(&c, 1, FuriWaitForever) == 0) return EOF; + return c; +} + +int __wrap_getc(FILE* stream) { + return __wrap_fgetc(stream); +} + +int __wrap_getchar(void) { + return __wrap_fgetc(stdin); +} + +char* __wrap_fgets(char* str, size_t n, FILE* stream) { + // leave space for the zero terminator + furi_check(n >= 1); + n--; + + if(stream != stdin) { + *str = '\0'; + return str; + } + + // read characters + int c; + do { + c = __wrap_fgetc(stdin); + if(c > 0) *(str++) = c; + } while(c != EOF && c != '\n' && --n); + + // place zero terminator + *str = '\0'; + return str; +} + +int __wrap_ungetc(int ch, FILE* stream) { + char c = ch; + if(stream != stdin) return EOF; + furi_thread_stdin_unread(&c, 1); + return ch; +} + __attribute__((__noreturn__)) void __wrap___assert(const char* file, int line, const char* e) { UNUSED(file); UNUSED(line); diff --git a/lib/print/wrappers.h b/lib/print/wrappers.h index 3cec88249a2..8a4599b4174 100644 --- a/lib/print/wrappers.h +++ b/lib/print/wrappers.h @@ -16,6 +16,12 @@ int __wrap_putc(int ch, FILE* stream); int __wrap_snprintf(char* str, size_t size, const char* format, ...); int __wrap_fflush(FILE* stream); +int __wrap_fgetc(FILE* stream); +int __wrap_getc(FILE* stream); +int __wrap_getchar(void); +char* __wrap_fgets(char* str, size_t n, FILE* stream); +int __wrap_ungetc(int ch, FILE* stream); + __attribute__((__noreturn__)) void __wrap___assert(const char* file, int line, const char* e); __attribute__((__noreturn__)) void diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index c121fc71681..0b2d28e8bba 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,77.2,, +Version,+,77.3,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -448,11 +448,16 @@ Function,-,__utoa,char*,"unsigned, char*, int" Function,+,__wrap___assert,void,"const char*, int, const char*" Function,+,__wrap___assert_func,void,"const char*, int, const char*, const char*" Function,+,__wrap_fflush,int,FILE* +Function,+,__wrap_fgetc,int,FILE* +Function,+,__wrap_fgets,char*,"char*, size_t, FILE*" +Function,+,__wrap_getc,int,FILE* +Function,+,__wrap_getchar,int, Function,+,__wrap_printf,int,"const char*, ..." Function,+,__wrap_putc,int,"int, FILE*" Function,+,__wrap_putchar,int,int Function,+,__wrap_puts,int,const char* Function,+,__wrap_snprintf,int,"char*, size_t, const char*, ..." +Function,+,__wrap_ungetc,int,"int, FILE*" Function,+,__wrap_vsnprintf,int,"char*, size_t, const char*, va_list" Function,-,_asiprintf_r,int,"_reent*, char**, const char*, ..." Function,-,_asniprintf_r,char*,"_reent*, char*, size_t*, const char*, ..." @@ -1863,6 +1868,7 @@ Function,+,furi_thread_get_return_code,int32_t,FuriThread* Function,+,furi_thread_get_signal_callback,FuriThreadSignalCallback,const FuriThread* Function,+,furi_thread_get_stack_space,uint32_t,FuriThreadId Function,+,furi_thread_get_state,FuriThreadState,FuriThread* +Function,+,furi_thread_get_stdin_callback,FuriThreadStdinReadCallback, Function,+,furi_thread_get_stdout_callback,FuriThreadStdoutWriteCallback, Function,+,furi_thread_is_suspended,_Bool,FuriThreadId Function,+,furi_thread_join,_Bool,FuriThread* @@ -1883,9 +1889,12 @@ Function,+,furi_thread_set_signal_callback,void,"FuriThread*, FuriThreadSignalCa Function,+,furi_thread_set_stack_size,void,"FuriThread*, size_t" Function,+,furi_thread_set_state_callback,void,"FuriThread*, FuriThreadStateCallback" Function,+,furi_thread_set_state_context,void,"FuriThread*, void*" +Function,+,furi_thread_set_stdin_callback,void,FuriThreadStdinReadCallback Function,+,furi_thread_set_stdout_callback,void,FuriThreadStdoutWriteCallback Function,+,furi_thread_signal,_Bool,"const FuriThread*, uint32_t, void*" Function,+,furi_thread_start,void,FuriThread* +Function,+,furi_thread_stdin_read,size_t,"char*, size_t, FuriWait" +Function,+,furi_thread_stdin_unread,void,"char*, size_t" Function,+,furi_thread_stdout_flush,int32_t, Function,+,furi_thread_stdout_write,size_t,"const char*, size_t" Function,+,furi_thread_suspend,void,FuriThreadId From 4c592439a7ab228d9e15b464954865900749be7a Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Thu, 31 Oct 2024 18:29:47 +0300 Subject: [PATCH 2/3] ci: fix f18 --- targets/f18/api_symbols.csv | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index b5d51a0dd5c..2b247d6f30c 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,78.1,, +Version,+,78.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -371,11 +371,16 @@ Function,-,__utoa,char*,"unsigned, char*, int" Function,+,__wrap___assert,void,"const char*, int, const char*" Function,+,__wrap___assert_func,void,"const char*, int, const char*, const char*" Function,+,__wrap_fflush,int,FILE* +Function,+,__wrap_fgetc,int,FILE* +Function,+,__wrap_fgets,char*,"char*, size_t, FILE*" +Function,+,__wrap_getc,int,FILE* +Function,+,__wrap_getchar,int, Function,+,__wrap_printf,int,"const char*, ..." Function,+,__wrap_putc,int,"int, FILE*" Function,+,__wrap_putchar,int,int Function,+,__wrap_puts,int,const char* Function,+,__wrap_snprintf,int,"char*, size_t, const char*, ..." +Function,+,__wrap_ungetc,int,"int, FILE*" Function,+,__wrap_vsnprintf,int,"char*, size_t, const char*, va_list" Function,-,_asiprintf_r,int,"_reent*, char**, const char*, ..." Function,-,_asniprintf_r,char*,"_reent*, char*, size_t*, const char*, ..." @@ -1654,6 +1659,7 @@ Function,+,furi_thread_get_return_code,int32_t,FuriThread* Function,+,furi_thread_get_signal_callback,FuriThreadSignalCallback,const FuriThread* Function,+,furi_thread_get_stack_space,uint32_t,FuriThreadId Function,+,furi_thread_get_state,FuriThreadState,FuriThread* +Function,+,furi_thread_get_stdin_callback,FuriThreadStdinReadCallback, Function,+,furi_thread_get_stdout_callback,FuriThreadStdoutWriteCallback, Function,+,furi_thread_is_suspended,_Bool,FuriThreadId Function,+,furi_thread_join,_Bool,FuriThread* @@ -1674,9 +1680,12 @@ Function,+,furi_thread_set_signal_callback,void,"FuriThread*, FuriThreadSignalCa Function,+,furi_thread_set_stack_size,void,"FuriThread*, size_t" Function,+,furi_thread_set_state_callback,void,"FuriThread*, FuriThreadStateCallback" Function,+,furi_thread_set_state_context,void,"FuriThread*, void*" +Function,+,furi_thread_set_stdin_callback,void,FuriThreadStdinReadCallback Function,+,furi_thread_set_stdout_callback,void,FuriThreadStdoutWriteCallback Function,+,furi_thread_signal,_Bool,"const FuriThread*, uint32_t, void*" Function,+,furi_thread_start,void,FuriThread* +Function,+,furi_thread_stdin_read,size_t,"char*, size_t, FuriWait" +Function,+,furi_thread_stdin_unread,void,"char*, size_t" Function,+,furi_thread_stdout_flush,int32_t, Function,+,furi_thread_stdout_write,size_t,"const char*, size_t" Function,+,furi_thread_suspend,void,FuriThreadId From f1eb60b36021df376f8cf46e6d2eadedfe5fb8be Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Thu, 7 Nov 2024 17:38:06 +0300 Subject: [PATCH 3/3] feat: stdio callback context --- .../unit_tests/tests/furi/furi_stdio_test.c | 20 +++++++++++-------- applications/services/cli/cli.c | 10 +++++----- applications/services/cli/cli_i.h | 3 ++- applications/services/cli/cli_vcp.c | 9 ++++++++- furi/core/thread.c | 12 +++++++---- furi/core/thread.h | 13 ++++++++---- targets/f18/api_symbols.csv | 6 +++--- targets/f7/api_symbols.csv | 6 +++--- 8 files changed, 50 insertions(+), 29 deletions(-) diff --git a/applications/debug/unit_tests/tests/furi/furi_stdio_test.c b/applications/debug/unit_tests/tests/furi/furi_stdio_test.c index a42973900a4..94e2f613b6a 100644 --- a/applications/debug/unit_tests/tests/furi/furi_stdio_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_stdio_test.c @@ -5,6 +5,8 @@ #define TAG "StdioTest" +#define CONTEXT_MAGIC ((void*)0xDEADBEEF) + // stdin static char mock_in[256]; @@ -17,8 +19,9 @@ static void set_mock_in(const char* str) { mock_in_pos = 0; } -static size_t mock_in_cb(char* buffer, size_t size, FuriWait wait) { +static size_t mock_in_cb(char* buffer, size_t size, FuriWait wait, void* context) { UNUSED(wait); + furi_check(context == CONTEXT_MAGIC); size_t remaining = mock_in_len - mock_in_pos; size = MIN(remaining, size); memcpy(buffer, mock_in + mock_in_pos, size); @@ -28,7 +31,7 @@ static size_t mock_in_cb(char* buffer, size_t size, FuriWait wait) { void test_stdin(void) { FuriThreadStdinReadCallback in_cb = furi_thread_get_stdin_callback(); - furi_thread_set_stdin_callback(mock_in_cb); + furi_thread_set_stdin_callback(mock_in_cb, CONTEXT_MAGIC); char buf[256]; // plain in @@ -60,7 +63,7 @@ void test_stdin(void) { fgets(buf, sizeof(buf), stdin); mu_assert_string_eq(" World!\n", buf); - furi_thread_set_stdin_callback(in_cb); + furi_thread_set_stdin_callback(in_cb, CONTEXT_MAGIC); } // stdout @@ -68,7 +71,8 @@ void test_stdin(void) { static FuriString* mock_out; FuriThreadStdoutWriteCallback original_out_cb; -static void mock_out_cb(const char* data, size_t size) { +static void mock_out_cb(const char* data, size_t size, void* context) { + furi_check(context == CONTEXT_MAGIC); // there's no furi_string_cat_strn :( for(size_t i = 0; i < size; i++) { furi_string_push_back(mock_out, data[i]); @@ -79,16 +83,16 @@ static void assert_and_clear_mock_out(const char* expected) { // return the original stdout callback for the duration of the check // if the check fails, we don't want the error to end up in our buffer, // we want to be able to see it! - furi_thread_set_stdout_callback(original_out_cb); + furi_thread_set_stdout_callback(original_out_cb, CONTEXT_MAGIC); mu_assert_string_eq(expected, furi_string_get_cstr(mock_out)); - furi_thread_set_stdout_callback(mock_out_cb); + furi_thread_set_stdout_callback(mock_out_cb, CONTEXT_MAGIC); furi_string_reset(mock_out); } void test_stdout(void) { original_out_cb = furi_thread_get_stdout_callback(); - furi_thread_set_stdout_callback(mock_out_cb); + furi_thread_set_stdout_callback(mock_out_cb, CONTEXT_MAGIC); mock_out = furi_string_alloc(); puts("Hello, World!"); @@ -100,5 +104,5 @@ void test_stdout(void) { assert_and_clear_mock_out("Hello!"); furi_string_free(mock_out); - furi_thread_set_stdout_callback(original_out_cb); + furi_thread_set_stdout_callback(original_out_cb, CONTEXT_MAGIC); } diff --git a/applications/services/cli/cli.c b/applications/services/cli/cli.c index 0d8f52c04ec..28ba417c261 100644 --- a/applications/services/cli/cli.c +++ b/applications/services/cli/cli.c @@ -431,9 +431,9 @@ void cli_session_open(Cli* cli, void* session) { cli->session = session; if(cli->session != NULL) { cli->session->init(); - furi_thread_set_stdout_callback(cli->session->tx_stdout); + furi_thread_set_stdout_callback(cli->session->tx_stdout, NULL); } else { - furi_thread_set_stdout_callback(NULL); + furi_thread_set_stdout_callback(NULL, NULL); } furi_semaphore_release(cli->idle_sem); furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); @@ -447,7 +447,7 @@ void cli_session_close(Cli* cli) { cli->session->deinit(); } cli->session = NULL; - furi_thread_set_stdout_callback(NULL); + furi_thread_set_stdout_callback(NULL, NULL); furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); } @@ -461,9 +461,9 @@ int32_t cli_srv(void* p) { furi_record_create(RECORD_CLI, cli); if(cli->session != NULL) { - furi_thread_set_stdout_callback(cli->session->tx_stdout); + furi_thread_set_stdout_callback(cli->session->tx_stdout, NULL); } else { - furi_thread_set_stdout_callback(NULL); + furi_thread_set_stdout_callback(NULL, NULL); } if(furi_hal_rtc_get_boot_mode() == FuriHalRtcBootModeNormal) { diff --git a/applications/services/cli/cli_i.h b/applications/services/cli/cli_i.h index d4cac6e7d92..d7351b9ffc3 100644 --- a/applications/services/cli/cli_i.h +++ b/applications/services/cli/cli_i.h @@ -28,8 +28,9 @@ struct CliSession { void (*init)(void); void (*deinit)(void); size_t (*rx)(uint8_t* buffer, size_t size, uint32_t timeout); + size_t (*rx_stdin)(uint8_t* buffer, size_t size, uint32_t timeout, void* context); void (*tx)(const uint8_t* buffer, size_t size); - void (*tx_stdout)(const char* data, size_t size); + void (*tx_stdout)(const char* data, size_t size, void* context); bool (*is_connected)(void); }; diff --git a/applications/services/cli/cli_vcp.c b/applications/services/cli/cli_vcp.c index cdabaaa0544..aa399e78a29 100644 --- a/applications/services/cli/cli_vcp.c +++ b/applications/services/cli/cli_vcp.c @@ -242,6 +242,11 @@ static size_t cli_vcp_rx(uint8_t* buffer, size_t size, uint32_t timeout) { return rx_cnt; } +static size_t cli_vcp_rx_stdin(uint8_t* data, size_t size, uint32_t timeout, void* context) { + UNUSED(context); + return cli_vcp_rx(data, size, timeout); +} + static void cli_vcp_tx(const uint8_t* buffer, size_t size) { furi_assert(vcp); furi_assert(buffer); @@ -267,7 +272,8 @@ static void cli_vcp_tx(const uint8_t* buffer, size_t size) { VCP_DEBUG("tx %u end", size); } -static void cli_vcp_tx_stdout(const char* data, size_t size) { +static void cli_vcp_tx_stdout(const char* data, size_t size, void* context) { + UNUSED(context); cli_vcp_tx((const uint8_t*)data, size); } @@ -310,6 +316,7 @@ CliSession cli_vcp = { cli_vcp_init, cli_vcp_deinit, cli_vcp_rx, + cli_vcp_rx_stdin, cli_vcp_tx, cli_vcp_tx_stdout, cli_vcp_is_connected, diff --git a/furi/core/thread.c b/furi/core/thread.c index 12b9e61887c..6e515795775 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -26,11 +26,13 @@ typedef struct { FuriThreadStdoutWriteCallback write_callback; FuriString* buffer; + void* context; } FuriThreadStdout; typedef struct { FuriThreadStdinReadCallback read_callback; FuriString* unread_buffer; // output.write_callback != NULL) { - thread->output.write_callback(data, size); + thread->output.write_callback(data, size, thread->output.context); } else { furi_log_tx((const uint8_t*)data, size); } @@ -726,7 +728,7 @@ static size_t __furi_thread_stdout_write(FuriThread* thread, const char* data, s static size_t __furi_thread_stdin_read(FuriThread* thread, char* data, size_t size, FuriWait timeout) { if(thread->input.read_callback != NULL) { - return thread->input.read_callback(data, size, timeout); + return thread->input.read_callback(data, size, timeout, thread->input.context); } else { return 0; } @@ -754,17 +756,19 @@ FuriThreadStdinReadCallback furi_thread_get_stdin_callback(void) { return thread->input.read_callback; } -void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback) { +void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback, void* context) { FuriThread* thread = furi_thread_get_current(); furi_check(thread); __furi_thread_stdout_flush(thread); thread->output.write_callback = callback; + thread->output.context = context; } -void furi_thread_set_stdin_callback(FuriThreadStdinReadCallback callback) { +void furi_thread_set_stdin_callback(FuriThreadStdinReadCallback callback, void* context) { FuriThread* thread = furi_thread_get_current(); furi_check(thread); thread->input.read_callback = callback; + thread->input.context = context; } size_t furi_thread_stdout_write(const char* data, size_t size) { diff --git a/furi/core/thread.h b/furi/core/thread.h index 219d839ee17..9abfde5cd85 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -74,8 +74,9 @@ typedef int32_t (*FuriThreadCallback)(void* context); * * @param[in] data pointer to the data to be written to the standard out * @param[in] size size of the data in bytes + * @param[in] context optional context */ -typedef void (*FuriThreadStdoutWriteCallback)(const char* data, size_t size); +typedef void (*FuriThreadStdoutWriteCallback)(const char* data, size_t size, void* context); /** * @brief Standard input callback function pointer type @@ -85,9 +86,11 @@ typedef void (*FuriThreadStdoutWriteCallback)(const char* data, size_t size); * @param[out] buffer buffer to read data into * @param[in] size maximum number of bytes to read into the buffer * @param[in] timeout how long to wait for (in ticks) before giving up + * @param[in] context optional context * @returns number of bytes that was actually read into the buffer */ -typedef size_t (*FuriThreadStdinReadCallback)(char* buffer, size_t size, FuriWait timeout); +typedef size_t ( + *FuriThreadStdinReadCallback)(char* buffer, size_t size, FuriWait timeout, void* context); /** * @brief State change callback function pointer type. @@ -490,14 +493,16 @@ FuriThreadStdinReadCallback furi_thread_get_stdin_callback(void); /** Set standard output callback for the current thread. * * @param[in] callback pointer to the callback function or NULL to clear + * @param[in] context context to be passed to the callback */ -void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback); +void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback, void* context); /** Set standard input callback for the current thread. * * @param[in] callback pointer to the callback function or NULL to clear + * @param[in] context context to be passed to the callback */ -void furi_thread_set_stdin_callback(FuriThreadStdinReadCallback callback); +void furi_thread_set_stdin_callback(FuriThreadStdinReadCallback callback, void* context); /** Write data to buffered standard output. * diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 2b247d6f30c..23421712d01 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,78.2,, +Version,+,79.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -1680,8 +1680,8 @@ Function,+,furi_thread_set_signal_callback,void,"FuriThread*, FuriThreadSignalCa Function,+,furi_thread_set_stack_size,void,"FuriThread*, size_t" Function,+,furi_thread_set_state_callback,void,"FuriThread*, FuriThreadStateCallback" Function,+,furi_thread_set_state_context,void,"FuriThread*, void*" -Function,+,furi_thread_set_stdin_callback,void,FuriThreadStdinReadCallback -Function,+,furi_thread_set_stdout_callback,void,FuriThreadStdoutWriteCallback +Function,+,furi_thread_set_stdin_callback,void,"FuriThreadStdinReadCallback, void*" +Function,+,furi_thread_set_stdout_callback,void,"FuriThreadStdoutWriteCallback, void*" Function,+,furi_thread_signal,_Bool,"const FuriThread*, uint32_t, void*" Function,+,furi_thread_start,void,FuriThread* Function,+,furi_thread_stdin_read,size_t,"char*, size_t, FuriWait" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 8418b6e0bcc..6f9fc5466e6 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,78.2,, +Version,+,79.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -1899,8 +1899,8 @@ Function,+,furi_thread_set_signal_callback,void,"FuriThread*, FuriThreadSignalCa Function,+,furi_thread_set_stack_size,void,"FuriThread*, size_t" Function,+,furi_thread_set_state_callback,void,"FuriThread*, FuriThreadStateCallback" Function,+,furi_thread_set_state_context,void,"FuriThread*, void*" -Function,+,furi_thread_set_stdin_callback,void,FuriThreadStdinReadCallback -Function,+,furi_thread_set_stdout_callback,void,FuriThreadStdoutWriteCallback +Function,+,furi_thread_set_stdin_callback,void,"FuriThreadStdinReadCallback, void*" +Function,+,furi_thread_set_stdout_callback,void,"FuriThreadStdoutWriteCallback, void*" Function,+,furi_thread_signal,_Bool,"const FuriThread*, uint32_t, void*" Function,+,furi_thread_start,void,FuriThread* Function,+,furi_thread_stdin_read,size_t,"char*, size_t, FuriWait"