diff --git a/src/boot/boot.c b/src/boot/boot.c index 6e55a659dd1..7431f6d1d16 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -11,6 +11,7 @@ #include "export-vars.h" #include "graphics.h" #include "initrd.h" +#include "line-edit.h" #include "linux.h" #include "measure.h" #include "memory-util-fundamental.h" @@ -150,254 +151,6 @@ enum { IDX_INVALID, }; -static void cursor_left(size_t *cursor, size_t *first) { - assert(cursor); - assert(first); - - if ((*cursor) > 0) - (*cursor)--; - else if ((*first) > 0) - (*first)--; -} - -static void cursor_right(size_t *cursor, size_t *first, size_t x_max, size_t len) { - assert(cursor); - assert(first); - - if ((*cursor)+1 < x_max) - (*cursor)++; - else if ((*first) + (*cursor) < len) - (*first)++; -} - -static bool line_edit(char16_t **line_in, size_t x_max, size_t y_pos) { - _cleanup_free_ char16_t *line = NULL, *print = NULL; - size_t size, len, first = 0, cursor = 0, clear = 0; - - /* Edit the line and return true if it should be executed, false if not. */ - - assert(line_in); - - len = strlen16(*line_in); - size = len + 1024; - line = xnew(char16_t, size); - print = xnew(char16_t, x_max + 1); - strcpy16(line, strempty(*line_in)); - - for (;;) { - EFI_STATUS err; - uint64_t key; - size_t j, cursor_color = EFI_TEXT_ATTR_SWAP(COLOR_EDIT); - - j = MIN(len - first, x_max); - memcpy(print, line + first, j * sizeof(char16_t)); - while (clear > 0 && j < x_max) { - clear--; - print[j++] = ' '; - } - print[j] = '\0'; - - /* See comment at edit_line() call site for why we start at 1. */ - print_at(1, y_pos, COLOR_EDIT, print); - - if (!print[cursor]) - print[cursor] = ' '; - print[cursor+1] = '\0'; - do { - print_at(cursor + 1, y_pos, cursor_color, print + cursor); - cursor_color = EFI_TEXT_ATTR_SWAP(cursor_color); - - err = console_key_read(&key, 750 * 1000); - if (!IN_SET(err, EFI_SUCCESS, EFI_TIMEOUT, EFI_NOT_READY)) - return false; - - print_at(cursor + 1, y_pos, COLOR_EDIT, print + cursor); - } while (err != EFI_SUCCESS); - - switch (key) { - case KEYPRESS(0, SCAN_ESC, 0): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'c'): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'g'): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('c')): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('g')): - return false; - - case KEYPRESS(0, SCAN_HOME, 0): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'a'): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('a')): - /* beginning-of-line */ - cursor = 0; - first = 0; - continue; - - case KEYPRESS(0, SCAN_END, 0): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'e'): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('e')): - /* end-of-line */ - cursor = len - first; - if (cursor+1 >= x_max) { - cursor = x_max-1; - first = len - (x_max-1); - } - continue; - - case KEYPRESS(0, SCAN_DOWN, 0): - case KEYPRESS(EFI_ALT_PRESSED, 0, 'f'): - case KEYPRESS(EFI_CONTROL_PRESSED, SCAN_RIGHT, 0): - /* forward-word */ - while (line[first + cursor] == ' ') - cursor_right(&cursor, &first, x_max, len); - while (line[first + cursor] && line[first + cursor] != ' ') - cursor_right(&cursor, &first, x_max, len); - continue; - - case KEYPRESS(0, SCAN_UP, 0): - case KEYPRESS(EFI_ALT_PRESSED, 0, 'b'): - case KEYPRESS(EFI_CONTROL_PRESSED, SCAN_LEFT, 0): - /* backward-word */ - if ((first + cursor) > 0 && line[first + cursor-1] == ' ') { - cursor_left(&cursor, &first); - while ((first + cursor) > 0 && line[first + cursor] == ' ') - cursor_left(&cursor, &first); - } - while ((first + cursor) > 0 && line[first + cursor-1] != ' ') - cursor_left(&cursor, &first); - continue; - - case KEYPRESS(0, SCAN_RIGHT, 0): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'f'): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('f')): - /* forward-char */ - if (first + cursor == len) - continue; - cursor_right(&cursor, &first, x_max, len); - continue; - - case KEYPRESS(0, SCAN_LEFT, 0): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'b'): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('b')): - /* backward-char */ - cursor_left(&cursor, &first); - continue; - - case KEYPRESS(EFI_CONTROL_PRESSED, SCAN_DELETE, 0): - case KEYPRESS(EFI_ALT_PRESSED, 0, 'd'): - /* kill-word */ - clear = 0; - - size_t k; - for (k = first + cursor; k < len && line[k] == ' '; k++) - clear++; - for (; k < len && line[k] != ' '; k++) - clear++; - - for (size_t i = first + cursor; i + clear < len; i++) - line[i] = line[i + clear]; - len -= clear; - line[len] = '\0'; - continue; - - case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'w'): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('w')): - case KEYPRESS(EFI_ALT_PRESSED, 0, '\b'): - /* backward-kill-word */ - clear = 0; - if ((first + cursor) > 0 && line[first + cursor-1] == ' ') { - cursor_left(&cursor, &first); - clear++; - while ((first + cursor) > 0 && line[first + cursor] == ' ') { - cursor_left(&cursor, &first); - clear++; - } - } - while ((first + cursor) > 0 && line[first + cursor-1] != ' ') { - cursor_left(&cursor, &first); - clear++; - } - - for (size_t i = first + cursor; i + clear < len; i++) - line[i] = line[i + clear]; - len -= clear; - line[len] = '\0'; - continue; - - case KEYPRESS(0, SCAN_DELETE, 0): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'd'): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('d')): - if (len == 0) - continue; - if (first + cursor == len) - continue; - for (size_t i = first + cursor; i < len; i++) - line[i] = line[i+1]; - clear = 1; - len--; - continue; - - case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'k'): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('k')): - /* kill-line */ - line[first + cursor] = '\0'; - clear = len - (first + cursor); - len = first + cursor; - continue; - - case KEYPRESS(0, 0, '\n'): - case KEYPRESS(0, 0, '\r'): - case KEYPRESS(0, SCAN_F3, 0): /* EZpad Mini 4s firmware sends malformed events */ - case KEYPRESS(0, SCAN_F3, '\r'): /* Teclast X98+ II firmware sends malformed events */ - if (!streq16(line, *line_in)) { - free(*line_in); - *line_in = TAKE_PTR(line); - } - return true; - - case KEYPRESS(0, 0, '\b'): - if (len == 0) - continue; - if (first == 0 && cursor == 0) - continue; - for (size_t i = first + cursor-1; i < len; i++) - line[i] = line[i+1]; - clear = 1; - len--; - if (cursor > 0) - cursor--; - if (cursor > 0 || first == 0) - continue; - /* show full line if it fits */ - if (len < x_max) { - cursor = first; - first = 0; - continue; - } - /* jump left to see what we delete */ - if (first > 10) { - first -= 10; - cursor = 10; - } else { - cursor = first; - first = 0; - } - continue; - - case KEYPRESS(0, 0, ' ') ... KEYPRESS(0, 0, '~'): - case KEYPRESS(0, 0, 0x80) ... KEYPRESS(0, 0, 0xffff): - if (len+1 == size) - continue; - for (size_t i = len; i > first + cursor; i--) - line[i] = line[i-1]; - line[first + cursor] = KEYCHAR(key); - len++; - line[len] = '\0'; - if (cursor+1 < x_max) - cursor++; - else if (first + cursor < len) - first++; - continue; - } - } -} static size_t entry_lookup_key(Config *config, size_t start, char16_t key) { assert(config); diff --git a/src/boot/line-edit.c b/src/boot/line-edit.c new file mode 100644 index 00000000000..ca9dc067a5d --- /dev/null +++ b/src/boot/line-edit.c @@ -0,0 +1,254 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "console.h" +#include "line-edit.h" +#include "util.h" + +static void cursor_left(size_t *cursor, size_t *first) { + assert(cursor); + assert(first); + + if ((*cursor) > 0) + (*cursor)--; + else if ((*first) > 0) + (*first)--; +} + +static void cursor_right(size_t *cursor, size_t *first, size_t x_max, size_t len) { + assert(cursor); + assert(first); + + if ((*cursor)+1 < x_max) + (*cursor)++; + else if ((*first) + (*cursor) < len) + (*first)++; +} + +bool line_edit(char16_t **line_in, size_t x_max, size_t y_pos) { + _cleanup_free_ char16_t *line = NULL, *print = NULL; + size_t size, len, first = 0, cursor = 0, clear = 0; + + /* Edit the line and return true if it should be executed, false if not. */ + + assert(line_in); + + len = strlen16(*line_in); + size = len + 1024; + line = xnew(char16_t, size); + print = xnew(char16_t, x_max + 1); + strcpy16(line, strempty(*line_in)); + + for (;;) { + EFI_STATUS err; + uint64_t key; + size_t j, cursor_color = EFI_TEXT_ATTR_SWAP(COLOR_EDIT); + + j = MIN(len - first, x_max); + memcpy(print, line + first, j * sizeof(char16_t)); + while (clear > 0 && j < x_max) { + clear--; + print[j++] = ' '; + } + print[j] = '\0'; + + /* See comment at edit_line() call site for why we start at 1. */ + print_at(1, y_pos, COLOR_EDIT, print); + + if (!print[cursor]) + print[cursor] = ' '; + print[cursor+1] = '\0'; + do { + print_at(cursor + 1, y_pos, cursor_color, print + cursor); + cursor_color = EFI_TEXT_ATTR_SWAP(cursor_color); + + err = console_key_read(&key, 750 * 1000); + if (!IN_SET(err, EFI_SUCCESS, EFI_TIMEOUT, EFI_NOT_READY)) + return false; + + print_at(cursor + 1, y_pos, COLOR_EDIT, print + cursor); + } while (err != EFI_SUCCESS); + + switch (key) { + case KEYPRESS(0, SCAN_ESC, 0): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'c'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'g'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('c')): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('g')): + return false; + + case KEYPRESS(0, SCAN_HOME, 0): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'a'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('a')): + /* beginning-of-line */ + cursor = 0; + first = 0; + continue; + + case KEYPRESS(0, SCAN_END, 0): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'e'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('e')): + /* end-of-line */ + cursor = len - first; + if (cursor+1 >= x_max) { + cursor = x_max-1; + first = len - (x_max-1); + } + continue; + + case KEYPRESS(0, SCAN_DOWN, 0): + case KEYPRESS(EFI_ALT_PRESSED, 0, 'f'): + case KEYPRESS(EFI_CONTROL_PRESSED, SCAN_RIGHT, 0): + /* forward-word */ + while (line[first + cursor] == ' ') + cursor_right(&cursor, &first, x_max, len); + while (line[first + cursor] && line[first + cursor] != ' ') + cursor_right(&cursor, &first, x_max, len); + continue; + + case KEYPRESS(0, SCAN_UP, 0): + case KEYPRESS(EFI_ALT_PRESSED, 0, 'b'): + case KEYPRESS(EFI_CONTROL_PRESSED, SCAN_LEFT, 0): + /* backward-word */ + if ((first + cursor) > 0 && line[first + cursor-1] == ' ') { + cursor_left(&cursor, &first); + while ((first + cursor) > 0 && line[first + cursor] == ' ') + cursor_left(&cursor, &first); + } + while ((first + cursor) > 0 && line[first + cursor-1] != ' ') + cursor_left(&cursor, &first); + continue; + + case KEYPRESS(0, SCAN_RIGHT, 0): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'f'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('f')): + /* forward-char */ + if (first + cursor == len) + continue; + cursor_right(&cursor, &first, x_max, len); + continue; + + case KEYPRESS(0, SCAN_LEFT, 0): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'b'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('b')): + /* backward-char */ + cursor_left(&cursor, &first); + continue; + + case KEYPRESS(EFI_CONTROL_PRESSED, SCAN_DELETE, 0): + case KEYPRESS(EFI_ALT_PRESSED, 0, 'd'): + /* kill-word */ + clear = 0; + + size_t k; + for (k = first + cursor; k < len && line[k] == ' '; k++) + clear++; + for (; k < len && line[k] != ' '; k++) + clear++; + + for (size_t i = first + cursor; i + clear < len; i++) + line[i] = line[i + clear]; + len -= clear; + line[len] = '\0'; + continue; + + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'w'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('w')): + case KEYPRESS(EFI_ALT_PRESSED, 0, '\b'): + /* backward-kill-word */ + clear = 0; + if ((first + cursor) > 0 && line[first + cursor-1] == ' ') { + cursor_left(&cursor, &first); + clear++; + while ((first + cursor) > 0 && line[first + cursor] == ' ') { + cursor_left(&cursor, &first); + clear++; + } + } + while ((first + cursor) > 0 && line[first + cursor-1] != ' ') { + cursor_left(&cursor, &first); + clear++; + } + + for (size_t i = first + cursor; i + clear < len; i++) + line[i] = line[i + clear]; + len -= clear; + line[len] = '\0'; + continue; + + case KEYPRESS(0, SCAN_DELETE, 0): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'd'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('d')): + if (len == 0) + continue; + if (first + cursor == len) + continue; + for (size_t i = first + cursor; i < len; i++) + line[i] = line[i+1]; + clear = 1; + len--; + continue; + + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'k'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('k')): + /* kill-line */ + line[first + cursor] = '\0'; + clear = len - (first + cursor); + len = first + cursor; + continue; + + case KEYPRESS(0, 0, '\n'): + case KEYPRESS(0, 0, '\r'): + case KEYPRESS(0, SCAN_F3, 0): /* EZpad Mini 4s firmware sends malformed events */ + case KEYPRESS(0, SCAN_F3, '\r'): /* Teclast X98+ II firmware sends malformed events */ + if (!streq16(line, *line_in)) { + free(*line_in); + *line_in = TAKE_PTR(line); + } + return true; + + case KEYPRESS(0, 0, '\b'): + if (len == 0) + continue; + if (first == 0 && cursor == 0) + continue; + for (size_t i = first + cursor-1; i < len; i++) + line[i] = line[i+1]; + clear = 1; + len--; + if (cursor > 0) + cursor--; + if (cursor > 0 || first == 0) + continue; + /* show full line if it fits */ + if (len < x_max) { + cursor = first; + first = 0; + continue; + } + /* jump left to see what we delete */ + if (first > 10) { + first -= 10; + cursor = 10; + } else { + cursor = first; + first = 0; + } + continue; + + case KEYPRESS(0, 0, ' ') ... KEYPRESS(0, 0, '~'): + case KEYPRESS(0, 0, 0x80) ... KEYPRESS(0, 0, 0xffff): + if (len+1 == size) + continue; + for (size_t i = len; i > first + cursor; i--) + line[i] = line[i-1]; + line[first + cursor] = KEYCHAR(key); + len++; + line[len] = '\0'; + if (cursor+1 < x_max) + cursor++; + else if (first + cursor < len) + first++; + continue; + } + } +} diff --git a/src/boot/line-edit.h b/src/boot/line-edit.h new file mode 100644 index 00000000000..523108d42e3 --- /dev/null +++ b/src/boot/line-edit.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "efi.h" + +bool line_edit(char16_t **line_in, size_t x_max, size_t y_pos); diff --git a/src/boot/meson.build b/src/boot/meson.build index 69c8e40ad3a..f88371b6b13 100644 --- a/src/boot/meson.build +++ b/src/boot/meson.build @@ -307,6 +307,7 @@ libefi_sources = files( systemd_boot_sources = files( 'boot.c', + 'line-edit.c', ) stub_sources = files(