ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
render_progress.c
Go to the documentation of this file.
1
2/* Storage capacities in elements; text buffers include the terminating NUL. */
3#define TUI_RENDER_PROGRESS_LABEL_BUFFER_SIZE 160
4#define TUI_RENDER_PROGRESS_TOTAL_BUFFER_SIZE 32
5#define TUI_RENDER_PROGRESS_TITLE_BUFFER_SIZE 64
6#define TUI_RENDER_PROGRESS_BAR_BUFFER_SIZE 4096
7
13#include <libplatform/compiler.h>
14#include <libplatform/system.h>
15#include <libplatform/terminal.h>
21#include <string.h>
22#include <stdio.h>
23
24// Preview storage is bounded independently of recording duration and queue depth.
25#define PREVIEW_MAX_BYTES (1024 * 1024)
26
27typedef struct {
33 bool failed;
34 size_t length;
36 // The NUL-terminated destination follows the NUL-terminated preview.
37 char frame[];
44
57
58static void progress_render(terminal_size_t size, const void *data) {
59 const progress_snapshot_t *snapshot = data;
60 frame_buffer_t *buffer = frame_buffer_create(size.rows, size.cols);
61 if (!buffer)
62 return;
63 char *line = SAFE_MALLOC(snapshot->length + 1, char *);
64 char *clipped = SAFE_MALLOC(snapshot->length + UI_CLIPPED_TEXT_OVERHEAD, char *);
65 const char *cursor = snapshot->frame;
66 const char *end = cursor + snapshot->length;
67 // Address each row explicitly, leaving the last column free to prevent wrapping.
68 for (int row = 1; row <= size.rows; ++row) {
69 frame_buffer_printf(buffer, "\033[%d;1H\033[0m\033[2K", row);
70 if (cursor < end) {
71 const char *newline = memchr(cursor, '\n', (size_t)(end - cursor));
72 size_t length = (size_t)((newline ? newline : end) - cursor);
73 memcpy(line, cursor, length);
74 line[length] = '\0';
75 char *expanded = ansi_expand_rle(line, length);
76 truncate_with_ellipsis(expanded ? expanded : line, clipped, snapshot->length + UI_CLIPPED_TEXT_OVERHEAD,
77 size.cols - 1);
78 SAFE_FREE(expanded);
79 frame_buffer_append(buffer, clipped, strlen(clipped));
80 cursor += length + (newline ? 1 : 0);
81 }
82 }
83 uint64_t elapsed = snapshot->started_ns ? time_get_ns() - snapshot->started_ns : 0;
84 uint64_t seconds = elapsed / NS_PER_SEC_INT;
86 if (snapshot->total_known)
87 snprintf(total, sizeof(total), "%llu", (unsigned long long)snapshot->total);
88 else
89 snprintf(total, sizeof(total), "?");
90 const char *phase = snapshot->failed ? "Recording failed"
91 : snapshot->finalizing ? "Finalizing file"
92 : "Rendering file";
93 snprintf(label, sizeof(label), " %c %s (%llu:%02llu) (frame %llu/%s) ",
94 "|/-\\"[(elapsed / (125 * NS_PER_MS_INT)) % 4], phase, (unsigned long long)(seconds / 60),
95 (unsigned long long)(seconds % 60), (unsigned long long)snapshot->completed, total);
96 int width = (int)strlen(label);
97 if (width > size.cols - 2) {
98 width = size.cols - 2;
99 label[width] = '\0';
100 }
102 truncate_with_ellipsis("--render-file Progress", title, sizeof(title), size.cols - 4);
103 int col = (size.cols - display_width(title) - 2) / 2 + 1;
104 frame_buffer_printf(buffer, "\033[%d;%dH\033[0;1;7m %s \033[0m", (size.rows + 1) / 2 - 1, col, title);
105 col = (size.cols - width) / 2 + 1;
106 frame_buffer_printf(buffer, "\033[%d;%dH\033[0;7m%s\033[0m", (size.rows + 1) / 2, col, label);
107 size_t destination_size = snapshot->destination_length + UI_CLIPPED_TEXT_OVERHEAD;
108 char *destination = SAFE_MALLOC(destination_size, char *);
109 truncate_with_ellipsis(snapshot->frame + snapshot->length + 1, destination, destination_size, size.cols - 4);
110 int destination_width = display_width(destination) + 2;
111 col = (size.cols - destination_width) / 2 + 1;
112 frame_buffer_printf(buffer, "\033[%d;%dH\033[0;7m %s \033[0m", (size.rows + 1) / 2 + 1, col, destination);
113 if (snapshot->total_known && snapshot->total && size.rows >= 9) {
115 int bar_width = size.cols > 44 ? 40 : size.cols - 4;
116 ui_progress_format(bar, sizeof(bar), (double)snapshot->completed / (double)snapshot->total, bar_width);
117 frame_buffer_printf(buffer, "\033[%d;%dH%s", (size.rows + 1) / 2 + 2, (size.cols - bar_width) / 2 + 1, bar);
118 }
119 SAFE_FREE(destination);
121 SAFE_FREE(line);
122 SAFE_FREE(clipped);
123 frame_buffer_destroy(buffer);
124}
125
126// The presentation callback reads only the controller-owned copy and never takes this mutex.
127static void progress_publish(render_progress_t *progress) {
128 if (progress->active)
129 ui_controller_submit(UI_SCREEN_RENDER_PROGRESS, progress->fd, (terminal_size_t){.cols = 20, .rows = 5},
130 progress_render, progress->snapshot,
131 sizeof(*progress->snapshot) + progress->snapshot->length +
132 progress->snapshot->destination_length + 2);
133}
134
135render_progress_t *render_progress_create(bool enabled, const char *destination) {
136 if (!enabled || !terminal_is_stdout_tty())
137 return NULL;
138 render_progress_t *progress = SAFE_CALLOC(1, sizeof(*progress), render_progress_t *);
139 progress->fd = platform_dup(STDOUT_FILENO);
140 if (progress->fd < 0) {
141 SAFE_FREE(progress);
142 return NULL;
143 }
144 mutex_init(&progress->mutex, "render_progress");
145 size_t destination_length = strlen(destination);
146 progress->snapshot = SAFE_CALLOC(1, sizeof(*progress->snapshot) + destination_length + 2, progress_snapshot_t *);
147 progress->snapshot->destination_length = destination_length;
148 char *name = progress->snapshot->frame + 1;
149 memcpy(name, destination, destination_length);
150 // A path must not inject terminal control sequences into the overlay.
151 for (size_t i = 0; i < destination_length; ++i) {
152 if ((unsigned char)name[i] < 0x20 || name[i] == 0x7f)
153 name[i] = '?';
154 }
155 progress->snapshot->started_ns = time_get_ns();
156 return progress;
157}
158
159void render_progress_frame(render_progress_t *progress, const char *frame, bool succeeded) {
160 if (!progress)
161 return;
162 mutex_lock(&progress->mutex);
163 uint64_t now = time_get_ns();
164 // The overlay refreshes at most eight times per second. Expanding and copying
165 // every full-resolution frame only slows the encoder and cannot be displayed.
166 if (succeeded && !progress->snapshot->failed && progress->last_preview_ns && now >= progress->last_preview_ns &&
167 now - progress->last_preview_ns < 125 * NS_PER_MS_INT) {
168 ++progress->snapshot->completed;
169 mutex_unlock(&progress->mutex);
170 return;
171 }
172 progress->last_preview_ns = now;
173 size_t length = strnlen(frame, (size_t)PREVIEW_MAX_BYTES + 1u);
174 // Oversized frames still encode normally; omit their terminal preview.
175 if (length > PREVIEW_MAX_BYTES)
176 length = 0;
177 progress_snapshot_t *next =
178 SAFE_CALLOC(1, sizeof(*next) + length + progress->snapshot->destination_length + 2, progress_snapshot_t *);
179 memcpy(next, progress->snapshot, sizeof(*next));
180 next->length = length;
181 memcpy(next->frame, frame, length);
182 memcpy(next->frame + length + 1, progress->snapshot->frame + progress->snapshot->length + 1,
183 next->destination_length + 1);
184 if (!next->started_ns)
185 next->started_ns = time_get_ns();
186 if (succeeded)
187 next->completed++;
188 else
189 next->failed = true;
190 SAFE_FREE(progress->snapshot);
191 progress->snapshot = next;
192 progress_publish(progress);
193 mutex_unlock(&progress->mutex);
194}
195
196void render_progress_begin(render_progress_t *progress, uint64_t total, bool total_known) {
197 if (!progress)
198 return;
199 mutex_lock(&progress->mutex);
200 progress->active = true;
201 progress->snapshot->total = total;
202 progress->snapshot->total_known = total_known;
203 progress_publish(progress);
204 mutex_unlock(&progress->mutex);
205}
206
208 if (!progress)
209 return;
210 mutex_lock(&progress->mutex);
211 progress->active = true;
212 progress->snapshot->finalizing = true;
213 if (!progress->snapshot->total_known) {
214 progress->snapshot->total = progress->snapshot->completed;
215 progress->snapshot->total_known = true;
216 }
217 progress_publish(progress);
218 mutex_unlock(&progress->mutex);
219}
220
222 if (!progress)
223 return;
225 platform_close(progress->fd);
226 SAFE_FREE(progress->snapshot);
227 mutex_destroy(&progress->mutex);
228 SAFE_FREE(progress);
229}
unsigned int width
Definition fft.c:23
void frame_buffer_append(frame_buffer_t *buf, const char *str, size_t len)
Append raw bytes to the buffer.
void frame_buffer_destroy(frame_buffer_t *buf)
Destroy a frame buffer.
size_t frame_buffer_get_length(const frame_buffer_t *buf)
Get the current content length of the frame buffer.
const char * frame_buffer_get_content(const frame_buffer_t *buf)
Get a pointer to the buffer content.
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.
bool enabled
Is filtering active?
Definition grep.c:62
#define SAFE_FREE(ptr)
Definition common.h:377
#define SAFE_MALLOC(size, cast)
Definition common.h:265
#define SAFE_CALLOC(count, size, cast)
Definition common.h:275
unsigned long long uint64_t
Definition common.h:63
#define NS_PER_SEC_INT
Definition time.h:169
#define NS_PER_MS_INT
Definition time.h:168
int platform_dup(int fd)
int display_width(const char *text)
Calculate the visible display width of text with ANSI escape codes.
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
char * ansi_expand_rle(const char *input, size_t input_len)
Expand RLE escape sequences in a string.
Definition rle.c:14
🖥️ Display Width and Terminal Centering Utilities
void ui_progress_format(char *out, size_t cap, double fraction, int width)
Definition progress.c:8
void render_progress_finalize(render_progress_t *progress)
#define TUI_RENDER_PROGRESS_TOTAL_BUFFER_SIZE
render_progress_t * render_progress_create(bool enabled, const char *destination)
void render_progress_frame(render_progress_t *progress, const char *frame, bool succeeded)
#define TUI_RENDER_PROGRESS_BAR_BUFFER_SIZE
#define PREVIEW_MAX_BYTES
#define TUI_RENDER_PROGRESS_TITLE_BUFFER_SIZE
void render_progress_begin(render_progress_t *progress, uint64_t total, bool total_known)
void render_progress_destroy(render_progress_t *progress)
#define TUI_RENDER_PROGRESS_LABEL_BUFFER_SIZE
struct render_progress render_progress_t
🔤 String Manipulation and Shell Escaping Utilities
render progress keeps the values that move through the subsystem: mutex, snapshot,...
uint64_t last_preview_ns
progress_snapshot_t * snapshot
#define UI_CLIPPED_TEXT_OVERHEAD
Definition text.h:4
⏱️ 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)
asciichat_error_t ui_controller_write(int fd, const char *data, size_t len)
@ UI_SCREEN_RENDER_PROGRESS