{"id":29669189,"url":"https://github.com/threeal/cursers","last_synced_at":"2026-01-20T16:52:22.951Z","repository":{"id":302886618,"uuid":"1013252783","full_name":"threeal/cursers","owner":"threeal","description":"A minimal threaded wrapper for Python curses","archived":false,"fork":false,"pushed_at":"2025-07-21T05:54:10.000Z","size":69,"stargazers_count":1,"open_issues_count":5,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-07-22T18:10:41.336Z","etag":null,"topics":["cli","cli-app","curses","python","python-cli","python3","terminal","thread"],"latest_commit_sha":null,"homepage":"https://threeal.github.io/cursers/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/threeal.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-07-03T15:38:23.000Z","updated_at":"2025-07-11T06:07:39.000Z","dependencies_parsed_at":"2025-07-04T16:13:31.951Z","dependency_job_id":"4b7be580-581c-40a5-92a3-c5fa2d5b32bd","html_url":"https://github.com/threeal/cursers","commit_stats":null,"previous_names":["threeal/cursers"],"tags_count":0,"template":false,"template_full_name":"threeal/python-starter","purl":"pkg:github/threeal/cursers","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/threeal%2Fcursers","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/threeal%2Fcursers/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/threeal%2Fcursers/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/threeal%2Fcursers/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/threeal","download_url":"https://codeload.github.com/threeal/cursers/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/threeal%2Fcursers/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272169248,"owners_count":24885405,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-26T02:00:07.904Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","cli-app","curses","python","python-cli","python3","terminal","thread"],"created_at":"2025-07-22T18:05:16.031Z","updated_at":"2026-01-20T16:52:22.922Z","avatar_url":"https://github.com/threeal.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Cursers\n\nA minimal threaded wrapper for [Python curses](https://docs.python.org/3/howto/curses.html) that simplifies terminal user interface development with built-in threading support and lifecycle management.\n\n## Features\n\n- Simple context manager for curses applications\n- Built-in update loop with configurable FPS\n- Lifecycle hooks for application logic\n- Threaded version for running updates in separate thread\n- Text drawing utilities with styling support\n\n## Installation\n\n```bash\npip install cursers\n```\n\n## Quick Start\n\n### Basic App\n\n```python\nimport cursers\n\n\nclass MyApp(cursers.App):\n    # Called when entering the application context\n    def on_enter(self, screen):\n        screen.draw_text(0, 0, \"Hello, World!\", bold=True)\n\n    # Called each frame with screen object for input/drawing\n    def on_update(self, screen):\n        key = screen.get_key()\n        if key == 27:  # ESC key\n            self.request_exit()\n\n\n# Run the application\nwith MyApp() as app:\n    while not app.is_exit_requested():\n        app.update()\n```\n\n### Threaded App\n\n```python\nimport cursers\nimport time\n\n\nclass MyThreadedApp(cursers.ThreadedApp):\n    def on_enter(self, screen):\n        screen.draw_text(0, 0, \"Running in background thread!\")\n\n    def on_update(self, screen):\n        key = screen.get_key()\n        if key == 27:  # ESC key\n            self.request_exit()\n\n\n# Run in background thread\nwith MyThreadedApp() as app:\n    while not app.is_exit_requested():\n        time.sleep(0.1)  # Do other work\n```\n\n## API Reference\n\nFor complete API documentation, visit the [project documentation](https://threeal.github.io/cursers/).\n\n### App Class\n\nThe main application class that provides a context manager for curses applications.\n\n#### Constructor\n\n```python\nApp(fps=30, keypad=False)\n```\n\n- `fps`: Target frames per second (default: 30)\n- `keypad`: Whether to enable arrow keys and function keys (default: False)\n\n#### Methods\n\n- `request_exit()`: Requests the application to exit\n- `is_exit_requested()`: Returns `True` if exit has been requested\n- `update()`: Updates the application state and handles input (call in main loop)\n\n#### Lifecycle Hooks\n\nOverride these methods in your subclass:\n\n- `on_enter(screen)`: Called when entering the context\n- `on_update(screen)`: Called every frame\n- `on_exit(screen)`: Called when exiting the context\n\n### ThreadedApp Class\n\nExtends `App` to run the update loop in a separate thread.\n\n```python\nclass MyThreadedApp(cursers.ThreadedApp):\n    def on_update(self, screen):\n        # Handle input and drawing\n        key = screen.get_key()\n        pass\n\n\nwith MyThreadedApp() as app:\n    # Update loop runs automatically in background thread\n    while not app.is_exit_requested():\n        # Main thread is free for other tasks\n        time.sleep(0.1)\n```\n\n### Screen Class\n\nLow-level screen management for curses applications. Provides direct access to curses screen operations.\n\n#### Methods\n\n- `get_key()`: Returns the next key code from input buffer (-1 if no key available)\n- `refresh()`: Refreshes the screen to display pending changes\n- `draw_text(y, x, text, bold=False, underline=False)`: Draw styled text at position\n\n### Thread Class\n\nBasic threading context manager for custom threading needs.\n\n```python\nclass MyThread(cursers.Thread):\n    def run(self):\n        # Your thread code here\n        pass\n\n\nwith MyThread() as thread:\n    # Thread runs automatically\n    pass\n```\n\n## Examples\n\nCheck out the [`examples/`](./examples/) directory for complete working examples:\n\n- [`examples/move_control.py`](./examples/move_control.py) - Basic movement control with keyboard input\n- [`examples/move_control_gravity.py`](./examples/move_control_gravity.py) - Threaded application with gravity simulation\n\n## Development\n\n### Setup\n\n```bash\n# Clone the repository\ngit clone https://github.com/threeal/cursers.git\ncd cursers\n\n# Install development dependencies\nuv sync\n\n# Install Git hooks\nuv run lefthook install\n```\n\n### Code Quality\n\n```bash\n# Format code\ndprint fmt\n\n# Run linter\nuv run ruff check --fix\n```\n\n## License\n\nThis project is licensed under the terms of the [MIT License](./LICENSE).\n\nCopyright © 2025 [Alfi Maulana](https://github.com/threeal)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthreeal%2Fcursers","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthreeal%2Fcursers","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthreeal%2Fcursers/lists"}