New format string "%{progress-conemu}" to emit OSC 9;4 (ConEmu progress bar) codes (#202).

This commit is contained in:
Andrew Wood
2026-07-30 23:04:53 +01:00
parent 7c57fb08ad
commit 1422edeb6a
10 changed files with 145 additions and 17 deletions
+1
View File
@@ -25,6 +25,7 @@ src/pv/format/averagerate.c \
src/pv/format/barstyle.c \
src/pv/format/bufferpercent.c \
src/pv/format/bytes.c \
src/pv/format/conemu.c \
src/pv/format/eta.c \
src/pv/format/fineta.c \
src/pv/format/lastwritten.c \
+1
View File
@@ -119,6 +119,7 @@ is acknowledged and greatly appreciated:
* [oech3](https://codeberg.org/oech3) - suggested and tested performance improvements using `splice()` in more situations on Linux ([#188](https://codeberg.org/ivarch/pv/issues/188), [#191](https://codeberg.org/ivarch/pv/issues/191), [#192](https://codeberg.org/ivarch/pv/issues/192))
* [Barak A. Pearlmutter](https://codeberg.org/barak) - suggested that "**--rate-limit**" should accept non-integer values ([#193](https://codeberg.org/ivarch/pv/issues/193))
* [dirkmueller](https://codeberg.org/dirkmueller) - fixed the potential for stdout closure under a combination of error conditions in **--monitor** mode ([#201](https://codeberg.org/ivarch/pv/pulls/201))
* [InkstainTheBat](https://codeberg.org/InkstainTheBat) - suggested ConEmu OSC 9;4 support ([#202](https://codeberg.org/ivarch/pv/issues/202))
Translations provided through [Codeberg Weblate](https://translate.codeberg.org/projects/pv/):
+1
View File
@@ -1,5 +1,6 @@
### UNRELEASED
* *feature:* new **--format** string **%{progress-conemu}** to show progress in the terminal title tab on terminals with ConEmu OSC 9;4 support ([#202](https://codeberg.org/ivarch/pv/issues/202))
* *cleanup:* eliminate potential for closure of standard output under a combination of error conditions when using **--monitor** mode ([#201](https://codeberg.org/ivarch/pv/pulls/201))
### 1.11.0 - 11 June 2026
+10 -1
View File
@@ -1,4 +1,4 @@
.TH PV 1 2026-06-11 pv-1.11.0 "User Commands"
.TH PV 1 2026-07-30 pv-1.11.0 "User Commands"
.\"
.SH NAME
pv \- monitor and manage the progress of data through a pipe
@@ -505,6 +505,10 @@ Expands to fill the remaining space unless prefixed by a number.
The percentage completion (or maximum rate, with \*(lq\fB\-\-gauge\fR\*(rq
when the size is unknown).
.TP
.B %{progress-conemu}
Emit the ConEmu "progress bar" sequences (OSC 9;4) to show the progress
percentage in the terminal's title tab, on terminals which support it.
.TP
.B %{bar\-plain}
Progress bar in the standard plain format, without any sides, and without
any percentage displayed afterwards.
@@ -675,6 +679,11 @@ Show only the elapsed time - useful as a simple timer, such as
.B pv \-pterb
The default behaviour: progress bar, elapsed time, estimated completion
time, current rate, and byte counter.
.TP
.B pv \-\-format='%{progress-conemu}%{bytes} %{timer} %{rate} %{progress} %{eta}'
Show the byte counter, elapsed time, current rate, progress bar, and ETA,
and show the percentage progress in the terminal's tab bar, on terminals
that support ConEmu OSC 9;4 sequences.
.PP
On macOS, it may be useful to specify \*(lq\fB\-\-buffer\-size\~1024\fR\*(rq
in a pipeline, as this may improve performance.
+12
View File
@@ -519,6 +519,12 @@ contain the following sequences:
: The percentage completion (or maximum rate, with "**\--gauge**" when
the size is unknown).
**%{progress-conemu}**
: Emit the ConEmu \"progress bar\" sequences (OSC 9;4) to show the
progress percentage in the terminal\'s title tab, on terminals which
support it.
**%{bar-plain}**
: Progress bar in the standard plain format, without any sides, and
@@ -679,6 +685,12 @@ Some suggested common switch combinations:
: The default behaviour: progress bar, elapsed time, estimated
completion time, current rate, and byte counter.
**pv \--format=\'%{progress-conemu}%{bytes} %{timer} %{rate} %{progress} %{eta}\'**
: Show the byte counter, elapsed time, current rate, progress bar, and
ETA, and show the percentage progress in the terminal\'s tab bar, on
terminals that support ConEmu OSC 9;4 sequences.
On macOS, it may be useful to specify "**\--buffer-size 1024**" in a
pipeline, as this may improve performance.
+2
View File
@@ -348,6 +348,7 @@ struct pvstate_s {
bool format_uses_colour; /* set if the format string uses colours */
bool colour_permitted; /* whether colour is permitted for this display */
bool sgr_code_active; /* set while SGR code is active in a display line */
bool using_osc94; /* set if the format contains an OSC 9;4 code */
bool final_update; /* set internally on the final update */
bool output_produced; /* set once anything written to terminal */
@@ -628,6 +629,7 @@ pvdisplay_bytecount_t pv_formatter_segmentcontent(char *, pvformatter_args_t);
pvdisplay_bytecount_t pv_formatter_progress(pvformatter_args_t);
pvdisplay_bytecount_t pv_formatter_progress_bar_only(pvformatter_args_t);
pvdisplay_bytecount_t pv_formatter_progress_amount_only(pvformatter_args_t);
pvdisplay_bytecount_t pv_formatter_progress_conemu(pvformatter_args_t);
pvdisplay_bytecount_t pv_formatter_bar_default(pvformatter_args_t);
pvdisplay_bytecount_t pv_formatter_bar_plain(pvformatter_args_t);
pvdisplay_bytecount_t pv_formatter_bar_block(pvformatter_args_t);
+4
View File
@@ -543,6 +543,7 @@ pvdisplay_bytecount_t pv_formatter_segmentcontent(char *content, pvformatter_arg
{ "{progress}", &pv_formatter_progress, true },
{ "{progress-amount-only}", &pv_formatter_progress_amount_only, false },
{ "{progress-bar-only}", &pv_formatter_bar_default, true },
{ "{progress-conemu}", &pv_formatter_progress_conemu, false },
{ "{bar-plain}", &pv_formatter_bar_plain, true },
{ "{bar-block}", &pv_formatter_bar_block, true },
{ "{bar-granular}", &pv_formatter_bar_granular, true },
@@ -1093,6 +1094,9 @@ static bool pv_format(pvprogramstatus_t status, readonly_pvcontrol_t control, re
/* Clear the SGR active codes flag, for the SGR formatter. */
display->sgr_code_active = false;
/* Clear the OSC 9;4 active flag. */
display->using_osc94 = false;
/*
* Populate the internal segments buffer with each component's
* output, in two passes.
+62
View File
@@ -0,0 +1,62 @@
/*
* Formatter function for OSC 9;4 - ConEmu progress bar - codes.
*
* Copyright 2024-2026 Andrew Wood
*
* License GPLv3+: GNU GPL version 3 or later; see `docs/COPYING'.
*/
#include "config.h"
#include "pv.h"
#include "pv-internal.h"
#include <string.h>
#if HAVE_MATH_H
#include <math.h>
#endif
/*
* Produce OSC 9;4 progress bar codes for terminal title tabs.
*/
pvdisplay_bytecount_t pv_formatter_progress_conemu(pvformatter_args_t args)
{
char content[128]; /* flawfinder: ignore */
/* flawfinder - null-terminated and bounded with pv_snprintf(). */
memset(content, 0, sizeof(content));
if (args->control->size > 0 || args->control->rate_gauge) {
/* Known size or rate gauge - percentage progress. */
(void) pv_snprintf(content, sizeof(content), "\033]9;4;1;%.0f\033\\", args->calc->percentage);
} else {
/* Unknown size - indeterminate progress. */
/* See pv_formatter_progress_unknownsize() in progressbar.c. */
double indicator_position;
indicator_position = args->calc->percentage;
if (indicator_position > 200.0)
#if HAVE_FMOD
indicator_position = fmod(indicator_position, 200.0);
#else
{
while (indicator_position > 200.0)
indicator_position -= 200.0;
}
#endif
if (indicator_position > 100.0) {
indicator_position = 200.0 - indicator_position;
}
if (indicator_position < 0.0) {
indicator_position = 0.0;
}
(void) pv_snprintf(content, sizeof(content), "\033]9;4;3;%.0f\033\\", indicator_position);
}
args->display->using_osc94 = true;
return pv_formatter_segmentcontent(content, args);
}
+19
View File
@@ -62,6 +62,15 @@ static long ldsqrt(long double value)
#endif
/*
* Turn off the OSC 9;4 (ConEmu) progress bar.
*/
static void pv_clear_osc94(pvstate_t state)
{
pv_tty_write(&(state->flags), "\033]9;4;0;0\033\\", 11);
}
/*
* If the flag is set to say that a terminal resize signal was received,
* clear the flag, resize the display, and return true.
@@ -465,6 +474,8 @@ int pv_main_loop(pvstate_t state)
if (input_fd < 0) {
if (state->control.cursor)
pv_crs_fini(&(state->cursor), &(state->control), &(state->flags));
if (state->display.using_osc94)
pv_clear_osc94(state);
return state->status.exit_status;
}
#if HAVE_POSIX_FADVISE
@@ -638,6 +649,8 @@ int pv_main_loop(pvstate_t state)
debug("%s: %s", "write error from pv_transfer", strerror(errno));
if (state->control.cursor)
pv_crs_fini(&(state->cursor), &(state->control), &(state->flags));
if (state->display.using_osc94)
pv_clear_osc94(state);
return state->status.exit_status;
}
@@ -891,6 +904,9 @@ int pv_main_loop(pvstate_t state)
pv_tty_write(&(state->flags), "\n", 1);
}
if (state->display.using_osc94)
pv_clear_osc94(state);
/* Tell the error routines that progress bar display has finished. */
pv_end_display();
@@ -1585,6 +1601,9 @@ int pv_query_loop(pvstate_t state, pid_t query)
pv_tty_write(&(state->flags), "\n", 1);
}
if (state->display.using_osc94)
pv_clear_osc94(state);
/* Tell the error routines that progress bar display has finished. */
pv_end_display();
+33 -16
View File
@@ -321,12 +321,12 @@ void *pv_memrchr(const void *buffer, int match, size_t length)
* Return the number of display columns needed to show the
* non-null-terminated string "string" whose length in bytes is "bytes".
*
* Skips ECMA-48 CSI (ESC [ ...) sequences, but any other control characters
* are treated as printable.
* Skips ECMA-48 CSI (ESC [ ...) sequences, and OSC (ESC ] ...) sequences,
* but any other control characters are treated as printable.
*
* Internally, after skipping CSI sequences, the string is converted to a
* wide character string, and each wide character's width is checked with
* "wcswidth()".
* Internally, after skipping CSI or OSC sequences, the string is converted
* to a wide character string, and each wide character's width is checked
* with "wcswidth()".
*
* If NLS is disabled, or the string cannot be converted, this just returns
* the number of bytes in the string that aren't part of CSI sequences.
@@ -364,19 +364,36 @@ size_t pv_strwidth(const char *string, size_t bytes)
raw_string = allocated_raw;
}
/* Copy the original string, skipping ECMA-48 CSI sequences. */
/*
* Copy the original string, skipping ECMA-48 CSI and OSC sequences.
*/
for (read_pos = 0, write_pos = 0; read_pos < bytes; read_pos++) {
if ((string[read_pos] != '\033') || (read_pos >= bytes - 1) || (string[read_pos + 1] != '[')) {
if ((string[read_pos] == '\033') && (read_pos < bytes - 1) && (string[read_pos + 1] == '[')) {
/* Skip CSI - ends with anything other than 0-9 or ';'. */
read_pos += 2;
while ((read_pos < bytes - 1)
&& ((string[read_pos] >= '0' && string[read_pos] <= '9')
|| (';' == string[read_pos])
)
) {
read_pos++;
}
} else if ((string[read_pos] == '\033') && (read_pos < bytes - 1) && (string[read_pos + 1] == ']')) {
/* Skip OSC - ends with BEL or ST (ESC \). */
read_pos += 2;
while (read_pos < bytes - 1) {
if (string[read_pos] == '\007') {
break;
}
if ((string[read_pos] == '\033') && (string[1 + read_pos] == '\\')) {
read_pos++;
break;
}
read_pos++;
}
} else {
/* Don't skip, copy across. */
raw_string[write_pos++] = string[read_pos];
continue;
}
read_pos += 2;
while ((read_pos < bytes - 1)
&& ((string[read_pos] >= '0' && string[read_pos] <= '9')
|| (';' == string[read_pos])
)
) {
read_pos++;
}
}
raw_string[write_pos] = '\0';