Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ license = { text = "MIT" }
keywords = ["python", "textual", "tui", "window", "desktop", "shell", "multiplexer", "terminal", "cli", "command-line"]
dependencies = [
"ezpubsub>=0.2.0",
"python-json-logger>=3.3.0",
"textual>=5.3.0",
"textual-autocomplete>=4.0.4",
"textual-coloromatic>=1.0.0",
Expand Down Expand Up @@ -54,7 +55,7 @@ build-backend = "hatchling.build"
term-desktop = "term_desktop.main:run"

[tool.black]
line-length = 110
line-length = 100

[tool.mypy]
pretty = true
Expand All @@ -67,4 +68,4 @@ include = ["src"]
typeCheckingMode = "strict"

[tool.pytest.ini_options]
asyncio_mode = "auto"
asyncio_mode = "auto"
8 changes: 6 additions & 2 deletions src/term_desktop/aceofbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class ProcessType(enum.Enum):
SCREEN = "screen"
SHELL = "shell"
WINDOW = "window"
DATABASE = "database"
LOGGER = "logger"
# Add more process types as needed.


Expand All @@ -30,7 +32,7 @@ class ProcessContext(TypedDict, total=True):
sessions to 1) have access to all services directly through
self properties, and 2) access their own unique process identifiers
that is used to track them in the system, if they need to do so.

Remember that that process "children" here refers to things spawned
by the process, but not the process itself (ie an app process
needs to spawn its own Textual widget).
Expand Down Expand Up @@ -119,4 +121,6 @@ def validate_stage2(cls, required_members: dict[str, str]) -> None:
raise NotImplementedError(f"{cls.__name__} must implement {attr_name} ({kind}).")
else:
if attr is None:
raise NotImplementedError(f"{cls.__name__} must implement {attr_name} ({kind}).")
raise NotImplementedError(
f"{cls.__name__} must implement {attr_name} ({kind})."
)
4 changes: 2 additions & 2 deletions src/term_desktop/app_sdk/appbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ class TDEMainWidget(Widget):

#! NOTE: NOT FOR SCREENS, STILL NEED TO BUILD SUPPORT FOR THEM.

The app_context is passed in by the Process Manager when it initializes the app.
The app_context is passed in by the App Service when it initializes the app.
"""

class Initialized(Message):
Expand All @@ -286,7 +286,7 @@ def __init__(self, window: Window):
self.window = window

def __init__(self, process_context: ProcessContext):
"""The process context is passed in by the Process Manager when it initializes the app.
"""The process context is passed in by the App Service when it initializes the app.
It contains the process type, process ID, process UID, and services manager.

If you override this method, you must have an argument named `process_context`
Expand Down
4 changes: 3 additions & 1 deletion src/term_desktop/apps/notepad/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,9 @@ def on_mount(self) -> None:
for button in buttons:
button.compact = True

menu.offset = Offset(self.menu_offset.x, self.menu_offset.y + 1) # +1 to go below the button
menu.offset = Offset(
self.menu_offset.x, self.menu_offset.y + 1
) # +1 to go below the button

def on_mouse_up(self) -> None:

Expand Down
4 changes: 3 additions & 1 deletion src/term_desktop/apps/sysinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,9 @@ def get_static_system_info(self) -> dict[str, str]:

return {
"OS": f"{uname.system} {uname.release}",
"Freedesktop_os label": str(platform.freedesktop_os_release().get("PRETTY_NAME", "Unknown")),
"Freedesktop_os label": str(
platform.freedesktop_os_release().get("PRETTY_NAME", "Unknown")
),
"Machine": uname.machine,
"Architecture": platform.architecture()[0],
"CPU Model": self.get_cpu_model(),
Expand Down
111 changes: 99 additions & 12 deletions src/term_desktop/apps/syslogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,22 @@

# Python imports
from __future__ import annotations
import logging
import time

# import inspect

# from typing import Any, Type
# import os
# import sys
# import platform

# Textual imports
# import rich.repr
from textual.app import ComposeResult
from textual.widgets import RichLog # , Static
from textual.widgets import DataTable #, Static
# from textual.containers import Container
from rich.text import Text

# Unused Textual imports (for reference):
# from textual import events, on
Expand All @@ -29,6 +36,7 @@
LaunchMode,
CustomWindowSettings,
)
# from term_desktop.services.tde_logging import LogPayload


class SysLogsMeta(TDEAppBase):
Expand Down Expand Up @@ -64,28 +72,107 @@ def custom_window_settings(self) -> CustomWindowSettings:
"""
return {
# This returns an empty dictionary when not overridden.
"starting_horizontal": "right", # default is "center"
# "start_open": False, # default is True
# "allow_resize": False, # default is True
# "allow_maximize": False, # default is True
# see CustomWindowSettings for more options
}

