Compare commits

...

4 Commits

Author SHA1 Message Date
Lephenixnoir d7d1df46bb pe: add scrolling in shell 2022-12-11 17:33:27 +01:00
Lephenixnoir 40a2de1a5f pe: add shell scrollbar (render only) 2022-12-11 17:33:27 +01:00
Lephenixnoir 8f5af62f9f ports/sh: fix AC/ON locking after some time
The Python program being run is in charge of keyboard events with the
gint module. Most programs don't care though, and simply let events
accumulate until the queue is full.

The async filter is able to receive events even when the queue is full.
However, it filtered only AC/ON presses, not releases, so the releases
were sent back to the driver to queue. This was impossible as the queue
was full, so the release was never recorded. This failure then repeated
at every tick, forever.

Since the key was never properly released, further presses were just
seen as a continuation of the current press and thus did not produce
any new event, so the async filter was no longer called and the Python
program could no longer be interrupted.
2022-12-11 17:33:27 +01:00
Lephenixnoir edf57c6f07 ports/sh: basic improvements to USB debugging (not quite ready yet)
The USB driver isn't stable enough to support funky uncontrolled async
messages yet. Tends to freeze and need a reconnect. Future problem.
2022-12-11 17:33:27 +01:00
8 changed files with 242 additions and 76 deletions

View File

@ -20,6 +20,8 @@ Bugs to fix:
- Fix not resetting the shell when importing a file from command-line
- Fix current working directory not changing during a module import (for
relative imports)
- Fix accumulated events being processed when the program "ends" (if we can
detect that)
Python features:
- Compare features with existing implementations and other brands

View File

