trying to make a TUI
This commit is contained in:
parent
6ccc8606ef
commit
b2f5e65c06
264
simple_tui.py
Executable file
264
simple_tui.py
Executable file
@ -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()
|
||||||
158
wallpaper_tui.py
Normal file
158
wallpaper_tui.py
Normal file
@ -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()
|
||||||
Loading…
x
Reference in New Issue
Block a user