ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
tui/mdns.c
Go to the documentation of this file.
1
2/* Storage capacities in elements; text buffers include the terminating NUL. */
3#define TUI_MDNS_INPUT_BUFFER_SIZE 32
4#define TUI_MDNS_HEADING_BUFFER_SIZE 128
5#define TUI_MDNS_LINE_BUFFER_SIZE 512
6#define TUI_MDNS_CLIPPED_BUFFER_SIZE 512
7
10#include <ascii-chat/ui/text.h>
11#include <ascii-chat/ui/table.h>
19
24#include <ascii-chat/tui/mdns.h>
31#include <libplatform/atomic.h>
34#include <ascii-chat/log/log.h>
35#include <libplatform/compiler.h>
36#include <libplatform/system.h>
37#include <libplatform/terminal.h>
38#include <ascii-chat/common.h>
40
41#include <stdio.h>
42#include <stdlib.h>
43#include <string.h>
44#ifndef _WIN32
45#include <unistd.h>
46#endif
47
48/* ============================================================================
49 * Log Management
50 * ========================================================================== */
51
52void ui_mdns_log_init(void) {
54 if (buf) {
56 }
57}
58
63
67
68void ui_mdns_log_append(const char *message) {
69 if (message) {
70 log_info("%s", message);
71 }
72}
73
80ui_mdns_server_t *ui_mdns_query(const ui_mdns_config_t *config, int *out_count) {
81 if (!out_count) {
82 SET_ERRNO(ERROR_INVALID_PARAM, "out_count pointer is NULL");
83 return NULL;
84 }
85
86 *out_count = 0;
87
88 // Apply defaults if needed
89 int timeout_ms = (config && config->timeout_ms > 0) ? config->timeout_ms : 2000;
90 int max_servers = (config && config->max_servers > 0) ? config->max_servers : 20;
91 bool quiet = (config && config->quiet);
92
93 // Call the core mDNS discovery function from discovery.c
94 return discovery_mdns_query(timeout_ms, max_servers, quiet, out_count);
95}
96
103
107int ui_mdns_prompt_selection(const ui_mdns_server_t *servers, int count) {
108 if (terminal_can_prompt_user(!GET_OPTION(snapshot_mode)))
109 return ui_mdns_select(servers, count);
110 if (!servers || count <= 0) {
111 return -1;
112 }
113
114 log_error("Cannot select a discovered server without an interactive terminal; specify a server address.");
115 return -1;
116}
117
138
139#define MDNS_LIVE_CAPACITY 128
140#define MDNS_LIST_MAX_ROWS 12
141typedef struct {
142 mutex_t mutex;
143 atomic_t stop;
144 platform_thread_t thread;
146 unsigned generation;
147 int count;
152
153static int compare_mdns_servers(const void *left, const void *right) {
154 const ui_mdns_server_t *a = left, *b = right;
155 int address = strcmp(ui_mdns_get_best_address(a), ui_mdns_get_best_address(b));
156 return address ? address : (int)a->port - (int)b->port;
157}
158
159static void *refresh_mdns_servers(void *data) {
160 mdns_refresh_t *refresh = data;
161 ui_mdns_config_t config = {.timeout_ms = 500, .max_servers = MDNS_LIVE_CAPACITY, .quiet = true};
162 unsigned empty_refreshes = 0;
163 while (!atomic_load_bool(&refresh->stop)) {
164 int count = 0;
165 ui_mdns_server_t *servers = ui_mdns_query(&config, &count);
166 if (count > 1)
167 qsort(servers, (size_t)count, sizeof(*servers), compare_mdns_servers);
168 if (!atomic_load_bool(&refresh->stop)) {
169 mutex_lock(&refresh->mutex);
170 bool apply_snapshot = true;
171 if (count == 0 && refresh->count > 0)
172 apply_snapshot = ++empty_refreshes >= 2;
173 else
174 empty_refreshes = 0;
175 if (apply_snapshot) {
176 bool changed = count != refresh->count ||
177 (count && memcmp(refresh->servers, servers, (size_t)count * sizeof(*servers)) != 0);
178 refresh->count = count;
179 if (count)
180 memcpy(refresh->servers, servers, (size_t)count * sizeof(*servers));
181 if (changed)
182 ++refresh->generation;
183 }
184 mutex_unlock(&refresh->mutex);
185 }
186 ui_mdns_free_results(servers);
187 for (int i = 0; i < 10 && !atomic_load_bool(&refresh->stop); ++i)
189 }
190 return NULL;
191}
192
193static void mdns_divider(frame_buffer_t *buffer, int columns, int width, bool unicode, bool colors) {
194 frame_buffer_printf(buffer, "%*s", (columns - width) / 2, "");
195 frame_buffer_render_divider(buffer, width, unicode, colors);
196 frame_buffer_printf(buffer, "\r");
197}
198
199static void mdns_footer(frame_buffer_t *buffer, int columns, const char *input, bool colors, bool live) {
200 const char *parts[] = {"Arrows", "/", "j", "/", "k", ": move | ",
201 "Enter", ": select | ", "Esc", "/", "q", live ? ": cancel | Auto-refresh" : ": cancel",
202 input};
203 bool keys[] = {true, false, true, false, true, false, true, false, true, false, true, false, false};
204 int length = 0;
205 for (size_t i = 0; i < sizeof(parts) / sizeof(parts[0]); ++i)
206 length += (int)strlen(parts[i]);
207 int width = length < columns - 1 ? length : columns - 1;
208 frame_buffer_printf(buffer, "%*s", (columns - 1 - width) / 2, "");
209 int used = 0;
210 for (size_t i = 0; i < sizeof(parts) / sizeof(parts[0]) && used < width; ++i) {
211 int shown = (int)strlen(parts[i]);
212 if (shown > width - used)
213 shown = width - used;
214 frame_buffer_printf(buffer, "%s%.*s", colors ? ui_text_color_prefix(keys[i] ? LOG_COLOR_DEV : LOG_COLOR_GREY) : "",
215 shown, parts[i]);
216 used += shown;
217 }
218 frame_buffer_printf(buffer, "%s", colors ? "\033[0m" : "");
219}
220
221static void render_mdns_selection(terminal_size_t size, const void *data) {
222 const mdns_snapshot_t *snapshot = data;
223 frame_buffer_t *buffer = frame_buffer_create(size.rows, size.cols);
224 if (!buffer)
225 return;
226 frame_buffer_printf(buffer, "\033[0m\033[H\033[2J");
227 bool colors = !GET_OPTION(strip_ansi) && terminal_should_color_output(STDERR_FILENO, options_terminal_settings());
228 bool unicode = terminal_should_use_utf8(GET_OPTION(force_utf8));
229 int panel_width = size.cols - 1 < 80 ? size.cols - 1 : 80;
230 bool show_title = size.rows >= 5;
231 int chrome = show_title ? 4 : 3;
232 bool compact = size.cols < 52 || size.rows < 10;
233 int logo_rows = size.rows >= 9 ? (compact ? 1 : 4) : 0;
234 int logo_height = logo_rows ? logo_rows + 1 : 0;
235 int available = size.rows - logo_height - chrome;
236 if (available < 1 || size.cols < 2) {
237 frame_buffer_flush(buffer);
238 frame_buffer_destroy(buffer);
239 return;
240 }
241 if (available > MDNS_LIST_MAX_ROWS)
242 available = MDNS_LIST_MAX_ROWS;
243 int shown = snapshot->count < available ? snapshot->count : available;
244 if (!shown)
245 shown = 1;
246 int height = logo_height + shown + chrome;
247 int top = (size.rows - height) / 2 + 1;
248 frame_buffer_printf(buffer, "\033[%d;1H", top);
249 if (logo_rows) {
251 int tick = sample ? (int)(sample->seconds * 60) : (int)((time_get_ns() / NS_PER_MS_INT / 16) % 100000);
252 invitation_render_logo(buffer, size.cols - 1, tick, colors, compact);
253 frame_buffer_printf(buffer, "\r\n");
254 }
255 char heading[TUI_MDNS_HEADING_BUFFER_SIZE];
256 if (show_title) {
257 truncate_with_ellipsis("LAN servers", heading, sizeof(heading), size.cols - 1);
258 frame_buffer_render_centered(buffer, heading, size.cols - 1);
259 frame_buffer_printf(buffer, "\r");
260 }
261 mdns_divider(buffer, size.cols - 1, panel_width, unicode, colors);
262 if (snapshot->count == 0) {
263 truncate_with_ellipsis("Searching for LAN servers...", heading, sizeof(heading), size.cols - 1);
264 frame_buffer_render_centered(buffer, heading, size.cols - 1);
265 frame_buffer_printf(buffer, "\r");
266 }
267 ui_selectable_list_t list = snapshot->list;
268 ui_selectable_list_keep_visible(&list, snapshot->count, available);
269 int first = list.first_visible;
270 for (int i = first; i < snapshot->count && i < first + available; ++i) {
272 const char *address = ui_mdns_get_best_address(&snapshot->servers[i]);
273 snprintf(line, sizeof(line), "%s [%d] %s (%s%s%s:%u)", i == list.selected ? ">" : " ", i + 1,
274 snapshot->servers[i].name, strchr(address, ':') ? "[" : "", address, strchr(address, ':') ? "]" : "",
275 snapshot->servers[i].port);
276 truncate_with_ellipsis(line, clipped, sizeof(clipped), size.cols - 2);
277 if (colors && i == list.selected)
279 frame_buffer_render_centered(buffer, clipped, size.cols - 1);
280 frame_buffer_printf(buffer, "\033[0m\r");
281 }
282 if (snapshot->count > available)
283 ui_scrollbar_render(buffer, top + logo_height + (show_title ? 2 : 1), size.cols, available, (size_t)snapshot->count,
284 (size_t)available, (size_t)first);
285 mdns_divider(buffer, size.cols - 1, panel_width, unicode, colors);
286 mdns_footer(buffer, size.cols, snapshot->input, colors, snapshot->live);
287 frame_buffer_flush(buffer);
288 frame_buffer_destroy(buffer);
289}
290
291int ui_mdns_select(const ui_mdns_server_t *servers, int count) {
292 if (!servers || count <= 0)
293 return -1;
294 return ui_mdns_select_until(servers, count, ui_input_deadline(30));
295}
296
297static int select_mdns_until(const ui_mdns_server_t *servers, int count, uint64_t deadline, mdns_refresh_t *refresh,
298 ui_mdns_server_t *chosen) {
299 if (count < 0 || (!servers && count > 0))
300 return -1;
301 if (!terminal_can_prompt_user(!GET_OPTION(snapshot_mode)))
302 return ui_mdns_prompt_selection(servers, count);
304 return -1;
305 size_t bytes = sizeof(mdns_snapshot_t) + (size_t)(refresh ? MDNS_LIVE_CAPACITY : count) * sizeof(*servers);
306 mdns_snapshot_t *snapshot = SAFE_CALLOC(1, bytes, mdns_snapshot_t *);
307 if (!snapshot)
308 return -1;
309 snapshot->count = count;
310 snapshot->live = refresh != NULL;
311 if (count > 0)
312 memcpy(snapshot->servers, servers, (size_t)count * sizeof(*servers));
313 bool logging = log_get_terminal_output();
315 if (refresh) {
316 thread_init(&refresh->thread);
317 refresh->running = platform_thread_create(&refresh->thread, "lan_refresh", refresh_mdns_servers, refresh) == 0;
318 if (!refresh->running) {
320 SAFE_FREE(snapshot);
321 return -1;
322 }
323 }
324 unsigned generation = 0;
325 int selection = -1;
326 size_t length = 0;
327 bool timed_out = false;
328 while (!shutdown_is_requested()) {
329 if (refresh) {
330 mutex_lock(&refresh->mutex);
331 if (generation != refresh->generation) {
332 ui_mdns_server_t previous = {0};
333 if (count)
334 previous = snapshot->servers[snapshot->list.selected];
335 count = refresh->count;
336 snapshot->count = count;
337 memcpy(snapshot->servers, refresh->servers, (size_t)count * sizeof(*servers));
338 ui_selectable_list_reset(&snapshot->list);
339 for (int i = 0; i < count; ++i) {
340 if (previous.port == snapshot->servers[i].port &&
341 strcmp(ui_mdns_get_best_address(&previous), ui_mdns_get_best_address(&snapshot->servers[i])) == 0) {
342 snapshot->list.selected = i;
343 break;
344 }
345 }
346 length = 0;
347 snapshot->input[0] = '\0';
348 generation = refresh->generation;
349 }
350 mutex_unlock(&refresh->mutex);
351 }
352 if (ui_controller_submit(UI_SCREEN_MDNS, STDERR_FILENO, (terminal_size_t){.cols = 2, .rows = 4},
353 render_mdns_selection, snapshot, bytes) != ASCIICHAT_OK)
354 break;
356 if (ui_input_expired(deadline)) {
357 timed_out = true;
358 break;
359 }
360 if (key == KEY_ESCAPE || key == 'q' || key == 3)
361 break;
362 if (key == KEY_HOME || key == KEY_END) {
363 ui_viewport_t viewport = {.selected = (size_t)snapshot->list.selected,
364 .first_visible = (size_t)snapshot->list.first_visible};
365 ui_viewport_key(&viewport, (size_t)count, MDNS_LIST_MAX_ROWS, key);
366 snapshot->list.selected = (int)viewport.selected;
367 snapshot->list.first_visible = (int)viewport.first_visible;
368 length = 0;
369 snapshot->input[0] = '\0';
370 continue;
371 }
372 if (count && (key == KEY_UP || key == 'k' || key == KEY_DOWN || key == 'j')) {
373 int delta = (key == KEY_UP || key == 'k') ? -1 : 1;
374 snapshot->list.selected = (snapshot->list.selected + delta + count) % count;
376 length = 0;
377 snapshot->input[0] = '\0';
378 continue;
379 }
380 if (key == '\r' || key == '\n') {
381 if (!count)
382 continue;
383 if (!length) {
384 selection = snapshot->list.selected;
385 break;
386 }
387 long value = strtol(snapshot->input, NULL, 10);
388 if (value >= 1 && value <= count) {
389 selection = (int)value - 1;
390 break;
391 }
392 length = 0;
393 snapshot->input[0] = '\0';
394 } else if ((key == 8 || key == 127) && length) {
395 snapshot->input[--length] = '\0';
396 } else if (key >= '0' && key <= '9' && length < sizeof(snapshot->input) - 1) {
397 snapshot->input[length++] = (char)key;
398 snapshot->input[length] = '\0';
399 }
400 }
401 if (chosen && selection >= 0)
402 *chosen = snapshot->servers[selection];
403 if (refresh && refresh->running) {
404 atomic_store_bool(&refresh->stop, true);
405 thread_join(&refresh->thread, NULL);
406 }
409 SAFE_FREE(snapshot);
410 if (timed_out)
411 ui_input_timeout_report(30u, "server selection cancelled; specify a server address for unattended use");
412 return selection;
413}
414
415int ui_mdns_select_until(const ui_mdns_server_t *servers, int count, uint64_t deadline) {
416 return select_mdns_until(servers, count, deadline, NULL, NULL);
417}
418
419int ui_mdns_select_live(const ui_mdns_server_t *servers, int count, uint64_t deadline, ui_mdns_server_t *chosen) {
420 if (!chosen || count < 0 || count > MDNS_LIVE_CAPACITY || (count && !servers))
421 return -1;
422 mdns_refresh_t refresh = {0};
423 refresh.count = count;
424 if (count)
425 memcpy(refresh.servers, servers, (size_t)count * sizeof(*servers));
426 if (mutex_init(&refresh.mutex, "lan_results") != 0)
427 return -1;
428 int selected = select_mdns_until(servers, count, deadline, &refresh, chosen);
429 mutex_destroy(&refresh.mutex);
430 return selected < 0 ? -1 : 0;
431}
432
439const char *ui_mdns_get_best_address(const ui_mdns_server_t *server) {
440 if (!server) {
441 return "";
442 }
443
444 // The service instance name is a display label, not a resolvable hostname.
445 if (server->ipv4[0] != '\0') {
446 return server->ipv4;
447 }
448 if (server->ipv6[0] != '\0') {
449 return server->ipv6;
450 }
451
452 return server->address; // Fallback to address field
453}
#define SET_ERRNO(code,...)
⚙️ Common definitions, error codes, macros, and types shared throughout the application
void discovery_mdns_destroy(ui_mdns_server_t *servers)
Free memory from mDNS discovery results.
Definition discovery.c:288
ui_mdns_server_t * discovery_mdns_query(int timeout_ms, int max_servers, bool quiet, int *out_count)
Public mDNS query function used by both parallel discovery and TUI wrapper.
Definition discovery.c:192
unsigned char colors[(512 *256 *2) *3]
Definition fft.c:22
unsigned int height
Definition fft.c:23
unsigned int width
Definition fft.c:23
int frame_buffer_render_centered(frame_buffer_t *buf, const char *text, int cols)
Render centered text (used by both splash and status screens).
void frame_buffer_destroy(frame_buffer_t *buf)
Destroy a frame buffer.
void frame_buffer_flush(frame_buffer_t *buf)
Flush the entire buffer to stdout in one atomic write.
void frame_buffer_render_divider(frame_buffer_t *buf, int width, bool use_utf8, bool use_colors)
frame_buffer_t * frame_buffer_create(int rows, int cols)
Create a new frame buffer.
void frame_buffer_printf(frame_buffer_t *buf, const char *fmt,...)
Printf-style append to the buffer.
Buffered terminal frame rendering.
struct frame_buffer frame_buffer_t
Opaque frame buffer handle.
#define SAFE_FREE(ptr)
Definition common.h:377
#define SAFE_CALLOC(count, size, cast)
Definition common.h:275
unsigned long long uint64_t
Definition common.h:63
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_INVALID_PARAM
bool log_get_terminal_output(void)
Get current terminal output setting.
Definition log.c:763
struct session_log_buffer session_log_buffer_t
Thread-safe fixed-size history ring for session-screen log lines.
#define log_error(...)
Log an ERROR message.
Definition log/log.h:634
void log_clear_session_log_buffer(void)
Unregister the session log buffer.
Definition log.c:2057
void log_set_session_log_buffer(session_log_buffer_t *buf)
Register a session log buffer with the logger.
Definition log.c:2048
#define log_info(...)
Log an INFO message.
Definition log/log.h:608
void log_set_terminal_output(bool enabled)
Control stderr output to terminal.
Definition log.c:746
@ LOG_COLOR_GREY
Definition log/log.h:154
@ LOG_COLOR_DEV
Definition log/log.h:148
@ LOG_COLOR_DEBUG
Definition log/log.h:149
#define NS_PER_MS_INT
Definition time.h:168
#define GET_OPTION(field)
Safely get a specific option field (lock-free read).
Definition options.h:1336
#define options_terminal_settings()
Definition options.h:1296
asciichat_error_t keyboard_init(void)
Initialize keyboard input system.
void platform_sleep_ms(unsigned int ms)
Sleep for a specified number of milliseconds.
keyboard_key_t
Options for interactive line editing.
Definition keyboard.h:53
@ KEY_UP
Up arrow key.
Definition keyboard.h:57
@ KEY_ESCAPE
Escape key (ESC).
Definition keyboard.h:55
@ KEY_END
End key (move to end of line).
Definition keyboard.h:63
@ KEY_DOWN
Down arrow key.
Definition keyboard.h:58
@ KEY_HOME
Home key (move to start of line).
Definition keyboard.h:62
bool shutdown_is_requested(void)
Check if shutdown has been requested.
Definition common.c:86
void truncate_with_ellipsis(const char *input, char *output, size_t output_size, int max_width)
Truncate a string to a maximum display width, with ellipsis.
Definition string.c:370
🖥️ Display Width and Terminal Centering Utilities
void invitation_render_logo(frame_buffer_t *buf, int cols, int frame, bool colors, bool compact)
Definition invitation.c:34
📝 Logging API with multiple log levels and terminal output control
⚙️ Unified options parsing system for ascii-chat with builder pattern and lock-free access
void ui_scrollbar_render(frame_buffer_t *out, int row, int col, int height, size_t total, size_t visible, size_t offset)
Definition scrollbar.c:40
void ui_selectable_list_reset(ui_selectable_list_t *list)
void ui_selectable_list_keep_visible(ui_selectable_list_t *list, int count, int rows)
Thread-safe circular log buffer for session screens (status, splash).
Shutdown check system for clean library/application separation.
🔤 String Manipulation and Shell Escaping Utilities
For animation config, this is the live record that ties type, fps, speed, hidden_policy together....
platform_thread_t thread
Definition tui/mdns.c:144
mutex_t mutex
Definition tui/mdns.c:142
atomic_t stop
Definition tui/mdns.c:143
ui_mdns_server_t servers[128]
Definition tui/mdns.c:148
unsigned generation
Definition tui/mdns.c:146
TUI-based server selection with formatted display.
Definition tui/mdns.c:131
char input[32]
Definition tui/mdns.c:135
ui_selectable_list_t list
Definition tui/mdns.c:134
ui_mdns_server_t servers[]
Definition tui/mdns.c:136
Configuration for TUI discovery.
Definition tui/mdns.h:79
int max_servers
Maximum servers to collect (default: 20).
Definition tui/mdns.h:81
int timeout_ms
Maximum time to wait for responses (default: 2000).
Definition tui/mdns.h:80
bool quiet
Suppress discovery messages (default: false).
Definition tui/mdns.h:82
Discovered server information from mDNS.
Definition tui/mdns.h:66
char name[256]
Service instance name (e.g., "swift-river-canyon").
Definition tui/mdns.h:67
char ipv4[16]
IPv4 address (if available).
Definition tui/mdns.h:71
char ipv6[46]
IPv6 address (if available).
Definition tui/mdns.h:72
uint16_t port
Server port number.
Definition tui/mdns.h:70
char address[256]
Server address (IPv4, IPv6, or hostname).
Definition tui/mdns.h:69
ui selectable list keeps the values that move through the subsystem: selected, first_visible....
For ui viewport, this is the live record that ties selected, first_visible, page_size together....
Definition viewport.h:10
size_t first_visible
Definition viewport.h:11
size_t selected
Definition viewport.h:11
void terminal_screen_log_destroy(void)
Standard log cleanup for terminal screens.
void terminal_screen_log_clear(void)
Clear buffered logs for terminal screens.
session_log_buffer_t * terminal_screen_log_init(void)
Initialize session log buffer for terminal screens.
Reusable "fixed header + scrolling logs" terminal screen abstraction.
const char * ui_text_color_prefix(log_color_t color)
Definition text.c:18
⏱️ High-precision timing utilities using sokol_time.h and uthash
void ui_controller_remove(ui_screen_t screen)
asciichat_error_t ui_controller_submit(ui_screen_t screen, int fd, terminal_size_t minimum, ui_render_fn render, const void *snapshot, size_t bytes)
const animation_sample_t * ui_controller_animation_sample(void)
@ UI_SCREEN_MDNS
keyboard_key_t ui_input_wait_key(ui_screen_t screen, unsigned timeout_ms)
Definition tui/input.c:554
void ui_input_timeout_report(unsigned seconds, const char *consequence)
Definition tui/input.c:56
uint64_t ui_input_deadline(unsigned seconds)
Definition tui/input.c:48
bool ui_input_expired(uint64_t deadline)
Definition tui/input.c:52
#define MDNS_LIST_MAX_ROWS
Definition tui/mdns.c:140
ui_mdns_server_t * ui_mdns_query(const ui_mdns_config_t *config, int *out_count)
TUI-based server selection with formatted display.
Definition tui/mdns.c:80
int ui_mdns_select_live(const ui_mdns_server_t *servers, int count, uint64_t deadline, ui_mdns_server_t *chosen)
Definition tui/mdns.c:419
#define TUI_MDNS_HEADING_BUFFER_SIZE
Definition tui/mdns.c:4
#define TUI_MDNS_INPUT_BUFFER_SIZE
Definition tui/mdns.c:3
void ui_mdns_free_results(ui_mdns_server_t *servers)
Free results from mDNS discovery.
Definition tui/mdns.c:100
int ui_mdns_select_until(const ui_mdns_server_t *servers, int count, uint64_t deadline)
Definition tui/mdns.c:415
void ui_mdns_log_append(const char *message)
Append message to mDNS discovery log buffer.
Definition tui/mdns.c:68
const char * ui_mdns_get_best_address(const ui_mdns_server_t *server)
This is the handoff point for mdns refresh. Callers fill mutex, stop, thread, running before passing ...
Definition tui/mdns.c:439
void ui_mdns_log_init(void)
Discovered server information from mDNS.
Definition tui/mdns.c:52
#define TUI_MDNS_LINE_BUFFER_SIZE
Definition tui/mdns.c:5
int ui_mdns_select(const ui_mdns_server_t *servers, int count)
Definition tui/mdns.c:291
void ui_mdns_log_clear(void)
Clear mDNS discovery log buffer.
Definition tui/mdns.c:64
void ui_mdns_log_destroy(void)
Destroy mDNS discovery log buffer.
Definition tui/mdns.c:59
int ui_mdns_prompt_selection(const ui_mdns_server_t *servers, int count)
Interactive server selection.
Definition tui/mdns.c:107
#define MDNS_LIVE_CAPACITY
Definition tui/mdns.c:139
#define TUI_MDNS_CLIPPED_BUFFER_SIZE
Definition tui/mdns.c:6
mDNS service discovery UI with terminal_screen rendering
bool ui_viewport_key(ui_viewport_t *v, size_t count, size_t rows, keyboard_key_t key)
Definition viewport.c:38