fix little endian vs big endian in the macros... again... but this time correct
[RRG-proxmark3.git] / client / src / ui.c
bloba044d7312d8b36723b168339157f890c8c67ab59
1 //-----------------------------------------------------------------------------
2 // Copyright (C) 2009 Michael Gernoth <michael at gernoth.net>
3 // Copyright (C) 2010 iZsh <izsh at fail0verflow.com>
4 //
5 // This code is licensed to you under the terms of the GNU GPL, version 2 or,
6 // at your option, any later version. See the LICENSE.txt file for the text of
7 // the license.
8 //-----------------------------------------------------------------------------
9 // UI utilities
10 //-----------------------------------------------------------------------------
12 /* Ensure strtok_r is available even with -std=c99; must be included before
14 #if !defined(_WIN32)
15 #define _POSIX_C_SOURCE 200112L
16 #endif
17 #include "ui.h"
18 #include "commonutil.h" // ARRAYLEN
20 #include <stdio.h> // for Mingw readline
21 #include <stdarg.h>
22 #include <stdlib.h>
24 #ifdef HAVE_READLINE
25 //Load readline after stdio.h
26 #include <readline/readline.h>
27 #endif
29 #include <complex.h>
30 #include "util.h"
31 #include "proxmark3.h" // PROXLOG
32 #include "fileutils.h"
33 #include "pm3_cmd.h"
35 #ifdef _WIN32
36 # include <direct.h> // _mkdir
37 #endif
39 #include <time.h>
40 #include "emojis.h"
41 #include "emojis_alt.h"
42 session_arg_t session;
44 double CursorScaleFactor = 1;
45 char CursorScaleFactorUnit[11] = {0};
46 double PlotGridX = 0, PlotGridY = 0, PlotGridXdefault = 64, PlotGridYdefault = 64;
47 uint32_t CursorCPos = 0, CursorDPos = 0;
48 double GraphPixelsPerPoint = 1.f; // How many visual pixels are between each sample point (x axis)
49 static bool flushAfterWrite = 0;
50 double GridOffset = 0;
51 bool GridLocked = false;
52 bool showDemod = true;
54 pthread_mutex_t print_lock = PTHREAD_MUTEX_INITIALIZER;
56 static void fPrintAndLog(FILE *stream, const char *fmt, ...);
58 // needed by flasher, so let's put it here instead of fileutils.c
59 int searchHomeFilePath(char **foundpath, const char *subdir, const char *filename, bool create_home) {
60 if (foundpath == NULL)
61 return PM3_EINVARG;
63 const char *user_path = get_my_user_directory();
64 if (user_path == NULL) {
65 fprintf(stderr, "Could not retrieve $HOME from the environment\n");
66 return PM3_EFILE;
69 size_t pathlen = strlen(user_path) + strlen(PM3_USER_DIRECTORY) + 1;
70 char *path = calloc(pathlen, sizeof(char));
71 if (path == NULL)
72 return PM3_EMALLOC;
74 strcpy(path, user_path);
75 strcat(path, PM3_USER_DIRECTORY);
77 int result;
78 #ifdef _WIN32
79 struct _stat st;
80 // Mingw _stat fails if path ends with /, so let's use a stripped path
81 if (path[strlen(path) - 1] == '/') {
82 path[strlen(path) - 1] = '\0';
83 result = _stat(path, &st);
84 path[strlen(path)] = '/';
85 } else {
86 result = _stat(path, &st);
88 #else
89 struct stat st;
90 result = stat(path, &st);
91 #endif
92 if ((result != 0) && create_home) {
94 #ifdef _WIN32
95 if (_mkdir(path))
96 #else
97 if (mkdir(path, 0700))
98 #endif
100 fprintf(stderr, "Could not create user directory %s\n", path);
101 free(path);
102 return PM3_EFILE;
105 if (subdir != NULL) {
106 pathlen += strlen(subdir);
107 char *tmp = realloc(path, pathlen * sizeof(char));
108 if (tmp == NULL) {
109 //free(path);
110 return PM3_EMALLOC;
112 path = tmp;
113 strcat(path, subdir);
115 #ifdef _WIN32
116 // Mingw _stat fails if path ends with /, so let's use a stripped path
117 if (path[strlen(path) - 1] == '/') {
118 path[strlen(path) - 1] = '\0';
119 result = _stat(path, &st);
120 path[strlen(path)] = '/';
121 } else {
122 result = _stat(path, &st);
124 #else
125 result = stat(path, &st);
126 #endif
127 if ((result != 0) && create_home) {
129 #ifdef _WIN32
130 if (_mkdir(path))
131 #else
132 if (mkdir(path, 0700))
133 #endif
135 fprintf(stderr, "Could not create user directory %s\n", path);
136 free(path);
137 return PM3_EFILE;
142 if (filename == NULL) {
143 *foundpath = path;
144 return PM3_SUCCESS;
146 pathlen += strlen(filename);
147 char *tmp = realloc(path, pathlen * sizeof(char));
148 if (tmp == NULL) {
149 //free(path);
150 return PM3_EMALLOC;
152 path = tmp;
153 strcat(path, filename);
154 *foundpath = path;
155 return PM3_SUCCESS;
158 void PrintAndLogOptions(const char *str[][2], size_t size, size_t space) {
159 char buff[2000] = "Options:\n";
160 char format[2000] = "";
161 size_t counts[2] = {0, 0};
162 for (size_t i = 0; i < size; i++)
163 for (size_t j = 0 ; j < 2 ; j++)
164 if (counts[j] < strlen(str[i][j])) {
165 counts[j] = strlen(str[i][j]);
167 for (size_t i = 0; i < size; i++) {
168 for (size_t j = 0; j < 2; j++) {
169 if (j == 0)
170 snprintf(format, sizeof(format), "%%%zus%%%zus", space, counts[j]);
171 else
172 snprintf(format, sizeof(format), "%%%zus%%-%zus", space, counts[j]);
173 snprintf(buff + strlen(buff), sizeof(buff) - strlen(buff), format, " ", str[i][j]);
175 if (i < size - 1)
176 strncat(buff, "\n", sizeof(buff) - strlen(buff) - 1);
178 PrintAndLogEx(NORMAL, "%s", buff);
181 static uint8_t PrintAndLogEx_spinidx = 0;
183 void PrintAndLogEx(logLevel_t level, const char *fmt, ...) {
185 // skip debug messages if client debugging is turned off i.e. 'DATA SETDEBUG -0'
186 if (g_debugMode == 0 && level == DEBUG)
187 return;
189 // skip HINT messages if client has hints turned off i.e. 'HINT 0'
190 if (session.show_hints == false && level == HINT)
191 return;
193 char prefix[40] = {0};
194 char buffer[MAX_PRINT_BUFFER] = {0};
195 char buffer2[MAX_PRINT_BUFFER + sizeof(prefix)] = {0};
196 char *token = NULL;
197 char *tmp_ptr = NULL;
198 FILE *stream = stdout;
199 const char *spinner[] = {_YELLOW_("[\\]"), _YELLOW_("[|]"), _YELLOW_("[/]"), _YELLOW_("[-]")};
200 const char *spinner_emoji[] = {" :clock1: ", " :clock2: ", " :clock3: ", " :clock4: ", " :clock5: ", " :clock6: ",
201 " :clock7: ", " :clock8: ", " :clock9: ", " :clock10: ", " :clock11: ", " :clock12: "
203 switch (level) {
204 case ERR:
205 if (session.emoji_mode == EMO_EMOJI)
206 strncpy(prefix, "[" _RED_("!!") "] :rotating_light: ", sizeof(prefix) - 1);
207 else
208 strncpy(prefix, "[" _RED_("!!") "] ", sizeof(prefix) - 1);
209 stream = stderr;
210 break;
211 case FAILED:
212 if (session.emoji_mode == EMO_EMOJI)
213 strncpy(prefix, "[" _RED_("-") "] :no_entry: ", sizeof(prefix) - 1);
214 else
215 strncpy(prefix, "[" _RED_("-") "] ", sizeof(prefix) - 1);
216 break;
217 case DEBUG:
218 strncpy(prefix, "[" _BLUE_("#") "] ", sizeof(prefix) - 1);
219 break;
220 case HINT:
221 strncpy(prefix, "[" _YELLOW_("?") "] ", sizeof(prefix) - 1);
222 break;
223 case SUCCESS:
224 strncpy(prefix, "[" _GREEN_("+") "] ", sizeof(prefix) - 1);
225 break;
226 case WARNING:
227 if (session.emoji_mode == EMO_EMOJI)
228 strncpy(prefix, "[" _CYAN_("!") "] :warning: ", sizeof(prefix) - 1);
229 else
230 strncpy(prefix, "[" _CYAN_("!") "] ", sizeof(prefix) - 1);
231 break;
232 case INFO:
233 strncpy(prefix, "[" _YELLOW_("=") "] ", sizeof(prefix) - 1);
234 break;
235 case INPLACE:
236 if (session.emoji_mode == EMO_EMOJI) {
237 strncpy(prefix, spinner_emoji[PrintAndLogEx_spinidx], sizeof(prefix) - 1);
238 PrintAndLogEx_spinidx++;
239 if (PrintAndLogEx_spinidx >= ARRAYLEN(spinner_emoji))
240 PrintAndLogEx_spinidx = 0;
241 } else {
242 strncpy(prefix, spinner[PrintAndLogEx_spinidx], sizeof(prefix) - 1);
243 PrintAndLogEx_spinidx++;
244 if (PrintAndLogEx_spinidx >= ARRAYLEN(spinner))
245 PrintAndLogEx_spinidx = 0;
247 break;
248 case NORMAL:
249 // no prefixes for normal
250 break;
253 va_list args;
254 va_start(args, fmt);
255 vsnprintf(buffer, sizeof(buffer), fmt, args);
256 va_end(args);
258 // no prefixes for normal & inplace
259 if (level == NORMAL) {
260 fPrintAndLog(stream, "%s", buffer);
261 return;
264 if (strchr(buffer, '\n')) {
266 const char delim[2] = "\n";
268 // line starts with newline
269 if (buffer[0] == '\n')
270 fPrintAndLog(stream, "");
272 token = strtok_r(buffer, delim, &tmp_ptr);
274 while (token != NULL) {
276 size_t size = strlen(buffer2);
278 if (strlen(token))
279 snprintf(buffer2 + size, sizeof(buffer2) - size, "%s%s\n", prefix, token);
280 else
281 snprintf(buffer2 + size, sizeof(buffer2) - size, "\n");
283 token = strtok_r(NULL, delim, &tmp_ptr);
285 fPrintAndLog(stream, "%s", buffer2);
286 } else {
287 snprintf(buffer2, sizeof(buffer2), "%s%s", prefix, buffer);
288 if (level == INPLACE) {
289 char buffer3[sizeof(buffer2)] = {0};
290 char buffer4[sizeof(buffer2)] = {0};
291 memcpy_filter_ansi(buffer3, buffer2, sizeof(buffer2), !session.supports_colors);
292 memcpy_filter_emoji(buffer4, buffer3, sizeof(buffer3), session.emoji_mode);
293 fprintf(stream, "\r%s", buffer4);
294 fflush(stream);
295 } else {
296 fPrintAndLog(stream, "%s", buffer2);
301 static void fPrintAndLog(FILE *stream, const char *fmt, ...) {
302 va_list argptr;
303 static FILE *logfile = NULL;
304 static int logging = 1;
305 char buffer[MAX_PRINT_BUFFER] = {0};
306 char buffer2[MAX_PRINT_BUFFER] = {0};
307 char buffer3[MAX_PRINT_BUFFER] = {0};
308 // lock this section to avoid interlacing prints from different threads
309 pthread_mutex_lock(&print_lock);
310 bool linefeed = true;
312 if (logging && session.incognito) {
313 logging = 0;
315 if ((g_printAndLog & PRINTANDLOG_LOG) && logging && !logfile) {
316 char *my_logfile_path = NULL;
317 char filename[40];
318 struct tm *timenow;
319 time_t now = time(NULL);
320 timenow = gmtime(&now);
321 strftime(filename, sizeof(filename), PROXLOG, timenow);
322 if (searchHomeFilePath(&my_logfile_path, LOGS_SUBDIR, filename, true) != PM3_SUCCESS) {
323 printf(_YELLOW_("[-]") " Logging disabled!\n");
324 my_logfile_path = NULL;
325 logging = 0;
326 } else {
327 logfile = fopen(my_logfile_path, "a");
328 if (logfile == NULL) {
329 printf(_YELLOW_("[-]") " Can't open logfile %s, logging disabled!\n", my_logfile_path);
330 logging = 0;
331 } else {
333 if (session.supports_colors) {
334 printf("["_YELLOW_("=")"] Session log " _YELLOW_("%s") "\n", my_logfile_path);
335 } else {
336 printf("[=] Session log %s\n", my_logfile_path);
340 free(my_logfile_path);
345 // If there is an incoming message from the hardware (eg: lf hid read) in
346 // the background (while the prompt is displayed and accepting user input),
347 // stash the prompt and bring it back later.
348 #ifdef RL_STATE_READCMD
349 // We are using GNU readline. libedit (OSX) doesn't support this flag.
350 int need_hack = (rl_readline_state & RL_STATE_READCMD) > 0;
351 char *saved_line;
352 int saved_point;
354 if (need_hack) {
355 saved_point = rl_point;
356 saved_line = rl_copy_text(0, rl_end);
357 rl_save_prompt();
358 rl_replace_line("", 0);
359 rl_redisplay();
361 #endif
363 va_start(argptr, fmt);
364 vsnprintf(buffer, sizeof(buffer), fmt, argptr);
365 va_end(argptr);
366 if (strlen(buffer) > 0 && buffer[strlen(buffer) - 1] == NOLF[0]) {
367 linefeed = false;
368 buffer[strlen(buffer) - 1] = 0;
370 bool filter_ansi = !session.supports_colors;
371 memcpy_filter_ansi(buffer2, buffer, sizeof(buffer), filter_ansi);
372 if (g_printAndLog & PRINTANDLOG_PRINT) {
373 memcpy_filter_emoji(buffer3, buffer2, sizeof(buffer2), session.emoji_mode);
374 fprintf(stream, "%s", buffer3);
375 if (linefeed)
376 fprintf(stream, "\n");
379 #ifdef RL_STATE_READCMD
380 // We are using GNU readline. libedit (OSX) doesn't support this flag.
381 if (need_hack) {
382 rl_restore_prompt();
383 rl_replace_line(saved_line, 0);
384 rl_point = saved_point;
385 rl_redisplay();
386 free(saved_line);
388 #endif
390 if ((g_printAndLog & PRINTANDLOG_LOG) && logging && logfile) {
391 memcpy_filter_emoji(buffer3, buffer2, sizeof(buffer2), EMO_ALTTEXT);
392 if (filter_ansi) { // already done
393 fprintf(logfile, "%s", buffer3);
394 } else {
395 memcpy_filter_ansi(buffer, buffer3, sizeof(buffer3), true);
396 fprintf(logfile, "%s", buffer);
398 if (linefeed)
399 fprintf(logfile, "\n");
400 fflush(logfile);
403 if (flushAfterWrite)
404 fflush(stdout);
406 //release lock
407 pthread_mutex_unlock(&print_lock);
410 void SetFlushAfterWrite(bool value) {
411 flushAfterWrite = value;
414 void memcpy_filter_rlmarkers(void *dest, const void *src, size_t n) {
415 uint8_t *rdest = (uint8_t *)dest;
416 uint8_t *rsrc = (uint8_t *)src;
417 uint16_t si = 0;
418 for (uint16_t i = 0; i < n; i++) {
419 if ((rsrc[i] == '\001') || (rsrc[i] == '\002'))
420 // skip readline special markers
421 continue;
422 rdest[si++] = rsrc[i];
426 void memcpy_filter_ansi(void *dest, const void *src, size_t n, bool filter) {
427 if (filter) {
428 // Filter out ANSI sequences on these OS
429 uint8_t *rdest = (uint8_t *)dest;
430 uint8_t *rsrc = (uint8_t *)src;
431 uint16_t si = 0;
432 for (uint16_t i = 0; i < n; i++) {
433 if ((i < n - 1)
434 && (rsrc[i] == '\x1b')
435 && (rsrc[i + 1] >= 0x40)
436 && (rsrc[i + 1] <= 0x5F)) { // entering ANSI sequence
438 i++;
439 if ((i < n - 1) && (rsrc[i] == '[')) { // entering CSI sequence
440 i++;
442 while ((i < n - 1) && (rsrc[i] >= 0x30) && (rsrc[i] <= 0x3F)) { // parameter bytes
443 i++;
446 while ((i < n - 1) && (rsrc[i] >= 0x20) && (rsrc[i] <= 0x2F)) { // intermediate bytes
447 i++;
450 if ((rsrc[i] >= 0x40) && (rsrc[i] <= 0x7F)) { // final byte
451 continue;
453 } else {
454 continue;
457 rdest[si++] = rsrc[i];
459 } else {
460 memcpy(dest, src, n);
464 static bool emojify_token(const char *token, uint8_t token_length, const char **emojified_token, uint8_t *emojified_token_length, emojiMode_t mode) {
465 int i = 0;
466 while (EmojiTable[i].alias && EmojiTable[i].emoji) {
467 if ((strlen(EmojiTable[i].alias) == token_length) && (0 == memcmp(EmojiTable[i].alias, token, token_length))) {
468 switch (mode) {
469 case EMO_EMOJI: {
470 *emojified_token = EmojiTable[i].emoji;
471 *emojified_token_length = strlen(EmojiTable[i].emoji);
472 break;
474 case EMO_ALTTEXT: {
475 int j = 0;
476 *emojified_token_length = 0;
477 while (EmojiAltTable[j].alias && EmojiAltTable[j].alttext) {
478 if ((strlen(EmojiAltTable[j].alias) == token_length) && (0 == memcmp(EmojiAltTable[j].alias, token, token_length))) {
479 *emojified_token = EmojiAltTable[j].alttext;
480 *emojified_token_length = strlen(EmojiAltTable[j].alttext);
481 break;
483 ++j;
485 break;
487 case EMO_NONE: {
488 *emojified_token_length = 0;
489 break;
491 case EMO_ALIAS: { // should never happen
492 return false;
495 return true;
497 ++i;
499 return false;
502 static bool token_charset(uint8_t c) {
503 if ((c >= '0') && (c <= '9')) return true;
504 if ((c >= 'a') && (c <= 'z')) return true;
505 if ((c >= 'A') && (c <= 'Z')) return true;
506 if ((c == '_') || (c == '+') || (c == '-')) return true;
507 return false;
510 void memcpy_filter_emoji(void *dest, const void *src, size_t n, emojiMode_t mode) {
511 if (mode == EMO_ALIAS) {
512 memcpy(dest, src, n);
513 } else {
514 // tokenize emoji
515 const char *emojified_token = NULL;
516 uint8_t emojified_token_length = 0;
517 char *current_token = NULL;
518 uint8_t current_token_length = 0;
519 char current_char;
520 char *rdest = (char *)dest;
521 char *rsrc = (char *)src;
522 uint16_t si = 0;
523 for (uint16_t i = 0; i < n; i++) {
524 current_char = rsrc[i];
526 if (current_token_length == 0) {
527 // starting a new token.
528 if (current_char == ':') {
529 current_token = rsrc + i;
530 current_token_length = 1;
531 } else { // not starting a new token.
532 rdest[si++] = current_char;
534 } else {
535 // finishing the current token.
536 if (current_char == ':') {
537 // nothing changed? we still need the ending ':' as it might serve for an upcoming emoji
538 if (! emojify_token(current_token, current_token_length + 1, &emojified_token, &emojified_token_length, mode)) {
539 memcpy(rdest + si, current_token, current_token_length);
540 si += current_token_length;
541 current_token = rsrc + i;
542 current_token_length = 1;
543 } else {
544 memcpy(rdest + si, emojified_token, emojified_token_length);
545 si += emojified_token_length;
546 current_token_length = 0;
548 } else if (token_charset(current_char)) { // continuing the current token.
549 current_token_length++;
550 } else { // dropping the current token.
551 current_token_length++;
552 memcpy(rdest + si, current_token, current_token_length);
553 si += current_token_length;
554 current_token_length = 0;
558 memcpy(rdest + si, current_token, current_token_length);
563 // If reactivated, beware it doesn't compile on Android (DXL)
564 void iceIIR_Butterworth(int *data, const size_t len) {
566 int *output = (int *) calloc(sizeof(int) * len, sizeof(uint8_t));
567 if (!output) return;
569 // clear mem
570 memset(output, 0x00, len);
572 size_t adjustedLen = len;
573 float fc = 0.1125f; // center frequency
575 // create very simple low-pass filter to remove images (2nd-order Butterworth)
576 float complex iir_buf[3] = {0, 0, 0};
577 float b[3] = {0.003621681514929, 0.007243363029857, 0.003621681514929};
578 float a[3] = {1.000000000000000, -1.822694925196308, 0.837181651256023};
580 for (size_t i = 0; i < adjustedLen; ++i) {
582 float sample = data[i]; // input sample read from array
583 float complex x_prime = 1.0f; // save sample for estimating frequency
584 float complex x;
586 // remove DC offset and mix to complex baseband
587 x = (sample - 127.5f) * cexpf(_Complex_I * 2 * M_PI * fc * i);
589 // apply low-pass filter, removing spectral image (IIR using direct-form II)
590 iir_buf[2] = iir_buf[1];
591 iir_buf[1] = iir_buf[0];
592 iir_buf[0] = x - a[1] * iir_buf[1] - a[2] * iir_buf[2];
593 x = b[0] * iir_buf[0] +
594 b[1] * iir_buf[1] +
595 b[2] * iir_buf[2];
597 // compute instantaneous frequency by looking at phase difference
598 // between adjacent samples
599 float freq = cargf(x * conjf(x_prime));
600 x_prime = x; // retain this sample for next iteration
602 output[i] = (freq > 0) ? 127 : -127;
605 // show data
606 //memcpy(data, output, adjustedLen);
607 for (size_t j = 0; j < adjustedLen; ++j)
608 data[j] = output[j];
610 free(output);
614 void iceSimple_Filter(int *data, const size_t len, uint8_t k) {
615 // ref: http://www.edn.com/design/systems-design/4320010/A-simple-software-lowpass-filter-suits-embedded-system-applications
616 // parameter K
617 #define FILTER_SHIFT 4
619 int32_t filter_reg = 0;
620 int8_t shift = (k <= 8) ? k : FILTER_SHIFT;
622 for (size_t i = 0; i < len; ++i) {
623 // Update filter with current sample
624 filter_reg = filter_reg - (filter_reg >> shift) + data[i];
626 // Scale output for unity gain
627 data[i] = filter_reg >> shift;
631 void print_progress(size_t count, uint64_t max, barMode_t style) {
632 int cols = 100 + 35;
633 #ifdef HAVE_READLINE
634 static int prev_cols = 0;
635 int rows;
636 rl_reset_screen_size(); // refresh Readline idea of the actual screen width
637 rl_get_screen_size(&rows, &cols);
638 (void) rows;
639 if (prev_cols > cols) {
640 PrintAndLogEx(NORMAL, _CLEAR_ _TOP_ "");
642 prev_cols = cols;
643 #endif
644 int width = cols - 35;
645 if (width < 1)
646 return;
648 #define PERCENTAGE(V, T) ((V * width) / T)
649 // x/8 fractional part of the percentage
650 #define PERCENTAGEFRAC(V, T) ((uint8_t)(((((float)V * width) / T) - ((V * width) / T)) * 8))
652 const char *smoothtable[] = {
653 "\xe2\x80\x80",
654 "\xe2\x96\x8F",
655 "\xe2\x96\x8E",
656 "\xe2\x96\x8D",
657 "\xe2\x96\x8C",
658 "\xe2\x96\x8B",
659 "\xe2\x96\x8A",
660 "\xe2\x96\x89",
661 "\xe2\x96\x88",
664 int mode = (session.emoji_mode == EMO_EMOJI);
666 const char *block[] = {"#", "\xe2\x96\x88"};
667 // use a 3-byte space in emoji mode to ease computations
668 const char *space[] = {" ", "\xe2\x80\x80"};
670 size_t unit = strlen(block[mode]);
671 // +1 for \0
672 char *bar = (char *)calloc(unit * width + 1, sizeof(uint8_t));
674 uint8_t value = PERCENTAGE(count, max);
676 int i = 0;
677 // prefix is added already.
678 for (; i < unit * value; i += unit) {
679 memcpy(bar + i, block[mode], unit);
681 // add last block
682 if (mode == 1) {
683 memcpy(bar + i, smoothtable[PERCENTAGEFRAC(count, max)], unit);
684 } else {
685 memcpy(bar + i, space[mode], unit);
687 i += unit;
688 // add spaces
689 for (; i < unit * width; i += unit) {
690 memcpy(bar + i, space[mode], unit);
692 // color buffer
693 size_t collen = strlen(bar) + 40;
694 char *cbar = (char *)calloc(collen, sizeof(uint8_t));
696 // Add colors
697 if (session.supports_colors) {
698 int p60 = unit * (width * 60 / 100);
699 int p20 = unit * (width * 20 / 100);
700 snprintf(cbar, collen, _GREEN_("%.*s"), p60, bar);
701 snprintf(cbar + strlen(cbar), collen - strlen(cbar), _CYAN_("%.*s"), p20, bar + p60);
702 snprintf(cbar + strlen(cbar), collen - strlen(cbar), _YELLOW_("%.*s"), (int)(unit * width - p60 - p20), bar + p60 + p20);
703 } else {
704 snprintf(cbar, collen, "%s", bar);
707 size_t olen = strlen(cbar) + 40;
708 char *out = (char *)calloc(olen, sizeof(uint8_t));
710 switch (style) {
711 case STYLE_BAR: {
712 sprintf(out, "%s", cbar);
713 printf("\b%c[2K\r[" _YELLOW_("=")"] %s", 27, out);
714 break;
716 case STYLE_MIXED: {
717 sprintf(out, "%s [ %zu mV / %2u V / %2u Vmax ]", cbar, count, (uint32_t)(count / 1000), (uint32_t)(max / 1000));
718 printf("\b%c[2K\r[" _YELLOW_("=")"] %s", 27, out);
719 break;
721 case STYLE_VALUE: {
722 printf("[" _YELLOW_("=")"] %zu mV / %2u V / %2u Vmax \r", count, (uint32_t)(count / 1000), (uint32_t)(max / 1000));
723 break;
726 fflush(stdout);
727 free(out);
728 free(bar);
729 free(cbar);