mirror of
https://github.com/mpv-player/mpv
synced 2025-04-11 04:01:31 +00:00
msg: refactor how terminal messages are printed
- prepare string before printing - reduce amount of fflush(), especially for multiline messages - clear status line and keep it always at the bottom - indent module name to the longest value - disable cursor for status line - properly support wrapped status line Overall makes status line less flickering and remove stray status instead of scrolling them up.
This commit is contained in:
parent
e682834234
commit
24270b8587
251
common/msg.c
251
common/msg.c
@ -74,6 +74,8 @@ struct mp_log_root {
|
|||||||
struct mp_log_buffer *early_filebuffer;
|
struct mp_log_buffer *early_filebuffer;
|
||||||
FILE *stats_file;
|
FILE *stats_file;
|
||||||
bstr buffer;
|
bstr buffer;
|
||||||
|
bstr term_msg;
|
||||||
|
bstr term_msg_prefix;
|
||||||
// --- must be accessed atomically
|
// --- must be accessed atomically
|
||||||
/* This is incremented every time the msglevels must be reloaded.
|
/* This is incremented every time the msglevels must be reloaded.
|
||||||
* (This is perhaps better than maintaining a globally accessible and
|
* (This is perhaps better than maintaining a globally accessible and
|
||||||
@ -88,6 +90,7 @@ struct mp_log_root {
|
|||||||
struct mp_log_buffer *log_file_buffer;
|
struct mp_log_buffer *log_file_buffer;
|
||||||
// --- protected by log_file_lock
|
// --- protected by log_file_lock
|
||||||
bool log_file_thread_active; // also termination signal for the thread
|
bool log_file_thread_active; // also termination signal for the thread
|
||||||
|
int module_indent;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct mp_log {
|
struct mp_log {
|
||||||
@ -187,53 +190,58 @@ int mp_msg_level(struct mp_log *log)
|
|||||||
// Reposition cursor and clear lines for outputting the status line. In certain
|
// Reposition cursor and clear lines for outputting the status line. In certain
|
||||||
// cases, like term OSD and subtitle display, the status can consist of
|
// cases, like term OSD and subtitle display, the status can consist of
|
||||||
// multiple lines.
|
// multiple lines.
|
||||||
static void prepare_status_line(struct mp_log_root *root, char *new_status)
|
static void prepare_prefix(struct mp_log_root *root, bstr *out, int lev, int term_lines)
|
||||||
{
|
{
|
||||||
FILE *f = stderr;
|
int new_lines = lev == MSGL_STATUS ? term_lines : 0;
|
||||||
|
out->len = 0;
|
||||||
|
|
||||||
size_t new_lines = 1;
|
// Set cursor state
|
||||||
char *tmp = new_status;
|
if (new_lines && !root->status_lines) {
|
||||||
while (1) {
|
bstr_xappend(root, out, bstr0("\033[?25l"));
|
||||||
tmp = strchr(tmp, '\n');
|
} else if (!new_lines && root->status_lines) {
|
||||||
if (!tmp)
|
bstr_xappend(root, out, bstr0("\033[?25h"));
|
||||||
break;
|
|
||||||
new_lines++;
|
|
||||||
tmp++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t old_lines = root->status_lines;
|
int line_skip = 0;
|
||||||
if (!new_status[0] && old_lines == 0)
|
if (root->status_lines) {
|
||||||
return; // nothing to clear
|
// Clear previous status line
|
||||||
|
bstr_xappend(root, out, bstr0("\033[1K\r"));
|
||||||
|
bstr up_clear = bstr0("\033[A\033[K");
|
||||||
|
for (int i = 1; i < root->status_lines; ++i)
|
||||||
|
bstr_xappend(root, out, up_clear);
|
||||||
|
// Reposition cursor after last message
|
||||||
|
line_skip = (new_lines ? new_lines : root->blank_lines) - root->status_lines;
|
||||||
|
line_skip = MPMIN(root->blank_lines - root->status_lines, line_skip);
|
||||||
|
if (line_skip)
|
||||||
|
bstr_xappend_asprintf(root, out, "\033[%dA", line_skip);
|
||||||
|
} else if (new_lines) {
|
||||||
|
line_skip = new_lines - root->blank_lines;
|
||||||
|
}
|
||||||
|
|
||||||
size_t clear_lines = MPMIN(MPMAX(new_lines, old_lines), root->blank_lines);
|
if (line_skip < 0) {
|
||||||
|
// Reposition cursor to keep status line at the same line
|
||||||
// clear the status line itself
|
line_skip = MPMIN(root->blank_lines, -line_skip);
|
||||||
fprintf(f, "\r\033[K");
|
if (line_skip)
|
||||||
// and clear all previous old lines
|
bstr_xappend_asprintf(root, out, "\033[%dB", line_skip);
|
||||||
for (size_t n = 1; n < clear_lines; n++)
|
}
|
||||||
fprintf(f, "\033[A\r\033[K");
|
|
||||||
// skip "unused" blank lines, so that status is aligned to term bottom
|
|
||||||
for (size_t n = new_lines; n < clear_lines; n++)
|
|
||||||
fprintf(f, "\n");
|
|
||||||
|
|
||||||
|
root->blank_lines = MPMAX(0, root->blank_lines - term_lines);
|
||||||
root->status_lines = new_lines;
|
root->status_lines = new_lines;
|
||||||
root->blank_lines = MPMAX(root->blank_lines, new_lines);
|
root->blank_lines += root->status_lines;
|
||||||
}
|
|
||||||
|
|
||||||
static void flush_status_line(struct mp_log_root *root)
|
|
||||||
{
|
|
||||||
// If there was a status line, don't overwrite it, but skip it.
|
|
||||||
if (root->status_lines)
|
|
||||||
fprintf(stderr, "\n");
|
|
||||||
root->status_lines = 0;
|
|
||||||
root->blank_lines = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void mp_msg_flush_status_line(struct mp_log *log)
|
void mp_msg_flush_status_line(struct mp_log *log)
|
||||||
{
|
{
|
||||||
if (log->root) {
|
if (log->root) {
|
||||||
mp_mutex_lock(&log->root->lock);
|
mp_mutex_lock(&log->root->lock);
|
||||||
flush_status_line(log->root);
|
if (log->root->status_lines) {
|
||||||
|
bstr term_msg = (bstr){0};
|
||||||
|
prepare_prefix(log->root, &term_msg, MSGL_STATUS, 0);
|
||||||
|
if (term_msg.len) {
|
||||||
|
fprintf(stderr, "%.*s", BSTR_P(term_msg));
|
||||||
|
talloc_free(term_msg.start);
|
||||||
|
}
|
||||||
|
}
|
||||||
mp_mutex_unlock(&log->root->lock);
|
mp_mutex_unlock(&log->root->lock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -257,39 +265,39 @@ bool mp_msg_has_status_line(struct mpv_global *global)
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void set_term_color(FILE *stream, int c)
|
static void set_term_color(void *talloc_ctx, bstr *text, int c)
|
||||||
{
|
{
|
||||||
if (c == -1) {
|
return c == -1 ? bstr_xappend(talloc_ctx, text, bstr0("\033[0m"))
|
||||||
fprintf(stream, "\033[0m");
|
: bstr_xappend_asprintf(talloc_ctx, text,
|
||||||
} else {
|
"\033[%d;3%dm", c >> 3, c & 7);
|
||||||
fprintf(stream, "\033[%d;3%dm", c >> 3, c & 7);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void set_msg_color(void *talloc_ctx, bstr *text, int lev)
|
||||||
static void set_msg_color(FILE* stream, int lev)
|
|
||||||
{
|
{
|
||||||
static const int v_colors[] = {9, 1, 3, -1, -1, 2, 8, 8, 8, -1};
|
static const int v_colors[] = {9, 1, 3, -1, -1, 2, 8, 8, 8, -1};
|
||||||
set_term_color(stream, v_colors[lev]);
|
return set_term_color(talloc_ctx, text, v_colors[lev]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void pretty_print_module(FILE* stream, const char *prefix, bool use_color, int lev)
|
static void pretty_print_module(struct mp_log_root *root, bstr *text,
|
||||||
|
const char *prefix, int lev)
|
||||||
{
|
{
|
||||||
|
size_t prefix_len = strlen(prefix);
|
||||||
|
root->module_indent = MPMAX(10, MPMAX(root->module_indent, prefix_len));
|
||||||
|
|
||||||
// Use random color based on the name of the module
|
// Use random color based on the name of the module
|
||||||
if (use_color) {
|
if (root->color) {
|
||||||
size_t prefix_len = strlen(prefix);
|
|
||||||
unsigned int mod = 0;
|
unsigned int mod = 0;
|
||||||
for (int i = 0; i < prefix_len; ++i)
|
for (int i = 0; i < prefix_len; ++i)
|
||||||
mod = mod * 33 + prefix[i];
|
mod = mod * 33 + prefix[i];
|
||||||
set_term_color(stream, (mod + 1) % 15 + 1);
|
set_term_color(root, text, (mod + 1) % 15 + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
fprintf(stream, "%10s", prefix);
|
bstr_xappend_asprintf(root, text, "%*s", root->module_indent, prefix);
|
||||||
if (use_color)
|
if (root->color)
|
||||||
set_term_color(stream, -1);
|
set_term_color(root, text, -1);
|
||||||
fprintf(stream, ": ");
|
bstr_xappend(root, text, bstr0(": "));
|
||||||
if (use_color)
|
if (root->color)
|
||||||
set_msg_color(stream, lev);
|
set_msg_color(root, text, lev);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool test_terminal_level(struct mp_log *log, int lev)
|
static bool test_terminal_level(struct mp_log *log, int lev)
|
||||||
@ -298,42 +306,60 @@ static bool test_terminal_level(struct mp_log *log, int lev)
|
|||||||
!(lev == MSGL_STATUS && terminal_in_background());
|
!(lev == MSGL_STATUS && terminal_in_background());
|
||||||
}
|
}
|
||||||
|
|
||||||
static void print_terminal_line(struct mp_log *log, int lev,
|
// This is very basic way to infer needed width for a string.
|
||||||
char *text, char *trail)
|
static int term_disp_width(bstr str, size_t start, size_t end)
|
||||||
{
|
{
|
||||||
if (!test_terminal_level(log, lev))
|
int width = 0;
|
||||||
return;
|
bool escape = false;
|
||||||
|
|
||||||
|
const char *line = str.start;
|
||||||
|
for (size_t i = start; i < end && i < str.len; ++i) {
|
||||||
|
if (escape) {
|
||||||
|
escape = !(line[i] >= '@' && line[i] <= '~');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line[i] == '\033' && line[i + 1] == '[') {
|
||||||
|
escape = true;
|
||||||
|
++i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line[i] == '\n')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
width++;
|
||||||
|
|
||||||
|
// Assume that everything before \r should be discarded for simplicity
|
||||||
|
if (line[i] == '\r')
|
||||||
|
width = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void append_terminal_line(struct mp_log *log, int lev,
|
||||||
|
bstr text, bstr *term_msg, int *line_w)
|
||||||
|
{
|
||||||
struct mp_log_root *root = log->root;
|
struct mp_log_root *root = log->root;
|
||||||
FILE *stream = (root->force_stderr || lev == MSGL_STATUS || lev == MSGL_FATAL ||
|
|
||||||
lev == MSGL_ERR || lev == MSGL_WARN) ? stderr : stdout;
|
|
||||||
|
|
||||||
if (lev != MSGL_STATUS)
|
size_t start = term_msg->len;
|
||||||
flush_status_line(root);
|
|
||||||
|
|
||||||
if (root->color)
|
|
||||||
set_msg_color(stream, lev);
|
|
||||||
|
|
||||||
if (root->show_time)
|
if (root->show_time)
|
||||||
fprintf(stream, "[%10.6f] ", mp_time_sec());
|
bstr_xappend_asprintf(root, term_msg, "[%10.6f] ", mp_time_sec());
|
||||||
|
|
||||||
const char *prefix = log->prefix;
|
const char *log_prefix = (lev >= MSGL_V) || root->verbose || root->module
|
||||||
if ((lev >= MSGL_V) || root->verbose || root->module)
|
? log->verbose_prefix : log->prefix;
|
||||||
prefix = log->verbose_prefix;
|
if (log_prefix) {
|
||||||
|
|
||||||
if (prefix) {
|
|
||||||
if (root->module) {
|
if (root->module) {
|
||||||
pretty_print_module(stream, prefix, root->color, lev);
|
pretty_print_module(root, term_msg, log_prefix, lev);
|
||||||
} else {
|
} else {
|
||||||
fprintf(stream, "[%s] ", prefix);
|
bstr_xappend_asprintf(root, term_msg, "[%s] ", log_prefix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fprintf(stream, "%s%s", text, trail);
|
bstr_xappend(root, term_msg, text);
|
||||||
|
*line_w = term_disp_width(*term_msg, start, term_msg->len);
|
||||||
if (root->color)
|
|
||||||
set_term_color(stream, -1);
|
|
||||||
fflush(stream);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct mp_log_buffer_entry *log_buffer_read(struct mp_log_buffer *buffer)
|
static struct mp_log_buffer_entry *log_buffer_read(struct mp_log_buffer *buffer)
|
||||||
@ -345,7 +371,7 @@ static struct mp_log_buffer_entry *log_buffer_read(struct mp_log_buffer *buffer)
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void write_msg_to_buffers(struct mp_log *log, int lev, char *text)
|
static void write_msg_to_buffers(struct mp_log *log, int lev, bstr text)
|
||||||
{
|
{
|
||||||
struct mp_log_root *root = log->root;
|
struct mp_log_root *root = log->root;
|
||||||
for (int n = 0; n < root->num_buffers; n++) {
|
for (int n = 0; n < root->num_buffers; n++) {
|
||||||
@ -386,7 +412,7 @@ static void write_msg_to_buffers(struct mp_log *log, int lev, char *text)
|
|||||||
*entry = (struct mp_log_buffer_entry) {
|
*entry = (struct mp_log_buffer_entry) {
|
||||||
.prefix = talloc_strdup(entry, log->verbose_prefix),
|
.prefix = talloc_strdup(entry, log->verbose_prefix),
|
||||||
.level = lev,
|
.level = lev,
|
||||||
.text = talloc_strdup(entry, text),
|
.text = bstrdup0(entry, text),
|
||||||
};
|
};
|
||||||
int pos = (buffer->entry0 + buffer->num_entries) % buffer->capacity;
|
int pos = (buffer->entry0 + buffer->num_entries) % buffer->capacity;
|
||||||
buffer->entries[pos] = entry;
|
buffer->entries[pos] = entry;
|
||||||
@ -431,32 +457,62 @@ void mp_msg_va(struct mp_log *log, int lev, const char *format, va_list va)
|
|||||||
} else if (lev == MSGL_STATUS && !test_terminal_level(log, lev)) {
|
} else if (lev == MSGL_STATUS && !test_terminal_level(log, lev)) {
|
||||||
/* discard */
|
/* discard */
|
||||||
} else {
|
} else {
|
||||||
if (lev == MSGL_STATUS)
|
bool print_term = test_terminal_level(log, lev);
|
||||||
prepare_status_line(root, text);
|
int term_w = 0, term_h = 0;
|
||||||
|
if (print_term)
|
||||||
|
terminal_get_size(&term_w, &term_h);
|
||||||
|
|
||||||
// Split away each line. Normally we require full lines; buffer partial
|
// Split away each line. Normally we require full lines; buffer partial
|
||||||
// lines if they happen.
|
// lines if they happen.
|
||||||
while (1) {
|
root->term_msg.len = 0;
|
||||||
char *end = strchr(text, '\n');
|
int term_msg_lines = 0;
|
||||||
if (!end)
|
|
||||||
|
bstr str = root->buffer;
|
||||||
|
while (str.len) {
|
||||||
|
bstr line = bstr_getline(str, &str);
|
||||||
|
if (line.start[line.len - 1] != '\n') {
|
||||||
|
assert(str.len == 0);
|
||||||
|
str = line;
|
||||||
break;
|
break;
|
||||||
char *next = &end[1];
|
}
|
||||||
char saved = next[0];
|
|
||||||
next[0] = '\0';
|
if (print_term) {
|
||||||
print_terminal_line(log, lev, text, "");
|
int line_w;
|
||||||
write_msg_to_buffers(log, lev, text);
|
append_terminal_line(log, lev, line, &root->term_msg, &line_w);
|
||||||
next[0] = saved;
|
term_msg_lines += (!line_w || !term_w)
|
||||||
text = next;
|
? 1 : (line_w + term_w - 1) / term_w;
|
||||||
|
}
|
||||||
|
write_msg_to_buffers(log, lev, line);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lev == MSGL_STATUS) {
|
if (lev == MSGL_STATUS && print_term) {
|
||||||
if (text[0])
|
int line_w = 0;
|
||||||
print_terminal_line(log, lev, text, "\r");
|
if (str.len)
|
||||||
} else if (text[0]) {
|
append_terminal_line(log, lev, str, &root->term_msg, &line_w);
|
||||||
int size = strlen(text) + 1;
|
term_msg_lines += !term_w ? (str.len ? 1 : 0)
|
||||||
|
: (line_w + term_w - 1) / term_w;
|
||||||
|
} else if (str.len) {
|
||||||
|
int size = str.len + 1;
|
||||||
if (talloc_get_size(log->partial[lev]) < size)
|
if (talloc_get_size(log->partial[lev]) < size)
|
||||||
log->partial[lev] = talloc_realloc(NULL, log->partial[lev], char, size);
|
log->partial[lev] = talloc_realloc(NULL, log->partial[lev], char, size);
|
||||||
memcpy(log->partial[lev], text, size);
|
memcpy(log->partial[lev], str.start, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (print_term && (root->term_msg.len || lev == MSGL_STATUS)) {
|
||||||
|
prepare_prefix(root, &root->term_msg_prefix, lev, term_msg_lines);
|
||||||
|
if (root->color && root->term_msg.len) {
|
||||||
|
set_msg_color(root, &root->term_msg_prefix, lev);
|
||||||
|
set_term_color(root, &root->term_msg, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE *stream = (root->force_stderr || lev == MSGL_STATUS ||
|
||||||
|
lev == MSGL_FATAL || lev == MSGL_ERR ||
|
||||||
|
lev == MSGL_WARN) ? stderr : stdout;
|
||||||
|
if (root->term_msg_prefix.len || root->term_msg.len) {
|
||||||
|
fprintf(stream, "%.*s%.*s", BSTR_P(root->term_msg_prefix),
|
||||||
|
BSTR_P(root->term_msg));
|
||||||
|
fflush(stream);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -735,6 +791,7 @@ bool mp_msg_has_log_file(struct mpv_global *global)
|
|||||||
void mp_msg_uninit(struct mpv_global *global)
|
void mp_msg_uninit(struct mpv_global *global)
|
||||||
{
|
{
|
||||||
struct mp_log_root *root = global->log->root;
|
struct mp_log_root *root = global->log->root;
|
||||||
|
mp_msg_flush_status_line(global->log);
|
||||||
terminate_log_file_thread(root);
|
terminate_log_file_thread(root);
|
||||||
mp_msg_log_buffer_destroy(root->early_buffer);
|
mp_msg_log_buffer_destroy(root->early_buffer);
|
||||||
mp_msg_log_buffer_destroy(root->early_filebuffer);
|
mp_msg_log_buffer_destroy(root->early_filebuffer);
|
||||||
|
Loading…
Reference in New Issue
Block a user