def window_styles(self) -> WindowStylesDict:

return {
"width": 60, #
"height": 30, #
# "max_width": None, # default is 'size of the parent container'
# "max_height": None, # default is 'size of the parent container'
# "min_width": 12, #
# "min_height": 6, #
"width": 87,
"height": 30,
}


class SysLogsWidget(TDEMainWidget):

# DEFAULT_CSS = """
# #title { border: solid $primary; }
# #content { width: auto; height: auto; }
# """
DEFAULT_CSS = """
#menubar_placeholder { width: 1fr; height: 1; }
"""

def compose(self) -> ComposeResult:

yield RichLog(id="log_viewer")
self.initialized = False
self.counter = 0
yield DataTable(
id="log_viewer_table",
zebra_stripes=True,
cursor_type="row",
)

def on_mount(self) -> None:

table: DataTable[str | Text] = self.query_one("#log_viewer_table", DataTable)
self.index_key = table.add_column(" ", key="index")
table.add_column("Message", key="message", width=60)
table.add_column("Group", key="level")
table.add_column("Path", key="path")
table.add_column("Line", key="line_number")
table.add_column("Time", key="created")
table.add_column("Session ID", key="session_id")

log_memory = self.services.logging_service.memory_buffer

# write out the memory buffer to the log viewer
for record in log_memory:
self.handle_new_log(record)

# subscribe to new log records
self.services.logging_service.subscribe_to_signal(self.handle_new_log)

table.scroll_end()
self.initialized = True


def handle_new_log(self, log_record: logging.LogRecord) -> None:

table: DataTable[str | Text] = self.query_one("#log_viewer_table", DataTable)
row_key = table.add_row(
str(self.counter),
Text(str(log_record.msg), overflow="fold"),
str(log_record.__dict__.get("group")),
Text(str(log_record.__dict__.get("path")), overflow="fold"),
str(log_record.__dict__.get("line_number")),
time.strftime("%H:%M:%S", time.localtime(log_record.__dict__.get("timestamp"))),
str(log_record.__dict__.get("session_id")),
)
# self.counter += 1
row_index = table.get_row_index(row_key)
table.update_cell(row_key, self.index_key, str(row_index))
if self.initialized:
table.scroll_end()

# name=tde_logger ▊
# msg=RichLog(id='log_viewer') was focused ▊
# args=() ▊
# levelname=DEBUG ▊
# levelno=10 ▊
# pathname=/home/brent/vscode-projects/term-desktop/src/term_desktop/services/tde_logging.py ▊
# filename=tde_logging.py ▊
# module=tde_logging ▊
# exc_info=None ▊
# exc_text=None ▊
# stack_info=None ▊
# lineno=154 ▊
# funcName=log ▊
# created=1756861622.4891434 ▊
# msecs=489.0 ▊
# relativeCreated=8955812.801361084 ▊
# thread=139809355833472 ▊
# threadName=MainThread ▊
# processName=MainProcess ▊
# process=60827 ▊
# taskName=Task-1 ▊
# session_id=139809345227616 ▊
# group=DEBUG ▊
# path=/home/brent/vscode-projects/term-desktop/.venv/lib/python3.12/site-packages/textual/screen.py ▊
# line_number=1042 ▊
# message=RichLog(id='log_viewer') was focused


def on_unmount(self) -> None:
# unsubscribe from log records
self.services.logging_service.unsubscribe_from_signal(self.handle_new_log)
Loading
Loading