@ -103,7 +103,7 @@ void console_line_update_render_lines(console_line_t *line, int width)
}
int console_line_render(int x, int y, console_line_t *line, int w, int dy,
int show_from, int cursor)
int show_from, int show_until, int cursor)
{
char const *p = line->data;
char const *endline = p + strlen(p);
@ -114,7 +114,7 @@ int console_line_render(int x, int y, console_line_t *line, int w, int dy,
char const *endscreen = drsize(p, NULL, w, NULL);
int len = endscreen - p;
if(line_number >= show_from) {
if(line_number >= show_from && line_number < show_until) {
dtext_opt(x, y, C_BLACK, C_NONE, DTEXT_LEFT, DTEXT_TOP, p, len);
if(cursor >= line_offset && cursor <= line_offset + len) {
int w, h;
@ -139,8 +139,16 @@ console_t *console_create(int backlog_size)
console_t *cons = malloc(sizeof *cons);
cons->lines = NULL;
cons->line_count = 0;
cons->absolute_lineno = 1;
cons->backlog_size = max(backlog_size, PE_CONSOLE_LINE_MAX_LENGTH);
cons->render_needed = true;
cons->render_font = NULL;
cons->render_width = 0;
cons->render_lines = 0;
cons->render_total_lines = 0;
if(!console_new_line(cons)) {
free(cons);
return NULL;
@ -169,49 +177,39 @@ bool console_new_line(console_t *cons)
return true;
}
/* static console_line_t *console_get_absolute_line(console_t const *cons,
int lineno)
{
int i = lineno - cons->absolute_lineno;
return (i >= 0 && i < cons->line_count) ? &cons->lines[i] : NULL;
} */
void console_clean_backlog(console_t *cons)
{
int total_size = 0, i = cons->line_count;
/* Find how many lines we can keep while using at most cons->backlog_size
bytes to store them. The console must always have at least one line. */
int first_kept = cons->line_count;
int line_size = 0;
int total_size = 0;
/* Determine how many lines (starting from the end) we can fit */
while(total_size < cons->backlog_size) {
i--;
total_size += cons->lines[i].size;
do {
total_size += line_size;
first_kept--;
line_size = cons->lines[first_kept].size;
}
while(first_kept > 0 && total_size + line_size <= cons->backlog_size);
/* Make sure to keep at least one line */
int delete_until = i + (i < cons->line_count - 1);
int remains = cons->line_count - delete_until;
/* Remove `delete_until` lines */
for(int j = 0; j < delete_until; j++)
/* Remove old lines */
for(int j = 0; j < first_kept; j++)
console_line_deinit(&cons->lines[j]);
/* Don't realloc() yet, it will happen soon enough anyway */
memmove(cons->lines, cons->lines + delete_until, remains);
cons->line_count = remains;
}
memmove(cons->lines, cons->lines + first_kept,
(cons->line_count - first_kept) * sizeof cons->lines[0]);
cons->line_count -= first_kept;
void console_render(int x, int y, console_t const *cons, int w, int dy,
int lines)
{
int watermark = lines;
for(int i = 0; i < cons->line_count; i++) {
console_line_update_render_lines(&cons->lines[i], w);
watermark -= cons->lines[i].render_lines;
}
/* Show only visible lines */
for(int i = 0; i < cons->line_count; i++) {
console_line_t *line = &cons->lines[i];
bool show_cursor = (i == cons->line_count - 1);
if(watermark + line->render_lines > 0)
y = console_line_render(x, y, line, w, dy, -watermark,
show_cursor ? cons->cursor : -1);
watermark += line->render_lines;
}
/* Update the absolute number of the first line */
cons->absolute_lineno += first_kept;
}
void console_clear_render_flag(console_t *cons)
@ -227,6 +225,104 @@ void console_destroy(console_t *cons)
free(cons);
}
//=== Rendering functions ===//
/* Compute the shell's horizontal layout as:
1. Text width (region where the text is renderer)
2. Spacing left of the scrollbar
3. Width of the scrollbar */
static void view_params(int w,
int *text_w, int *scroll_spacing, int *scroll_width)
{
#ifdef FX9860G
if(text_w)
*text_w = w - 2;
if(scroll_spacing)
*scroll_spacing = 1;
if(scroll_width)
*scroll_width = 1;
#else
if(text_w)
*text_w = w - 4;
if(scroll_spacing)
*scroll_spacing = 2;
if(scroll_width)
*scroll_width = 2;
#endif
}
void console_compute_view(console_t *cons, font_t const *font,
int width, int lines)
{
cons->render_font = font;
cons->render_width = width;
cons->render_lines = lines;
cons->render_total_lines = 0;
int text_w;
view_params(width, &text_w, NULL, NULL);
for(int i = 0; i < cons->line_count; i++) {
console_line_update_render_lines(&cons->lines[i], text_w);
cons->render_total_lines += cons->lines[i].render_lines;
}
}
console_scrollpos_t console_clamp_scrollpos(console_t const *cons,
console_scrollpos_t pos)
{
/* No scrolling case */
if(cons->render_total_lines < cons->render_lines)
return 0;
/* Normal clamp */
return max(0, min(pos, cons->render_total_lines - cons->render_lines));
}
void console_render(int x, int y0, console_t const *cons, int dy,
console_scrollpos_t pos)
{
int total_lines = cons->render_total_lines;
int visible_lines = cons->render_lines;
int w = cons->render_width;
int watermark = visible_lines - total_lines + pos;
int y = y0;
int text_w, scroll_spacing, scroll_w;
view_params(w, &text_w, &scroll_spacing, &scroll_w);
font_t const *old_font = dfont(cons->render_font);
/* Show only visible lines */
for(int i = 0; i < cons->line_count; i++) {
console_line_t *line = &cons->lines[i];
bool show_cursor = (i == cons->line_count - 1);
if(watermark + line->render_lines > 0)
y = console_line_render(x, y, line, text_w, dy, -watermark,
visible_lines - watermark, show_cursor ? cons->cursor : -1);
watermark += line->render_lines;
}
dfont(old_font);
/* Scrollbar */
if(total_lines > visible_lines) {
int first_shown = total_lines - visible_lines - pos;
int h = dy * visible_lines;
int y1 = y0 + h * first_shown / total_lines;
int y2 = y0 + h * (first_shown + visible_lines) / total_lines;
int color = C_BLACK;
#ifdef FXCG50
if(pos == 0) color = C_RGB(24, 24, 24);
#endif
drect(x + text_w + scroll_spacing, y1,
x + text_w + scroll_spacing + scroll_w - 1, y2,
color);
}
}
//=== Edition functions ===//
bool console_write_block_at_cursor(console_t *cons, char const *str, int n)

View File

@ -12,12 +12,18 @@
// * Dynamically-sized lines with reflow
// * Cap memory usage based on the total amount of text, not just line count
// * Basic ANSI-escape-based edition features (but only on the last line)
//
// The console tries fairly hard to focus on text manipulation and separate
// rendering. To render, one must first compute a "view" of the terminal, which
// essentially determines line wrapping and scrolling bounds, and then use that
// view and a valid scroll position within it to render.
//---
#ifndef __PYTHONEXTRA_CONSOLE_H
#define __PYTHONEXTRA_CONSOLE_H
#include <gint/keyboard.h>
#include <gint/display.h>
#include <stdbool.h>
/* Maximum line length, to ensure the console can threshold its memory usage
@ -62,6 +68,10 @@ void console_line_delete(console_line_t *line, int p, int n);
/* Update the number of render lines for the chosen width. */
void console_line_update_render_lines(console_line_t *line, int width);
/* Render a vertical slice of the wrapped line. */
int console_line_render(int x, int y, console_line_t *line, int w, int dy,
int show_from, int show_until, int cursor);
//=== Terminal emulator ===//
typedef struct
@ -75,14 +85,26 @@ typedef struct
than PE_CONSOLE_LINE_MAX_LENGTH. */
int backlog_size;
/* Absolute number of the first line in the dynamic line array. This is
the line number in the user-facing sense (it includes lines that were
cleaned up to free memory). */
int absolute_lineno;
/* Cursor position within the last line. */
int cursor;
/* Whether new data has been added and a frame should be rendered. */
bool render_needed;
/* View parameters from last console_compute_view() */
font_t const *render_font;
int render_width;
int render_lines;
int render_total_lines;
} console_t;
/* Scroll position measured as a number of lines up from the bottom. */
typedef int console_scrollpos_t;
/* Create a new console with the specified backlog size. */
console_t *console_create(int backlog_size);
@ -93,15 +115,33 @@ bool console_new_line(console_t *cons);
/* Clean up backlog if the total memory usage is exceeded. */
void console_clean_backlog(console_t *cons);
/* Render the console with the current font, at (x,y) in a rectangle of width
`w` and `lines` total lines separated by `dy` pixels. */
void console_render(int x, int y, console_t const *cons, int w, int dy,
int lines);
/* Clear the console's render flag, which is used to notify of changes. This
function is used when handing control of the display from the console to a
Python program so the console doesn't override the program's output. */
void console_clear_render_flag(console_t *cons);
/* Destroy the console and free all associated memory. */
void console_destroy(console_t *cons);
//=== Rendering interface ===//
/* Compute a view of the console for rendering and scrolling.
@font Font to render text with (use to compute line wrapping)
@width View width in pixels
@lines Number of text lines (spacing can be changed later) */
void console_compute_view(console_t *cons, font_t const *font,
int width, int lines);
/* Clamp a scrolling position to the range valid of the last computed view. */
console_scrollpos_t console_clamp_scrollpos(console_t const *cons,
console_scrollpos_t pos);
/* Render the console at (x,y). The render `width`, the number of `lines` and
the text `font` are all as specified by the latest console_compute_view().
`dy` indicates line height. */
void console_render(int x, int y, console_t const *cons, int dy,
console_scrollpos_t pos);
//=== Edition functions ===//
/* Write string at the cursor's position within the last line. This writes a

View File

@ -4,9 +4,11 @@
#include <gint/kmalloc.h>
#include <gint/display.h>
#include <gint/keyboard.h>
#include <gint/timer.h>
#include <gint/cpu.h>
#include <stdio.h>
#include <stdlib.h>
#include "py/mpstate.h"
void pe_debug_panic(char const *msg)
{
@ -23,44 +25,53 @@ void pe_debug_panic(char const *msg)
#if PE_DEBUG
#if 0 // Timeout fxlink not supported yet
static bool timeout_popup(void)
{
dclear(C_BLACK);
dtext_opt(DWIDTH/2, DHEIGHT/2 - 21, C_WHITE, C_NONE, DTEXT_MIDDLE,
DTEXT_MIDDLE, "An fxlink message timed out!");
dtext_opt(DWIDTH/2, DHEIGHT/2 - 7, C_WHITE, C_NONE, DTEXT_MIDDLE,
DTEXT_MIDDLE, "Start fxlink and press [EXE]:");
dtext_opt(DWIDTH/2, DHEIGHT/2 + 7, C_WHITE, C_NONE, DTEXT_MIDDLE,
DTEXT_MIDDLE, "% fxlink -iqw");
dtext_opt(DWIDTH/2, DHEIGHT/2 + 21, C_WHITE, C_NONE, DTEXT_MIDDLE,
DTEXT_MIDDLE, "or press [EXIT] to drop the message");
dupdate();
while(1) {
int key = getkey().key;
if(key == KEY_EXE)
return false;
if(key == KEY_EXIT)
return true;
}
}
#endif
void pe_debug_init(void)
{
usb_interface_t const *intf[] = { &usb_ff_bulk, NULL };
usb_open(intf, GINT_CALL_NULL);
dclear(C_BLACK);
dtext_opt(DWIDTH/2, DHEIGHT/2 - 3, C_WHITE, C_NONE, DTEXT_MIDDLE,
DTEXT_BOTTOM, "Waiting for USB connection...");
dtext_opt(DWIDTH/2, DHEIGHT/2 + 3, C_WHITE, C_NONE, DTEXT_MIDDLE,
DTEXT_TOP, "% fxlink -iqw");
dupdate();
while(!usb_is_open()) sleep();
usb_open_wait();
}
void pe_debug_printf(char const *fmt, ...)
int pe_debug_printf(char const *fmt, ...)
{
char str_default[256];
char str[512];
va_list args;
va_start(args, fmt);
char *str;
vasprintf(&str, fmt, args);
int rc = vsnprintf(str, sizeof str, fmt, args);
va_end(args);
if(str == NULL) {
va_start(args, fmt);
str = str_default;
vsnprintf(str, sizeof str_default, fmt, args);
va_end(args);
}
if(usb_is_open())
usb_fxlink_text(str, 0);
usb_open_wait();
usb_fxlink_text(str, 0);
return rc;
}
/* This function is used in MicroPython. */
void DEBUG_printf(char const *fmt, ...)
int DEBUG_printf(char const *fmt, ...)
__attribute__((alias("pe_debug_printf")));
void pe_debug_kmalloc(void)
@ -78,8 +89,8 @@ void pe_debug_kmalloc(void)
void pe_debug_screenshot(void)
{
if(usb_is_open())
usb_fxlink_screenshot(true);
usb_open_wait();
usb_fxlink_screenshot(true);
}
#endif /* PE_DEBUG */

View File

@ -25,7 +25,7 @@ __attribute__((noreturn));
/* Print to the debug stream. This function is also called DEBUG_printf in
MicroPython code. */
void pe_debug_printf(char const *fmt, ...);
int pe_debug_printf(char const *fmt, ...);
/* Print information about allocation status. */
void pe_debug_kmalloc(void);

View File

@ -113,13 +113,17 @@ static char *path_to_module(char const *path)
interrupt MicroPython instead. */
static bool async_filter(key_event_t ev)
{
if(mp_interrupt_char < 0)
return true;
if(ev.type == KEYEV_DOWN && ev.key == KEY_ACON) {
/* This function is designed to be called asynchronously. */
mp_sched_keyboard_interrupt();
/* Gobble all events related to AC/ON to make sure that the keyboard driver
treats them as handled. Otherwise, we'd run the risk of filling the
event queue (if the user doesn't read from it) thus preventing the
driver from handling AC/ON releases, which disables further presses. */
if(mp_interrupt_char >= 0 && ev.key == KEY_ACON) {
/* This function supports asynchronous calls, by design. */
if(ev.type == KEYEV_DOWN)
mp_sched_keyboard_interrupt();
return false;
}
return true;
}

View File

@ -94,6 +94,7 @@ widget_shell *widget_shell_create(console_t *console, void *parent)
s->font = dfont_default();
s->color = C_BLACK;
s->line_spacing = 0;
s->scroll = 0;
s->lines = 0;
s->shift = MOD_IDLE;
@ -167,11 +168,11 @@ static void widget_shell_poly_render(void *s0, int x, int y)
widget_shell *s = s0;
int line_height = s->font->line_height + s->line_spacing;
font_t const *old_font = dfont(s->font);
console_render(x, y, s->console, jwidget_content_width(s), line_height,
console_compute_view(s->console, s->font, jwidget_content_width(s),
s->lines);
s->scroll = console_clamp_scrollpos(s->console, s->scroll);
console_render(x, y, s->console, line_height, s->scroll);
console_clear_render_flag(s->console);
dfont(old_font);
}
static void widget_shell_update_mod(widget_shell *s, key_event_t ev)
@ -206,6 +207,16 @@ static bool widget_shell_poly_event(void *s0, jevent e)
widget_shell_update_mod(s, ev);
return true;
}
if(ev.type != KEYEV_UP && ev.key == KEY_UP) {
s->scroll++;
s->widget.update = true;
return true;
}
if(ev.type != KEYEV_UP && ev.key == KEY_DOWN) {
s->scroll--;
s->widget.update = true;
return true;
}
if(ev.type == KEYEV_UP)
return false;

View File

@ -56,6 +56,8 @@ typedef struct {
uint16_t color;
/* Extra line spacing */
int8_t line_spacing;
/* Current scroll position */
console_scrollpos_t scroll;
/* Internal information */
int timer_id;