summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2014-01-13 20:05:41 +0100
committerwm4 <wm4@nowhere>2014-01-13 20:08:13 +0100
commit6759941fca5820d62dbc30948b4863f49df21419 (patch)
tree19f76cb1284d37cb7f76514265981abf41252fb8
parent8721f9151fe1f6aaf37bef7749dc57d9417a6dd9 (diff)
downloadmpv-6759941fca5820d62dbc30948b4863f49df21419.tar.bz2
mpv-6759941fca5820d62dbc30948b4863f49df21419.tar.xz
player: redo terminal OSD and status line handling
The terminal OSD code includes the handling of the terminal status line, showing player OSD messages on the terminal, and showing subtitles on terminal (the latter two only if there is no video window, or if terminal OSD is forced). This didn't handle some corner cases correctly. For example, showing an OSD message on the terminal always cleared the previous line, even if the line was an important message (or even just the command prompt, if most other messages were silenced). Attempt to handle this correctly by keeping track of how many lines the terminal OSD currently consists of. Since there could be race conditions with other messages being printed, implement this in msg.c. Now msg.c expects that MSGL_STATUS messages rewrite the status line, so the caller is forced to use a single mp_msg() call to set the status line. Instead of littering print_status() all over the place, update the status only once per playloop iteration in update_osd_msg(). In audio- only mode, the status line might now be a little bit off, but it's perhaps ok. Print the status line only if it has changed, or if another message was printed. This might help with extremely slow terminals, although in audio+video mode, it'll still be updated very often (A-V sync display changes on every frame). Instead of hardcoding the terminal sequences, use terminfo/termcap to get the sequences. Remove the --term-osd-esc option, which allowed to override the hardcoded escapes - it's useless now. The fallback for terminals with no escape sequences for moving the cursor and clearing a line is removed. This somewhat breaks status line display on these terminals, including the MS Windows console: instead of querying the terminal size and clearing the line manually by padding the output with spaces, the line is simply not cleared. I don't expect this to be a problem on UNIX, and on MS Windows we could emulate escape sequences. Note that terminal OSD (other than the status line) was broken anyway on these terminals. In osd.c, the function get_term_width() is not used anymore, so remove it. To remind us that the MS Windows console apparently adds a line break when writint the last column, adjust screen_width in terminal- win.c accordingly.
-rw-r--r--DOCS/man/en/options.rst5
-rw-r--r--common/msg.c92
-rw-r--r--common/msg.h2
-rw-r--r--options/options.c2
-rw-r--r--options/options.h1
-rw-r--r--osdep/terminal-unix.c15
-rw-r--r--osdep/terminal-win.c5
-rw-r--r--osdep/terminal.h4
-rw-r--r--player/command.c2
-rw-r--r--player/core.h5
-rw-r--r--player/main.c2
-rw-r--r--player/misc.c6
-rw-r--r--player/osd.c104
-rw-r--r--player/playloop.c6
14 files changed, 159 insertions, 92 deletions
diff --git a/DOCS/man/en/options.rst b/DOCS/man/en/options.rst
index 27aa56a7f0..ad5eabd232 100644
--- a/DOCS/man/en/options.rst
+++ b/DOCS/man/en/options.rst
@@ -2378,11 +2378,6 @@ OPTIONS
Display OSD messages on the console when no video output is available.
Enabled by default.
-``--term-osd-esc=<string>``
- Specify the escape sequence to use before writing an OSD message on the
- console. The escape sequence should move the pointer to the beginning of
- the line used for the OSD and clear it (default: ``^[[A\r^[[K``).
-
``--title=<string>``
Set the window title. Properties are expanded on playback start.
(See `Property Expansion`_.)
diff --git a/common/msg.c b/common/msg.c
index 841fab30c7..2cd2e19b27 100644
--- a/common/msg.c
+++ b/common/msg.c
@@ -43,8 +43,12 @@ struct mp_log_root {
struct mpv_global *global;
// --- protected by mp_msg_lock
char *msglevels;
- bool smode; // slave mode compatibility glue
+ bool smode; // slave mode compatibility glue
bool module;
+ bool termosd; // use terminal control codes for status line
+ bool header; // indicate that message header should be printed
+ int blank_lines; // number of lines useable by status
+ int status_lines; // number of current status lines
// --- semi-atomic access
bool color;
int verbose;
@@ -55,8 +59,6 @@ struct mp_log_root {
* (This is perhaps better than maintaining a globally accessible and
* synchronized mp_log tree.) */
int64_t reload_counter;
- int header; // indicate if last line printed ended with \n or \r
- int statusline; // indicates if last line printed was a status line
};
struct mp_log {
@@ -117,6 +119,61 @@ bool mp_msg_test(struct mp_log *log, int lev)
return lev <= log->level || (log->root->smode && lev == MSGL_SMODE);
}
+// 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_status_line(struct mp_log_root *root, char *new_status)
+{
+ FILE *f = stderr;
+ size_t old_lines = root->blank_lines;
+
+ size_t new_lines = 1;
+ char *tmp = new_status;
+ while (1) {
+ tmp = strchr(tmp, '\n');
+ if (!tmp)
+ break;
+ new_lines++;
+ tmp++;
+ }
+
+ // clear the status line itself
+ fprintf(f, "\r%s", terminal_erase_to_end_of_line);
+ // and clear all previous old lines
+ for (size_t n = 1; n < old_lines; n++)
+ fprintf(f, "%s\r%s", terminal_cursor_up, terminal_erase_to_end_of_line);
+ // skip "unused" blank lines, so that status is aligned to term bottom
+ for (size_t n = new_lines; n < old_lines; n++)
+ fprintf(f, "\n");
+
+ root->status_lines = new_lines;
+ root->blank_lines = MPMAX(root->blank_lines, new_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 mpv_global *global)
+{
+ pthread_mutex_lock(&mp_msg_lock);
+ flush_status_line(global->log->root);
+ pthread_mutex_unlock(&mp_msg_lock);
+}
+
+bool mp_msg_has_status_line(struct mpv_global *global)
+{
+ pthread_mutex_lock(&mp_msg_lock);
+ bool r = global->log->root->status_lines > 0;
+ pthread_mutex_unlock(&mp_msg_lock);
+ return r;
+}
+
static void set_msg_color(FILE* stream, int lev)
{
static const int v_colors[] = {9, 1, 3, -1, -1, 2, 8, 8, -1};
@@ -139,12 +196,21 @@ void mp_msg_va(struct mp_log *log, int lev, const char *format, va_list va)
tmp[MSGSIZE_MAX - 2] = '\n';
tmp[MSGSIZE_MAX - 1] = 0;
- /* A status line is normally intended to be overwritten by the next
- * status line, and does not end with a '\n'. If we're printing a normal
- * line instead after the status one print '\n' to change line. */
- if (root->statusline && lev != MSGL_STATUS)
- fprintf(stderr, "\n");
- root->statusline = lev == MSGL_STATUS;
+ char *terminate = "";
+
+ if (lev == MSGL_STATUS) {
+ if (root->termosd) {
+ prepare_status_line(root, tmp);
+ terminate = "\r";
+ } else {
+ terminate = "\n";
+ }
+ root->header = true;
+ } else {
+ flush_status_line(root);
+ size_t len = strlen(tmp);
+ root->header = len && tmp[len - 1] == '\n';
+ }
if (root->color)
set_msg_color(stream, lev);
@@ -156,10 +222,7 @@ void mp_msg_va(struct mp_log *log, int lev, const char *format, va_list va)
}
}
- size_t len = strlen(tmp);
- root->header = len && (tmp[len - 1] == '\n' || tmp[len - 1] == '\r');
-
- fprintf(stream, "%s", tmp);
+ fprintf(stream, "%s%s", tmp, terminate);
if (root->color)
terminal_set_foreground_color(stream, -1);
@@ -211,7 +274,7 @@ void mp_msg_init(struct mpv_global *global)
struct mp_log_root *root = talloc_zero(NULL, struct mp_log_root);
root->global = global;
- root->header = 1;
+ root->header = true;
root->reload_counter = 1;
struct mp_log dummy = { .root = root };
@@ -236,6 +299,7 @@ void mp_msg_update_msglevels(struct mpv_global *global)
root->module = opts->msg_module;
root->smode = opts->msg_identify;
root->color = opts->msg_color && isatty(fileno(stdout));
+ root->termosd = !opts->slave_mode && isatty(fileno(stdout));
talloc_free(root->msglevels);
root->msglevels = talloc_strdup(root, global->opts->msglevels);
diff --git a/common/msg.h b/common/msg.h
index 561efe5e53..50420c9044 100644
--- a/common/msg.h
+++ b/common/msg.h
@@ -82,6 +82,8 @@ void mp_msg_uninit(struct mpv_global *global);
void mp_msg_update_msglevels(struct mpv_global *global);
void mp_msg_mute(struct mpv_global *global, bool mute);
void mp_msg_force_stderr(struct mpv_global *global, bool force_stderr);
+void mp_msg_flush_status_line(struct mpv_global *global);
+bool mp_msg_has_status_line(struct mpv_global *global);
struct bstr;
int mp_msg_split_msglevel(struct bstr *s, struct bstr *out_mod, int *out_level);
diff --git a/options/options.c b/options/options.c
index 1fa6a68cfe..dbc06bdf7f 100644
--- a/options/options.c
+++ b/options/options.c
@@ -597,8 +597,6 @@ const m_option_t mp_opts[] = {
{"auto", 2},
{"no", 0})),
- OPT_STRING("term-osd-esc", term_osd_esc, M_OPT_PARSE_ESCAPES,
- OPTDEF_STR("\x1b[A\r\x1b[K")),
OPT_STRING("playing-msg", playing_msg, M_OPT_PARSE_ESCAPES),
OPT_STRING("status-msg", status_msg, M_OPT_PARSE_ESCAPES),
OPT_STRING("osd-status-msg", osd_status_msg, M_OPT_PARSE_ESCAPES),
diff --git a/options/options.h b/options/options.h
index 6b5e1c7392..5608392a99 100644
--- a/options/options.h
+++ b/options/options.h
@@ -124,7 +124,6 @@ typedef struct MPOpts {
int softsleep;
int frame_dropping;
int term_osd;
- char *term_osd_esc;
char *playing_msg;
char *status_msg;
char *osd_status_msg;
diff --git a/osdep/terminal-unix.c b/osdep/terminal-unix.c
index 193cd8557f..de7087b9b8 100644
--- a/osdep/terminal-unix.c
+++ b/osdep/terminal-unix.c
@@ -56,7 +56,8 @@ static volatile int tio_orig_set;
int screen_width = 80;
int screen_height = 24;
-char * erase_to_end_of_line = NULL;
+char *terminal_erase_to_end_of_line = "\033[A";
+char *terminal_cursor_up = "\033[K";
typedef struct {
char *cap;
@@ -267,10 +268,16 @@ static int load_termcap(char *termtype){
#endif
ensure_cap(&termcap_buf, 2048);
- static char term_buf[64];
+ static char term_buf[128];
char *buf_ptr = &term_buf[0];
-
- erase_to_end_of_line = tgetstr("ce", &buf_ptr);
+ char *tmp;
+
+ tmp = tgetstr("ce", &buf_ptr);
+ if (tmp)
+ terminal_erase_to_end_of_line = tmp;
+ tmp = tgetstr("ku", &buf_ptr);
+ if (tmp)
+ terminal_cursor_up = tmp;
screen_width = tgetnum("co");
screen_height = tgetnum("li");
diff --git a/osdep/terminal-win.c b/osdep/terminal-win.c
index 5186c301f2..1ecb520f94 100644
--- a/osdep/terminal-win.c
+++ b/osdep/terminal-win.c
@@ -33,9 +33,10 @@
#include "input/input.h"
#include "terminal.h"
-int screen_width = 80;
+int screen_width = 79;
int screen_height = 24;
-char *erase_to_end_of_line = NULL;
+char *terminal_erase_to_end_of_line = "";
+char *terminal_cursor_up = "";
#define hSTDOUT GetStdHandle(STD_OUTPUT_HANDLE)
#define hSTDERR GetStdHandle(STD_ERROR_HANDLE)
diff --git a/osdep/terminal.h b/osdep/terminal.h
index c524adbc2b..651e31ed71 100644
--- a/osdep/terminal.h
+++ b/osdep/terminal.h
@@ -33,8 +33,8 @@ struct input_ctx;
extern int screen_width;
extern int screen_height;
-/* Termcap code to erase to end of line */
-extern char * erase_to_end_of_line;
+extern char *terminal_erase_to_end_of_line;
+extern char *terminal_cursor_up;
/* Global initialization for terminal output. */
int terminal_init(void);
diff --git a/player/command.c b/player/command.c
index f6f3e43a63..6ee50dea87 100644
--- a/player/command.c
+++ b/player/command.c
@@ -3058,7 +3058,7 @@ void run_command(MPContext *mpctx, mp_cmd_t *cmd)
case MP_CMD_RUN: {
#ifndef __MINGW32__
- mp_msg(mpctx->statusline, MSGL_STATUS, "\n");
+ mp_msg_flush_status_line(mpctx->global);
char *args[MP_CMD_MAX_ARGS + 1] = {0};
for (int n = 0; n < cmd->nargs; n++)
args[n] = cmd->args[n].v.s;
diff --git a/player/core.h b/player/core.h
index 3285e4848d..271f96595b 100644
--- a/player/core.h
+++ b/player/core.h
@@ -160,7 +160,9 @@ typedef struct MPContext {
struct input_ctx *input;
struct osd_state *osd;
struct mp_osd_msg *osd_msg_stack;
- char *terminal_osd_text;
+ char *term_osd_text;
+ char *term_osd_status;
+ char *term_osd_contents;
char *last_window_title;
int add_osd_seek_info; // bitfield of enum mp_osd_seek_info
@@ -408,7 +410,6 @@ void update_window_title(struct MPContext *mpctx, bool force);
void stream_dump(struct MPContext *mpctx);
// osd.c
-void write_status_line(struct MPContext *mpctx, const char *line);
void print_status(struct MPContext *mpctx);
void set_osd_bar(struct MPContext *mpctx, int type, const char* name,
double min, double max, double val);
diff --git a/player/main.c b/player/main.c
index afb3db46ea..ff9f7b732a 100644
--- a/player/main.c
+++ b/player/main.c
@@ -287,7 +287,7 @@ static int mpv_main(int argc, char *argv[])
struct MPContext *mpctx = talloc(NULL, MPContext);
*mpctx = (struct MPContext){
.last_dvb_step = 1,
- .terminal_osd_text = talloc_strdup(mpctx, ""),
+ .term_osd_contents = talloc_strdup(mpctx, ""),
.playlist = talloc_struct(mpctx, struct playlist, {0}),
};
diff --git a/player/misc.c b/player/misc.c
index eb8c0c147e..700def3ae4 100644
--- a/player/misc.c
+++ b/player/misc.c
@@ -170,10 +170,8 @@ void stream_dump(struct MPContext *mpctx)
if (!opts->quiet && ((stream->pos / (1024 * 1024)) % 2) == 1) {
uint64_t pos = stream->pos - stream->start_pos;
uint64_t end = stream->end_pos - stream->start_pos;
- char *line = talloc_asprintf(NULL, "Dumping %lld/%lld...",
- (long long int)pos, (long long int)end);
- write_status_line(mpctx, line);
- talloc_free(line);
+ MP_MSG(mpctx, MSGL_STATUS, "Dumping %lld/%lld...",
+ (long long int)pos, (long long int)end);
}
stream_fill_buffer(stream);
for (;;) {
diff --git a/player/osd.c b/player/osd.c
index 93340d78c2..7999847720 100644
--- a/player/osd.c
+++ b/player/osd.c
@@ -56,44 +56,67 @@ static void sadd_percentage(char **buf, int percent) {
*buf = talloc_asprintf_append(*buf, " (%d%%)", percent);
}
-static int get_term_width(void)
+static char *join_lines(void *ta_ctx, char **parts, int num_parts)
{
- get_screen_size();
- int width = screen_width > 0 ? screen_width : 80;
-#if defined(__MINGW32__) || defined(__CYGWIN__)
- /* Windows command line is broken (MinGW's rxvt works, but we
- * should not depend on that). */
- width--;
-#endif
- return width;
+ char *res = talloc_strdup(ta_ctx, "");
+ for (int n = 0; n < num_parts; n++)
+ res = talloc_asprintf_append(res, "%s%s", n ? "\n" : "", parts[n]);
+ return res;
}
-void write_status_line(struct MPContext *mpctx, const char *line)
+static void term_osd_update(struct MPContext *mpctx)
{
- struct MPOpts *opts = mpctx->opts;
- if (opts->slave_mode) {
- mp_msg(mpctx->statusline, MSGL_STATUS, "%s\n", line);
- } else if (erase_to_end_of_line) {
- mp_msg(mpctx->statusline, MSGL_STATUS, "%s%s\r", line, erase_to_end_of_line);
+ int num_parts = 0;
+ char *parts[2] = {0};
+
+ if (mpctx->term_osd_text && mpctx->term_osd_text[0])
+ parts[num_parts++] = mpctx->term_osd_text;
+ if (mpctx->term_osd_status && mpctx->term_osd_status[0])
+ parts[num_parts++] = mpctx->term_osd_status;
+
+ char *s = join_lines(mpctx, parts, num_parts);
+
+ if (strcmp(mpctx->term_osd_contents, s) == 0 &&
+ mp_msg_has_status_line(mpctx->global))
+ {
+ talloc_free(s);
} else {
- int pos = strlen(line);
- int width = get_term_width() - pos;
- mp_msg(mpctx->statusline, MSGL_STATUS, "%s%*s\r", line, width, "");
+ talloc_free(mpctx->term_osd_contents);
+ mpctx->term_osd_contents = s;
+ mp_msg(mpctx->statusline, MSGL_STATUS, "%s", s);
}
}
+static void term_osd_set_text(struct MPContext *mpctx, const char *text)
+{
+ if (mpctx->video_out && mpctx->opts->term_osd != 1)
+ text = ""; // disable
+ talloc_free(mpctx->term_osd_text);
+ mpctx->term_osd_text = talloc_strdup(mpctx, text);
+ term_osd_update(mpctx);
+}
+
+static void term_osd_set_status(struct MPContext *mpctx, const char *text)
+{
+ talloc_free(mpctx->term_osd_status);
+ mpctx->term_osd_status = talloc_strdup(mpctx, text);
+ term_osd_update(mpctx);
+}
+
void print_status(struct MPContext *mpctx)
{
struct MPOpts *opts = mpctx->opts;
update_window_title(mpctx, false);
- if (opts->quiet)
+ if (opts->quiet) {
+ term_osd_set_status(mpctx, "");
return;
+ }
if (opts->status_msg) {
char *r = mp_property_expand_string(mpctx, opts->status_msg);
- write_status_line(mpctx, r);
+ term_osd_set_status(mpctx, r);
talloc_free(r);
return;
}
@@ -160,7 +183,7 @@ void print_status(struct MPContext *mpctx)
saddf(&line, " Cache: %d%%", cache);
// end
- write_status_line(mpctx, line);
+ term_osd_set_status(mpctx, line);
talloc_free(line);
}
@@ -477,22 +500,13 @@ void update_osd_msg(struct MPContext *mpctx)
double pos = get_current_pos_ratio(mpctx, false);
update_osd_bar(mpctx, OSD_BAR_SEEK, 0, 1, MPCLAMP(pos, 0, 1));
+ print_status(mpctx);
+
// Look if we have a msg
mp_osd_msg_t *msg = get_osd_msg(mpctx);
if (msg && !msg->show_position) {
- if (mpctx->video_out && opts->term_osd != 1) {
- osd_set_text(osd, msg->msg);
- } else if (opts->term_osd) {
- if (strcmp(mpctx->terminal_osd_text, msg->msg)) {
- talloc_free(mpctx->terminal_osd_text);
- mpctx->terminal_osd_text = talloc_strdup(mpctx, msg->msg);
- // Multi-line message => clear what will be the second line
- write_status_line(mpctx, "");
- mp_msg(mpctx->statusline, MSGL_STATUS,
- "%s%s\n", opts->term_osd_esc, mpctx->terminal_osd_text);
- print_status(mpctx);
- }
- }
+ osd_set_text(osd, msg->msg);
+ term_osd_set_text(mpctx, msg->msg);
return;
}
@@ -500,21 +514,15 @@ void update_osd_msg(struct MPContext *mpctx)
if (msg && msg->show_position)
osd_level = 3;
- if (mpctx->video_out && opts->term_osd != 1) {
- // fallback on the timer
- char *text = NULL;
+ // clear, or if OSD level demands it, show the status
+ char *text = NULL;
- if (osd_level >= 2)
- sadd_osd_status(&text, mpctx, osd_level == 3);
+ if (osd_level >= 2)
+ sadd_osd_status(&text, mpctx, osd_level == 3);
- osd_set_text(osd, text);
- talloc_free(text);
- return;
- }
+ osd_set_text(osd, text);
+ talloc_free(text);
- // Clear the term osd line
- if (opts->term_osd && mpctx->terminal_osd_text[0]) {
- mpctx->terminal_osd_text[0] = '\0';
- mp_msg(mpctx->statusline, MSGL_STATUS, "%s\n", opts->term_osd_esc);
- }
+ // always clear (term-osd has separate status line)
+ term_osd_set_text(mpctx, "");
}
diff --git a/player/playloop.c b/player/playloop.c
index c61ba419b0..95e9a9c5a4 100644
--- a/player/playloop.c
+++ b/player/playloop.c
@@ -1164,7 +1164,6 @@ void run_playloop(struct MPContext *mpctx)
get_relative_time(mpctx);
}
update_avsync(mpctx);
- print_status(mpctx);
screenshot_flip(mpctx);
new_frame_shown = true;
@@ -1188,10 +1187,6 @@ void run_playloop(struct MPContext *mpctx)
update_osd_msg(mpctx);
- // The cache status is part of the status line. Possibly update it.
- if (mpctx->paused && mp_get_cache_percent(mpctx) >= 0)
- print_status(mpctx);
-
if (!video_left && (!mpctx->paused || was_restart)) {
double a_pos = 0;
if (mpctx->d_audio) {
@@ -1199,7 +1194,6 @@ void run_playloop(struct MPContext *mpctx)
mpctx->opts->playback_speed * buffered_audio);
}
mpctx->playback_pts = a_pos;
- print_status(mpctx);
}
update_subtitles(mpctx);