/* * This file is part of mpv. * * mpv is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * mpv 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with mpv. If not, see <http://www.gnu.org/licenses/>. */ #include <assert.h>#include <stdarg.h>#include <stdatomic.h>#include <stdint.h>#include <stdio.h>#include <stdlib.h>#include <string.h> #include "mpv_talloc.h" #include "common/common.h"#include "common/global.h"#include "misc/codepoint_width.h"#include "options/options.h"#include "options/path.h"#include "osdep/io.h"#include "osdep/terminal.h"#include "osdep/threads.h"#include "osdep/timer.h" #include "mpv/client.h" #include "msg.h"#include "msg_control.h" // log buffer size (lines) logfile level#define FILE_BUF 100 // lines to accumulate before any client requests the terminal loglevel#define EARLY_TERM_BUF 100 // logfile lines to accumulate during init before we know the log file name.// thousands of logfile lines during init can happen (especially with many// scripts, big config, etc), so we set 5000. If it cycles and messages are// overwritten, then the first (virtual) log line indicates how many were lost.#define EARLY_FILE_BUF 5000 struct mp_log_root { struct mpv_global *global; mp_mutex lock; mp_mutex log_file_lock; mp_cond log_file_wakeup; // --- protected by lock char **msg_levels; bool use_terminal; // make accesses to stderr/stdout bool module; bool show_time; int blank_lines; // number of lines usable by status int status_lines; // number of current status lines bool color[STDERR_FILENO + 1]; bool isatty[STDERR_FILENO + 1]; int verbose; bool really_quiet; bool force_stderr; struct mp_log_buffer **buffers; int num_buffers; struct mp_log_buffer *early_buffer; struct mp_log_buffer *early_filebuffer; FILE *stats_file; bstr buffer; bstr term_msg; bstr term_msg_tmp; bstr status_line; struct mp_log *status_log; bstr term_status_msg; // --- must be accessed atomically /* This is incremented every time the msglevels must be reloaded. * (This is perhaps better than maintaining a globally accessible and * synchronized mp_log tree.) */ atomic_ulong reload_counter; // --- owner thread only (caller of mp_msg_init() etc.) char *log_path; char *stats_path; mp_thread log_file_thread; // --- owner thread only, but frozen while log_file_thread is running FILE *log_file; struct mp_log_buffer *log_file_buffer; // --- protected by log_file_lock bool log_file_thread_active; // also termination signal for the thread int module_indent;}; struct mp_log { struct mp_log_root *root; const char *prefix; const char *verbose_prefix; int max_level; // minimum log level for this instance int level; // minimum log level for any outputs int terminal_level; // minimum log level for terminal output atomic_ulong reload_counter; bstr partial[MSGL_MAX + 1];}; struct mp_log_buffer { struct mp_log_root *root; mp_mutex lock; // --- protected by lock struct mp_log_buffer_entry **entries; // ringbuffer int capacity; // total space in entries[] int entry0; // first (oldest) entry index int num_entries; // number of valid entries after entry0 uint64_t dropped; // number of skipped entries bool silent; // --- immutable void (*wakeup_cb)(void *ctx); void *wakeup_cb_ctx; int level;}; static const struct mp_log null_log = {0};struct mp_log *const mp_null_log = (struct mp_log *)&null_log; static bool match_mod(const char *name, const char *mod){ if (!strcmp(mod, "all")) return true; // Path prefix matches bstr b = bstr0(name); return bstr_eatstart0(&b, mod) && (bstr_eatstart0(&b, "/") || !b.len);} static void update_loglevel(struct mp_log *log){ struct mp_log_root *root = log->root; mp_mutex_lock(&root->lock); log->level = MSGL_STATUS + root->verbose; // default log level if (root->really_quiet) log->level = -1; for (int n = 0; root->msg_levels && root->msg_levels[n * 2 + 0]; n++) { if (match_mod(log->verbose_prefix, root->msg_levels[n * 2 + 0])) log->level = mp_msg_find_level(root->msg_levels[n * 2 + 1]); } log->terminal_level = log->level; for (int n = 0; n < log->root->num_buffers; n++) { int buffer_level = log->root->buffers[n]->level; if (buffer_level == MP_LOG_BUFFER_MSGL_LOGFILE) buffer_level = MSGL_DEBUG; if (buffer_level != MP_LOG_BUFFER_MSGL_TERM) log->level = MPMAX(log->level, buffer_level); } if (log->root->log_file) log->level = MPMAX(log->level, MSGL_DEBUG); if (log->root->stats_file) log->level = MPMAX(log->level, MSGL_STATS); log->level = MPMIN(log->level, log->max_level); atomic_store(&log->reload_counter, atomic_load(&log->root->reload_counter)); mp_mutex_unlock(&root->lock);} // Set (numerically) the maximum level that should still be output for this log// instances. E.g. lev=MSGL_WARN => show only warnings and errors.void mp_msg_set_max_level(struct mp_log *log, int lev){ if (!log->root) return; mp_mutex_lock(&log->root->lock); log->max_level = MPCLAMP(lev, -1, MSGL_MAX); mp_mutex_unlock(&log->root->lock); update_loglevel(log);} // Get the current effective msg level.// Thread-safety: see mp_msg().int mp_msg_level(struct mp_log *log){ struct mp_log_root *root = log->root; if (!root) return -1; if (atomic_load_explicit(&log->reload_counter, memory_order_relaxed) != atomic_load_explicit(&root->reload_counter, memory_order_relaxed)) { update_loglevel(log); } return log->level;} static inline int term_msg_fileno(struct mp_log_root *root, int lev){ return root->force_stderr ? STDERR_FILENO : STDOUT_FILENO;} static inline FILE *term_msg_fp(struct mp_log_root *root, int lev){ return term_msg_fileno(root, lev) == STDERR_FILENO ? stderr : stdout;} static inline bool is_status_output(struct mp_log_root *root, int lev){ if (lev == MSGL_STATUS) return true; int msg_out = term_msg_fileno(root, lev); int status_out = term_msg_fileno(root, MSGL_STATUS); if (msg_out != status_out && root->isatty[msg_out] != root->isatty[status_out]) return false; return true;} // Reposition cursor and clear lines for outputting the status line. In certain// cases, like term OSD and subtitle display, the status can consist of// multiple lines.static void prepare_prefix(struct mp_log_root *root, bstr *out, int lev, int term_lines){ int new_lines = lev == MSGL_STATUS ? term_lines : 0; out->len = 0; if (!is_status_output(root, lev)) return; if (!root->isatty[term_msg_fileno(root, lev)]) { if (root->status_lines) bstr_xappend(root, out, bstr0("\n")); root->status_lines = new_lines; return; } // Set cursor state if (new_lines && !root->status_lines) { bstr_xappend(root, out, bstr0(TERM_ESC_HIDE_CURSOR)); } else if (!new_lines && root->status_lines) { bstr_xappend(root, out, bstr0(TERM_ESC_RESTORE_CURSOR)); } int line_skip = 0; if (root->status_lines) { // 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); mp_assert(root->status_lines > 0 && root->blank_lines >= root->status_lines); line_skip = root->blank_lines - root->status_lines; } if (new_lines) line_skip -= MPMAX(0, root->blank_lines - new_lines); if (line_skip) bstr_xappend_asprintf(root, out, line_skip > 0 ? "\033[%dA" : "\033[%dB", abs(line_skip)); root->blank_lines = MPMAX(0, root->blank_lines - term_lines); root->status_lines = new_lines; root->blank_lines += root->status_lines;} static void msg_flush_status_line(struct mp_log_root *root, bool clear){ if (!root->status_lines) goto done; FILE *fp = term_msg_fp(root, MSGL_STATUS); if (!clear) { if (root->isatty[term_msg_fileno(root, MSGL_STATUS)]) fprintf(fp, TERM_ESC_RESTORE_CURSOR); fprintf(fp, "\n"); root->blank_lines = 0; root->status_lines = 0; goto done; } bstr term_msg = {0}; prepare_prefix(root, &term_msg, MSGL_STATUS, 0); if (term_msg.len) { fprintf(fp, "%.*s", BSTR_P(term_msg)); talloc_free(term_msg.start); } done: root->status_line.len = 0;} void mp_msg_flush_status_line(struct mp_log *log, bool clear){ if (!log->root) return; mp_mutex_lock(&log->root->lock); msg_flush_status_line(log->root, clear); mp_mutex_unlock(&log->root->lock);} void mp_msg_set_term_title(struct mp_log *log, const char *title){ if (log->root && title) { // Lock because printf to terminal is not necessarily atomic. mp_mutex_lock(&log->root->lock); fprintf(term_msg_fp(log->root, MSGL_STATUS), "\033]0;%s\007", title); mp_mutex_unlock(&log->root->lock); }} bool mp_msg_has_status_line(struct mpv_global *global){ struct mp_log_root *root = global->log->root; mp_mutex_lock(&root->lock); bool r = root->status_lines > 0; mp_mutex_unlock(&root->lock); return r;} static void set_term_color(void *talloc_ctx, bstr *text, int c){ if (c == -1) { bstr_xappend(talloc_ctx, text, bstr0("\033[0m")); return; } // Pure black to gray if (c == 0) c += 8; // Pure white to light one if (c == 15) c -= 8; bstr_xappend_asprintf(talloc_ctx, text, "\033[%d%dm", c >= 8 ? 9 : 3, c & 7);} static void set_msg_color(void *talloc_ctx, bstr *text, int lev){ static const int v_colors[] = { [MSGL_FATAL] = 9, // bright red [MSGL_ERR] = 1, // red [MSGL_WARN] = 3, // yellow [MSGL_INFO] = -1, // default [MSGL_STATUS] = -1, // default [MSGL_V] = 2, // green [MSGL_DEBUG] = 4, // blue [MSGL_TRACE] = 8, // bright black aka. gray }; set_term_color(talloc_ctx, text, v_colors[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)); bool color = root->color[term_msg_fileno(root, lev)]; // Use random color based on the name of the module if (color) { unsigned int mod = 0; for (int i = 0; i < prefix_len; ++i) mod = mod * 33 + prefix[i]; set_term_color(root, text, (mod + 1) % 15 + 1); } bstr_xappend_asprintf(root, text, "%*s", root->module_indent, prefix); if (color) set_term_color(root, text, -1); bstr_xappend(root, text, bstr0(": ")); if (color) set_msg_color(root, text, lev);} static bool test_terminal_level(struct mp_log *log, int lev){ return lev <= log->terminal_level && log->root->use_terminal && !(lev == MSGL_STATUS && terminal_in_background());} static void append_terminal_line(struct mp_log *log, int lev, bstr text, bstr *term_msg, int *line_w, int term_w){ struct mp_log_root *root = log->root; size_t start = term_msg->len; if (root->show_time) bstr_xappend_asprintf(root, term_msg, "[%10.6f] ", mp_time_sec()); const char *log_prefix = (lev >= MSGL_V) || root->verbose || root->module ? log->verbose_prefix : log->prefix; if (log_prefix) { if (root->module) { pretty_print_module(root, term_msg, log_prefix, lev); } else { bstr_xappend_asprintf(root, term_msg, "[%s] ", log_prefix); } } bstr_xappend(root, term_msg, text); const unsigned char *cut_pos = NULL; int ellipsis_width = 2; int width = term_disp_width(bstr_splice(*term_msg, start, term_msg->len), term_w - ellipsis_width, &cut_pos); if (cut_pos) { int new_len = cut_pos - term_msg->start; bstr rem = bstrdup(NULL, (bstr){(unsigned char *)cut_pos, term_msg->len - new_len}); void *ptr = rem.start; term_msg->len = new_len; bstr_xappend(root, term_msg, bstr0("..")); while (rem.len) { if (bstr_eatstart0(&rem, "\n")) { bstr_xappend(root, term_msg, bstr0("\n")); continue; } if (bstr_eatstart0(&rem, "\033[")) { bstr_xappend(root, term_msg, bstr0("\033[")); while (rem.len && !((*rem.start >= '@' && *rem.start <= '~') || *rem.start == 'm')) { bstr_xappend(root, term_msg, bstr_splice(rem, 0, 1)); rem = bstr_cut(rem, 1); } bstr_xappend(root, term_msg, bstr_splice(rem, 0, 1)); } rem = bstr_cut(rem, 1); } talloc_free(ptr); width += ellipsis_width; } *line_w = root->isatty[term_msg_fileno(root, lev)] ? width : 0;} static struct mp_log_buffer_entry *log_buffer_read(struct mp_log_buffer *buffer){ mp_assert(buffer->num_entries); struct mp_log_buffer_entry *res = buffer->entries[buffer->entry0]; buffer->entry0 = (buffer->entry0 + 1) % buffer->capacity; buffer->num_entries -= 1; return res;} static void write_msg_to_buffers(struct mp_log *log, int lev, bstr text){ struct mp_log_root *root = log->root; for (int n = 0; n < root->num_buffers; n++) { struct mp_log_buffer *buffer = root->buffers[n]; bool wakeup = false; mp_mutex_lock(&buffer->lock); int buffer_level = buffer->level; if (buffer_level == MP_LOG_BUFFER_MSGL_TERM) buffer_level = log->terminal_level; if (buffer_level == MP_LOG_BUFFER_MSGL_LOGFILE) buffer_level = MPMAX(log->terminal_level, MSGL_DEBUG); if (lev <= buffer_level && lev != MSGL_STATUS) { if (buffer->level == MP_LOG_BUFFER_MSGL_LOGFILE) { // If the buffer is full, block until we can write again, // unless there's no write thread (died, or early filebuffer) bool dead = false; while (buffer->num_entries == buffer->capacity && !dead) { // Temporary unlock is OK; buffer->level is immutable, and // buffer can't go away because the global log lock is held. mp_mutex_unlock(&buffer->lock); mp_mutex_lock(&root->log_file_lock); if (root->log_file_thread_active) { mp_cond_wait(&root->log_file_wakeup, &root->log_file_lock); } else { dead = true; } mp_mutex_unlock(&root->log_file_lock); mp_mutex_lock(&buffer->lock); } } if (buffer->num_entries == buffer->capacity) { struct mp_log_buffer_entry *skip = log_buffer_read(buffer); talloc_free(skip); buffer->dropped += 1; } struct mp_log_buffer_entry *entry = talloc_ptrtype(NULL, entry); *entry = (struct mp_log_buffer_entry) { .prefix = talloc_strdup(entry, log->verbose_prefix), .level = lev, .text = bstrdup0(entry, text), }; int pos = (buffer->entry0 + buffer->num_entries) % buffer->capacity; buffer->entries[pos] = entry; buffer->num_entries += 1; if (buffer->wakeup_cb && !buffer->silent) wakeup = true; } mp_mutex_unlock(&buffer->lock); if (wakeup) buffer->wakeup_cb(buffer->wakeup_cb_ctx); }} static void dump_stats(struct mp_log *log, int lev, bstr text){ struct mp_log_root *root = log->root; if (lev == MSGL_STATS && root->stats_file)Lines 1–500 of 1159. Load more
/* * This file is part of mpv. * * mpv is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * mpv 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with mpv. If not, see <http://www.gnu.org/licenses/>. */ #include <assert.h> #include <stdarg.h> #include <stdatomic.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "mpv_talloc.h" #include "common/common.h" #include "common/global.h" #include "misc/codepoint_width.h" #include "options/options.h" #include "options/path.h" #include "osdep/io.h" #include "osdep/terminal.h" #include "osdep/threads.h" #include "osdep/timer.h" #include "mpv/client.h" #include "msg.h" #include "msg_control.h" // log buffer size (lines) logfile level #define FILE_BUF 100 // lines to accumulate before any client requests the terminal loglevel #define EARLY_TERM_BUF 100 // logfile lines to accumulate during init before we know the log file name. // thousands of logfile lines during init can happen (especially with many // scripts, big config, etc), so we set 5000. If it cycles and messages are // overwritten, then the first (virtual) log line indicates how many were lost. #define EARLY_FILE_BUF 5000 struct mp_log_root { struct mpv_global *global; mp_mutex lock; mp_mutex log_file_lock; mp_cond log_file_wakeup; // --- protected by lock char **msg_levels; bool use_terminal; // make accesses to stderr/stdout bool module; bool show_time; int blank_lines; // number of lines usable by status int status_lines; // number of current status lines bool color[STDERR_FILENO + 1]; bool isatty[STDERR_FILENO + 1]; int verbose; bool really_quiet; bool force_stderr; struct mp_log_buffer **buffers; int num_buffers; struct mp_log_buffer *early_buffer; struct mp_log_buffer *early_filebuffer; FILE *stats_file; bstr buffer; bstr term_msg; bstr term_msg_tmp; bstr status_line; struct mp_log *status_log; bstr term_status_msg; // --- must be accessed atomically /* This is incremented every time the msglevels must be reloaded. * (This is perhaps better than maintaining a globally accessible and * synchronized mp_log tree.) */ atomic_ulong reload_counter; // --- owner thread only (caller of mp_msg_init() etc.) char *log_path; char *stats_path; mp_thread log_file_thread; // --- owner thread only, but frozen while log_file_thread is running FILE *log_file; struct mp_log_buffer *log_file_buffer; // --- protected by log_file_lock bool log_file_thread_active; // also termination signal for the thread int module_indent; }; struct mp_log { struct mp_log_root *root; const char *prefix; const char *verbose_prefix; int max_level; // minimum log level for this instance int level; // minimum log level for any outputs int terminal_level; // minimum log level for terminal output atomic_ulong reload_counter; bstr partial[MSGL_MAX + 1]; }; struct mp_log_buffer { struct mp_log_root *root; mp_mutex lock; // --- protected by lock struct mp_log_buffer_entry **entries; // ringbuffer int capacity; // total space in entries[] int entry0; // first (oldest) entry index int num_entries; // number of valid entries after entry0 uint64_t dropped; // number of skipped entries bool silent; // --- immutable void (*wakeup_cb)(void *ctx); void *wakeup_cb_ctx; int level; }; static const struct mp_log null_log = {0}; struct mp_log *const mp_null_log = (struct mp_log *)&null_log; static bool match_mod(const char *name, const char *mod) { if (!strcmp(mod, "all")) return true; // Path prefix matches bstr b = bstr0(name); return bstr_eatstart0(&b, mod) && (bstr_eatstart0(&b, "/") || !b.len); } static void update_loglevel(struct mp_log *log) { struct mp_log_root *root = log->root; mp_mutex_lock(&root->lock); log->level = MSGL_STATUS + root->verbose; // default log level if (root->really_quiet) log->level = -1; for (int n = 0; root->msg_levels && root->msg_levels[n * 2 + 0]; n++) { if (match_mod(log->verbose_prefix, root->msg_levels[n * 2 + 0])) log->level = mp_msg_find_level(root->msg_levels[n * 2 + 1]); } log->terminal_level = log->level; for (int n = 0; n < log->root->num_buffers; n++) { int buffer_level = log->root->buffers[n]->level; if (buffer_level == MP_LOG_BUFFER_MSGL_LOGFILE) buffer_level = MSGL_DEBUG; if (buffer_level != MP_LOG_BUFFER_MSGL_TERM) log->level = MPMAX(log->level, buffer_level); } if (log->root->log_file) log->level = MPMAX(log->level, MSGL_DEBUG); if (log->root->stats_file) log->level = MPMAX(log->level, MSGL_STATS); log->level = MPMIN(log->level, log->max_level); atomic_store(&log->reload_counter, atomic_load(&log->root->reload_counter)); mp_mutex_unlock(&root->lock); } // Set (numerically) the maximum level that should still be output for this log // instances. E.g. lev=MSGL_WARN => show only warnings and errors. void mp_msg_set_max_level(struct mp_log *log, int lev) { if (!log->root) return; mp_mutex_lock(&log->root->lock); log->max_level = MPCLAMP(lev, -1, MSGL_MAX); mp_mutex_unlock(&log->root->lock); update_loglevel(log); } // Get the current effective msg level. // Thread-safety: see mp_msg(). int mp_msg_level(struct mp_log *log) { struct mp_log_root *root = log->root; if (!root) return -1; if (atomic_load_explicit(&log->reload_counter, memory_order_relaxed) != atomic_load_explicit(&root->reload_counter, memory_order_relaxed)) { update_loglevel(log); } return log->level; } static inline int term_msg_fileno(struct mp_log_root *root, int lev) { return root->force_stderr ? STDERR_FILENO : STDOUT_FILENO; } static inline FILE *term_msg_fp(struct mp_log_root *root, int lev) { return term_msg_fileno(root, lev) == STDERR_FILENO ? stderr : stdout; } static inline bool is_status_output(struct mp_log_root *root, int lev) { if (lev == MSGL_STATUS) return true; int msg_out = term_msg_fileno(root, lev); int status_out = term_msg_fileno(root, MSGL_STATUS); if (msg_out != status_out && root->isatty[msg_out] != root->isatty[status_out]) return false; return true; } // Reposition cursor and clear lines for outputting the status line. In certain // cases, like term OSD and subtitle display, the status can consist of // multiple lines. static void prepare_prefix(struct mp_log_root *root, bstr *out, int lev, int term_lines) { int new_lines = lev == MSGL_STATUS ? term_lines : 0; out->len = 0; if (!is_status_output(root, lev)) return; if (!root->isatty[term_msg_fileno(root, lev)]) { if (root->status_lines) bstr_xappend(root, out, bstr0("\n")); root->status_lines = new_lines; return; } // Set cursor state if (new_lines && !root->status_lines) { bstr_xappend(root, out, bstr0(TERM_ESC_HIDE_CURSOR)); } else if (!new_lines && root->status_lines) { bstr_xappend(root, out, bstr0(TERM_ESC_RESTORE_CURSOR)); } int line_skip = 0; if (root->status_lines) { // 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); mp_assert(root->status_lines > 0 && root->blank_lines >= root->status_lines); line_skip = root->blank_lines - root->status_lines; } if (new_lines) line_skip -= MPMAX(0, root->blank_lines - new_lines); if (line_skip) bstr_xappend_asprintf(root, out, line_skip > 0 ? "\033[%dA" : "\033[%dB", abs(line_skip)); root->blank_lines = MPMAX(0, root->blank_lines - term_lines); root->status_lines = new_lines; root->blank_lines += root->status_lines; } static void msg_flush_status_line(struct mp_log_root *root, bool clear) { if (!root->status_lines) goto done; FILE *fp = term_msg_fp(root, MSGL_STATUS); if (!clear) { if (root->isatty[term_msg_fileno(root, MSGL_STATUS)]) fprintf(fp, TERM_ESC_RESTORE_CURSOR); fprintf(fp, "\n"); root->blank_lines = 0; root->status_lines = 0; goto done; } bstr term_msg = {0}; prepare_prefix(root, &term_msg, MSGL_STATUS, 0); if (term_msg.len) { fprintf(fp, "%.*s", BSTR_P(term_msg)); talloc_free(term_msg.start); } done: root->status_line.len = 0; } void mp_msg_flush_status_line(struct mp_log *log, bool clear) { if (!log->root) return; mp_mutex_lock(&log->root->lock); msg_flush_status_line(log->root, clear); mp_mutex_unlock(&log->root->lock); } void mp_msg_set_term_title(struct mp_log *log, const char *title) { if (log->root && title) { // Lock because printf to terminal is not necessarily atomic. mp_mutex_lock(&log->root->lock); fprintf(term_msg_fp(log->root, MSGL_STATUS), "\033]0;%s\007", title); mp_mutex_unlock(&log->root->lock); } } bool mp_msg_has_status_line(struct mpv_global *global) { struct mp_log_root *root = global->log->root; mp_mutex_lock(&root->lock); bool r = root->status_lines > 0; mp_mutex_unlock(&root->lock); return r; } static void set_term_color(void *talloc_ctx, bstr *text, int c) { if (c == -1) { bstr_xappend(talloc_ctx, text, bstr0("\033[0m")); return; } // Pure black to gray if (c == 0) c += 8; // Pure white to light one if (c == 15) c -= 8; bstr_xappend_asprintf(talloc_ctx, text, "\033[%d%dm", c >= 8 ? 9 : 3, c & 7); } static void set_msg_color(void *talloc_ctx, bstr *text, int lev) { static const int v_colors[] = { [MSGL_FATAL] = 9, // bright red [MSGL_ERR] = 1, // red [MSGL_WARN] = 3, // yellow [MSGL_INFO] = -1, // default [MSGL_STATUS] = -1, // default [MSGL_V] = 2, // green [MSGL_DEBUG] = 4, // blue [MSGL_TRACE] = 8, // bright black aka. gray }; set_term_color(talloc_ctx, text, v_colors[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)); bool color = root->color[term_msg_fileno(root, lev)]; // Use random color based on the name of the module if (color) { unsigned int mod = 0; for (int i = 0; i < prefix_len; ++i) mod = mod * 33 + prefix[i]; set_term_color(root, text, (mod + 1) % 15 + 1); } bstr_xappend_asprintf(root, text, "%*s", root->module_indent, prefix); if (color) set_term_color(root, text, -1); bstr_xappend(root, text, bstr0(": ")); if (color) set_msg_color(root, text, lev); } static bool test_terminal_level(struct mp_log *log, int lev) { return lev <= log->terminal_level && log->root->use_terminal && !(lev == MSGL_STATUS && terminal_in_background()); } static void append_terminal_line(struct mp_log *log, int lev, bstr text, bstr *term_msg, int *line_w, int term_w) { struct mp_log_root *root = log->root; size_t start = term_msg->len; if (root->show_time) bstr_xappend_asprintf(root, term_msg, "[%10.6f] ", mp_time_sec()); const char *log_prefix = (lev >= MSGL_V) || root->verbose || root->module ? log->verbose_prefix : log->prefix; if (log_prefix) { if (root->module) { pretty_print_module(root, term_msg, log_prefix, lev); } else { bstr_xappend_asprintf(root, term_msg, "[%s] ", log_prefix); } } bstr_xappend(root, term_msg, text); const unsigned char *cut_pos = NULL; int ellipsis_width = 2; int width = term_disp_width(bstr_splice(*term_msg, start, term_msg->len), term_w - ellipsis_width, &cut_pos); if (cut_pos) { int new_len = cut_pos - term_msg->start; bstr rem = bstrdup(NULL, (bstr){(unsigned char *)cut_pos, term_msg->len - new_len}); void *ptr = rem.start; term_msg->len = new_len; bstr_xappend(root, term_msg, bstr0("..")); while (rem.len) { if (bstr_eatstart0(&rem, "\n")) { bstr_xappend(root, term_msg, bstr0("\n")); continue; } if (bstr_eatstart0(&rem, "\033[")) { bstr_xappend(root, term_msg, bstr0("\033[")); while (rem.len && !((*rem.start >= '@' && *rem.start <= '~') || *rem.start == 'm')) { bstr_xappend(root, term_msg, bstr_splice(rem, 0, 1)); rem = bstr_cut(rem, 1); } bstr_xappend(root, term_msg, bstr_splice(rem, 0, 1)); } rem = bstr_cut(rem, 1); } talloc_free(ptr); width += ellipsis_width; } *line_w = root->isatty[term_msg_fileno(root, lev)] ? width : 0; } static struct mp_log_buffer_entry *log_buffer_read(struct mp_log_buffer *buffer) { mp_assert(buffer->num_entries); struct mp_log_buffer_entry *res = buffer->entries[buffer->entry0]; buffer->entry0 = (buffer->entry0 + 1) % buffer->capacity; buffer->num_entries -= 1; return res; } static void write_msg_to_buffers(struct mp_log *log, int lev, bstr text) { struct mp_log_root *root = log->root; for (int n = 0; n < root->num_buffers; n++) { struct mp_log_buffer *buffer = root->buffers[n]; bool wakeup = false; mp_mutex_lock(&buffer->lock); int buffer_level = buffer->level; if (buffer_level == MP_LOG_BUFFER_MSGL_TERM) buffer_level = log->terminal_level; if (buffer_level == MP_LOG_BUFFER_MSGL_LOGFILE) buffer_level = MPMAX(log->terminal_level, MSGL_DEBUG); if (lev <= buffer_level && lev != MSGL_STATUS) { if (buffer->level == MP_LOG_BUFFER_MSGL_LOGFILE) { // If the buffer is full, block until we can write again, // unless there's no write thread (died, or early filebuffer) bool dead = false; while (buffer->num_entries == buffer->capacity && !dead) { // Temporary unlock is OK; buffer->level is immutable, and // buffer can't go away because the global log lock is held. mp_mutex_unlock(&buffer->lock); mp_mutex_lock(&root->log_file_lock); if (root->log_file_thread_active) { mp_cond_wait(&root->log_file_wakeup, &root->log_file_lock); } else { dead = true; } mp_mutex_unlock(&root->log_file_lock); mp_mutex_lock(&buffer->lock); } } if (buffer->num_entries == buffer->capacity) { struct mp_log_buffer_entry *skip = log_buffer_read(buffer); talloc_free(skip); buffer->dropped += 1; } struct mp_log_buffer_entry *entry = talloc_ptrtype(NULL, entry); *entry = (struct mp_log_buffer_entry) { .prefix = talloc_strdup(entry, log->verbose_prefix), .level = lev, .text = bstrdup0(entry, text), }; int pos = (buffer->entry0 + buffer->num_entries) % buffer->capacity; buffer->entries[pos] = entry; buffer->num_entries += 1; if (buffer->wakeup_cb && !buffer->silent) wakeup = true; } mp_mutex_unlock(&buffer->lock); if (wakeup) buffer->wakeup_cb(buffer->wakeup_cb_ctx); } } static void dump_stats(struct mp_log *log, int lev, bstr text) { struct mp_log_root *root = log->root; if (lev == MSGL_STATS && root->stats_file)