diff --git a/src/#__main__.py# b/src/#__main__.py# new file mode 100755 index 0000000..3c8e6f2 --- /dev/null +++ b/src/#__main__.py# @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +import curses +import argparse +import sys +from curses import wrapper +from .libs.config import Config +from .libs.absapi import ABSApi, Endpoint, ABSResponse, Library +from .libs.terminal import Terminal, ExitTerminal + +parser = argparse.ArgumentParser() +_: argparse.Action = parser.add_argument("--debug", help="Debug the program", action="store_true") +args = parser.parse_args() + +config: Config = Config() + +# stdscr: curses.window = curses.initscr() + +def main(stdscr: curses.window) -> None: + terminal: Terminal = Terminal(stdscr) + Terminal.startup(terminal) + +if __name__ == "__main__": + if args.debug: + _api = ABSApi() + _response: list[Library]|None = _api.get_libraries() + _id = _response[0].id if _response else "" + _books = _api.get_books(_id) + for book in _books if _books else []: + breakpoint() + print(book.media.metadata.title) + sys.exit(0) + try: + while True: + wrapper(main) + except ExitTerminal: + pass + diff --git a/src/__main__.py b/src/__main__.py index b1ceee9..eb06884 100755 --- a/src/__main__.py +++ b/src/__main__.py @@ -9,12 +9,21 @@ from .libs.terminal import Terminal, ExitTerminal parser = argparse.ArgumentParser() _: argparse.Action = parser.add_argument("--debug", help="Debug the program", action="store_true") +_: argparse.Action = parser.add_argument("--remote-debug", help="Enable remote debugging with pudb", action="store_true") +_: argparse.Action = parser.add_argument("--debug-port", help="Port for remote debugging", type=int, default=6899) args = parser.parse_args() config: Config = Config() # stdscr: curses.window = curses.initscr() def main(stdscr: curses.window) -> None: - terminal: Terminal = Terminal(stdscr) + if args.remote_debug: + import os + from pudb.remote import set_trace + _term_size: os.terminal_size = os.get_terminal_size() + # set_trace(term_size=(_term_size.columns, _term_size.lines), host='0.0.0.0', port=args.debug_port) + terminal: Terminal = Terminal(stdscr, remote_debug=args.remote_debug, debug_port=args.debug_port) + else: + terminal: Terminal = Terminal(stdscr) Terminal.startup(terminal) if __name__ == "__main__": diff --git a/src/libs/terminal.py b/src/libs/terminal.py index f148234..8198aab 100644 --- a/src/libs/terminal.py +++ b/src/libs/terminal.py @@ -6,9 +6,11 @@ class ExitTerminal(Exception): pass class Terminal: - def __init__(self, stdscr: curses.window, running: bool = True) -> None: + def __init__(self, stdscr: curses.window, running: bool = True, remote_debug: bool = False, debug_port: int = 6899) -> None: self.stdscr: curses.window = stdscr self.running: bool = running + self.remote_debug: bool = remote_debug + self.debug_port: int = debug_port self.api: ABSApi = ABSApi() self.height: int = self.stdscr.getmaxyx()[0] self.width: int = self.stdscr.getmaxyx()[1] @@ -28,6 +30,11 @@ class Terminal: ] self.active_window: str = "library" self.playing: bool = False + self.row: int = 0 + self.book_count: int = 0 + self.libraries: list[Library]|None = None + self.books: list[Book]|None = None + self.library_pad: curses.window|None = None def alignC(self, text: str) -> int: return (self.width // 2) - (len(text) // 2) @@ -35,9 +42,41 @@ class Terminal: def alignR(self, text: str) -> int: return (self.height // 2) - (len(text) // 2) + def pad_content(self, strings: list[str], columns: int) -> list[str]: + col_width = self.width // columns + padded_strings = [] + for i, s in enumerate(strings): + if i < len(strings) - 1: # Don't pad the last column + padded_strings.append(s.ljust(col_width)) + else: + padded_strings.append(s) + return padded_strings + + def initialize_colors(self) -> None: + if curses.has_colors() and curses.can_change_color(): + curses.start_color() + curses.init_color(17, 500, 500, 900) + curses.init_pair(1, curses.COLOR_WHITE, 17) + curses.init_pair(2, curses.COLOR_BLACK, curses.COLOR_CYAN) + curses.init_pair(3, curses.COLOR_BLACK, curses.COLOR_WHITE) + curses.init_pair(4, curses.COLOR_WHITE, curses.COLOR_BLACK) + + elif curses.has_colors(): + curses.start_color() + curses.use_default_colors() + curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE) + curses.init_pair(2, curses.COLOR_BLACK, curses.COLOR_CYAN) + curses.init_pair(3, curses.COLOR_BLACK, curses.COLOR_WHITE) + curses.init_pair(4, curses.COLOR_WHITE, curses.COLOR_BLACK) + + else: + pass + + def startup(self) -> None: self.stdscr.clear() self.menu() + self.initialize_colors() self.stdscr.refresh() self.library_window() self.footer() @@ -76,20 +115,46 @@ class Terminal: return curses.newwin(self.main_content_height, self.main_content_width, self.main_content_height + 3, 0) + def highlight_line(self, pad: curses.window, line: int, content: str) -> None: + if curses.has_colors() and curses.can_change_color(): + pad.addstr(line, 0, "".join(content), curses.color_pair(17)) + elif curses.has_colors(): + pad.addstr(line, 0, "".join(content), curses.color_pair(2)) + else: + pad.addstr(line, 0, "".join(content), curses.A_STANDOUT) + + def library_window(self) -> None: - _y: int = 0 - libraries: list[Library]|None = self.api.get_libraries() - books: list[Book]|None = self.api.get_books(libraries[0].id) if libraries else None - if books is not None: - _pad: curses.window = self.new_main_pad(len(books) if libraries else 1) - for book in books: - _pad.addstr(_y, 0, f"- {book.media.metadata.title}\t {book.media.metadata.authorName} \t {book.media.duration}") + # Load data if needed + if self.libraries is None: + self.libraries = self.api.get_libraries() + if self.books is None: + self.books = self.api.get_books(self.libraries[0].id) if self.libraries else None + self.book_count = self.books.__len__() if self.books is not None else 0 + + if self.books is not None: + # Always recreate the pad to update highlighting + self.library_pad = self.new_main_pad(len(self.books)) + _y: int = 0 + for book in self.books: + _row_contents: list[str] = [f"{_y} :: {book.media.metadata.title}", f"{book.media.metadata.seriesName}", f"{book.media.metadata.authorName}", f"{book.media.duration}"] + _padded_contents = self.pad_content(_row_contents, 4) + # Highlight the current row + if _y == self.row: + self.highlight_line(self.library_pad, _y, "".join(_padded_contents)) + # self.library_pad.addstr(_y, 0, "".join(_padded_contents), curses.A_REVERSE) + else: + self.library_pad.addstr(_y, 0, "".join(_padded_contents), curses.A_STANDOUT if _y % 2 == 0 else curses.A_NORMAL) _y += 1 - _pad.refresh(0, 0, self.main_content_begin_y, self.main_content_begin_x, - self.main_content_begin_y + self.main_content_height - 1, - self.main_content_begin_x + self.main_content_width - 1) else: self.help_window("No books found in the library.") + + if self.library_pad is not None: + # Always scroll by one line to create smooth scrolling effect + scroll_offset = max(0, self.row - (self.main_content_height // 2)) + self.library_pad.refresh(scroll_offset, 0, self.main_content_begin_y, self.main_content_begin_x, + self.main_content_begin_y + self.main_content_height - 1, + self.main_content_begin_x + self.main_content_width - 1) self.active_window = "library" def search_window(self) -> None: @@ -140,6 +205,22 @@ class Terminal: self.playing = True self.footer(f"Playing {self.playing} - Feature not implemented yet.") + def set_remote_breakpoint(self) -> None: + if self.remote_debug: + import os + from pudb.remote import set_trace + _term_size: os.terminal_size = os.get_terminal_size() + set_trace(term_size=(_term_size.columns, _term_size.lines), host='0.0.0.0', port=self.debug_port) + self.stdscr.clear() + self.menu() + self.stdscr.refresh() + self.library_window() if self.active_window == "library" else None + self.search_window() if self.active_window == "search" else None + self.now_playing_window() if self.active_window == "now_playing" else None + self.settings_window() if self.active_window == "settings" else None + self.help_window(None) if self.active_window == "help" else None + self.footer() + def key_handler(self) -> None: while True: key = self.stdscr.getch() @@ -158,3 +239,14 @@ class Terminal: raise ExitTerminal() elif key == ord(' '): self.play_toggle() + + elif key == ord("j") and self.active_window == "library": + if self.row < self.book_count - 1: self.row = self.row + 1 + else: self.row = 0 + self.library_window() + elif key == ord("k") and self.active_window == "library": + if self.row > 0: self.row = self.row - 1 + else: self.row = self.book_count - 1 + self.library_window() + elif key == ord("d") and self.remote_debug: + self.set_remote_breakpoint()