396 lines
9.4 KiB
C
396 lines
9.4 KiB
C
/*
|
|
* Copyright (c) 2015, 2016, 2017, 2023, 2025 Jonas 'Sortie' Termansen.
|
|
* Copyright (c) 2023 Juhani 'nortti' Krekelä.
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*
|
|
* interactive.c
|
|
* Interactive utility functions.
|
|
*/
|
|
|
|
#include <sys/display.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/termmode.h>
|
|
#include <sys/types.h>
|
|
|
|
#include <ctype.h>
|
|
#include <display.h>
|
|
#include <err.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <termios.h>
|
|
#include <unistd.h>
|
|
#include <wchar.h>
|
|
|
|
#include <display.h>
|
|
|
|
#include "autoconf.h"
|
|
#include "execute.h"
|
|
#include "interactive.h"
|
|
|
|
#define REQUEST_DISPLAYS_ID 0
|
|
#define REQUEST_DISPLAY_MODE_ID 1
|
|
|
|
static uint32_t displays_count;
|
|
static bool displays_count_received;
|
|
|
|
static struct dispmsg_crtc_mode display_mode;
|
|
static int request_display_mode_error;
|
|
static bool display_mode_received;
|
|
|
|
void shlvl(void)
|
|
{
|
|
long shlvl = 0;
|
|
if ( getenv("SHLVL") && (shlvl = atol(getenv("SHLVL"))) < 0 )
|
|
shlvl = 0;
|
|
if ( shlvl < LONG_MAX )
|
|
shlvl++;
|
|
char shlvl_string[sizeof(long) * 3];
|
|
snprintf(shlvl_string, sizeof(shlvl_string), "%li", shlvl);
|
|
setenv("SHLVL", shlvl_string, 1);
|
|
}
|
|
|
|
void text(const char* str)
|
|
{
|
|
fflush(stdout);
|
|
struct winsize ws;
|
|
if ( tcgetwinsize(1, &ws) < 0 )
|
|
{
|
|
if ( errno == ENOTTY )
|
|
{
|
|
fputs(str, stdout);
|
|
fflush(stdout);
|
|
return;
|
|
}
|
|
err(2, "tcgetwinsize");
|
|
}
|
|
size_t columns = ws.ws_col;
|
|
size_t column = 0;
|
|
struct wincurpos wcp;
|
|
if ( tcgetwincurpos(1, &wcp) == 0 )
|
|
column = wcp.wcp_col;
|
|
bool blank = false;
|
|
while ( str[0] )
|
|
{
|
|
if ( str[0] == '\n' )
|
|
{
|
|
putchar('\n');
|
|
blank = false;
|
|
column = 0;
|
|
str++;
|
|
continue;
|
|
}
|
|
else if ( isblank((unsigned char) str[0]) )
|
|
{
|
|
blank = true;
|
|
str++;
|
|
continue;
|
|
}
|
|
size_t word_length = 0;
|
|
size_t word_columns = 0;
|
|
mbstate_t ps = { 0 };
|
|
while ( str[word_length] &&
|
|
str[word_length] != '\n' &&
|
|
!isblank((unsigned char) str[word_length]) )
|
|
{
|
|
wchar_t wc;
|
|
size_t amount = mbrtowc(&wc, str + word_length, SIZE_MAX, &ps);
|
|
if ( amount == (size_t) -2 )
|
|
break;
|
|
if ( amount == (size_t) -1 )
|
|
{
|
|
memset(&ps, 0, sizeof(ps));
|
|
amount = 1;
|
|
}
|
|
if ( amount == (size_t) 0 )
|
|
break;
|
|
word_length += amount;
|
|
int width = wcwidth(wc);
|
|
if ( width < 0 )
|
|
continue;
|
|
word_columns += width;
|
|
}
|
|
if ( (column && blank ? 1 : 0) + word_columns <= columns - column )
|
|
{
|
|
if ( column && blank )
|
|
{
|
|
putchar(' ');
|
|
column++;
|
|
}
|
|
blank = false;
|
|
fwrite(str, 1, word_length, stdout);
|
|
column += word_columns;
|
|
if ( column == columns )
|
|
column = 0;
|
|
}
|
|
else
|
|
{
|
|
if ( column != 0 && column != columns )
|
|
putchar('\n');
|
|
column = 0;
|
|
blank = false;
|
|
fwrite(str, 1, word_length, stdout);
|
|
column += word_columns;
|
|
column %= columns;
|
|
}
|
|
str += word_length;
|
|
}
|
|
fflush(stdout);
|
|
}
|
|
|
|
void textf(const char* format, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, format);
|
|
char* str;
|
|
int len = vasprintf(&str, format, ap);
|
|
va_end(ap);
|
|
if ( len < 0 )
|
|
{
|
|
vprintf(format, ap);
|
|
return;
|
|
}
|
|
text(str);
|
|
free(str);
|
|
}
|
|
|
|
void prompt(char* buffer,
|
|
size_t buffer_size,
|
|
const char* autoconf_name,
|
|
const char* question,
|
|
const char* answer)
|
|
{
|
|
promptx(buffer, buffer_size, autoconf_name, question, answer, false);
|
|
}
|
|
|
|
void promptx(char* buffer,
|
|
size_t buffer_size,
|
|
const char* autoconf_name,
|
|
const char* question,
|
|
const char* answer,
|
|
bool catch_if_shell)
|
|
{
|
|
bool is_tty = isatty(1);
|
|
char* autoconf_answer = autoconf_eval(autoconf_name);
|
|
while ( true )
|
|
{
|
|
if ( is_tty )
|
|
printf("\e[1m");
|
|
fflush(stdout);
|
|
text(question);
|
|
if ( answer )
|
|
printf(" [%s] ", answer);
|
|
else
|
|
printf(" ");
|
|
fflush(stdout);
|
|
const char* accept_defaults = autoconf_get("accept_defaults");
|
|
const char* automatic_answer = NULL;
|
|
if ( autoconf_answer )
|
|
{
|
|
automatic_answer = autoconf_answer;
|
|
if ( !automatic_answer[0] )
|
|
automatic_answer = answer;
|
|
}
|
|
else if ( accept_defaults && !strcasecmp(accept_defaults, "yes") )
|
|
automatic_answer = answer;
|
|
if ( automatic_answer )
|
|
{
|
|
if ( is_tty )
|
|
printf("\e[93m");
|
|
printf("%s\n", automatic_answer);
|
|
if ( is_tty )
|
|
printf("\e[m");
|
|
fflush(stdout);
|
|
strlcpy(buffer, automatic_answer, buffer_size);
|
|
free(autoconf_answer);
|
|
return;
|
|
}
|
|
fgets(buffer, buffer_size, stdin);
|
|
if ( is_tty )
|
|
printf("\e[22m");
|
|
fflush(stdout);
|
|
size_t buffer_length = strlen(buffer);
|
|
if ( buffer_length && buffer[buffer_length-1] == '\n' )
|
|
buffer[--buffer_length] = '\0';
|
|
while ( buffer_length && buffer[buffer_length-1] == ' ' )
|
|
buffer[--buffer_length] = '\0';
|
|
if ( !strcmp(buffer, "") )
|
|
{
|
|
if ( !answer )
|
|
continue;
|
|
strlcpy(buffer, answer, buffer_size);
|
|
}
|
|
if ( !strcmp(buffer, "!") )
|
|
{
|
|
printf("Type 'exit' to return to the %s.\n", prompt_man_page);
|
|
fflush(stdout);
|
|
execute((const char*[]) { "sh", NULL }, "f");
|
|
if ( catch_if_shell )
|
|
break;
|
|
continue;
|
|
}
|
|
if ( !strcmp(buffer, "!man") )
|
|
{
|
|
execute((const char*[]) { "man", prompt_man_section,
|
|
prompt_man_page, NULL }, "f");
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
free(autoconf_answer);
|
|
}
|
|
|
|
void password(char* buffer,
|
|
size_t buffer_size,
|
|
const char* question)
|
|
{
|
|
unsigned int termmode;
|
|
gettermmode(0, &termmode);
|
|
settermmode(0, termmode & ~TERMMODE_ECHO);
|
|
printf("\e[1m");
|
|
fflush(stdout);
|
|
text(question);
|
|
printf(" ");
|
|
fflush(stdout);
|
|
fflush(stdin);
|
|
// TODO: This may leave a copy of the password in the stdio buffer.
|
|
fgets(buffer, buffer_size, stdin);
|
|
fflush(stdin);
|
|
printf("\e[22m\n");
|
|
size_t buffer_length = strlen(buffer);
|
|
if ( buffer_length && buffer[buffer_length-1] == '\n' )
|
|
buffer[--buffer_length] = '\0';
|
|
settermmode(0, termmode);
|
|
}
|
|
|
|
static bool has_program(const char* program)
|
|
{
|
|
return execute((const char*[]) { "which", "--", program, NULL }, "q") == 0;
|
|
}
|
|
|
|
bool missing_program(const char* program)
|
|
{
|
|
if ( has_program(program) )
|
|
return false;
|
|
warnx("%s: Program is absent", program);
|
|
return true;
|
|
}
|
|
|
|
static void on_displays(void* ctx, uint32_t id, uint32_t displays)
|
|
{
|
|
(void) ctx;
|
|
if ( id != REQUEST_DISPLAYS_ID )
|
|
return;
|
|
displays_count = displays;
|
|
displays_count_received = true;
|
|
}
|
|
|
|
static void on_display_mode(void* ctx, uint32_t id,
|
|
struct dispmsg_crtc_mode mode)
|
|
{
|
|
(void) ctx;
|
|
if ( id != REQUEST_DISPLAY_MODE_ID )
|
|
return;
|
|
display_mode = mode;
|
|
request_display_mode_error = 0;
|
|
display_mode_received = true;
|
|
}
|
|
|
|
static void on_ack(void* ctx, uint32_t id, int32_t error)
|
|
{
|
|
(void) ctx;
|
|
if ( id != REQUEST_DISPLAY_MODE_ID )
|
|
return;
|
|
if ( error )
|
|
{
|
|
request_display_mode_error = error;
|
|
display_mode_received = true;
|
|
}
|
|
}
|
|
|
|
bool get_video_mode(struct dispmsg_crtc_mode* mode)
|
|
{
|
|
if ( getenv("DISPLAY_SOCKET") )
|
|
{
|
|
struct display_connection* connection = display_connect_default();
|
|
if ( !connection )
|
|
return false;
|
|
struct display_event_handlers handlers = {0};
|
|
handlers.displays_handler = on_displays;
|
|
handlers.display_mode_handler = on_display_mode;
|
|
handlers.ack_handler = on_ack;
|
|
display_request_displays(connection, REQUEST_DISPLAYS_ID);
|
|
displays_count_received = false;
|
|
while ( !displays_count_received )
|
|
display_wait_event(connection, &handlers);
|
|
if ( displays_count < 1 )
|
|
{
|
|
display_disconnect(connection);
|
|
return false;
|
|
}
|
|
// TODO: Multimonitor support.
|
|
display_request_display_mode(connection, REQUEST_DISPLAY_MODE_ID, 0);
|
|
display_mode_received = false;
|
|
while ( !display_mode_received )
|
|
display_wait_event(connection, &handlers);
|
|
display_disconnect(connection);
|
|
if ( request_display_mode_error )
|
|
return false;
|
|
*mode = display_mode;
|
|
return true;
|
|
}
|
|
|
|
struct tiocgdisplay display;
|
|
struct tiocgdisplays gdisplays;
|
|
memset(&gdisplays, 0, sizeof(gdisplays));
|
|
gdisplays.count = 1;
|
|
gdisplays.displays = &display;
|
|
struct dispmsg_get_driver_name dgdn = { 0 };
|
|
if ( ioctl(1, TIOCGDISPLAYS, &gdisplays) < 0 || gdisplays.count == 0 )
|
|
return false;
|
|
dgdn.device = display.device;
|
|
dgdn.msgid = DISPMSG_GET_DRIVER_NAME;
|
|
dgdn.device = display.device;
|
|
dgdn.driver_index = 0;
|
|
dgdn.name.byte_size = 0;
|
|
dgdn.name.str = NULL;
|
|
if ( dispmsg_issue(&dgdn, sizeof(dgdn)) < 0 && errno == ENODEV )
|
|
return false;
|
|
struct dispmsg_get_crtc_mode get_mode;
|
|
memset(&get_mode, 0, sizeof(get_mode));
|
|
get_mode.msgid = DISPMSG_GET_CRTC_MODE;
|
|
get_mode.device = display.device;
|
|
get_mode.connector = display.connector;
|
|
// TODO: Still allow setting the video mode if none was already set.
|
|
if ( dispmsg_issue(&get_mode, sizeof(get_mode)) < 0 )
|
|
return false;
|
|
*mode = get_mode.mode;
|
|
return true;
|
|
}
|
|
|
|
void gui_shutdown(int code)
|
|
{
|
|
if ( getenv("DISPLAY_SOCKET") )
|
|
{
|
|
struct display_connection* connection = display_connect_default();
|
|
if ( connection )
|
|
display_shutdown(connection, code);
|
|
else
|
|
warn("display_connect_default");
|
|
}
|
|
}
|