6 Commits

Author SHA1 Message Date
225685de63 add command history
minor bump
2026-03-28 15:48:34 +00:00
5178b0066b patch bump 2026-03-25 07:20:16 +00:00
68926fa67b add command timings 2026-03-25 07:20:02 +00:00
d273ca57ca add pre-commit config 2026-03-21 14:18:21 +00:00
ce660fb6c8 add Use section 2026-03-20 09:58:31 +00:00
514dda463a closes #1 2026-02-26 20:29:50 +00:00
6 changed files with 86 additions and 5 deletions

View File

@@ -30,7 +30,7 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install "virtualenv<21" hatch pip install hatch
- name: Build package - name: Build package
run: hatch build run: hatch build

7
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,7 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.3.0
hooks:
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace

View File

@@ -66,6 +66,12 @@ Q3RCON_TUI_RAW=false
Q3RCON_TUI_APPEND=false Q3RCON_TUI_APPEND=false
``` ```
## Use
Type in your Rcon command and press ENTER.
Press `Ctrl+q` to exit from the application.
## Special Thanks ## Special Thanks
- [lapetus-11](https://github.com/Iapetus-11) for writing the [aio-q3-rcon](https://github.com/Iapetus-11/aio-q3-rcon) package. - [lapetus-11](https://github.com/Iapetus-11) for writing the [aio-q3-rcon](https://github.com/Iapetus-11/aio-q3-rcon) package.

View File

@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2026-present onyx-and-iris <code@onyxandiris.online> # SPDX-FileCopyrightText: 2026-present onyx-and-iris <code@onyxandiris.online>
# #
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
__version__ = '0.7.1' __version__ = '0.8.0'

23
src/q3rcon_tui/history.py Normal file
View File

@@ -0,0 +1,23 @@
from collections import UserList
class CommandHistory(UserList):
"""A simple list to store command history."""
def add(self, command: str):
"""Add a command to the history if it's not empty and not a duplicate of the last."""
command = command.strip()
if command and (not self.data or command != self.data[-1]):
self.data.append(command)
def get_previous(self, index: int) -> str:
"""Get the previous command based on the current index."""
if 0 <= index < len(self.data):
return self.data[index]
return ''
def get_next(self, index: int) -> str:
"""Get the next command based on the current index."""
if 0 <= index < len(self.data):
return self.data[index]
return ''

View File

@@ -4,17 +4,29 @@ from textual.containers import Grid
from textual.widgets import Button, Input, RichLog from textual.widgets import Button, Input, RichLog
from .configscreen import ConfigScreen from .configscreen import ConfigScreen
from .history import CommandHistory
from .settings import Settings from .settings import Settings
from .writable import Writable from .writable import Writable
class Q3RconTUI(App): class Q3RconTUI(App):
CSS_PATH = 'q3rcon_tui.tcss' CSS_PATH = 'q3rcon_tui.tcss'
CMD_CONFIG = {
'status': (2, 0.25, False),
'fast_restart': (3, 1, True),
'map_restart': (3, 1, True),
'map': (3, 1, True),
'map_rotate': (3, 1, True),
}
DEFAULT_TIMEOUT = 2
DEFAULT_FRAGMENT_READ_TIMEOUT = 0.25
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self._settings = Settings() self._settings = Settings()
self.writable = Writable(self) self.writable = Writable(self)
self._command_history = CommandHistory()
self._history_index = None
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
yield Grid( yield Grid(
@@ -31,9 +43,33 @@ class Q3RconTUI(App):
if self.screen and isinstance(self.screen, ConfigScreen): if self.screen and isinstance(self.screen, ConfigScreen):
return return
command_input = self.query_one('#command', Input)
match event.key: match event.key:
case 'enter' if self.query_one('#command', Input).has_focus: case 'enter' if command_input.has_focus:
value = command_input.value.strip()
if value:
self._command_history.add(value)
self._history_index = None
self.query_one('#send', Button).press() self.query_one('#send', Button).press()
case 'up' if command_input.has_focus:
if self._command_history:
if self._history_index is None:
self._history_index = len(self._command_history) - 1
elif self._history_index > 0:
self._history_index -= 1
command_input.value = self._command_history.get_previous(
self._history_index
)
case 'down' if command_input.has_focus:
if self._command_history and self._history_index is not None:
if self._history_index < len(self._command_history) - 1:
self._history_index += 1
command_input.value = self._command_history.get_next(
self._history_index
)
else:
self._history_index = None
command_input.value = ''
case 'f2': case 'f2':
self.query_one('#config', Button).press() self.query_one('#config', Button).press()
@@ -65,11 +101,20 @@ class Q3RconTUI(App):
self.app.bell() self.app.bell()
return return
timeout, fragment_read_timeout, interpret = Q3RconTUI.CMD_CONFIG.get(
cmd.split()[0].lower(),
(Q3RconTUI.DEFAULT_TIMEOUT, Q3RconTUI.DEFAULT_FRAGMENT_READ_TIMEOUT, False),
)
try: try:
async with Client( async with Client(
self._settings.host, self._settings.port, self._settings.password self._settings.host,
self._settings.port,
self._settings.password,
timeout=timeout,
fragment_read_timeout=fragment_read_timeout,
) as client: ) as client:
response = await client.send_command(cmd) response = await client.send_command(cmd, interpret=interpret)
self.query_one('#response', RichLog).write( self.query_one('#response', RichLog).write(
self.writable.parse(cmd, response) self.writable.parse(cmd, response)
) )