Files
flatpak/app/flatpak-tty-utils.c
Sebastian Wick 947d4a6ce4 flatpak: Enable progress escape sequence by default
In 4febfb59 ("flatpak: Disable progress escape sequence by default") the
escape sequence has been disabled by default, but we want to enable it
again for 1.18.
2025-11-18 01:19:36 +00:00

558 lines
12 KiB
C

/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s:
* Copyright © 1995-1998 Free Software Foundation, Inc.
* Copyright © 2014-2019 Red Hat, Inc
*
* This program 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.
*
* This library 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 this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors:
* Alexander Larsson <alexl@redhat.com>
*/
#include "config.h"
#include "flatpak-tty-utils-private.h"
#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>
#include <glib/gi18n-lib.h>
static int fancy_output = -1;
void
flatpak_disable_fancy_output (void)
{
fancy_output = FALSE;
}
void
flatpak_enable_fancy_output (void)
{
fancy_output = TRUE;
}
gboolean
flatpak_fancy_output (void)
{
static gsize fancy_output_once = 0;
enum {
PLAIN_OUTPUT = 1,
FANCY_OUTPUT = 2
};
if (fancy_output != -1)
return fancy_output;
if (g_once_init_enter (&fancy_output_once))
{
if (g_strcmp0 (g_getenv ("FLATPAK_FANCY_OUTPUT"), "0") == 0)
g_once_init_leave (&fancy_output_once, PLAIN_OUTPUT);
else if (getenv ("G_MESSAGES_DEBUG"))
g_once_init_leave (&fancy_output_once, PLAIN_OUTPUT);
else if (!isatty (STDOUT_FILENO))
g_once_init_leave (&fancy_output_once, PLAIN_OUTPUT);
else
g_once_init_leave (&fancy_output_once, FANCY_OUTPUT);
}
return fancy_output_once == FANCY_OUTPUT;
}
gboolean
flatpak_allow_fuzzy_matching (const char *term)
{
if (strchr (term, '/') != NULL || strchr (term, '.') != NULL)
return FALSE;
/* This env var is used by the unit tests and only skips the tty test not the
* check above.
*/
if (g_strcmp0 (g_getenv ("FLATPAK_FORCE_ALLOW_FUZZY_MATCHING"), "1") == 0)
return TRUE;
if (!isatty (STDIN_FILENO) || !isatty (STDOUT_FILENO))
return FALSE;
return TRUE;
}
char *
flatpak_prompt (gboolean allow_empty,
const char *prompt, ...)
{
char buf[512];
va_list var_args;
g_autofree char *s = NULL;
va_start (var_args, prompt);
s = g_strdup_vprintf (prompt, var_args);
va_end (var_args);
while (TRUE)
{
g_print ("%s: ", s);
if (!isatty (STDIN_FILENO) || !isatty (STDOUT_FILENO))
{
g_print ("n\n");
return NULL;
}
if (fgets (buf, sizeof (buf), stdin) == NULL)
return NULL;
g_strstrip (buf);
if (buf[0] != 0 || allow_empty)
return g_strdup (buf);
}
}
char *
flatpak_password_prompt (const char *prompt, ...)
{
char buf[512];
va_list var_args;
g_autofree char *s = NULL;
gboolean was_echo;
va_start (var_args, prompt);
s = g_strdup_vprintf (prompt, var_args);
va_end (var_args);
while (TRUE)
{
g_print ("%s: ", s);
if (!isatty (STDIN_FILENO) || !isatty (STDOUT_FILENO))
return NULL;
was_echo = flatpak_set_tty_echo (FALSE);
if (fgets (buf, sizeof (buf), stdin) == NULL)
return NULL;
flatpak_set_tty_echo (was_echo);
g_strstrip (buf);
/* We stole the return, so manual new line */
g_print ("\n");
return g_strdup (buf);
}
}
gboolean
flatpak_yes_no_prompt (gboolean default_yes, const char *prompt, ...)
{
char buf[512];
va_list var_args;
g_autofree char *s = NULL;
va_start (var_args, prompt);
s = g_strdup_vprintf (prompt, var_args);
va_end (var_args);
while (TRUE)
{
g_print ("%s %s: ", s, default_yes ? "[Y/n]" : "[y/n]");
if (!isatty (STDIN_FILENO) || !isatty (STDOUT_FILENO))
{
g_print ("n\n");
return FALSE;
}
if (fgets (buf, sizeof (buf), stdin) == NULL)
return FALSE;
g_strstrip (buf);
if (default_yes && strlen (buf) == 0)
return TRUE;
if (g_ascii_strcasecmp (buf, "y") == 0 ||
g_ascii_strcasecmp (buf, "yes") == 0)
return TRUE;
if (g_ascii_strcasecmp (buf, "n") == 0 ||
g_ascii_strcasecmp (buf, "no") == 0)
return FALSE;
}
}
static gboolean
is_number (const char *s)
{
if (*s == '\0')
return FALSE;
while (*s != 0)
{
if (!g_ascii_isdigit (*s))
return FALSE;
s++;
}
return TRUE;
}
long
flatpak_number_prompt (gboolean default_yes, int min, int max, const char *prompt, ...)
{
char buf[512];
va_list var_args;
g_autofree char *s = NULL;
va_start (var_args, prompt);
s = g_strdup_vprintf (prompt, var_args);
va_end (var_args);
while (TRUE)
{
g_print ("%s [%d-%d]: ", s, min, max);
if (!isatty (STDIN_FILENO) || !isatty (STDOUT_FILENO))
{
g_print ("0\n");
return 0;
}
if (fgets (buf, sizeof (buf), stdin) == NULL)
return 0;
g_strstrip (buf);
if (default_yes && strlen (buf) == 0 &&
max - min == 1 && min == 0)
return 1;
if (is_number (buf))
{
long res = strtol (buf, NULL, 10);
if (res >= min && res <= max)
return res;
}
}
}
static gboolean
parse_range (const char *s, int *a, int *b)
{
char *p;
p = strchr (s, '-');
if (!p)
return FALSE;
p++;
p[-1] = '\0';
if (is_number (s) && is_number (p))
{
*a = (int) strtol (s, NULL, 10);
*b = (int) strtol (p, NULL, 10);
p[-1] = '-';
return TRUE;
}
p[-1] = '-';
return FALSE;
}
static void
add_number (GArray *numbers,
int num)
{
int i;
for (i = 0; i < numbers->len; i++)
{
if (g_array_index (numbers, int, i) == num)
return;
}
g_array_append_val (numbers, num);
}
int *
flatpak_parse_numbers (const char *buf,
int min,
int max)
{
g_autoptr(GArray) numbers = g_array_new (FALSE, FALSE, sizeof (int));
g_auto(GStrv) parts = g_strsplit_set (buf, " ,", 0);
int i, j;
for (i = 0; parts[i]; i++)
{
int a, b;
g_strstrip (parts[i]);
if (parse_range (parts[i], &a, &b) &&
min <= a && a <= max &&
min <= b && b <= max)
{
for (j = a; j <= b; j++)
add_number (numbers, j);
}
else if (is_number (parts[i]))
{
int res = (int) strtol (parts[i], NULL, 10);
if (min <= res && res <= max)
add_number (numbers, res);
else
return NULL;
}
else
return NULL;
}
j = 0;
g_array_append_val (numbers, j);
return (int *) g_array_free (g_steal_pointer (&numbers), FALSE);
}
/* Returns a 0-terminated array of ints. Free with g_free */
int *
flatpak_numbers_prompt (gboolean default_yes, int min, int max, const char *prompt, ...)
{
char buf[512];
va_list var_args;
g_autofree char *s = NULL;
g_autofree int *choice = g_new0 (int, 2);
int *numbers;
va_start (var_args, prompt);
s = g_strdup_vprintf (prompt, var_args);
va_end (var_args);
while (TRUE)
{
g_print ("%s [%d-%d]: ", s, min, max);
if (!isatty (STDIN_FILENO) || !isatty (STDOUT_FILENO))
{
g_print ("0\n");
choice[0] = 0;
return g_steal_pointer (&choice);
}
if (fgets (buf, sizeof (buf), stdin) == NULL)
{
choice[0] = 0;
return g_steal_pointer (&choice);
}
g_strstrip (buf);
if (default_yes && strlen (buf) == 0 &&
max - min == 1 && min == 0)
{
choice[0] = 0;
return g_steal_pointer (&choice);
}
numbers = flatpak_parse_numbers (buf, min, max);
if (numbers)
return numbers;
}
}
void
flatpak_format_choices (const char **choices,
const char *prompt,
...)
{
va_list var_args;
g_autofree char *s = NULL;
int i;
va_start (var_args, prompt);
s = g_strdup_vprintf (prompt, var_args);
va_end (var_args);
g_print ("%s\n\n", s);
for (i = 0; choices[i]; i++)
g_print (" %2d) %s\n", i + 1, choices[i]);
g_print ("\n");
}
void
flatpak_get_window_size (int *rows, int *cols)
{
struct winsize w;
if (ioctl (STDOUT_FILENO, TIOCGWINSZ, &w) == 0)
{
/* For whatever reason, in buildbot this returns 0, 0 so add a fallback */
if (w.ws_row == 0)
w.ws_row = 24;
if (w.ws_col == 0)
w.ws_col = 80;
*rows = w.ws_row;
*cols = w.ws_col;
}
else
{
*rows = 24;
*cols = 80;
}
}
gboolean
flatpak_set_tty_echo (gboolean echo)
{
struct termios term;
gboolean was;
tcgetattr (STDIN_FILENO, &term);
was = (term.c_lflag & ECHO) != 0;
if (echo)
term.c_lflag |= ECHO;
else
term.c_lflag &= ~ECHO;
tcsetattr (STDIN_FILENO, TCSANOW, &term);
return was;
}
gboolean
flatpak_get_cursor_pos (int * row, int *col)
{
fd_set readset;
struct timeval time;
struct termios term, initial_term;
int res = 0;
tcgetattr (STDIN_FILENO, &initial_term);
term = initial_term;
term.c_lflag &= ~ICANON;
term.c_lflag &= ~ECHO;
tcsetattr (STDIN_FILENO, TCSANOW, &term);
printf ("\033[6n");
fflush (stdout);
FD_ZERO (&readset);
FD_SET (STDIN_FILENO, &readset);
time.tv_sec = 0;
time.tv_usec = 100000;
if (select (STDIN_FILENO + 1, &readset, NULL, NULL, &time) == 1)
res = scanf ("\033[%d;%dR", row, col);
tcsetattr (STDIN_FILENO, TCSADRAIN, &initial_term);
return res == 2;
}
void
flatpak_hide_cursor (void)
{
const size_t flatpak_hide_cursor_len = strlen (FLATPAK_ANSI_HIDE_CURSOR);
const ssize_t write_ret = write (STDOUT_FILENO, FLATPAK_ANSI_HIDE_CURSOR,
flatpak_hide_cursor_len);
if (write_ret < 0)
g_warning ("write() failed: %zd = write(STDOUT_FILENO, FLATPAK_ANSI_HIDE_CURSOR, %zu)",
write_ret, flatpak_hide_cursor_len);
}
void
flatpak_show_cursor (void)
{
const size_t flatpak_show_cursor_len = strlen (FLATPAK_ANSI_SHOW_CURSOR);
const ssize_t write_ret = write (STDOUT_FILENO, FLATPAK_ANSI_SHOW_CURSOR,
flatpak_show_cursor_len);
if (write_ret < 0)
g_warning ("write() failed: %zd = write(STDOUT_FILENO, FLATPAK_ANSI_SHOW_CURSOR, %zu)",
write_ret, flatpak_show_cursor_len);
}
void
flatpak_enable_raw_mode (void)
{
struct termios raw;
tcgetattr (STDIN_FILENO, &raw);
raw.c_lflag &= ~(ECHO | ICANON);
tcsetattr (STDIN_FILENO, TCSAFLUSH, &raw);
}
void
flatpak_disable_raw_mode (void)
{
struct termios raw;
tcgetattr (STDIN_FILENO, &raw);
raw.c_lflag |= (ECHO | ICANON);
tcsetattr (STDIN_FILENO, TCSAFLUSH, &raw);
}
void
flatpak_print_escaped_string (const char *s,
FlatpakEscapeFlags flags)
{
g_autofree char *escaped = flatpak_escape_string (s, flags);
g_print ("%s", escaped);
}
static gboolean
use_progress_escape_sequence (void)
{
static gsize tty_progress_once = 0;
enum {
TTY_PROGRESS_ENABLED = 1,
TTY_PROGRESS_DISABLED = 2
};
if (g_once_init_enter (&tty_progress_once))
{
if (g_strcmp0 (g_getenv ("FLATPAK_TTY_PROGRESS"), "0") == 0)
g_once_init_leave (&tty_progress_once, TTY_PROGRESS_DISABLED);
else
g_once_init_leave (&tty_progress_once, TTY_PROGRESS_ENABLED);
}
return tty_progress_once == TTY_PROGRESS_ENABLED;
}
void
flatpak_pty_clear_progress (void)
{
if (use_progress_escape_sequence ())
g_print ("\033]9;4;0\e\\");
}
void
flatpak_pty_set_progress (guint percent)
{
if (use_progress_escape_sequence ())
g_print ("\033]9;4;1;%d\e\\", MIN (percent, 100));
}