Archived
1
1
Fork 0

tui: rename file/function, add screenshot binding

This commit is contained in:
Josh Lay 2023-04-29 12:15:02 -05:00
parent 4ca199aec4
commit 95c234d31d
Signed by: jlay
GPG key ID: B265E45CACAD108A
2 changed files with 45 additions and 33 deletions

View file

@ -1,13 +1,10 @@
#!/usr/bin/python3 #!/usr/bin/python3
"""Pretty Textual-based stats for AMD GPUs """Pretty Textual-based stats for AMD GPUs
TODO: restore argparse / --card, in case detection fails.
will require separating the hwmon finding tasks from 'find_card'
rich markup reference: rich markup reference:
https://rich.readthedocs.io/en/stable/markup.html https://rich.readthedocs.io/en/stable/markup.html
""" """
if __name__ == "__main__": if __name__ == "__main__":
from .interface import tui from .tui import interface
tui() interface()

View file

@ -1,7 +1,9 @@
""" """
interface.py tui.py
This module provides the TUI aspect of 'amdgpu-stats' This file provides the user interface of `amdgpu-stats`
Can be used as a way to monitor GPU(s) in your terminal, or inform other utilities.
Classes: Classes:
- GPUStats: the object for the _Application_, instantiated at runtime - GPUStats: the object for the _Application_, instantiated at runtime
@ -12,9 +14,11 @@ Classes:
- LogScreen: Second screen with the logging widget, header, and footer - LogScreen: Second screen with the logging widget, header, and footer
Functions: Functions:
- tui: Renders the TUI using the classes above - interface: Renders the TUI using the classes above
""" """
import sys import sys
from datetime import datetime
from os import path
from textual.binding import Binding from textual.binding import Binding
from textual.app import App, ComposeResult from textual.app import App, ComposeResult
@ -44,7 +48,7 @@ class LogScreen(Screen):
On first display in this case.""" On first display in this case."""
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
yield Header() yield Header(show_clock=True)
yield Container(self.text_log) yield Container(self.text_log)
yield Footer() yield Footer()
@ -72,17 +76,21 @@ class GPUStats(App):
# initialize log screen # initialize log screen
SCREENS = {"logs": LogScreen()} SCREENS = {"logs": LogScreen()}
# title the app after the card
TITLE = CARD
# setup keybinds # setup keybinds
# Binding("l", "push_screen('logs')", "Toggle logs", priority=True), # Binding("l", "push_screen('logs')", "Toggle logs", priority=True),
BINDINGS = [ BINDINGS = [
Binding("c", "toggle_dark", "Toggle colors", priority=True), Binding("s", "screenshot_wrapper", "Screenshot"),
Binding("l", "toggle_log", "Toggle logs", priority=True), Binding("c", "custom_dark", "Toggle colors"),
Binding("q", "quit_app", "Quit", priority=True) Binding("l", "toggle_log", "Toggle logs"),
Binding("q", "quit", "Quit")
] ]
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
"""Create child widgets for the app.""" """Create child widgets for the app."""
yield Header() yield Header(show_clock=True)
yield Container(GPUStatsWidget()) yield Container(GPUStatsWidget())
self.update_log("[bold green]App started, logging begin!") self.update_log("[bold green]App started, logging begin!")
self.update_log(f"[bold italic]Information source:[/] {hwmon_dir}") self.update_log(f"[bold italic]Information source:[/] {hwmon_dir}")
@ -93,16 +101,23 @@ class GPUStats(App):
# self.update_log(f'[bold] {metric} temperature:[/] {source}') # self.update_log(f'[bold] {metric} temperature:[/] {source}')
yield Footer() yield Footer()
def action_toggle_dark(self) -> None: async def action_custom_dark(self) -> None:
"""An action to toggle dark mode.""" """An action to toggle dark mode.
Wraps 'action_toggle_dark' with logging and a refresh"""
self.dark = not self.dark self.dark = not self.dark
self.update_log(f"[bold]Dark side: [italic]{self.dark}") self.update_log(f"[bold]Dark side: [italic]{self.dark}")
self.refresh()
# self.dark = not self.dark
def action_quit_app(self) -> None: def action_screenshot_wrapper(self, screen_dir: str = '/tmp') -> None:
"""An action to quit the program""" """Action that fires when the user presses 's' for a screenshot"""
message = "Exiting on user request" # construct the screenshot elements + path
self.update_log(f"[bold]{message}") timestamp = datetime.now().isoformat().replace(":", "_")
self.exit(message) screen_name = 'amdgpu_stats_' + timestamp + '.svg'
screen_path = path.join(screen_dir, screen_name)
self.action_screenshot(path=screen_dir, filename=screen_name)
self.update_log(f'[bold]Screenshot taken: [italic]{screen_path}')
def action_toggle_log(self) -> None: def action_toggle_log(self) -> None:
"""Toggle between the main screen and the LogScreen.""" """Toggle between the main screen and the LogScreen."""
@ -144,14 +159,14 @@ class MiscDisplay(Static):
for temp_node in self.initial_stats: for temp_node in self.initial_stats:
# capitalize the first letter for display # capitalize the first letter for display
caption = temp_node[0].upper() + temp_node[1:] caption = temp_node[0].upper() + temp_node[1:]
yield Horizontal(Label(f' {caption}:',), yield Horizontal(Label(f' {caption}:',),
Label("", id="temp_" + temp_node, Label("", id="temp_" + temp_node,
classes="statvalue")) classes="statvalue"))
yield Horizontal(Label("[underline]Fan RPM"), yield Horizontal(Label("[underline]Fan RPM"),
Label("", classes="statvalue")) Label("", classes="statvalue"))
yield Horizontal(Label(" Current:",), yield Horizontal(Label(" Current:",),
Label("", id="fan_rpm", classes="statvalue")) Label("", id="fan_rpm", classes="statvalue"))
yield Horizontal(Label(" Target:",), yield Horizontal(Label(" Target:",),
Label("", id="fan_rpm_target", classes="statvalue")) Label("", id="fan_rpm_target", classes="statvalue"))
self.composed = True self.composed = True
@ -202,17 +217,17 @@ class ClockDisplay(Static):
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
yield Horizontal(Label("[underline]Clocks"), yield Horizontal(Label("[underline]Clocks"),
Label("", classes="statvalue")) Label("", classes="statvalue"))
yield Horizontal(Label(" GPU core:",), yield Horizontal(Label(" GPU core:",),
Label("", id="clk_core_val", classes="statvalue")) Label("", id="clk_core_val", classes="statvalue"))
yield Horizontal(Label(" Memory:"), yield Horizontal(Label(" Memory:"),
Label("", id="clk_memory_val", classes="statvalue")) Label("", id="clk_memory_val", classes="statvalue"))
# padding to split groups # padding to split groups
yield Horizontal(Label(""), Label("", classes="statvalue")) yield Horizontal(Label(""), Label("", classes="statvalue"))
yield Horizontal(Label("[underline]Core"), yield Horizontal(Label("[underline]Core"),
Label("", classes="statvalue")) Label("", classes="statvalue"))
yield Horizontal(Label(" Utilization:",), yield Horizontal(Label(" Utilization:",),
Label("", id="util_pct", classes="statvalue")) Label("", id="util_pct", classes="statvalue"))
yield Horizontal(Label(" Voltage:",), yield Horizontal(Label(" Voltage:",),
Label("", id="clk_voltage_val", classes="statvalue")) Label("", id="clk_voltage_val", classes="statvalue"))
def on_mount(self) -> None: def on_mount(self) -> None:
@ -253,17 +268,17 @@ class PowerDisplay(Static):
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
yield Horizontal(Label("[underline]Power"), yield Horizontal(Label("[underline]Power"),
Label("", classes="statvalue")) Label("", classes="statvalue"))
yield Horizontal(Label(" Usage:",), yield Horizontal(Label(" Usage:",),
Label("", id="pwr_avg_val", classes="statvalue")) Label("", id="pwr_avg_val", classes="statvalue"))
# padding to split groups # padding to split groups
yield Horizontal(Label(""), Label("", classes="statvalue")) yield Horizontal(Label(""), Label("", classes="statvalue"))
yield Horizontal(Label("[underline]Limits"), yield Horizontal(Label("[underline]Limits"),
Label("", classes="statvalue")) Label("", classes="statvalue"))
yield Horizontal(Label(" Configured:",), yield Horizontal(Label(" Configured:",),
Label("", id="pwr_lim_val", classes="statvalue")) Label("", id="pwr_lim_val", classes="statvalue"))
yield Horizontal(Label(" Default:",), yield Horizontal(Label(" Default:",),
Label("", id="pwr_def_val", classes="statvalue")) Label("", id="pwr_def_val", classes="statvalue"))
yield Horizontal(Label(" Board capability:",), yield Horizontal(Label(" Board capability:",),
Label("", id="pwr_cap_val", classes="statvalue")) Label("", id="pwr_cap_val", classes="statvalue"))
def on_mount(self) -> None: def on_mount(self) -> None:
@ -290,10 +305,10 @@ class PowerDisplay(Static):
Static).update(f"{watts['capability']}W") Static).update(f"{watts['capability']}W")
def tui() -> None: def interface() -> None:
'''Spawns the textual UI only during CLI invocation / after argparse''' '''Spawns the textual UI only during CLI invocation / after argparse'''
if len(AMDGPU_CARDS) > 0: if len(AMDGPU_CARDS) > 0:
app = GPUStats() app = GPUStats(watch_css=True)
app.run() app.run()
else: else:
sys.exit('Could not find an AMD GPU, exiting.') sys.exit('Could not find an AMD GPU, exiting.')