From e41c7e56c4b2562440742c41bc592197fc2cfd29 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Tue, 6 May 2025 17:25:43 -0500 Subject: [PATCH 1/3] update boot.py to use adafruit_argv_file, fix new file functionality, allow saving files in /saves/ and /sd/ when main storage is readonly, ctrl-p hotkey to open picker from editor --- builtin_apps/editor/adafruit_editor/editor.py | 60 ++++++++++++++++--- builtin_apps/editor/adafruit_editor/picker.py | 48 ++++++++++++--- src/boot.py | 33 ++++------ src/code.py | 5 +- 4 files changed, 106 insertions(+), 40 deletions(-) diff --git a/builtin_apps/editor/adafruit_editor/editor.py b/builtin_apps/editor/adafruit_editor/editor.py index 9321b6a..7907920 100644 --- a/builtin_apps/editor/adafruit_editor/editor.py +++ b/builtin_apps/editor/adafruit_editor/editor.py @@ -116,6 +116,16 @@ def clamp(x, lower, upper): return x +def _count_leading_characters(text, char): + count = 0 + for c in text: + if c == char: + count += 1 + else: + break + return count + + class Cursor: def __init__(self, row=0, col=0, col_hint=None): self.row = row @@ -220,18 +230,31 @@ def end(window, buffer, cursor): def editor(stdscr, filename, visible_cursor): # pylint: disable=too-many-branches,too-many-statements + + def _only_spaces_before(cursor): + i = cursor.col - 1 + while i >= 0: + print(f"i: {i} chr: '{buffer.lines[cursor.row][i]}'") + if buffer.lines[cursor.row][i] != " ": + return False + i -= 1 + return True if os_exists(filename): with open(filename, "r", encoding="utf-8") as f: buffer = Buffer(f.read().splitlines()) else: buffer = Buffer([""]) + absolute_filepath = os.getcwd() + "/" + filename + user_message = None window = Window(curses.LINES - 1, curses.COLS - 1) cursor = Cursor() - visible_cursor.text = buffer[0][0] - last_refresh_time = -1 + try: + visible_cursor.text = buffer[0][0] + except IndexError: + visible_cursor.text = " " stdscr.erase() @@ -258,10 +281,13 @@ def setline(row, line): row = curses.LINES - 1 if user_message is None: - if util.readonly(): - line = f"{filename:12} (mnt RO ^W) | ^R run | ^C: quit{gc_mem_free_hint()}" + if (not absolute_filepath.startswith("/saves/") and + not absolute_filepath.startswith("/sd/") and + util.readonly()): + + line = f"{filename:12} (mnt RO ^W) | ^R run | ^P exit & picker | ^C: quit{gc_mem_free_hint()}" else: - line = f"{filename:12} (mnt RW ^W) | ^R run | ^S save | ^X: save & exit | ^C: exit no save{gc_mem_free_hint()}" + line = f"{filename:12} (mnt RW ^W) | ^R run | ^S save | ^X: save & exit | ^P exit & picker | ^C: exit no save{gc_mem_free_hint()}" else: line = user_message user_message = None @@ -289,13 +315,16 @@ def setline(row, line): print(row) print("---- end file contents ----") elif k == "\x13": # Ctrl-S - if not util.readonly(): + if (absolute_filepath.startswith("/saves/") or + absolute_filepath.startswith("/sd/") or + util.readonly()): + with open(filename, "w", encoding="utf-8") as f: for row in buffer: f.write(f"{row}\n") user_message = "Saved" else: - print("Unable to Save due to readonly mode!") + user_message = "Unable to Save due to readonly mode!" elif k == "\x11": # Ctrl-Q print("ctrl-Q") for row in buffer: @@ -315,6 +344,10 @@ def setline(row, line): supervisor.set_next_code_file(filename, sticky_on_reload=False, reload_on_error=True, working_directory=Path(filename).parent.absolute()) supervisor.reload() + elif k == "\x10": # Ctrl-P + supervisor.set_next_code_file("/apps/editor/code.py", sticky_on_reload=False, reload_on_error=True, + working_directory="/apps/editor") + supervisor.reload() elif k == "KEY_HOME": home(window, buffer, cursor) elif k == "KEY_END": @@ -344,8 +377,12 @@ def setline(row, line): elif k == "KEY_RIGHT": right(window, buffer, cursor) elif k == "\n": + leading_spaces = _count_leading_characters(buffer.lines[cursor.row], " ") buffer.split(cursor) right(window, buffer, cursor) + for i in range(leading_spaces): + buffer.insert(cursor, " ") + right(window, buffer, cursor) elif k in ("KEY_DELETE", "\x04"): print("delete") if cursor.row < len(buffer.lines) - 1 or \ @@ -359,8 +396,13 @@ def setline(row, line): elif k in ("KEY_BACKSPACE", "\x7f", "\x08"): print(f"backspace {bytes(k, 'utf-8')}") if (cursor.row, cursor.col) > (0, 0): - left(window, buffer, cursor) - buffer.delete(cursor) + if cursor.col > 0 and buffer.lines[cursor.row][cursor.col-1] == " " and _only_spaces_before(cursor): + for i in range(4): + left(window, buffer, cursor) + buffer.delete(cursor) + else: + left(window, buffer, cursor) + buffer.delete(cursor) else: print(f"unhandled k: {k}") diff --git a/builtin_apps/editor/adafruit_editor/picker.py b/builtin_apps/editor/adafruit_editor/picker.py index 9db71ae..93b515f 100644 --- a/builtin_apps/editor/adafruit_editor/picker.py +++ b/builtin_apps/editor/adafruit_editor/picker.py @@ -4,6 +4,8 @@ # SPDX-License-Identifier: MIT import os +import time + import usb_cdc from . import dang as curses from . import util @@ -115,23 +117,55 @@ def _draw_file_list(): # ctrl-N elif k == "\x0E": - if not util.readonly(): + # if not util.readonly(): new_file_name = new_file(stdscr) if new_file_name is not None: return new_file_name + else: + time.sleep(2) + stdscr.erase() + old_idx = None + _draw_file_list() + + + +def terminal_input(stdscr, message): + stdscr.erase() + stdscr.addstr(0, 0, message) + input_str_list = [] + k = stdscr.getkey() + while k != "\n": + if len(k) == 1 and " " <= k <= "~": + input_str_list.append(k) + stdscr.addstr(0, len(message) + len(input_str_list) - 1, k) + elif k == "\x08": + input_str_list.pop(len(input_str_list) - 1) + stdscr.addstr(0, len(message) + len(input_str_list) - 1, k) + k = stdscr.getkey() + # submit after enter pressed + return "".join(input_str_list) # pylint: disable=inconsistent-return-statements def new_file(stdscr): stdscr.erase() - new_file_name = input("New File Name: ") + new_file_name = terminal_input(stdscr, "New File Name: ") if os_exists(new_file_name): - print("Error: File Already Exists") + stdscr.addstr(1,0, "Error: File Already Exists") return - with open(new_file_name, "w") as f: - f.write("") - - return new_file_name + print(f"new filename: {new_file_name}") + if not new_file_name.startswith("/saves/") and not new_file_name.startswith("/sd/"): + if not util.readonly(): + with open(new_file_name, "w") as f: + f.write("") + + return new_file_name + else: + stdscr.addstr(1, 0, "Error: Cannot create file in readonly storage") + else: + with open(new_file_name, "w") as f: + f.write("") + return new_file_name def _files_list(): diff --git a/src/boot.py b/src/boot.py index 5757f83..a8f201f 100644 --- a/src/boot.py +++ b/src/boot.py @@ -1,7 +1,9 @@ +# SPDX-FileCopyrightText: 2025 Tim Cocks for Adafruit Industries +# SPDX-License-Identifier: MIT import os import supervisor -from argv_file_helper import argv_filename +from adafruit_argv_file import read_argv, write_argv import json import storage @@ -15,15 +17,10 @@ 2-N: args to pass to next code file """ -try: - arg_file = argv_filename(__file__) - print(f"arg files: {arg_file}") - with open(arg_file, "r") as f: - args = json.load(f) - print("args file found and loaded") - os.remove(arg_file) - print("args file removed") + +args = read_argv(__file__) +if args is not None and len(args) > 0: readonly = args[0] next_code_file = None @@ -35,20 +32,14 @@ remaining_args = args[2:] if remaining_args is not None: - next_code_argv_filename = argv_filename(next_code_file) - with open(next_code_argv_filename, "w") as f: - f.write(json.dumps(remaining_args)) - print("next code args written") + write_argv(next_code_file, remaining_args) - print(f"setting storage readonly to: {readonly}") + # print(f"setting storage readonly to: {readonly}") storage.remount("/", readonly=readonly) next_code_file = next_code_file - supervisor.set_next_code_file(next_code_file) - print(f"launching: {next_code_file}") - # os.rename("/saves/.boot_py_argv", "/saves/.not_boot_py_argv") - + supervisor.set_next_code_file(next_code_file, sticky_on_reload=False, reload_on_error=True, + working_directory="/".join(next_code_file.split("/")[:-1])) -except OSError: - print("launching boot animation") - supervisor.set_next_code_file("boot_animation.py") \ No newline at end of file +else: + supervisor.set_next_code_file("boot_animation.py") diff --git a/src/code.py b/src/code.py index 7ecb2ee..219647c 100644 --- a/src/code.py +++ b/src/code.py @@ -1,9 +1,8 @@ -# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-FileCopyrightText: 2025 Tim Cocks for Adafruit Industries # SPDX-License-Identifier: MIT """ -This example uses adafruit_display_text.label to display text using a custom font -loaded by adafruit_bitmap_font +Fruit Jam OS Launcher """ import array import atexit From dfe3eec91252f5f2a0cacf1cc7fd3ee3599270f0 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Thu, 8 May 2025 11:00:51 -0500 Subject: [PATCH 2/3] add mouse support to editor, make getkey() non-blocking, swap visible cursor to use TilePaletteMapper, use ctrl-O for open, fix saving files, highlight bottom row --- builtin_apps/editor/adafruit_editor/dang.py | 4 +- builtin_apps/editor/adafruit_editor/editor.py | 276 ++++++++++-------- builtin_apps/editor/adafruit_editor/picker.py | 14 +- builtin_apps/editor/code.py | 50 +++- 4 files changed, 208 insertions(+), 136 deletions(-) diff --git a/builtin_apps/editor/adafruit_editor/dang.py b/builtin_apps/editor/adafruit_editor/dang.py index 4225099..1b16912 100644 --- a/builtin_apps/editor/adafruit_editor/dang.py +++ b/builtin_apps/editor/adafruit_editor/dang.py @@ -113,7 +113,9 @@ def getkey(self): self._pending = pending[1:] return pending[0] else: - c = self._terminal_read_blocking() + c = self._terminal_read_timeout(50) + if c is None: + return None c = pending + c code = special_keys.get(c) diff --git a/builtin_apps/editor/adafruit_editor/editor.py b/builtin_apps/editor/adafruit_editor/editor.py index 7907920..95ab136 100644 --- a/builtin_apps/editor/adafruit_editor/editor.py +++ b/builtin_apps/editor/adafruit_editor/editor.py @@ -229,7 +229,7 @@ def end(window, buffer, cursor): window.horizontal_scroll(cursor) -def editor(stdscr, filename, visible_cursor): # pylint: disable=too-many-branches,too-many-statements +def editor(stdscr, filename, mouse=None, terminal_tilegrid=None): # pylint: disable=too-many-branches,too-many-statements def _only_spaces_before(cursor): i = cursor.col - 1 @@ -244,17 +244,25 @@ def _only_spaces_before(cursor): buffer = Buffer(f.read().splitlines()) else: buffer = Buffer([""]) - - absolute_filepath = os.getcwd() + "/" + filename + print(f"cwd: {os.getcwd()} | {os.getcwd() == "/apps/editor"}") + if os.getcwd() != "/apps/editor" and os.getcwd() != "/": + absolute_filepath = os.getcwd() + "/" + filename + else: + absolute_filepath = filename user_message = None + user_message_shown_time = -1 + + clicked_tile_coords = [None, None] window = Window(curses.LINES - 1, curses.COLS - 1) cursor = Cursor() - try: - visible_cursor.text = buffer[0][0] - except IndexError: - visible_cursor.text = " " + terminal_tilegrid.pixel_shader[cursor.col,cursor.row] = [1, 0] + old_cursor_pos = (cursor.col, cursor.row) + # try: + # visible_cursor.text = buffer[0][0] + # except IndexError: + # visible_cursor.text = " " stdscr.erase() @@ -267,6 +275,7 @@ def setline(row, line): line += " " * (window.n_cols - len(line)) stdscr.addstr(row, 0, line) + print(f"cwd: {os.getcwd()} | abs path: {absolute_filepath} | filename: {filename}") while True: lastrow = 0 for row, line in enumerate(buffer[window.row: window.row + window.n_rows]): @@ -285,148 +294,179 @@ def setline(row, line): not absolute_filepath.startswith("/sd/") and util.readonly()): - line = f"{filename:12} (mnt RO ^W) | ^R run | ^P exit & picker | ^C: quit{gc_mem_free_hint()}" + line = f"{absolute_filepath:12} (mnt RO ^W) | ^R run | ^O Open | ^C: quit{gc_mem_free_hint()}" else: - line = f"{filename:12} (mnt RW ^W) | ^R run | ^S save | ^X: save & exit | ^P exit & picker | ^C: exit no save{gc_mem_free_hint()}" + line = f"{absolute_filepath:12} (mnt RW ^W) | ^R run | ^O Open | ^S save | ^X: save & exit | ^C: exit no save{gc_mem_free_hint()}" else: line = user_message - user_message = None + if user_message_shown_time + 3.0 < time.monotonic(): + user_message = None setline(row, line) stdscr.move(*window.translate(cursor)) - old_cursor_pos = (cursor.col, cursor.row) + # display.refresh(minimum_frames_per_second=20) k = stdscr.getkey() - # print(repr(k)) - if len(k) == 1 and " " <= k <= "~": - buffer.insert(cursor, k) - for _ in k: - right(window, buffer, cursor) - elif k == "\x18": # ctrl-x - if not util.readonly(): - with open(filename, "w", encoding="utf-8") as f: + if k is not None: + # print(repr(k)) + if len(k) == 1 and " " <= k <= "~": + buffer.insert(cursor, k) + for _ in k: + right(window, buffer, cursor) + elif k == "\x18": # ctrl-x + if not util.readonly(): + with open(filename, "w", encoding="utf-8") as f: + for row in buffer: + f.write(f"{row}\n") + return + else: + print("Unable to Save due to readonly mode! File Contents:") + print("---- begin file contents ----") for row in buffer: - f.write(f"{row}\n") - return - else: - print("Unable to Save due to readonly mode! File Contents:") - print("---- begin file contents ----") + print(row) + print("---- end file contents ----") + elif k == "\x13": # Ctrl-S + print(absolute_filepath) + print(f"starts with saves: {absolute_filepath.startswith("/saves/")}") + print(f"stars saves: {absolute_filepath.startswith("/saves/")}") + print(f"stars sd: {absolute_filepath.startswith("/sd/")}") + print(f"readonly: {util.readonly()}") + if (absolute_filepath.startswith("/saves/") or + absolute_filepath.startswith("/sd/") or + not util.readonly()): + + with open(absolute_filepath, "w", encoding="utf-8") as f: + for row in buffer: + f.write(f"{row}\n") + user_message = "Saved" + user_message_shown_time = time.monotonic() + else: + user_message = "Unable to Save due to readonly mode!" + user_message_shown_time = time.monotonic() + elif k == "\x11": # Ctrl-Q + print("ctrl-Q") for row in buffer: print(row) - print("---- end file contents ----") - elif k == "\x13": # Ctrl-S - if (absolute_filepath.startswith("/saves/") or - absolute_filepath.startswith("/sd/") or - util.readonly()): + elif k == "\x17": # Ctrl-W + boot_args_file = argv_filename("/boot.py") + with open(boot_args_file, "w") as f: + f.write(json.dumps([not util.readonly(), "/apps/editor/code.py", Path(filename).absolute()])) + microcontroller.reset() + elif k == "\x12": # Ctrl-R + print(f"Run: {filename}") + + launcher_code_args_file = argv_filename("/code.py") + with open(launcher_code_args_file, "w") as f: + f.write(json.dumps(["/apps/editor/code.py", Path(filename).absolute()])) + + supervisor.set_next_code_file(filename, sticky_on_reload=False, reload_on_error=True, + working_directory=Path(filename).parent.absolute()) + supervisor.reload() + elif k == "\x0f": # Ctrl-O + + supervisor.set_next_code_file("/apps/editor/code.py", sticky_on_reload=False, reload_on_error=True, + working_directory="/apps/editor") + supervisor.reload() + + + elif k == "KEY_HOME": + home(window, buffer, cursor) + elif k == "KEY_END": + end(window, buffer, cursor) + elif k == "KEY_LEFT": + left(window, buffer, cursor) + elif k == "KEY_DOWN": - with open(filename, "w", encoding="utf-8") as f: - for row in buffer: - f.write(f"{row}\n") - user_message = "Saved" - else: - user_message = "Unable to Save due to readonly mode!" - elif k == "\x11": # Ctrl-Q - print("ctrl-Q") - for row in buffer: - print(row) - elif k == "\x17": # Ctrl-W - boot_args_file = argv_filename("/boot.py") - with open(boot_args_file, "w") as f: - f.write(json.dumps([not util.readonly(), "/apps/editor/code.py", Path(filename).absolute()])) - microcontroller.reset() - elif k == "\x12": # Ctrl-R - print(f"Run: {filename}") - - launcher_code_args_file = argv_filename("/code.py") - with open(launcher_code_args_file, "w") as f: - f.write(json.dumps(["/apps/editor/code.py", Path(filename).absolute()])) - - supervisor.set_next_code_file(filename, sticky_on_reload=False, reload_on_error=True, - working_directory=Path(filename).parent.absolute()) - supervisor.reload() - elif k == "\x10": # Ctrl-P - supervisor.set_next_code_file("/apps/editor/code.py", sticky_on_reload=False, reload_on_error=True, - working_directory="/apps/editor") - supervisor.reload() - elif k == "KEY_HOME": - home(window, buffer, cursor) - elif k == "KEY_END": - end(window, buffer, cursor) - elif k == "KEY_LEFT": - left(window, buffer, cursor) - elif k == "KEY_DOWN": - - cursor.down(buffer) - window.down(buffer, cursor) - window.horizontal_scroll(cursor) - print(f"scroll pos: {window.row}") - elif k == "KEY_PGDN": - for _ in range(window.n_rows): cursor.down(buffer) window.down(buffer, cursor) window.horizontal_scroll(cursor) - elif k == "KEY_UP": - cursor.up(buffer) - window.up(cursor) - window.horizontal_scroll(cursor) - elif k == "KEY_PGUP": - for _ in range(window.n_rows): + print(f"scroll pos: {window.row}") + elif k == "KEY_PGDN": + for _ in range(window.n_rows): + cursor.down(buffer) + window.down(buffer, cursor) + window.horizontal_scroll(cursor) + elif k == "KEY_UP": cursor.up(buffer) window.up(cursor) window.horizontal_scroll(cursor) - elif k == "KEY_RIGHT": - right(window, buffer, cursor) - elif k == "\n": - leading_spaces = _count_leading_characters(buffer.lines[cursor.row], " ") - buffer.split(cursor) - right(window, buffer, cursor) - for i in range(leading_spaces): - buffer.insert(cursor, " ") + elif k == "KEY_PGUP": + for _ in range(window.n_rows): + cursor.up(buffer) + window.up(cursor) + window.horizontal_scroll(cursor) + elif k == "KEY_RIGHT": + right(window, buffer, cursor) + elif k == "\n": + leading_spaces = _count_leading_characters(buffer.lines[cursor.row], " ") + buffer.split(cursor) right(window, buffer, cursor) - elif k in ("KEY_DELETE", "\x04"): - print("delete") - if cursor.row < len(buffer.lines) - 1 or \ - cursor.col < len(buffer.lines[cursor.row]): - buffer.delete(cursor) - try: - visible_cursor.text = buffer.lines[cursor.row][cursor.col] - except IndexError: - visible_cursor.text = " " - - elif k in ("KEY_BACKSPACE", "\x7f", "\x08"): - print(f"backspace {bytes(k, 'utf-8')}") - if (cursor.row, cursor.col) > (0, 0): - if cursor.col > 0 and buffer.lines[cursor.row][cursor.col-1] == " " and _only_spaces_before(cursor): - for i in range(4): + for i in range(leading_spaces): + buffer.insert(cursor, " ") + right(window, buffer, cursor) + elif k in ("KEY_DELETE", "\x04"): + print("delete") + if cursor.row < len(buffer.lines) - 1 or \ + cursor.col < len(buffer.lines[cursor.row]): + buffer.delete(cursor) + # try: + # visible_cursor.text = buffer.lines[cursor.row][cursor.col] + # except IndexError: + # visible_cursor.text = " " + + elif k in ("KEY_BACKSPACE", "\x7f", "\x08"): + print(f"backspace {bytes(k, 'utf-8')}") + if (cursor.row, cursor.col) > (0, 0): + if cursor.col > 0 and buffer.lines[cursor.row][cursor.col-1] == " " and _only_spaces_before(cursor): + for i in range(4): + left(window, buffer, cursor) + buffer.delete(cursor) + else: left(window, buffer, cursor) buffer.delete(cursor) - else: - left(window, buffer, cursor) - buffer.delete(cursor) - else: - print(f"unhandled k: {k}") - print(f"unhandled K: {ord(k)}") - print(f"unhandled k: {bytes(k, 'utf-8')}") + else: + print(f"unhandled k: {k}") + print(f"unhandled K: {ord(k)}") + print(f"unhandled k: {bytes(k, 'utf-8')}") + + + if mouse is not None: + pressed_btns = mouse.update() + if pressed_btns is not None and "left" in pressed_btns: + clicked_tile_coords[0] = mouse.x // 6 + clicked_tile_coords[1] = mouse.y // 12 + + if clicked_tile_coords[0] > len(buffer.lines[clicked_tile_coords[1]]): + clicked_tile_coords[0] = len(buffer.lines[clicked_tile_coords[1]]) + cursor.row = clicked_tile_coords[1] + cursor.col = clicked_tile_coords[0] + + # print("updating visible cursor") # print(f"anchored pos: {((cursor.col * 6) - 1, (cursor.row * 12) + 20)}") - if old_cursor_pos != (cursor.col, cursor.row): - # terminal_tilegrid.pixel_shader[old_cursor_pos[0], old_cursor_pos[1]] = [0,1] - # terminal_tilegrid.pixel_shader[cursor.col, cursor.row] = [1,0] + if old_cursor_pos != (cursor.col, cursor.row): + # print(f"old cursor: {old_cursor_pos}, new: {(cursor.col, cursor.row)}") + terminal_tilegrid.pixel_shader[old_cursor_pos[0], old_cursor_pos[1]] = [0,1] + terminal_tilegrid.pixel_shader[cursor.col, cursor.row] = [1,0] + # print(f"old: {terminal_tilegrid.pixel_shader[old_cursor_pos[0], old_cursor_pos[1]]} new: {terminal_tilegrid.pixel_shader[cursor.col, cursor.row]}") # visible_cursor.anchored_position = ((cursor.col * 6) - 1, (cursor.row * 12) + 20) - visible_cursor.anchored_position = ((cursor.col * 6), ((cursor.row - window.row) * 12)) - try: - visible_cursor.text = buffer.lines[cursor.row][cursor.col] - except IndexError: - visible_cursor.text = " " + # visible_cursor.anchored_position = ((cursor.col * 6), ((cursor.row - window.row) * 12)) + # + # try: + # visible_cursor.text = buffer.lines[cursor.row][cursor.col] + # except IndexError: + # visible_cursor.text = " " + + old_cursor_pos = (cursor.col, cursor.row) -def edit(filename, terminal=None, visible_cursor=None): +def edit(filename, terminal=None, mouse=None, terminal_tilegrid=None): with MaybeDisableReload(): if terminal is None: return curses.wrapper(editor, filename) else: - return curses.custom_terminal_wrapper(terminal, editor, filename, visible_cursor) + return curses.custom_terminal_wrapper(terminal, editor, filename, mouse, terminal_tilegrid) diff --git a/builtin_apps/editor/adafruit_editor/picker.py b/builtin_apps/editor/adafruit_editor/picker.py index 93b515f..04cab6e 100644 --- a/builtin_apps/editor/adafruit_editor/picker.py +++ b/builtin_apps/editor/adafruit_editor/picker.py @@ -135,12 +135,13 @@ def terminal_input(stdscr, message): input_str_list = [] k = stdscr.getkey() while k != "\n": - if len(k) == 1 and " " <= k <= "~": - input_str_list.append(k) - stdscr.addstr(0, len(message) + len(input_str_list) - 1, k) - elif k == "\x08": - input_str_list.pop(len(input_str_list) - 1) - stdscr.addstr(0, len(message) + len(input_str_list) - 1, k) + if k is not None: + if len(k) == 1 and " " <= k <= "~": + input_str_list.append(k) + stdscr.addstr(0, len(message) + len(input_str_list) - 1, k) + elif k == "\x08": + input_str_list.pop(len(input_str_list) - 1) + stdscr.addstr(0, len(message) + len(input_str_list) - 1, k) k = stdscr.getkey() # submit after enter pressed return "".join(input_str_list) @@ -149,6 +150,7 @@ def terminal_input(stdscr, message): # pylint: disable=inconsistent-return-statements def new_file(stdscr): stdscr.erase() + print(f"cwd inside new_file(): {os.getcwd()}") new_file_name = terminal_input(stdscr, "New File Name: ") if os_exists(new_file_name): stdscr.addstr(1,0, "Error: File Already Exists") diff --git a/builtin_apps/editor/code.py b/builtin_apps/editor/code.py index a5990fd..891de85 100644 --- a/builtin_apps/editor/code.py +++ b/builtin_apps/editor/code.py @@ -1,16 +1,17 @@ +import atexit import os import supervisor from displayio import Group, Palette, TileGrid import terminalio -from lvfontio import OnDiskFont from adafruit_display_text.bitmap_label import Label -from adafruit_bitmap_font import bitmap_font from adafruit_editor import editor, picker from tilepalettemapper import TilePaletteMapper -import json from adafruit_argv_file import read_argv, write_argv from adafruit_fruitjam.peripherals import request_display_config +from adafruit_usb_host_mouse import find_and_init_boot_mouse + +print(f"cwd in editor/code.py: {os.getcwd()}") request_display_config(720, 400) display = supervisor.runtime.display @@ -30,19 +31,30 @@ screen_size = (display.width // char_size[0], display.height // char_size[1]) print(screen_size) -terminal_area = TileGrid(bitmap=font.bitmap, pixel_shader=font_palette, width=screen_size[0], height=screen_size[1], +highlight_palette = Palette(3) +highlight_palette[0] = 0x000000 +highlight_palette[1] = 0xFFFFFF +highlight_palette[2] = 0xC9C9C9 + + + +terminal_area = TileGrid(bitmap=font.bitmap, width=screen_size[0], height=screen_size[1], tile_width=char_size[0], tile_height=char_size[1]) +tpm = TilePaletteMapper(highlight_palette, 2, terminal_area) +for x in range(screen_size[0]): + tpm[x,screen_size[1]-1] = [2,0] + main_group.append(terminal_area) terminal = terminalio.Terminal(terminal_area, font) -visible_cursor = Label(terminalio.FONT, text="", - color=0x000000, background_color=0xeeeeee, padding_left=1) -visible_cursor.hidden = False -visible_cursor.anchor_point = (0, 0) -visible_cursor.anchored_position = (0, 0) -main_group.append(visible_cursor) +# visible_cursor = Label(terminalio.FONT, text="", +# color=0x000000, background_color=0xeeeeee, padding_left=1) +# visible_cursor.hidden = False +# visible_cursor.anchor_point = (0, 0) +# visible_cursor.anchored_position = (0, 0) +# main_group.append(visible_cursor) file = None args = read_argv(__file__) @@ -51,4 +63,20 @@ else: file = picker.pick_file(terminal) -editor.edit(file, terminal, visible_cursor) +mouse = find_and_init_boot_mouse() +if mouse is not None: + mouse.x = display.width - 6 + main_group.append(mouse.tilegrid) + + +def atexit_callback(): + """ + re-attach USB devices to kernel if needed. + :return: + """ + print("inside atexit callback") + if mouse is not None: + mouse.release() + +atexit.register(atexit_callback) +editor.edit(file, terminal, mouse, terminal_area) From 45bd92db149a34ba2ce0d2515a98e1102a52aedc Mon Sep 17 00:00:00 2001 From: foamyguy Date: Thu, 8 May 2025 11:34:28 -0500 Subject: [PATCH 3/3] include libs used by builtin_apps in the zips --- build.py | 7 ++++++- mock_boot_out.txt | 4 ++++ requirements.txt | 2 ++ 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 mock_boot_out.txt create mode 100644 requirements.txt diff --git a/build.py b/build.py index 87bcd9b..1b31a4f 100644 --- a/build.py +++ b/build.py @@ -4,6 +4,7 @@ import shutil from pathlib import Path import requests +from circup.commands import main as circup_cli LEARN_PROJECT_URLS = [ "https://cdn-learn.adafruit.com/downloads/zip/3194974/Metro/Metro_RP2350_Snake.zip?timestamp={}", @@ -102,7 +103,11 @@ def create_font_specific_zip(font_path: Path, src_dir: Path, learn_projects_dir: # copy builtin apps shutil.copytree("builtin_apps", apps_dir, dirs_exist_ok=True) - + shutil.copyfile("mock_boot_out.txt", temp_dir / "boot_out.txt") + for builtin_app_dir in os.listdir("builtin_apps"): + circup_cli(["--path", temp_dir, "install", "--auto", "--auto-file", f"apps/{builtin_app_dir}/code.py"], + standalone_mode=False) + os.remove(temp_dir / "boot_out.txt") # Create the final zip file with zipfile.ZipFile(output_zip, 'w', zipfile.ZIP_DEFLATED) as zf: for file_path in temp_dir.rglob("*"): diff --git a/mock_boot_out.txt b/mock_boot_out.txt new file mode 100644 index 0000000..2cb6002 --- /dev/null +++ b/mock_boot_out.txt @@ -0,0 +1,4 @@ +Adafruit CircuitPython 10.0.0-alpha.4-2-g01da5c7c88-dirty on 2025-05-07; Adafruit Fruit Jam with rp2350b +Board ID:adafruit_fruit_jam +UID: +boot.py output: diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..3efa359 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +circup +requests