Files
proxmark3/client/src/frame_progress.c
T
2026-07-30 17:06:36 +02:00

405 lines
11 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//-----------------------------------------------------------------------------
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// See LICENSE.txt for the text of the license.
//-----------------------------------------------------------------------------
// Flasher progress
//-----------------------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <math.h>
#include <pthread.h>
#include "frame_progress.h"
#include "frame_data.h"
#include "util_posix.h"
#include "ansi.h"
// --- platform: terminal-width detection ----------------------------------/
#ifdef _WIN32
# include <windows.h>
static int terminal_width(void) {
CONSOLE_SCREEN_BUFFER_INFO csbi;
if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) {
return csbi.srWindow.Right - csbi.srWindow.Left + 1;
}
const char *c = getenv("COLUMNS");
if (c) {
int w = atoi(c);
if (w > 0) {
return w;
}
}
return 80;
}
#else
# include <sys/ioctl.h>
# include <unistd.h>
static int terminal_width(void) {
struct winsize ws;
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0) {
return (int)ws.ws_col;
}
const char *c = getenv("COLUMNS");
if (c) {
int w = atoi(c);
if (w > 0) {
return w;
}
}
return 80;
}
#endif
// --- wave parameters (unchanged from hadouken_wave.c) -------------------
#define ORIGIN_X 64.0
#define ORIGIN_Y 8.0
#define ASPECT 2.0
#define K 0.60
#define OMEGA 5.0
#define FLOOR 0.45
// BAR_OVERHEAD = visible fixed chars in the bar line: " Flashing [" (11) + "] 100%" (6)
#define BAR_OVERHEAD 17
#define BAR_MIN 10 // minimum inner fill width
#define BAR_MAX 512 // guard against absurdly wide terminals
#define BAR_ROW (FH + 2) // terminal row for the progress bar (1-based)
#define AFTER_ROW (FH + 3) // first free row after the animation+bar
// --- progress-bar role colours --------------------------------------------
// All escape sequences come from ansi.h, this only names the roles.
#define ANSI_BAR_LABEL ANSI_CYAN // label " Flashing "
#define ANSI_BAR_BRACKET ANSI_WHITE // brackets [ ]
#define ANSI_BAR_FILLED ANSI_BRIGHT_CYAN // filled '='
#define ANSI_BAR_EMPTY ANSI_DIM ANSI_WHITE // empty '-'
#define ANSI_BAR_PCT ANSI_BRIGHT_YELLOW // percentage number
static const char *GLYPHS = " .:;+xX#";
// --- shared state --------------------------------------------------------
static volatile sig_atomic_t g_resize = 0; // set by SIGWINCH
static volatile int g_running = 0;
static volatile int g_progress = 0; // 0..100
static pthread_t g_thread;
// animation config written once before hadouken_start()
static double g_fps = 30.0;
static int g_glyph_mode = 0;
static int g_wave_mode = 0; // 0 = radial, 1 = beam
void hadouken_on_sigint(int s) {
(void)s;
g_running = 0;
}
#ifndef _WIN32
void hadouken_on_sigwinch(int s) {
(void)s;
g_resize = 1;
}
#endif
/*
* Compute the inner fill width for the progress bar from the current terminal
* width, clamped to [BAR_MIN, BAR_MAX].
*/
static int bar_inner_width(void) {
int w = terminal_width() - BAR_OVERHEAD;
if (w < BAR_MIN) {
w = BAR_MIN;
}
if (w > BAR_MAX) {
w = BAR_MAX;
}
return w;
}
// --- colour helpers ------------------------------------------------------
static inline int clamp8(double v) {
if (v < 0) {
return 0;
}
if (v > 255) {
return 255;
}
return (int)(v + 0.5);
}
static void fire_color(int x, int y, const Cell *c, double t, int mode, int *r, int *g, int *b) {
double dx = x - ORIGIN_X;
double dy = (y - ORIGIN_Y) * ASPECT;
double d = sqrt(dx * dx + dy * dy);
double phase_var = mode ? (dx > 0 ? dx : 0.0) : d;
double wave = 0.5 + 0.5 * sin(phase_var * K - OMEGA * t);
double atten = 1.0 - d * 0.012;
if (atten < 0.35) {
atten = 0.35;
}
if (atten > 1.0) {
atten = 1.0;
}
double inten = (FLOOR + (1.0 - FLOOR) * wave) * atten;
double crest = wave * wave;
*r = clamp8(c->r * inten + 70 * crest);
*g = clamp8(c->g * inten + 110 * crest);
*b = clamp8(c->b * inten + 50 * crest);
}
// --- line renderers ------------------------------------------------------
// Render one animation row into dst (same logic as hadouken_wave.c).
static int render_anim_line(char *dst, int y, double t, int glyph_mode, int mode) {
int len = 0;
int last_r = -1, last_g = -1, last_b = -1, last_has = -2;
int pending_blanks = 0;
for (int x = 0; x < FW; x++) {
const Cell *c = &FRAME[y][x];
int ch = c->ch;
int has = c->has;
int r = 0;
int g = 0;
int b = 0;
if (ch == ' ' && !has) {
pending_blanks++;
continue;
}
if (c->fire) {
fire_color(x, y, c, t, mode, &r, &g, &b);
has = 1;
if (glyph_mode) {
double lum = (r + g + b) / 765.0;
int idx = (int)(lum * 8);
if (idx > 7) {
idx = 7;
}
ch = GLYPHS[idx];
if (ch == ' ') {
ch = '.';
}
}
} else if (has) {
r = c->r;
g = c->g;
b = c->b;
}
if (pending_blanks) {
memset(dst + len, ' ', pending_blanks);
len += pending_blanks;
pending_blanks = 0;
}
if (has != last_has || r != last_r || g != last_g || b != last_b) {
if (has) {
len += sprintf(dst + len, ANSI_FG_RGB, r, g, b);
} else {
len += sprintf(dst + len, AEND);
}
last_has = has;
last_r = r;
last_g = g;
last_b = b;
}
dst[len++] = (char)ch;
}
dst[len] = '\0';
return len;
}
/*
* Render the progress bar into dst. bar_width is the inner fill width,
* computed dynamically from the current terminal width each frame.
*
* Flashing [========================================----] 100%
* ^<--------- bar_width chars ----------->^
*/
static int render_progress_bar(char *dst, int pct, int bar_width) {
int len = 0;
int filled = (bar_width * pct) / 100;
if (filled > bar_width) {
filled = bar_width;
}
// label
len += sprintf(dst + len, ANSI_BAR_LABEL " Flashing " AEND ANSI_BAR_BRACKET "[" AEND);
// filled portion
if (filled > 0) {
len += sprintf(dst + len, ANSI_BAR_FILLED);
for (int i = 0; i < filled; i++) {
dst[len++] = '=';
}
}
// empty portion
if (filled < bar_width) {
len += sprintf(dst + len, ANSI_BAR_EMPTY);
for (int i = filled; i < bar_width; i++) {
dst[len++] = '-';
}
}
// closing bracket + percentage
len += sprintf(dst + len, AEND ANSI_BAR_BRACKET "] " AEND ANSI_BAR_PCT "%3d%%" AEND, pct);
dst[len] = '\0';
return len;
}
static double now_sec(void) {
return msclock() / 1000.0;
}
// --- animation thread ----------------------------------------------------
static void *anim_thread(void *arg) {
(void)arg;
// static keeps these off the thread stack (BSS)
static char cur [FH][FW * 24 + 16];
static char prev[FH][FW * 24 + 16];
static char out [FH * (FW * 24 + 32) + 512];
// bar buf: BAR_MAX fill chars + ANSI escapes per char + fixed overhead
static char bar [BAR_MAX + 256];
int have_prev = 0;
double frame_dt = 1.0 / g_fps;
double start = now_sec();
while (g_running) {
double t0 = now_sec();
double t = t0 - start;
int pct = g_progress; // single-read snapshot
// On terminal resize: flush the diff cache so all rows redraw cleanly
if (g_resize) {
g_resize = 0;
have_prev = 0;
}
// dynamic: re-queried every frame
int bw = bar_inner_width();
int n = 0;
// sync on, home
n += sprintf(out + n, _SYNC_ON_ _CURSOR_HOME_);
// animation rows (with per-line diffing)
for (int y = 0; y < FH; y++) {
int len = render_anim_line(cur[y], y, t, g_glyph_mode, g_wave_mode);
if (have_prev && strcmp(cur[y], prev[y]) == 0) {
continue;
}
n += sprintf(out + n, _CURSOR_ROW_ _CLR_LINE_, y + 1);
memcpy(out + n, cur[y], len);
n += len;
memcpy(prev[y], cur[y], len + 1);
}
// progress bar (always redrawn, width adapts to terminal)
int blen = render_progress_bar(bar, pct, bw);
n += sprintf(out + n, _CURSOR_ROW_ _CLR_LINE_, BAR_ROW);
memcpy(out + n, bar, blen);
n += blen;
// reset, sync off
n += sprintf(out + n, AEND _SYNC_OFF_);
have_prev = 1;
fwrite(out, 1, n, stdout);
fflush(stdout);
// fixed-timestep with render-time compensation
double remain = frame_dt - (now_sec() - t0);
if (remain > 0) {
msleep((uint32_t)(remain * 1000));
}
}
return NULL;
}
/*
* Start the animation thread.
* fps frames per second (e.g. 30.0)
* glyph_mode 1 = ASCII glyphs instead of colour blocks
* wave_mode 0 = radial rings, 1 = forward beam
*/
void hadouken_start(double fps, int glyph_mode, int wave_mode) {
g_fps = (fps > 0) ? fps : 30.0;
g_glyph_mode = glyph_mode;
g_wave_mode = wave_mode;
g_progress = 0;
g_running = 1;
// hide cursor, clear screen
fputs(_CURSOR_HIDE_ _CLEAR_, stdout);
fflush(stdout);
pthread_create(&g_thread, NULL, anim_thread, NULL);
}
/*
* Update the progress bar.
* Safe to call from any thread.
* pct is clamped to [0, 100].
*/
void hadouken_set_progress(int pct) {
if (pct < 0) {
pct = 0;
}
if (pct > 100) {
pct = 100;
}
g_progress = pct;
}
/*
* Stop the animation thread, then position the cursor on the line below the
* progress bar and restore it so subsequent printf/puts output is visible.
*/
void hadouken_stop(void) {
/* Wait 2 frame durations so the thread renders at least one more frame
* with the final g_progress value (e.g. 100%) before we signal exit.
* Without this, the thread may be mid-sleep and exit before painting 100%. */
msleep((uint32_t)(2000.0 / g_fps));
g_running = 0;
pthread_join(g_thread, NULL);
// Move below the progress bar, restore cursor visibility
fprintf(stdout, _CURSOR_ROW_ AEND _CURSOR_SHOW_ "\n", AFTER_ROW);
fflush(stdout);
}