Last active
March 8, 2026 21:19
-
-
Save FixeQD/175e1150cbcabb2267337dd69a4235fa to your computer and use it in GitHub Desktop.
Push-to-talk cuz discord doesn't detect keybinds on my wayland bruh
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| # ───────────────────────────────────────────── | |
| # CONFIG | |
| # ───────────────────────────────────────────── | |
| DEVICE_PATH = "/dev/input/by-path/platform-i8042-serio-0-event-kbd" | |
| PTT_KEY = "KEY_RIGHTCTRL" | |
| APP_NAME = "PTT" | |
| MAX_LOGS = 50 | |
| REFRESH_RATE = 4 | |
| COLOR_LIVE = "green" | |
| COLOR_MUTED = "red" | |
| COLOR_ACCENT = "cyan" | |
| COLOR_DIM = "dim" | |
| # ───────────────────────────────────────────── | |
| import time | |
| import threading | |
| import evdev | |
| import pulsectl | |
| import colorama | |
| from colorama import Fore, Style | |
| from rich.layout import Layout | |
| from rich.live import Live | |
| from rich.panel import Panel | |
| from rich.table import Table | |
| from rich.text import Text | |
| from datetime import datetime | |
| from collections import deque | |
| colorama.init() | |
| KEY_CODE = evdev.ecodes.ecodes[PTT_KEY] | |
| logs = deque(maxlen=MAX_LOGS) | |
| state = { | |
| "muted": True, | |
| "events": 0, | |
| "start": datetime.now(), | |
| "last_event": "—", | |
| "initial_mute": None, | |
| } | |
| LEVEL_COLORS = { | |
| "READY": COLOR_ACCENT, | |
| "OK": COLOR_LIVE, | |
| "INFO": "white", | |
| "WARN": "yellow", | |
| "ERR": COLOR_MUTED, | |
| } | |
| def log(msg: str, level: str = "INFO"): | |
| logs.append({ | |
| "time": datetime.now().strftime("%H:%M:%S"), | |
| "level": level, | |
| "msg": msg, | |
| }) | |
| def render_header() -> Panel: | |
| t = Text(justify="center") | |
| t.append(f" {APP_NAME} ", style=f"bold {COLOR_ACCENT}") | |
| t.append("Push To Talk", style="bold white") | |
| return Panel(t, style=COLOR_ACCENT) | |
| def render_status() -> Panel: | |
| if state["muted"]: | |
| label = Text("● MUTED", style=f"bold {COLOR_MUTED}") | |
| border = COLOR_MUTED | |
| else: | |
| label = Text("▶ LIVE", style=f"bold {COLOR_LIVE}") | |
| border = COLOR_LIVE | |
| uptime = str(datetime.now() - state["start"]).split(".")[0] | |
| initial_label = "muted" if state["initial_mute"] else "unmuted" | |
| grid = Table.grid(padding=(0, 2)) | |
| grid.add_column(style=COLOR_DIM) | |
| grid.add_column() | |
| grid.add_row("Status", label) | |
| grid.add_row("Key", Text(PTT_KEY, style=COLOR_ACCENT)) | |
| grid.add_row("Device", Text(DEVICE_PATH.split("/")[-1], style=COLOR_DIM)) | |
| grid.add_row("Uptime", Text(uptime, style="white")) | |
| grid.add_row("Events", Text(str(state["events"]), style="white")) | |
| grid.add_row("Last event", Text(state["last_event"], style=COLOR_DIM)) | |
| grid.add_row("Restore to", Text(initial_label, style=COLOR_DIM)) | |
| return Panel(grid, title="[bold]Status[/bold]", border_style=border, padding=(1, 2)) | |
| def render_logs() -> Panel: | |
| table = Table(show_header=True, header_style=f"bold {COLOR_DIM}", box=None, expand=True) | |
| table.add_column("Time", style=COLOR_DIM, width=10) | |
| table.add_column("Level", width=7) | |
| table.add_column("Message", ratio=1) | |
| for entry in logs: | |
| color = LEVEL_COLORS.get(entry["level"], "white") | |
| table.add_row( | |
| entry["time"], | |
| f"[{color}]{entry['level']}[/{color}]", | |
| entry["msg"], | |
| ) | |
| return Panel(table, title="[bold]Logs[/bold]", border_style=COLOR_DIM) | |
| def render_footer() -> Panel: | |
| t = Text(justify="center") | |
| t.append("Hold ", style=COLOR_DIM) | |
| t.append(PTT_KEY, style=f"bold {COLOR_ACCENT}") | |
| t.append(" to unmute | ", style=COLOR_DIM) | |
| t.append("Ctrl+C", style=f"bold {COLOR_MUTED}") | |
| t.append(" to exit", style=COLOR_DIM) | |
| return Panel(t, style=COLOR_DIM) | |
| def refresh(layout: Layout): | |
| layout["header"].update(render_header()) | |
| layout["status"].update(render_status()) | |
| layout["logs"].update(render_logs()) | |
| layout["footer"].update(render_footer()) | |
| def event_loop(device, pulse, src): | |
| for event in device.read_loop(): | |
| if event.type != evdev.ecodes.EV_KEY: | |
| continue | |
| key = evdev.categorize(event) | |
| if key.scancode != KEY_CODE: | |
| continue | |
| if key.keystate == evdev.KeyEvent.key_hold: | |
| continue | |
| now = datetime.now().strftime("%H:%M:%S") | |
| state["events"] += 1 | |
| if key.keystate == evdev.KeyEvent.key_down: | |
| state["muted"] = False | |
| state["last_event"] = f"unmuted @ {now}" | |
| pulse.mute(src, False) | |
| log("Mic UNMUTED", "OK") | |
| elif key.keystate == evdev.KeyEvent.key_up: | |
| state["muted"] = True | |
| state["last_event"] = f"muted @ {now}" | |
| pulse.mute(src, True) | |
| log("Mic MUTED", "INFO") | |
| def main(): | |
| pulse = pulsectl.Pulse(APP_NAME.lower()) | |
| device = evdev.InputDevice(DEVICE_PATH) | |
| src = pulse.get_source_by_name(pulse.server_info().default_source_name) | |
| initial_mute = bool(src.mute) | |
| state["initial_mute"] = initial_mute | |
| state["muted"] = True | |
| pulse.mute(src, True) | |
| log(f"Device → {DEVICE_PATH}", "READY") | |
| log(f"Key → {PTT_KEY}", "READY") | |
| log(f"Initial state → {'muted' if initial_mute else 'unmuted'}", "INFO") | |
| log("Mic muted on startup", "INFO") | |
| layout = Layout() | |
| layout.split_column( | |
| Layout(name="header", size=3), | |
| Layout(name="main"), | |
| Layout(name="footer", size=3), | |
| ) | |
| layout["main"].split_row( | |
| Layout(name="status", ratio=1), | |
| Layout(name="logs", ratio=2), | |
| ) | |
| t = threading.Thread(target=event_loop, args=(device, pulse, src), daemon=True) | |
| t.start() | |
| try: | |
| with Live(layout, refresh_per_second=REFRESH_RATE, screen=True): | |
| while t.is_alive(): | |
| refresh(layout) | |
| time.sleep(1 / REFRESH_RATE) | |
| finally: | |
| pulse.mute(src, initial_mute) | |
| if __name__ == "__main__": | |
| try: | |
| print(f"{Fore.CYAN}[{APP_NAME}]{Style.RESET_ALL} Starting...") | |
| main() | |
| except KeyboardInterrupt: | |
| print(f"\n{Fore.YELLOW}[{APP_NAME}]{Style.RESET_ALL} Restored mic state and exited.") | |
| except Exception as e: | |
| print(f"\n{Fore.RED}[{APP_NAME}] ERR:{Style.RESET_ALL} {e}") | |
| finally: | |
| colorama.deinit() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment