From b2f5e65c06eba6cb98fd8bddc167639fbd19dc51 Mon Sep 17 00:00:00 2001 From: nicholai Date: Mon, 8 Sep 2025 04:47:13 -0600 Subject: [PATCH] trying to make a TUI --- simple_tui.py | 264 +++++++++++++++++++++++++++++++++++++++++++++++ wallpaper_tui.py | 158 ++++++++++++++++++++++++++++ 2 files changed, 422 insertions(+) create mode 100755 simple_tui.py create mode 100644 wallpaper_tui.py diff --git a/simple_tui.py b/simple_tui.py new file mode 100755 index 0000000..1ee651f --- /dev/null +++ b/simple_tui.py @@ -0,0 +1,264 @@ +#!/usr/bin/env python3 +""" +Simple TUI for managing wallpaper scripts using curses +""" + +import curses +import subprocess +import sys +import os +from pathlib import Path + +class WallpaperTUI: + def __init__(self, stdscr): + self.stdscr = stdscr + self.scripts_dir = Path(__file__).parent + self.sort_script = self.scripts_dir / "sort_images_by_color.py" + self.sync_script = self.scripts_dir / "wallpapersync.sh" + self.wallpaper_menu = self.scripts_dir / "pywal" / "wallpapermenu.sh" + self.waybar_script = self.scripts_dir / "pywal" / "update-waybar-theme.sh" + self.openrgb_script = self.scripts_dir / "pywal" / "pywal-openrgb.py" + + # Initialize colors + curses.start_color() + curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLACK) + curses.init_pair(2, curses.COLOR_GREEN, curses.COLOR_BLACK) + curses.init_pair(3, curses.COLOR_RED, curses.COLOR_BLACK) + curses.init_pair(4, curses.COLOR_YELLOW, curses.COLOR_BLACK) + + self.current_selection = 0 + self.running = True + + def draw_menu(self): + self.stdscr.clear() + height, width = self.stdscr.getmaxyx() + + # Title + title = "Wallpaper Manager TUI" + self.stdscr.addstr(1, (width - len(title)) // 2, title, curses.color_pair(2) | curses.A_BOLD) + + # Menu items + menu_items = [ + "1. Sort and Rename Images", + "2. Sync Wallpapers", + "3. Select Wallpaper (Menu)", + "4. Update Waybar Theme", + "5. Set OpenRGB Color", + "6. Exit" + ] + + start_y = height // 4 + for i, item in enumerate(menu_items): + y = start_y + i * 2 + if i == self.current_selection: + self.stdscr.addstr(y, width // 2 - len(item) // 2, item, curses.color_pair(4) | curses.A_REVERSE) + else: + self.stdscr.addstr(y, width // 2 - len(item) // 2, item, curses.color_pair(1)) + + # Instructions + instructions = "Use arrow keys to navigate, Enter to select, Q to quit" + self.stdscr.addstr(height - 2, width // 2 - len(instructions) // 2, instructions, curses.color_pair(1)) + + self.stdscr.refresh() + + def run_sort(self): + try: + self.stdscr.clear() + self.stdscr.addstr(1, 1, "Running sort script...", curses.color_pair(2)) + self.stdscr.refresh() + + result = subprocess.run([sys.executable, str(self.sort_script), "."], + capture_output=True, text=True, check=True) + + self.stdscr.addstr(3, 1, "Sort completed successfully!", curses.color_pair(2)) + self.stdscr.addstr(5, 1, "Output:", curses.color_pair(1)) + + output_lines = result.stdout.split('\n') + for i, line in enumerate(output_lines[:15]): # Show first 15 lines + if i < 14: + self.stdscr.addstr(6 + i, 1, line[:width-2]) + + self.stdscr.addstr(22, 1, "Press any key to continue...", curses.color_pair(1)) + self.stdscr.refresh() + self.stdscr.getch() + + except subprocess.CalledProcessError as e: + self.stdscr.clear() + self.stdscr.addstr(1, 1, "Sort failed!", curses.color_pair(3)) + self.stdscr.addstr(3, 1, "Error:", curses.color_pair(1)) + error_lines = e.stderr.split('\n') + for i, line in enumerate(error_lines[:10]): + if i < 9: + self.stdscr.addstr(5 + i, 1, line[:width-2]) + self.stdscr.addstr(16, 1, "Press any key to continue...", curses.color_pair(1)) + self.stdscr.refresh() + self.stdscr.getch() + except Exception as e: + self.stdscr.clear() + self.stdscr.addstr(1, 1, "Error running sort script:", curses.color_pair(3)) + self.stdscr.addstr(3, 1, str(e), curses.color_pair(1)) + self.stdscr.addstr(5, 1, "Press any key to continue...", curses.color_pair(1)) + self.stdscr.refresh() + self.stdscr.getch() + + def run_sync(self): + try: + self.stdscr.clear() + self.stdscr.addstr(1, 1, "Running sync script...", curses.color_pair(2)) + self.stdscr.refresh() + + result = subprocess.run([str(self.sync_script)], + capture_output=True, text=True, check=True) + + self.stdscr.addstr(3, 1, "Sync completed successfully!", curses.color_pair(2)) + self.stdscr.addstr(5, 1, "Output:", curses.color_pair(1)) + + output_lines = result.stdout.split('\n') + for i, line in enumerate(output_lines[:15]): # Show first 15 lines + if i < 14: + self.stdscr.addstr(6 + i, 1, line[:width-2]) + + self.stdscr.addstr(22, 1, "Press any key to continue...", curses.color_pair(1)) + self.stdscr.refresh() + self.stdscr.getch() + + except subprocess.CalledProcessError as e: + self.stdscr.clear() + self.stdscr.addstr(1, 1, "Sync failed!", curses.color_pair(3)) + self.stdscr.addstr(3, 1, "Error:", curses.color_pair(1)) + error_lines = e.stderr.split('\n') + for i, line in enumerate(error_lines[:10]): + if i < 9: + self.stdscr.addstr(5 + i, 1, line[:width-2]) + self.stdscr.addstr(16, 1, "Press any key to continue...", curses.color_pair(1)) + self.stdscr.refresh() + self.stdscr.getch() + except Exception as e: + self.stdscr.clear() + self.stdscr.addstr(1, 1, "Error running sync script:", curses.color_pair(3)) + self.stdscr.addstr(3, 1, str(e), curses.color_pair(1)) + self.stdscr.addstr(5, 1, "Press any key to continue...", curses.color_pair(1)) + self.stdscr.refresh() + self.stdscr.getch() + + def run_wallpaper_menu(self): + try: + self.stdscr.clear() + self.stdscr.addstr(1, 1, "Opening wallpaper menu...", curses.color_pair(2)) + self.stdscr.addstr(3, 1, "(Interactive menu will open)", curses.color_pair(1)) + self.stdscr.addstr(5, 1, "Note: This requires nsxiv to be installed.", curses.color_pair(1)) + self.stdscr.addstr(7, 1, "Press any key to continue...", curses.color_pair(1)) + self.stdscr.refresh() + self.stdscr.getch() + except Exception as e: + self.stdscr.clear() + self.stdscr.addstr(1, 1, "Error opening wallpaper menu:", curses.color_pair(3)) + self.stdscr.addstr(3, 1, str(e), curses.color_pair(1)) + self.stdscr.addstr(5, 1, "Press any key to continue...", curses.color_pair(1)) + self.stdscr.refresh() + self.stdscr.getch() + + def run_waybar_update(self): + try: + self.stdscr.clear() + self.stdscr.addstr(1, 1, "Waybar theme update initiated", curses.color_pair(2)) + self.stdscr.addstr(3, 1, "Note: This requires a wallpaper path.", curses.color_pair(1)) + self.stdscr.addstr(5, 1, "Press any key to continue...", curses.color_pair(1)) + self.stdscr.refresh() + self.stdscr.getch() + except Exception as e: + self.stdscr.clear() + self.stdscr.addstr(1, 1, "Error updating waybar:", curses.color_pair(3)) + self.stdscr.addstr(3, 1, str(e), curses.color_pair(1)) + self.stdscr.addstr(5, 1, "Press any key to continue...", curses.color_pair(1)) + self.stdscr.refresh() + self.stdscr.getch() + + def run_openrgb(self): + try: + self.stdscr.clear() + self.stdscr.addstr(1, 1, "Setting OpenRGB color...", curses.color_pair(2)) + self.stdscr.refresh() + + result = subprocess.run([sys.executable, str(self.openrgb_script)], + capture_output=True, text=True, check=True) + + self.stdscr.addstr(3, 1, "OpenRGB color set successfully!", curses.color_pair(2)) + self.stdscr.addstr(5, 1, "Output:", curses.color_pair(1)) + + output_lines = result.stdout.split('\n') + for i, line in enumerate(output_lines[:10]): # Show first 10 lines + if i < 9: + self.stdscr.addstr(6 + i, 1, line[:width-2]) + + self.stdscr.addstr(16, 1, "Press any key to continue...", curses.color_pair(1)) + self.stdscr.refresh() + self.stdscr.getch() + + except subprocess.CalledProcessError as e: + self.stdscr.clear() + self.stdscr.addstr(1, 1, "OpenRGB failed!", curses.color_pair(3)) + self.stdscr.addstr(3, 1, "Error:", curses.color_pair(1)) + error_lines = e.stderr.split('\n') + for i, line in enumerate(error_lines[:10]): + if i < 9: + self.stdscr.addstr(5 + i, 1, line[:width-2]) + self.stdscr.addstr(16, 1, "Press any key to continue...", curses.color_pair(1)) + self.stdscr.refresh() + self.stdscr.getch() + except Exception as e: + self.stdscr.clear() + self.stdscr.addstr(1, 1, "Error setting OpenRGB color:", curses.color_pair(3)) + self.stdscr.addstr(3, 1, str(e), curses.color_pair(1)) + self.stdscr.addstr(5, 1, "Press any key to continue...", curses.color_pair(1)) + self.stdscr.refresh() + self.stdscr.getch() + + def handle_input(self, key): + if key == ord('q') or key == ord('Q'): + self.running = False + elif key == curses.KEY_UP: + self.current_selection = max(0, self.current_selection - 1) + elif key == curses.KEY_DOWN: + self.current_selection = min(5, self.current_selection + 1) + elif key == 10: # Enter key + if self.current_selection == 0: + self.run_sort() + elif self.current_selection == 1: + self.run_sync() + elif self.current_selection == 2: + self.run_wallpaper_menu() + elif self.current_selection == 3: + self.run_waybar_update() + elif self.current_selection == 4: + self.run_openrgb() + elif self.current_selection == 5: + self.running = False + + def run(self): + curses.curs_set(0) # Hide cursor + self.stdscr.nodelay(False) + self.stdscr.timeout(100) + + while self.running: + self.draw_menu() + key = self.stdscr.getch() + self.handle_input(key) + + curses.endwin() + +def main(): + # Check if we're in a terminal + if not sys.stdout.isatty(): + print("This program must be run in a terminal.") + return + + try: + curses.wrapper(WallpaperTUI) + except KeyboardInterrupt: + print("Exiting...") + except Exception as e: + print(f"An error occurred: {e}") + +if __name__ == "__main__": + main() diff --git a/wallpaper_tui.py b/wallpaper_tui.py new file mode 100644 index 0000000..4f3dde3 --- /dev/null +++ b/wallpaper_tui.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python3 +""" +TUI for managing wallpaper scripts with Gemini/claude-style interface +""" + +import os +import subprocess +import sys +from pathlib import Path +from typing import List, Optional +import asyncio +import threading +from datetime import datetime + +import rich +from rich.console import Console +from rich.panel import Panel +from rich.table import Table +from rich.text import Text +from rich.layout import Layout +from rich.live import Live +from rich.prompt import Prompt, Confirm +from rich.syntax import Syntax +from textual.app import App, ComposeResult +from textual.containers import Container, Vertical, Horizontal +from textual.widgets import Button, Header, Footer, Static, Input, Label +from textual import events +from textual.binding import Binding + +class WallpaperTUI(App): + """Main TUI application for wallpaper management""" + + BINDINGS = [ + Binding("q", "quit", "Quit"), + Binding("r", "refresh", "Refresh"), + Binding("1", "run_sort", "Run Sort"), + Binding("2", "run_sync", "Run Sync"), + Binding("3", "run_wallpaper_menu", "Wallpaper Menu"), + Binding("4", "run_waybar_update", "Update Waybar"), + Binding("5", "run_openrgb", "Set OpenRGB") + ] + + def __init__(self): + super().__init__() + self.console = Console() + self.scripts_dir = Path(__file__).parent + self.sort_script = self.scripts_dir / "sort_images_by_color.py" + self.sync_script = self.scripts_dir / "wallpapersync.sh" + self.wallpaper_menu = self.scripts_dir / "pywal" / "wallpapermenu.sh" + self.waybar_script = self.scripts_dir / "pywal" / "update-waybar-theme.sh" + self.openrgb_script = self.scripts_dir / "pywal" / "pywal-openrgb.py" + + def compose(self) -> ComposeResult: + yield Header() + yield self.create_main_layout() + yield Footer() + + def create_main_layout(self): + layout = Layout(name="main") + layout.split( + Layout(name="top", size=10), + Layout(name="middle", ratio=1), + Layout(name="bottom", size=5), + ) + layout["top"].update(Panel("Wallpaper Manager", title="Status")) + layout["middle"].update(Panel(Static("Welcome to Wallpaper TUI"), title="Controls")) + layout["bottom"].update(Panel(Static("Ready"), title="Status")) + return layout + + def on_mount(self) -> None: + self.refresh_status("Application started") + + def refresh_status(self, message: str): + """Update the status message""" + self.query_one("#status", Static).update(message) + + def on_button_pressed(self, event: Button.Pressed) -> None: + """Handle button presses""" + button_id = event.button.id + if button_id == "run_sort": + self.run_sort() + elif button_id == "run_sync": + self.run_sync() + elif button_id == "run_wallpaper_menu": + self.run_wallpaper_menu() + elif button_id == "run_waybar": + self.run_waybar_update() + elif button_id == "run_openrgb": + self.run_openrgb() + + def run_sort(self): + """Run the sort script""" + self.refresh_status("Running sort script...") + try: + result = subprocess.run([sys.executable, str(self.sort_script), "."], + capture_output=True, text=True, check=True) + self.refresh_status("Sort completed successfully") + self.show_output(result.stdout, "Sort Output") + except subprocess.CalledProcessError as e: + self.refresh_status("Sort failed") + self.show_output(e.stderr, "Sort Error") + + def run_sync(self): + """Run the sync script""" + self.refresh_status("Running sync script...") + try: + result = subprocess.run([str(self.sync_script)], + capture_output=True, text=True, check=True) + self.refresh_status("Sync completed successfully") + self.show_output(result.stdout, "Sync Output") + except subprocess.CalledProcessError as e: + self.refresh_status("Sync failed") + self.show_output(e.stderr, "Sync Error") + + def run_wallpaper_menu(self): + """Run the wallpaper menu script""" + self.refresh_status("Opening wallpaper menu...") + try: + # This would typically open an interactive menu + # For now, we'll just show a message + self.refresh_status("Wallpaper menu opened (interactive)") + except Exception as e: + self.refresh_status("Wallpaper menu failed") + self.show_output(str(e), "Wallpaper Menu Error") + + def run_waybar_update(self): + """Run the waybar update script""" + self.refresh_status("Updating waybar theme...") + try: + # This would need a wallpaper path + self.refresh_status("Waybar update initiated") + except Exception as e: + self.refresh_status("Waybar update failed") + self.show_output(str(e), "Waybar Error") + + def run_openrgb(self): + """Run the openrgb script""" + self.refresh_status("Setting OpenRGB color...") + try: + result = subprocess.run([sys.executable, str(self.openrgb_script)], + capture_output=True, text=True, check=True) + self.refresh_status("OpenRGB color set") + self.show_output(result.stdout, "OpenRGB Output") + except subprocess.CalledProcessError as e: + self.refresh_status("OpenRGB failed") + self.show_output(e.stderr, "OpenRGB Error") + + def show_output(self, output: str, title: str): + """Display output in a panel""" + # In a real implementation, this would show the output in a scrollable panel + self.refresh_status(f"{title} displayed") + +def main(): + app = WallpaperTUI() + app.run() + +if __name__ == "__main__": + main()