Files
gosleep/timer.py
T
forust 8501fd074c feat: initial gosleep-timer implementation
Go TUI sleep timer with modular architecture.
- Config: YAML-based, profile inheritance, auto WM detection
- Engine: timer, executor, stage pipeline (pre→timer→post)
- Modules: workspace (niri/hyprland/kde), media, lock, kill,
  notify, sound, brightness, mute, custom, script
- TUI: bubbletea app with profiles, module toggles, progress bar
- CLI: run, list-profiles, export, import, history, stats
- History: JSONL log with statistics
- QR: config export as ASCII QR
2026-07-03 01:43:22 +02:00

163 lines
4.8 KiB
Python
Executable File

#!/usr/bin/env python3
import subprocess
import asyncio
from textual.app import App, ComposeResult
from textual.widgets import (
Static, Input, Checkbox, Button, Footer, Header, Select
)
from textual.containers import Vertical, Horizontal, Center
class TimerTUI(App):
CSS = """
Screen {
align: center middle;
background: #101010;
}
#main {
width: 50%;
border: solid #33ccff;
padding: 1 2;
background: #181818;
border-title-align: center;
border-title-color: cyan;
}
.label {
color: #33ccff;
text-align: center;
height: auto;
}
Input, Select {
width: 100%;
border: round #444;
padding: 0 1;
}
Checkbox {
width: 100%;
}
Button {
width: 48%;
margin: 0 1;
}
#status {
color: cyan;
text-align: center;
padding-top: 1;
}
"""
def __init__(self):
super().__init__()
self.timer_process = None
self.message_label = Static("", id="status")
def compose(self) -> ComposeResult:
yield Header()
with Center():
with Vertical(id="main"):
yield Static("⏱ TIMER CONFIGURATION ⏱", classes="label")
self.input_time = Input(value="", placeholder="Время (например 20m)")
self.cb_workspace = Checkbox("Переключить воркспейс", value=True)
self.input_workspace = Input(value="12", placeholder="Номер воркспейса")
self.cb_player = Checkbox("Управлять плеером", value=True)
self.select_player_action = Select(
options=[
("Ничего", "none"),
("stop", "stop"),
("pause", "pause"),
("next", "next"),
("previous", "previous"),
],
value="stop",
)
self.cb_lock = Checkbox("Заблокировать экран", value=True)
self.cb_kill = Checkbox("Убить приложения", value=False)
self.input_kill = Input(value="firefox, telegram-desktop", placeholder="через запятую")
yield self.input_time
yield self.cb_workspace
yield self.input_workspace
yield self.cb_player
yield self.select_player_action
yield self.cb_lock
yield self.cb_kill
yield self.input_kill
with Horizontal():
yield Button("▶ Start / Restart Timer", id="start", variant="success")
yield Button("✖ Quit", id="quit", variant="error")
yield self.message_label
yield Footer()
async def on_button_pressed(self, event: Button.Pressed) -> None:
if event.button.id == "quit":
await self.stop_timer()
await self.action_quit()
elif event.button.id == "start":
await self.restart_timer()
async def stop_timer(self):
if self.timer_process and self.timer_process.poll() is None:
self.timer_process.terminate()
await asyncio.sleep(0.2)
if self.timer_process.poll() is None:
self.timer_process.kill()
async def restart_timer(self):
await self.stop_timer()
self.notify("⏳ Запуск таймера...")
time_str = self.input_time.value.strip()
commands = []
# Workspace switch
if self.cb_workspace.value:
workspace = self.input_workspace.value.strip()
commands.append(f"niri msg action focus-workspace {workspace}")
# Player action
if self.cb_player.value:
action = self.select_player_action.value
if action != "none":
commands.append(f"playerctl {action}")
# Lock screen
if self.cb_lock.value:
commands.append("qs -c noctalia-shell ipc call lockScreen lock")
# Kill apps
if self.cb_kill.value and self.input_kill.value.strip():
for proc in self.input_kill.value.split(","):
proc = proc.strip()
if proc:
commands.append(f"pkill {proc}")
cmd_after = " ; ".join(commands)
full_cmd = f"timer {time_str} && bash -c '{cmd_after}'"
self.timer_process = subprocess.Popen(
full_cmd,
shell=True,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
self.notify(f"✅ Таймер запущен на {time_str}")
def notify(self, msg: str):
self.message_label.update(msg)
if __name__ == "__main__":
TimerTUI().run()