diff --git a/submissions/terminal-os/.gitignore b/submissions/terminal-os/.gitignore new file mode 100644 index 00000000..b3106680 --- /dev/null +++ b/submissions/terminal-os/.gitignore @@ -0,0 +1,123 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# TerminalOS specific +config/ +logs/ +screenshots/ +workspaces/ +custom_apps/ \ No newline at end of file diff --git a/submissions/terminal-os/LICENSE b/submissions/terminal-os/LICENSE new file mode 100644 index 00000000..bd2f4380 --- /dev/null +++ b/submissions/terminal-os/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Sithum Sathsara Rajapaksha | 000x + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/submissions/terminal-os/Makefile b/submissions/terminal-os/Makefile new file mode 100644 index 00000000..e64a4c67 --- /dev/null +++ b/submissions/terminal-os/Makefile @@ -0,0 +1,57 @@ +.PHONY: help install install-dev test lint format clean build upload docs + +help: + @echo "TerminalOS Development Commands:" + @echo " install Install TerminalOS" + @echo " install-dev Install in development mode" + @echo " test Run tests" + @echo " lint Run linting" + @echo " format Format code" + @echo " clean Clean build artifacts" + @echo " build Build package" + @echo " upload Upload to PyPI" + @echo " docs Build documentation" + +install: + pip install . + +install-dev: + pip install -e ".[dev]" + pre-commit install + +test: + pytest tests/ -v --cov=terminalos --cov-report=html + +lint: + flake8 terminalos tests + mypy terminalos + +format: + black terminalos tests + isort terminalos tests + +clean: + rm -rf build/ + rm -rf dist/ + rm -rf *.egg-info/ + rm -rf htmlcov/ + find . -type d -name __pycache__ -delete + find . -type f -name "*.pyc" -delete + +build: clean + python -m build + +upload: build + twine upload dist/* + +docs: + cd docs && make html + +run: + python -m terminalos + +demo: + python -m terminalos --theme matrix --no-boot + +dev-run: + python -m terminalos --debug --theme cyberpunk \ No newline at end of file diff --git a/submissions/terminal-os/README.md b/submissions/terminal-os/README.md new file mode 100644 index 00000000..e4fd8cad --- /dev/null +++ b/submissions/terminal-os/README.md @@ -0,0 +1,269 @@ +# TerminalOS + + +## πŸ–₯️ Overview + +TerminalOS is a full desktop-like experience within your terminal. Built using Python and the Textual framework, it offers a responsive and visually appealing TUI (Text User Interface) with multiple applications, window management, theme support, and keyboard navigationβ€”all running inside a terminal window. +![alt text](image-1.png) +![alt text](image-2.png) +![alt text](image.png) + +## ✨ Features + +### πŸ–₯️ Desktop Environment + +* Beautiful ASCII desktop with real-time clock and system info +* Window management with modals and multiple screen support +* Theme support: Dark, Light, Matrix, Cyberpunk +* Keyboard shortcuts for quick navigation + +### πŸ“± Built-in Applications + +| App | Description | Status | Shortcut | +| ----------------- | ---------------------------------- | -------------- | --------------- | +| πŸ“ File Manager | Browse directories and file info | βœ… Ready | `1` in launcher | +| πŸ“ Text Editor | Syntax-highlighted file editing | βœ… Ready | `2` in launcher | +| πŸ”’ Calculator | Scientific calculator with history | βœ… Ready | `3` in launcher | +| πŸ“Š System Monitor | CPU, memory, and disk usage | βœ… Ready | `4` in launcher | +| πŸ’» Terminal | Built-in CLI emulator | 🚧 Coming Soon | `5` in launcher | +| 🎡 Music Player | Playlist and audio playback | 🚧 Coming Soon | `6` in launcher | + +### 🎨 Themes & Customization + +* Dark, Light, Matrix, and Cyberpunk themes +* Persistent configuration settings +* Responsive layout for different terminal sizes +* ASCII art and Unicode icons + +### ⌨️ Keyboard Shortcuts + +* `F1`: Help +* `F12`: App Launcher +* `Ctrl+Q`: Quit +* `ESC`: Close current dialog/app +* `Tab`: Move between UI elements + +## πŸš€ Quick Start + + +### Installation + +```bash +git clone https://github.com/000xs/terminalos.git +cd terminalos +pip install -r requirements.txt +pip install -e . +terminalos +``` + +## πŸ“‹ Requirements + +* Python 3.8+ +* Unicode-supported terminal +* Minimum 80x24 (120x40 recommended) +* Cross-platform (Windows, Linux, macOS) + +### Dependencies + +* `textual>=0.41.0` +* `rich>=13.0.0` +* `click>=8.1.0` +* `psutil>=5.9.0` +* `pyfiglet>=0.8.0` +* `pygments>=2.14.0` + +## πŸ› οΈ Installation Methods + +### Method 1: Direct Launcher + +```bash +git clone https://github.com/000xs/terminalos.git +cd terminalos +python start_terminalos.py +``` + +### Method 2: Package Install + +```bash +git clone https://github.com/000xs/terminalos.git +cd terminalos +pip install -e . +terminalos +``` + + + +## πŸ“– Usage Guide + +### Start TerminalOS + +```bash +terminalos +# Optional flags: +--debug +--no-boot +--version +``` + +### Navigation + +* Boot screen: `Enter` +* App launcher: `F12` +* Return to desktop: `ESC` +* Help: `F1` + +## πŸ“š Application Guide + +### πŸ“ File Manager + +* Browse using arrow keys +* View metadata in sidebar + +### πŸ“ Text Editor + +* Syntax highlighting +* Real-time line/word/char count +* Tabs and file operations + +### πŸ”’ Calculator + +* Arithmetic + scientific functions +* Keyboard/mouse input + +### πŸ“Š System Monitor + +* Live CPU, memory, and disk usage + +## 🎨 Theme System + +### Available Themes + +* Dark (Default) +* Light +* Matrix +* Cyberpunk + +### Change Theme + +```bash +terminalos --theme matrix +``` + +## βš™οΈ Configuration File + +Located at: + +* Windows: `%USERPROFILE%\.config\terminalos\` +* Linux/macOS: `~/.config/terminalos/` + +Example `config.json`: + +```json +{ + "theme": "dark", + "debug": false, + "auto_save": true, + "appearance": { + "show_boot": true, + "animations": true, + "font_size": 12 + }, + "file_manager": { + "show_hidden": false, + "default_path": "~", + "sort_by": "name" + }, + "text_editor": { + "syntax_highlighting": true, + "line_numbers": true, + "tab_size": 4 + } +} +``` + +## 🧩 Architecture + +``` +terminalos/ +β”œβ”€β”€ core/ # App framework +β”œβ”€β”€ desktop/ # Desktop and taskbar +β”œβ”€β”€ apps/ # Built-in apps +β”œβ”€β”€ config/ # Settings and themes +└── utils/ # Helpers and logging +``` + +## πŸ”§ Development Guide + +### Setup + +```bash +git clone https://github.com/000xs/terminalos.git +cd terminalos + +pip install -e . +``` + + +### Create Custom App + +```python +from textual.screen import Screen +from textual.widgets import Header, Footer, Static + +class MyCustomApp(Screen): + def compose(self): + yield Header() + yield Static("Hello from my custom app!") + yield Footer() + + def on_key(self, event): + if event.key == "escape": + self.dismiss() +``` + +Add to launcher in `desktop/desktop.py`: + +```python +from ..apps.my_app import MyCustomApp +self.dismiss() +self.app.push_screen(MyCustomApp()) +``` + +## 🀝 Contributing + +* Bug reports: Include Python version, OS, steps +* Features: Describe use case + mockup if possible +* Pull requests: Follow PEP8, type hints, test coverage + +## πŸ“ Changelog + +### v1.0.0 + +* Initial release +* Desktop, file manager, editor, calculator, system monitor +* 4 themes + +### v1.1.0 (Planned) + +* Terminal emulator +* Music player +* Plugin system +* More themes + +## πŸ› Troubleshooting + +* `ModuleNotFoundError`: Use direct launcher +* Python conflict: Use correct Python version +* Missing deps: `pip install textual rich click psutil pyfiglet pygments` +* Terminal display issues: `echo $TERM` + +## πŸ† Credits + +* Textual, Rich +* Terminalcraft slack channel, josia idea + +## πŸ“„ License + +[MIT License ](LICENSE) + + \ No newline at end of file diff --git a/submissions/terminal-os/image-1.png b/submissions/terminal-os/image-1.png new file mode 100644 index 00000000..9083b454 Binary files /dev/null and b/submissions/terminal-os/image-1.png differ diff --git a/submissions/terminal-os/image-2.png b/submissions/terminal-os/image-2.png new file mode 100644 index 00000000..91c7cf6d Binary files /dev/null and b/submissions/terminal-os/image-2.png differ diff --git a/submissions/terminal-os/image.png b/submissions/terminal-os/image.png new file mode 100644 index 00000000..d065981a Binary files /dev/null and b/submissions/terminal-os/image.png differ diff --git a/submissions/terminal-os/pyproject.toml b/submissions/terminal-os/pyproject.toml new file mode 100644 index 00000000..9db30e37 --- /dev/null +++ b/submissions/terminal-os/pyproject.toml @@ -0,0 +1,76 @@ +[build-system] +requires = ["setuptools>=45", "wheel", "setuptools_scm[toml]>=6.2"] +build-backend = "setuptools.build_meta" + +[project] +name = "terminalos" +dynamic = ["version"] +description = "A complete operating system experience in your terminal" +readme = "README.md" +requires-python = ">=3.8" +license = {text = "MIT"} +authors = [ + {name = "000x", email = "SITHUMSS9122@gmail.com"}, +] +keywords = ["terminal", "os", "tui", "cli", "desktop"] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Topic :: Terminals", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", +] + +dependencies = [ + "textual>=0.41.0", + "rich>=13.0.0", + "click>=8.1.0", + "psutil>=5.9.0", + "pyfiglet>=0.8.0", + "pygments>=2.14.0", + "watchdog>=3.0.0", + "pyyaml>=6.0", +] + +[project.optional-dependencies] +dev = [ + "pytest>=7.0.0", + "pytest-cov>=4.0.0", + "black>=22.0.0", + "flake8>=5.0.0", + "mypy>=0.991", + "pre-commit>=2.20.0", +] +audio = [ + "pygame>=2.3.0", + "mutagen>=1.47.0", +] +media = [ + "pillow>=9.0.0", + "opencv-python>=4.5.0", +] + +[project.urls] +Homepage = "https://github.com/000xs/terminalos" +Documentation = "https://terminalos.readthedocs.io/" +Repository = "https://github.com/000xs/terminalos" +"Bug Tracker" = "https://github.com/000xs/terminalos/issues" + +[project.scripts] +terminalos = "terminalos.__main__:main" +tos = "terminalos.__main__:main" + +[tool.setuptools_scm] +write_to = "terminalos/_version.py" + +[tool.black] +line-length = 88 +target-version = ['py38'] + +[tool.mypy] +python_version = "3.8" +warn_return_any = true +warn_unused_configs = true +disallow_untyped_defs = true + + \ No newline at end of file diff --git a/submissions/terminal-os/requirements-dev.txt b/submissions/terminal-os/requirements-dev.txt new file mode 100644 index 00000000..68b4d0f8 --- /dev/null +++ b/submissions/terminal-os/requirements-dev.txt @@ -0,0 +1,10 @@ +pytest>=7.0.0 +pytest-cov>=4.0.0 +pytest-asyncio>=0.21.0 +black>=22.0.0 +flake8>=5.0.0 +mypy>=0.991 +pre-commit>=2.20.0 +tox>=4.0.0 +sphinx>=5.0.0 +sphinx-rtd-theme>=1.2.0 \ No newline at end of file diff --git a/submissions/terminal-os/requirements.txt b/submissions/terminal-os/requirements.txt new file mode 100644 index 00000000..9dd3e371 --- /dev/null +++ b/submissions/terminal-os/requirements.txt @@ -0,0 +1,9 @@ +textual>=0.41.0 +rich>=13.0.0 +click>=8.1.0 +psutil>=5.9.0 +pyfiglet>=0.8.0 +pygments>=2.14.0 +watchdog>=3.0.0 +pyyaml>=6.0 +typing-extensions>=4.0.0 \ No newline at end of file diff --git a/submissions/terminal-os/setup.py b/submissions/terminal-os/setup.py new file mode 100644 index 00000000..6ca016f6 --- /dev/null +++ b/submissions/terminal-os/setup.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +"""Setup script for TerminalOS.""" + +from setuptools import setup, find_packages +import os +from pathlib import Path + +# Read README for long description +this_directory = Path(__file__).parent +long_description = (this_directory / "README.md").read_text(encoding='utf-8') + +# Read requirements +def read_requirements(filename): + with open(filename, 'r') as f: + return [line.strip() for line in f if line.strip() and not line.startswith('#')] + +setup( + name="terminalos", + version="1.0.0", + author="000x", + author_email="SITHUMSS9122@gmacil.com", + description="A complete operating system experience in your terminal", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://github.com/000xs/terminalos", + packages=find_packages(), + classifiers=[ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Intended Audience :: System Administrators", + "Topic :: System :: Shells", + "Topic :: Terminals", + "Topic :: System :: System Shells", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Operating System :: OS Independent", + "Environment :: Console", + "Environment :: Console :: Curses", + ], + python_requires=">=3.8", + install_requires=read_requirements("requirements.txt"), + extras_require={ + "dev": read_requirements("requirements-dev.txt"), + "audio": ["pygame>=2.3.0", "mutagen>=1.47.0"], + "media": ["pillow>=9.0.0", "opencv-python>=4.5.0"], + }, + entry_points={ + 'console_scripts': [ + 'terminalos=terminalos.__main__:main', + ], +}, + include_package_data=True, + package_data={ + "terminalos": [ + "assets/themes/*.json", + "assets/sounds/*.wav", + "assets/icons/*.txt", + ], + }, + keywords="terminal, os, tui, cli, desktop, file-manager, text-editor", + project_urls={ + "Bug Reports": "https://github.com/000xs/terminalos/issues", + "Source": "https://github.com/000xs/terminalos", + "Documentation": "https://terminalos.readthedocs.io/", + }, +) \ No newline at end of file diff --git a/submissions/terminal-os/start.bat b/submissions/terminal-os/start.bat new file mode 100644 index 00000000..5ee68eb6 --- /dev/null +++ b/submissions/terminal-os/start.bat @@ -0,0 +1,29 @@ +@echo off +title TerminalOS Launcher +color 0B + +echo. +echo ╔══════════════════════════════════════════╗ +echo β•‘ πŸ–₯️ TerminalOS β•‘ +echo β•‘ Windows Launcher β•‘ +echo β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β• +echo. + +REM Check if Python is available +python --version >nul 2>&1 +if errorlevel 1 ( + echo ❌ Python not found! + echo Please install Python 3.8+ from python.org + echo. + pause + exit /b 1 +) + +REM Run TerminalOS launcher +python start_terminalos.py %* + +if errorlevel 1 ( + echo. + echo ❌ TerminalOS failed to start + pause +) \ No newline at end of file diff --git a/submissions/terminal-os/start_terminalos.py b/submissions/terminal-os/start_terminalos.py new file mode 100644 index 00000000..643eb657 --- /dev/null +++ b/submissions/terminal-os/start_terminalos.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 +""" +TerminalOS Direct Launcher v1.0.0 +Runs TerminalOS without installation - Just works! +""" + +import sys +import os +from pathlib import Path + +# Colors for terminal output +class Colors: + RED = '\033[91m' + GREEN = '\033[92m' + YELLOW = '\033[93m' + BLUE = '\033[94m' + CYAN = '\033[96m' + WHITE = '\033[97m' + BOLD = '\033[1m' + END = '\033[0m' + +def print_header(): + """Print TerminalOS header.""" + print(f"{Colors.CYAN}{Colors.BOLD}") + print("╔══════════════════════════════════════════╗") + print("β•‘ πŸ–₯️ TerminalOS β•‘") + print("β•‘ Direct Launcher v1.0.0 β•‘") + print("β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•") + print(f"{Colors.END}") + +def check_python_version(): + """Check Python version compatibility.""" + if sys.version_info < (3, 8): + print(f"{Colors.RED}❌ Python 3.8+ required. You have {sys.version}{Colors.END}") + return False + print(f"{Colors.GREEN}βœ… Python {sys.version.split()[0]} - OK{Colors.END}") + return True + +def install_missing_packages(): + """Install missing packages automatically.""" + required_packages = { + 'textual': 'textual>=0.41.0', + 'rich': 'rich>=13.0.0', + 'click': 'click>=8.1.0', + 'psutil': 'psutil>=5.9.0', + 'pyfiglet': 'pyfiglet>=0.8.0', + 'pygments': 'pygments>=2.14.0' + } + + missing = [] + for pkg in required_packages: + try: + __import__(pkg) + print(f"{Colors.GREEN}βœ… {pkg}{Colors.END}") + except ImportError: + missing.append(required_packages[pkg]) + print(f"{Colors.YELLOW}⚠️ {pkg} - Not found{Colors.END}") + + if missing: + print(f"\n{Colors.CYAN}πŸ“¦ Installing missing packages...{Colors.END}") + import subprocess + try: + cmd = [sys.executable, '-m', 'pip', 'install'] + missing + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode == 0: + print(f"{Colors.GREEN}βœ… All packages installed successfully!{Colors.END}") + return True + else: + print(f"{Colors.RED}❌ Failed to install packages: {result.stderr}{Colors.END}") + return False + except Exception as e: + print(f"{Colors.RED}❌ Installation error: {e}{Colors.END}") + return False + + return True + +def setup_paths(): + """Setup Python paths for imports.""" + current_dir = Path(__file__).parent + terminalos_dir = current_dir / "terminalos" + + if not terminalos_dir.exists(): + print(f"{Colors.RED}❌ terminalos directory not found at: {terminalos_dir}{Colors.END}") + return False + + # Add to Python path + sys.path.insert(0, str(current_dir)) + print(f"{Colors.GREEN}βœ… Python paths configured{Colors.END}") + return True + +def main(): + """Main launcher function.""" + print_header() + + # Check Python version + if not check_python_version(): + input("Press Enter to exit...") + return 1 + + # Setup paths + if not setup_paths(): + input("Press Enter to exit...") + return 1 + + # Check and install dependencies + print(f"\n{Colors.CYAN}πŸ” Checking dependencies...{Colors.END}") + if not install_missing_packages(): + input("Press Enter to exit...") + return 1 + + # Parse command line arguments + debug_mode = '--debug' in sys.argv + no_boot = '--no-boot' in sys.argv + + try: + print(f"\n{Colors.CYAN}πŸš€ Starting TerminalOS...{Colors.END}") + + # Import TerminalOS + from terminalos.core.app import TerminalOSApp + + # Create and configure app + app = TerminalOSApp() + app.configure({ + 'debug': debug_mode, + 'theme': 'dark', + 'no_boot': no_boot + }) + + # Run the application + app.run() + + print(f"\n{Colors.GREEN}πŸ‘‹ TerminalOS closed successfully!{Colors.END}") + return 0 + + except KeyboardInterrupt: + print(f"\n{Colors.YELLOW}⚠️ TerminalOS interrupted by user{Colors.END}") + return 0 + except ImportError as e: + print(f"\n{Colors.RED}❌ Import error: {e}{Colors.END}") + print(f"{Colors.YELLOW}πŸ’‘ Make sure all files are in the correct structure{Colors.END}") + return 1 + except Exception as e: + print(f"\n{Colors.RED}❌ Runtime error: {e}{Colors.END}") + if debug_mode: + import traceback + traceback.print_exc() + else: + print(f"{Colors.YELLOW}πŸ’‘ Run with --debug for detailed error information{Colors.END}") + return 1 + +if __name__ == "__main__": + try: + exit_code = main() + if exit_code != 0: + input("\nPress Enter to exit...") + sys.exit(exit_code) + except Exception as e: + print(f"{Colors.RED}❌ Fatal error: {e}{Colors.END}") + input("Press Enter to exit...") + sys.exit(1) \ No newline at end of file diff --git a/submissions/terminal-os/terminalos.py b/submissions/terminal-os/terminalos.py new file mode 100644 index 00000000..f1cfe37d --- /dev/null +++ b/submissions/terminal-os/terminalos.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +"""TerminalOS - A complete OS experience in your terminal.""" + +import asyncio +import sys +from pathlib import Path + +# Add src to path for imports +sys.path.insert(0, str(Path(__file__).parent / "src")) + +from desktop.desktop import TerminalOSApp + +def main(): + """Main entry point for TerminalOS.""" + app = TerminalOSApp() + app.run() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/submissions/terminal-os/terminalos/__init__.py b/submissions/terminal-os/terminalos/__init__.py new file mode 100644 index 00000000..be4a4046 --- /dev/null +++ b/submissions/terminal-os/terminalos/__init__.py @@ -0,0 +1,11 @@ +"""TerminalOS - A complete operating system experience in your terminal.""" + +__version__ = "1.0.0" +__author__ = "Terminal Developer" +__email__ = "dev@terminalos.com" +__description__ = "A complete operating system experience in your terminal" + +from .core.app import TerminalOSApp +from .config.settings import Settings + +__all__ = ["TerminalOSApp", "Settings", "__version__"] \ No newline at end of file diff --git a/submissions/terminal-os/terminalos/__main__.py b/submissions/terminal-os/terminalos/__main__.py new file mode 100644 index 00000000..9ae637f1 --- /dev/null +++ b/submissions/terminal-os/terminalos/__main__.py @@ -0,0 +1,4 @@ +from .cli import main + +if __name__ == "__main__": + main() diff --git a/submissions/terminal-os/terminalos/apps/calculator.py b/submissions/terminal-os/terminalos/apps/calculator.py new file mode 100644 index 00000000..1d3ffa79 --- /dev/null +++ b/submissions/terminal-os/terminalos/apps/calculator.py @@ -0,0 +1,113 @@ +"""Calculator application.""" + +from textual.app import ComposeResult +from textual.screen import Screen +from textual.widgets import Header, Footer, Button, Static +from textual.containers import Grid, Vertical +from textual.binding import Binding + + +class CalculatorApp(Screen): + """Calculator application.""" + + BINDINGS = [ + Binding("escape", "close", "Close"), + Binding("c", "clear", "Clear"), + ] + + def __init__(self): + super().__init__() + self.display = "0" + self.reset_on_next = False + + def compose(self) -> ComposeResult: + yield Header() + yield Vertical( + Static(self.display, id="display", classes="calc-display"), + Grid( + Button("C", id="clear", classes="calc-btn"), + Button("Β±", id="negate", classes="calc-btn"), + Button("%", id="percent", classes="calc-btn"), + Button("Γ·", id="divide", classes="calc-btn"), + + Button("7", id="seven", classes="calc-btn"), + Button("8", id="eight", classes="calc-btn"), + Button("9", id="nine", classes="calc-btn"), + Button("Γ—", id="multiply", classes="calc-btn"), + + Button("4", id="four", classes="calc-btn"), + Button("5", id="five", classes="calc-btn"), + Button("6", id="six", classes="calc-btn"), + Button("-", id="subtract", classes="calc-btn"), + + Button("1", id="one", classes="calc-btn"), + Button("2", id="two", classes="calc-btn"), + Button("3", id="three", classes="calc-btn"), + Button("+", id="add", classes="calc-btn"), + + Button("0", id="zero", classes="calc-btn"), + Button(".", id="decimal", classes="calc-btn"), + Button("=", id="equals", classes="calc-btn"), + Button("⌫", id="backspace", classes="calc-btn"), + + classes="calc-grid" + ), + classes="calculator" + ) + yield Footer() + + def on_button_pressed(self, event: Button.Pressed) -> None: + """Handle button press.""" + button_id = event.button.id + + if button_id in ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"]: + self.input_digit(button_id) + elif button_id == "clear": + self.clear_display() + elif button_id == "equals": + self.calculate() + + self.update_display() + + def input_digit(self, digit_id: str) -> None: + """Input a digit.""" + digit_map = { + "zero": "0", "one": "1", "two": "2", "three": "3", "four": "4", + "five": "5", "six": "6", "seven": "7", "eight": "8", "nine": "9" + } + + digit = digit_map[digit_id] + if self.display == "0" or self.reset_on_next: + self.display = digit + self.reset_on_next = False + else: + self.display += digit + + def clear_display(self) -> None: + """Clear the display.""" + self.display = "0" + self.reset_on_next = False + + def calculate(self) -> None: + """Perform calculation.""" + try: + # Simple calculation (in production, use proper parser) + result = eval(self.display.replace("Γ—", "*").replace("Γ·", "/")) + self.display = str(result) + self.reset_on_next = True + except: + self.display = "Error" + self.reset_on_next = True + + def update_display(self) -> None: + """Update the display.""" + self.query_one("#display").update(self.display) + + def action_close(self) -> None: + """Close calculator.""" + self.dismiss() + + def action_clear(self) -> None: + """Clear via keyboard.""" + self.clear_display() + self.update_display() \ No newline at end of file diff --git a/submissions/terminal-os/terminalos/apps/file_manager.py b/submissions/terminal-os/terminalos/apps/file_manager.py new file mode 100644 index 00000000..f48b615d --- /dev/null +++ b/submissions/terminal-os/terminalos/apps/file_manager.py @@ -0,0 +1,57 @@ +"""File manager application.""" + +from textual.app import ComposeResult +from textual.screen import Screen +from textual.widgets import Header, Footer, DirectoryTree, Static +from textual.containers import Horizontal, Vertical +from textual.binding import Binding +from pathlib import Path + + +class FileManagerApp(Screen): + """File manager application.""" + + BINDINGS = [ + Binding("escape", "close", "Close"), + Binding("f5", "refresh", "Refresh"), + ] + + def compose(self) -> ComposeResult: + yield Header() + yield Horizontal( + DirectoryTree(str(Path.home()), id="file_tree"), + Vertical( + Static("πŸ“ File Details", classes="panel-title"), + Static("Select a file to see details", id="file_details"), + classes="details-panel" + ), + classes="file-manager" + ) + yield Footer() + + def on_directory_tree_file_selected(self, event) -> None: + """Handle file selection.""" + file_path = Path(event.path) + details = f"πŸ“„ {file_path.name}\nπŸ“ {file_path.parent}\nπŸ“Š {self.get_file_size(file_path)}" + self.query_one("#file_details").update(details) + + def get_file_size(self, path: Path) -> str: + """Get formatted file size.""" + try: + size = path.stat().st_size + for unit in ['B', 'KB', 'MB', 'GB']: + if size < 1024: + return f"{size:.1f} {unit}" + size /= 1024 + return f"{size:.1f} TB" + except: + return "Unknown" + + def action_close(self) -> None: + """Close file manager.""" + self.dismiss() + + def action_refresh(self) -> None: + """Refresh file tree.""" + tree = self.query_one("#file_tree") + tree.reload() \ No newline at end of file diff --git a/submissions/terminal-os/terminalos/apps/system_monitor.py b/submissions/terminal-os/terminalos/apps/system_monitor.py new file mode 100644 index 00000000..5d8548cf --- /dev/null +++ b/submissions/terminal-os/terminalos/apps/system_monitor.py @@ -0,0 +1,93 @@ +"""System monitor application.""" + +from textual.app import ComposeResult +from textual.screen import Screen +from textual.widgets import Header, Footer, Static, ProgressBar +from textual.containers import Vertical, Horizontal +from textual.binding import Binding +import psutil + + +class SystemMonitorApp(Screen): + """System monitoring application.""" + + BINDINGS = [ + Binding("escape", "close", "Close"), + Binding("r", "refresh", "Refresh"), + ] + + def __init__(self): + super().__init__() + self.set_interval(2.0, self.update_stats) + + def compose(self) -> ComposeResult: + yield Header() + yield Vertical( + Static("πŸ“Š System Monitor", classes="title"), + Horizontal( + Vertical( + Static("πŸ’» CPU Usage", classes="label"), + ProgressBar(total=100, id="cpu_bar"), + Static("πŸ’Ύ Memory Usage", classes="label"), + ProgressBar(total=100, id="memory_bar"), + Static("πŸ’Ώ Disk Usage", classes="label"), + ProgressBar(total=100, id="disk_bar"), + classes="stats-panel" + ), + Vertical( + Static("πŸ“ˆ System Info", classes="label"), + Static("", id="system_info", classes="info-panel"), + classes="info-section" + ), + classes="monitor-content" + ), + classes="system-monitor" + ) + yield Footer() + + def update_stats(self) -> None: + """Update system statistics.""" + try: + # CPU Usage + cpu_percent = psutil.cpu_percent() + cpu_bar = self.query_one("#cpu_bar") + cpu_bar.progress = cpu_percent + + # Memory Usage + memory = psutil.virtual_memory() + memory_bar = self.query_one("#memory_bar") + memory_bar.progress = memory.percent + + # Disk Usage + disk = psutil.disk_usage('/') + disk_percent = (disk.used / disk.total) * 100 + disk_bar = self.query_one("#disk_bar") + disk_bar.progress = disk_percent + + # System Info + info = f"""πŸ–₯️ CPU: {psutil.cpu_count()} cores +πŸ’Ύ RAM: {self.format_bytes(memory.total)} +πŸ’Ώ Disk: {self.format_bytes(disk.total)} +⚑ Load: {cpu_percent:.1f}% +πŸ”‹ Available: {self.format_bytes(memory.available)}""" + + self.query_one("#system_info").update(info) + + except Exception as e: + self.notify(f"Error updating stats: {e}") + + def format_bytes(self, bytes_val: int) -> str: + """Format bytes in human readable format.""" + for unit in ['B', 'KB', 'MB', 'GB', 'TB']: + if bytes_val < 1024: + return f"{bytes_val:.1f} {unit}" + bytes_val /= 1024 + return f"{bytes_val:.1f} PB" + + def action_close(self) -> None: + """Close system monitor.""" + self.dismiss() + + def action_refresh(self) -> None: + """Force refresh stats.""" + self.update_stats() \ No newline at end of file diff --git a/submissions/terminal-os/terminalos/apps/text_editor.py b/submissions/terminal-os/terminalos/apps/text_editor.py new file mode 100644 index 00000000..cb29b5a7 --- /dev/null +++ b/submissions/terminal-os/terminalos/apps/text_editor.py @@ -0,0 +1,57 @@ +"""Text editor application.""" + +from textual.app import ComposeResult +from textual.screen import Screen +from textual.widgets import Header, Footer, TextArea, Static +from textual.containers import Horizontal, Vertical +from textual.binding import Binding + + +class TextEditorApp(Screen): + """Text editor application.""" + + BINDINGS = [ + Binding("escape", "close", "Close"), + Binding("ctrl+s", "save", "Save"), + Binding("ctrl+o", "open", "Open"), + ] + + def __init__(self): + super().__init__() + self.current_file = None + + def compose(self) -> ComposeResult: + yield Header() + yield Horizontal( + TextArea("# Welcome to TerminalOS Text Editor\n\nStart typing...", id="editor"), + Vertical( + Static("πŸ“ Editor Info", classes="panel-title"), + Static("New Document", id="file_info"), + Static("Lines: 3\nWords: 7\nChars: 45", id="stats"), + classes="editor-sidebar" + ), + classes="text-editor" + ) + yield Footer() + + def on_text_area_changed(self, event) -> None: + """Update statistics when text changes.""" + content = event.text_area.text + lines = len(content.split('\n')) + words = len(content.split()) + chars = len(content) + + stats = f"Lines: {lines}\nWords: {words}\nChars: {chars}" + self.query_one("#stats").update(stats) + + def action_close(self) -> None: + """Close text editor.""" + self.dismiss() + + def action_save(self) -> None: + """Save current document.""" + self.notify("Save functionality coming soon!") + + def action_open(self) -> None: + """Open document.""" + self.notify("Open functionality coming soon!") \ No newline at end of file diff --git a/submissions/terminal-os/terminalos/cli.py b/submissions/terminal-os/terminalos/cli.py new file mode 100644 index 00000000..c1ae4e38 --- /dev/null +++ b/submissions/terminal-os/terminalos/cli.py @@ -0,0 +1,451 @@ +"""Command-line interface for TerminalOS.""" + +import click +import sys +import os +import asyncio +from pathlib import Path +from typing import Optional, Dict, Any + +from .core.app import TerminalOSApp +from .config.settings import Settings +# from .utils.logger import setup_logger +from .utils.helpers import get_terminal_size, check_dependencies +from . import __version__ + + +def validate_theme(ctx, param, value): + """Validate theme parameter.""" + valid_themes = ['dark', 'light', 'matrix', 'cyberpunk', 'classic'] + if value not in valid_themes: + raise click.BadParameter(f'Theme must be one of: {", ".join(valid_themes)}') + return value + + +@click.group(invoke_without_command=True) +@click.option('--version', is_flag=True, help='Show version and exit') +@click.option('--theme', default='dark', callback=validate_theme, + help='Theme to use (dark, light, matrix, cyberpunk, classic)') +@click.option('--config', type=click.Path(exists=True), + help='Path to custom config file') +@click.option('--debug', is_flag=True, help='Enable debug logging') +@click.option('--fullscreen', is_flag=True, help='Start in fullscreen mode') +@click.option('--no-boot', is_flag=True, help='Skip boot animation') +@click.option('--safe-mode', is_flag=True, help='Start in safe mode') +@click.option('--width', type=int, help='Terminal width override') +@click.option('--height', type=int, help='Terminal height override') +@click.pass_context +def cli(ctx, version, theme, config, debug, fullscreen, no_boot, safe_mode, width, height): + """TerminalOS - A complete operating system experience in your terminal. + + πŸ–₯️ Features: + β€’ Desktop Environment with window management + β€’ File Manager with advanced operations + β€’ Text Editor with syntax highlighting + β€’ Terminal Emulator with command history + β€’ System Monitor with real-time stats + β€’ Calculator with scientific functions + β€’ Music Player with playlist support + β€’ Package Manager for extensions + + Examples: + terminalos # Start TerminalOS + terminalos --theme matrix # Start with Matrix theme + terminalos --debug # Start with debug logging + terminalos install cowsay # Install a package + terminalos config --editor # Configure settings + """ + + if version: + click.echo(f"πŸ–₯️ TerminalOS v{__version__}") + click.echo(f"πŸ“ Python {sys.version.split()[0]}") + click.echo(f"🏠 Config: {Settings.get_config_dir()}") + sys.exit(0) + + # Setup context + ctx.ensure_object(dict) + ctx.obj.update({ + 'theme': theme, + 'config': config, + 'debug': debug, + 'fullscreen': fullscreen, + 'no_boot': no_boot, + 'safe_mode': safe_mode, + 'width': width, + 'height': height + }) + + # Check system requirements + if not check_dependencies(): + click.echo("❌ System requirements not met. Run 'terminalos doctor' for details.") + sys.exit(1) + + # If no subcommand, run main app + if ctx.invoked_subcommand is None: + run_app(ctx.obj) + + +@cli.command() +@click.option('--app', help='Launch specific app directly') +@click.option('--workspace', help='Load specific workspace') +@click.pass_context +def run(ctx, app, workspace): + """Start TerminalOS with optional parameters.""" + options = ctx.obj.copy() + options.update({'direct_app': app, 'workspace': workspace}) + run_app(options) + + +@cli.command() +@click.argument('package_name', required=False) +@click.option('--upgrade', is_flag=True, help='Upgrade existing package') +@click.option('--force', is_flag=True, help='Force reinstall') +def install(package_name, upgrade, force): + """Install TerminalOS packages and extensions.""" + from .apps.package_manager import PackageManager + + if not package_name: + # Show available packages + manager = PackageManager() + packages = manager.list_available() + + click.echo("πŸ“¦ Available Packages:") + for pkg in packages: + status = "βœ… Installed" if manager.is_installed(pkg['name']) else "⬜ Available" + click.echo(f" {pkg['name']}: {pkg['description']} [{status}]") + return + + manager = PackageManager() + + with click.progressbar(length=100, label=f'Installing {package_name}') as bar: + try: + success = manager.install(package_name, upgrade=upgrade, force=force, + progress_callback=lambda p: bar.update(p)) + if success: + click.echo(f"βœ… Successfully installed {package_name}") + else: + click.echo(f"❌ Failed to install {package_name}") + except Exception as e: + click.echo(f"❌ Error installing {package_name}: {e}") + + +@cli.command() +@click.argument('package_name') +@click.option('--purge', is_flag=True, help='Remove all data') +def uninstall(package_name, purge): + """Uninstall TerminalOS packages.""" + from .apps.package_manager import PackageManager + + if click.confirm(f'Remove {package_name}?'): + manager = PackageManager() + if manager.uninstall(package_name, purge=purge): + click.echo(f"βœ… Successfully removed {package_name}") + else: + click.echo(f"❌ Failed to remove {package_name}") + + +@cli.command() +@click.option('--editor', is_flag=True, help='Open settings editor') +@click.option('--reset', is_flag=True, help='Reset to defaults') +@click.option('--export', type=click.Path(), help='Export settings to file') +@click.option('--import', 'import_file', type=click.Path(exists=True), + help='Import settings from file') +def config(editor, reset, export, import_file): + """Manage TerminalOS configuration.""" + settings = Settings() + + if reset: + if click.confirm('Reset all settings to defaults?'): + settings.reset_to_defaults() + click.echo("βœ… Settings reset to defaults") + return + + if export: + settings.export_config(export) + click.echo(f"βœ… Settings exported to {export}") + return + + if import_file: + settings.import_config(import_file) + click.echo(f"βœ… Settings imported from {import_file}") + return + + if editor: + # Launch settings app directly + run_app({'direct_app': 'settings'}) + else: + # Show current settings + config_data = settings.get_all() + click.echo("βš™οΈ Current TerminalOS Configuration:") + for section, values in config_data.items(): + click.echo(f"\n[{section}]") + for key, value in values.items(): + click.echo(f" {key} = {value}") + + +@cli.command() +def doctor(): + """Check system requirements and health.""" + click.echo("πŸ” TerminalOS System Check\n") + + checks = [ + ("Python Version", sys.version_info >= (3, 8)), + ("Terminal Size", get_terminal_size()[0] >= 80 and get_terminal_size()[1] >= 24), + ("Config Directory", Settings.get_config_dir().exists()), + ("Dependencies", check_dependencies()), + ] + + all_good = True + for check_name, result in checks: + status = "βœ… PASS" if result else "❌ FAIL" + click.echo(f"{check_name}: {status}") + if not result: + all_good = False + + if all_good: + click.echo("\nπŸŽ‰ All checks passed! TerminalOS is ready to run.") + else: + click.echo("\n⚠️ Some checks failed. Please fix the issues above.") + sys.exit(1) + + +@cli.command() +@click.option('--workspace', help='Create new workspace') +@click.option('--theme', help='Create new theme') +@click.option('--app', help='Create new app template') +def create(workspace, theme, app): + """Create new workspaces, themes, or apps.""" + if workspace: + create_workspace(workspace) + elif theme: + create_theme(theme) + elif app: + create_app_template(app) + else: + click.echo("Specify what to create: --workspace, --theme, or --app") + + +@cli.command() +@click.option('--logs', is_flag=True, help='Show recent logs') +@click.option('--stats', is_flag=True, help='Show usage statistics') +@click.option('--processes', is_flag=True, help='Show TerminalOS processes') +def status(logs, stats, processes): + """Show TerminalOS status and information.""" + if logs: + show_recent_logs() + elif stats: + show_usage_stats() + elif processes: + show_processes() + else: + show_general_status() + + +def run_app(options: Dict[str, Any]): + """Run the main TerminalOS application.""" + # Setup logging + log_level = 'DEBUG' if options.get('debug') else 'INFO' + # setup_logger(level=log_level) + + # Create and configure app + app = TerminalOSApp() + app.configure(options) + + try: + app.run() + except KeyboardInterrupt: + click.echo("\nπŸ‘‹ TerminalOS shutdown") + except Exception as e: + if options.get('debug'): + raise + click.echo(f"❌ Error: {e}") + sys.exit(1) + + +def create_workspace(name: str): + """Create a new workspace configuration.""" + workspace_dir = Settings.get_config_dir() / "workspaces" + workspace_dir.mkdir(exist_ok=True) + + workspace_file = workspace_dir / f"{name}.json" + if workspace_file.exists(): + click.echo(f"❌ Workspace '{name}' already exists") + return + + workspace_config = { + "name": name, + "apps": [], + "layout": "default", + "theme": "dark", + "created": str(Path.ctime(Path.now())) + } + + workspace_file.write_text(json.dumps(workspace_config, indent=2)) + click.echo(f"βœ… Created workspace '{name}'") + + +def create_theme(name: str): + """Create a new theme template.""" + theme_dir = Settings.get_config_dir() / "themes" + theme_dir.mkdir(exist_ok=True) + + theme_file = theme_dir / f"{name}.json" + if theme_file.exists(): + click.echo(f"❌ Theme '{name}' already exists") + return + + # Create basic theme template + theme_config = { + "name": name, + "colors": { + "primary": "#0066cc", + "secondary": "#4d94ff", + "background": "#000000", + "surface": "#1a1a1a", + "text": "#ffffff", + "accent": "#00ff00" + }, + "styles": { + "border": "solid", + "header_style": "bold", + "button_style": "dim" + } + } + + theme_file.write_text(json.dumps(theme_config, indent=2)) + click.echo(f"βœ… Created theme '{name}' template") + + +def create_app_template(name: str): + """Create a new app template.""" + app_dir = Settings.get_config_dir() / "custom_apps" + app_dir.mkdir(exist_ok=True) + + app_file = app_dir / f"{name}.py" + if app_file.exists(): + click.echo(f"❌ App '{name}' already exists") + return + + template = f'''"""Custom {name} app for TerminalOS.""" + +from textual.app import ComposeResult +from textual.screen import Screen +from textual.widgets import Header, Footer, Static +from textual.binding import Binding + +from terminalos.apps.base import BaseApp + + +class {name.title()}App(BaseApp): + """Custom {name} application.""" + + BINDINGS = [ + Binding("escape", "close", "Close"), + Binding("ctrl+r", "refresh", "Refresh"), + ] + + def __init__(self): + super().__init__() + self.title = "{name.title()}" + + def compose(self) -> ComposeResult: + yield Header() + yield Static(f"Welcome to {{self.title}}!", classes="welcome") + yield Footer() + + def action_close(self) -> None: + """Close the application.""" + self.dismiss() + + def action_refresh(self) -> None: + """Refresh the application.""" + # Add refresh logic here + pass +''' + + app_file.write_text(template) + click.echo(f"βœ… Created app template '{name}.py'") + + +def show_recent_logs(): + """Show recent TerminalOS logs.""" + log_file = Settings.get_config_dir() / "logs" / "terminalos.log" + if not log_file.exists(): + click.echo("πŸ“ No logs found") + return + + with open(log_file) as f: + lines = f.readlines() + for line in lines[-20:]: # Show last 20 lines + click.echo(line.rstrip()) + + +def show_usage_stats(): + """Show usage statistics.""" + stats_file = Settings.get_config_dir() / "stats.json" + if not stats_file.exists(): + click.echo("πŸ“Š No usage statistics available") + return + + import json + with open(stats_file) as f: + stats = json.load(f) + + click.echo("πŸ“Š TerminalOS Usage Statistics:") + click.echo(f" Total launches: {stats.get('launches', 0)}") + click.echo(f" Total runtime: {stats.get('runtime', 0)} minutes") + click.echo(f" Most used app: {stats.get('most_used_app', 'N/A')}") + + +def show_processes(): + """Show TerminalOS related processes.""" + import psutil + + processes = [] + for proc in psutil.process_iter(['pid', 'name', 'cmdline']): + try: + if 'terminalos' in ' '.join(proc.info['cmdline'] or []).lower(): + processes.append(proc) + except (psutil.NoSuchProcess, psutil.AccessDenied): + continue + + if not processes: + click.echo("πŸ” No TerminalOS processes found") + return + + click.echo("πŸ”„ TerminalOS Processes:") + for proc in processes: + click.echo(f" PID {proc.pid}: {proc.name()}") + + +def show_general_status(): + """Show general TerminalOS status.""" + settings = Settings() + + click.echo("πŸ–₯️ TerminalOS Status:") + click.echo(f" Version: {__version__}") + click.echo(f" Config Dir: {settings.get_config_dir()}") + click.echo(f" Current Theme: {settings.get('appearance.theme', 'dark')}") + click.echo(f" Debug Mode: {settings.get('system.debug', False)}") + + # Check if running + import psutil + running = any('terminalos' in p.name().lower() for p in psutil.process_iter()) + status = "🟒 Running" if running else "πŸ”΄ Not Running" + click.echo(f" Status: {status}") + + +def main(): + """Main CLI entry point.""" + try: + cli() + except KeyboardInterrupt: + click.echo("\nπŸ‘‹ Goodbye!") + sys.exit(0) + except Exception as e: + click.echo(f"❌ Unexpected error: {e}") + sys.exit(1) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/submissions/terminal-os/terminalos/core/app.py b/submissions/terminal-os/terminalos/core/app.py new file mode 100644 index 00000000..60d73d81 --- /dev/null +++ b/submissions/terminal-os/terminalos/core/app.py @@ -0,0 +1,358 @@ +"""Main TerminalOS application.""" + +import os +import sys +from datetime import datetime +from typing import Dict, Any, Optional + +from textual.app import App, ComposeResult +from textual.widgets import Header, Footer, Static +from textual.containers import Container, Vertical, Horizontal +from textual.binding import Binding +from textual.screen import ModalScreen +from rich.text import Text +from rich.panel import Panel +from rich.console import Console + +try: + import pyfiglet + PYFIGLET_AVAILABLE = True +except ImportError: + PYFIGLET_AVAILABLE = False + + +class BootScreen(ModalScreen): + """Boot screen with TerminalOS logo.""" + + def compose(self) -> ComposeResult: + boot_text = Text() + + if PYFIGLET_AVAILABLE: + try: + logo = pyfiglet.figlet_format("TerminalOS", font="slant") + boot_text.append(logo, style="bold cyan") + except: + boot_text.append("πŸ–₯️ TERMINALOS", style="bold cyan") + boot_text.append("\n" + "="*50 + "\n", style="cyan") + else: + boot_text.append("πŸ–₯️ TERMINALOS", style="bold cyan") + boot_text.append("\n" + "="*50 + "\n", style="cyan") + + boot_text.append("\nπŸš€ Initializing Terminal Operating System...", style="green") + boot_text.append("\n⚑ Loading desktop environment...", style="yellow") + boot_text.append("\nβœ… System ready!", style="bright_green") + boot_text.append("\n\nπŸ‘‹ Welcome! Press ENTER to continue", style="dim") + + yield Container( + Static(boot_text, classes="boot-screen"), + classes="boot-container" + ) + + def on_key(self, event) -> None: + if event.key in ["enter", "escape", "space"]: + self.dismiss() + + +class Desktop(Static): + """Main desktop area with ASCII art wallpaper.""" + + def __init__(self): + super().__init__() + self.update_timer = self.set_interval(5.0, self.update_wallpaper) + + def render(self) -> Text: + """Render desktop wallpaper.""" + wallpaper = Text() + + # Header + wallpaper.append("β•”" + "═"*78 + "β•—\n", style="blue") + wallpaper.append("β•‘" + " "*78 + "β•‘\n", style="blue") + wallpaper.append("β•‘" + "πŸ–₯️ TerminalOS Desktop Environment".center(78) + "β•‘\n", style="bold cyan") + wallpaper.append("β•‘" + " "*78 + "β•‘\n", style="blue") + wallpaper.append("β•‘" + "Your complete terminal operating system".center(78) + "β•‘\n", style="cyan") + wallpaper.append("β•‘" + " "*78 + "β•‘\n", style="blue") + + # Current time + current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + wallpaper.append("β•‘" + f"πŸ• {current_time}".center(78) + "β•‘\n", style="green") + wallpaper.append("β•‘" + " "*78 + "β•‘\n", style="blue") + + # Quick help + wallpaper.append("β•‘" + "Quick Actions:".center(78) + "β•‘\n", style="bold yellow") + wallpaper.append("β•‘" + "F1: Help β€’ F12: Apps β€’ Ctrl+Q: Quit".center(78) + "β•‘\n", style="white") + wallpaper.append("β•‘" + " "*78 + "β•‘\n", style="blue") + + # Available apps + wallpaper.append("β•‘" + "πŸ“± Available Applications:".center(78) + "β•‘\n", style="bold magenta") + wallpaper.append("β•‘" + "πŸ“ Files πŸ“ Editor πŸ”’ Calculator πŸ“Š Monitor".center(78) + "β•‘\n", style="green") + wallpaper.append("β•‘" + " "*78 + "β•‘\n", style="blue") + + # Footer + wallpaper.append("β•‘" + "Press F12 to launch applications".center(78) + "β•‘\n", style="dim") + wallpaper.append("β•‘" + " "*78 + "β•‘\n", style="blue") + wallpaper.append("β•š" + "═"*78 + "╝", style="blue") + + return wallpaper + + def update_wallpaper(self) -> None: + """Update wallpaper with current time.""" + self.refresh() + + +class HelpScreen(ModalScreen): + """Help screen with keyboard shortcuts and information.""" + + def compose(self) -> ComposeResult: + help_text = Text() + help_text.append("πŸ”§ TerminalOS Help & User Guide\n\n", style="bold cyan") + + help_text.append("πŸ“‹ Keyboard Shortcuts:\n", style="bold yellow") + help_text.append(" F1 - Show this help screen\n", style="green") + help_text.append(" F12 - Open application launcher\n", style="green") + help_text.append(" Ctrl+Q - Quit TerminalOS\n", style="green") + help_text.append(" ESC - Close current dialog/app\n", style="green") + help_text.append(" Tab - Navigate between UI elements\n", style="green") + help_text.append(" Enter - Select/Activate item\n", style="green") + + help_text.append("\nπŸ“± Available Applications:\n", style="bold yellow") + help_text.append(" πŸ“ File Manager - Browse and manage files\n", style="green") + help_text.append(" πŸ“ Text Editor - Edit text files with syntax highlighting\n", style="green") + help_text.append(" πŸ”’ Calculator - Scientific calculator with history\n", style="green") + help_text.append(" πŸ“Š System Monitor - Real-time system statistics\n", style="green") + help_text.append(" πŸ’» Terminal - Command-line interface (coming soon)\n", style="dim") + help_text.append(" 🎡 Music Player - Audio player (coming soon)\n", style="dim") + + help_text.append("\nπŸ’‘ Tips:\n", style="bold yellow") + help_text.append(" β€’ Use mouse or keyboard to navigate\n", style="cyan") + help_text.append(" β€’ Most apps support common shortcuts (Ctrl+S, Ctrl+O, etc.)\n", style="cyan") + help_text.append(" β€’ Press ESC to return to desktop from any app\n", style="cyan") + help_text.append(" β€’ Apps remember your settings between sessions\n", style="cyan") + + help_text.append("\n🌟 About TerminalOS:\n", style="bold yellow") + help_text.append(" Version: 1.0.0\n", style="white") + help_text.append(" Built with: Python + Textual\n", style="white") + help_text.append(" GitHub: github.com/yourusername/terminalos\n", style="white") + + help_text.append("\n" + "─"*60 + "\n", style="dim") + help_text.append("Press ESC to close this help screen", style="dim") + + yield Container( + Static(help_text, classes="help-screen"), + classes="help-container" + ) + + def on_key(self, event) -> None: + if event.key == "escape": + self.dismiss() + + +class AppLauncher(ModalScreen): + """Application launcher with app grid.""" + + BINDINGS = [ + Binding("escape", "dismiss", "Close"), + Binding("1", "launch_files", "Files"), + Binding("2", "launch_editor", "Editor"), + Binding("3", "launch_calculator", "Calculator"), + Binding("4", "launch_monitor", "Monitor"), + Binding("5", "launch_terminal", "Terminal"), + ] + + def compose(self) -> ComposeResult: + apps_text = Text() + apps_text.append("πŸ“± TerminalOS Application Launcher\n\n", style="bold cyan") + + # Available apps + apps_text.append("🟒 Available Applications:\n", style="bold green") + apps_text.append(" 1️⃣ πŸ“ File Manager - Browse files and directories\n", style="green") + apps_text.append(" 2️⃣ πŸ“ Text Editor - Edit code and text files\n", style="green") + apps_text.append(" 3️⃣ πŸ”’ Calculator - Perform calculations\n", style="green") + apps_text.append(" 4️⃣ πŸ“Š System Monitor - View system performance\n", style="green") + + # Coming soon + apps_text.append("\n🟑 Coming Soon:\n", style="bold yellow") + apps_text.append(" 5️⃣ πŸ’» Terminal - Command-line interface\n", style="dim") + apps_text.append(" 6️⃣ 🎡 Music Player - Audio playback\n", style="dim") + apps_text.append(" 7️⃣ 🌐 Web Browser - Browse the web\n", style="dim") + apps_text.append(" 8️⃣ πŸ“§ Email Client - Manage emails\n", style="dim") + + apps_text.append("\n" + "─"*50 + "\n", style="dim") + apps_text.append("πŸ’‘ Press number key to launch app or ESC to close", style="cyan") + + yield Container( + Static(apps_text, classes="app-launcher"), + classes="launcher-container" + ) + + def action_launch_files(self) -> None: + """Launch file manager.""" + try: + from ..apps.file_manager import FileManagerApp + self.dismiss() + self.app.push_screen(FileManagerApp()) + except Exception as e: + self.app.notify(f"Error launching File Manager: {e}") + + def action_launch_editor(self) -> None: + """Launch text editor.""" + try: + from ..apps.text_editor import TextEditorApp + self.dismiss() + self.app.push_screen(TextEditorApp()) + except Exception as e: + self.app.notify(f"Error launching Text Editor: {e}") + + def action_launch_calculator(self) -> None: + """Launch calculator.""" + try: + from ..apps.calculator import CalculatorApp + self.dismiss() + self.app.push_screen(CalculatorApp()) + except Exception as e: + self.app.notify(f"Error launching Calculator: {e}") + + def action_launch_monitor(self) -> None: + """Launch system monitor.""" + try: + from ..apps.system_monitor import SystemMonitorApp + self.dismiss() + self.app.push_screen(SystemMonitorApp()) + except Exception as e: + self.app.notify(f"Error launching System Monitor: {e}") + + def action_launch_terminal(self) -> None: + """Launch terminal (placeholder).""" + self.dismiss() + self.app.notify("πŸ’» Terminal app coming soon! Stay tuned for updates.") + + +class TerminalOSApp(App): + """Main TerminalOS application class.""" + + CSS = """ + /* Boot Screen Styles */ + .boot-container { + align: center middle; + } + + .boot-screen { + width: 90; + height: 25; + text-align: center; + border: solid $primary; + background: $surface; + padding: 2; + } + + /* Help Screen Styles */ + .help-container { + align: center middle; + } + + .help-screen { + width: 85; + height: 35; + border: solid $accent; + background: $surface; + padding: 2; + overflow-y: scroll; + } + + /* App Launcher Styles */ + .launcher-container { + align: center middle; + } + + .app-launcher { + width: 75; + height: 30; + text-align: center; + border: solid $success; + background: $surface; + padding: 2; + } + + /* Desktop Styles */ + .desktop { + height: 1fr; + background: $surface; + text-align: center; + padding: 1; + } + + /* Common Styles */ + Static { + text-align: center; + } + """ + + TITLE = "TerminalOS" + SUB_TITLE = "v1.0.0 - Your Terminal Operating System" + + BINDINGS = [ + Binding("f1", "show_help", "Help", show=True), + Binding("f12", "launch_apps", "Apps", show=True), + Binding("ctrl+q", "quit_app", "Quit", show=True), + Binding("ctrl+r", "refresh_desktop", "Refresh"), + ] + + def __init__(self): + super().__init__() + self.options: Dict[str, Any] = {} + self.startup_time = datetime.now() + + def configure(self, options: Dict[str, Any]): + """Configure app with startup options.""" + self.options = options + + # Apply debug mode + if options.get('debug'): + self.debug = True + + def compose(self) -> ComposeResult: + """Compose the main application UI.""" + yield Header( + show_clock=True, + name="πŸ–₯️ TerminalOS", + icon="πŸ–₯️" + ) + yield Container( + Desktop(), + classes="desktop" + ) + yield Footer() + + def on_mount(self) -> None: + """Initialize app when mounted.""" + # Show welcome message + self.notify("πŸš€ Welcome to TerminalOS! Press F12 for apps, F1 for help.") + + # Show boot screen unless disabled + if not self.options.get('no_boot', False): + self.call_after_refresh(self.show_boot_screen) + + def show_boot_screen(self) -> None: + """Show the boot screen.""" + self.push_screen(BootScreen()) + + def action_show_help(self) -> None: + """Show help screen.""" + self.push_screen(HelpScreen()) + + def action_launch_apps(self) -> None: + """Show application launcher.""" + self.push_screen(AppLauncher()) + + def action_quit_app(self) -> None: + """Quit TerminalOS with confirmation.""" + self.exit(message="πŸ‘‹ Thanks for using TerminalOS!") + + def action_refresh_desktop(self) -> None: + """Refresh the desktop.""" + self.refresh() + self.notify("πŸ”„ Desktop refreshed!") + + def on_key(self, event) -> None: + """Handle global key events.""" + # You can add global shortcuts here + pass \ No newline at end of file diff --git a/submissions/terminal-os/terminalos/utils/helpers.py b/submissions/terminal-os/terminalos/utils/helpers.py new file mode 100644 index 00000000..3f41adc0 --- /dev/null +++ b/submissions/terminal-os/terminalos/utils/helpers.py @@ -0,0 +1,233 @@ +"""Utility helper functions for TerminalOS.""" + +import os +import sys +import shutil +import subprocess +from pathlib import Path +from typing import Tuple, List, Optional, Dict, Any +import importlib.util + +from .logger import get_logger + +logger = get_logger(__name__) + + +def get_terminal_size() -> Tuple[int, int]: + """Get current terminal size.""" + try: + size = shutil.get_terminal_size(fallback=(80, 24)) + return size.columns, size.lines + except Exception: + return 80, 24 + + +def check_dependencies() -> bool: + """Check if all required dependencies are available.""" + required_packages = [ + 'textual', + 'rich', + 'click', + 'psutil', + 'pyfiglet', + 'pygments', + 'watchdog', + 'yaml' + ] + + missing = [] + for package in required_packages: + try: + importlib.import_module(package) + except ImportError: + missing.append(package) + + if missing: + logger.error(f"Missing required packages: {', '.join(missing)}") + return False + + return True + + +def run_command(command: str, capture_output: bool = True, timeout: int = 30) -> Dict[str, Any]: + """Run a shell command and return result.""" + try: + result = subprocess.run( + command, + shell=True, + capture_output=capture_output, + text=True, + timeout=timeout + ) + + return { + 'success': result.returncode == 0, + 'returncode': result.returncode, + 'stdout': result.stdout if capture_output else '', + 'stderr': result.stderr if capture_output else '', + } + + except subprocess.TimeoutExpired: + return { + 'success': False, + 'returncode': -1, + 'stdout': '', + 'stderr': f'Command timed out after {timeout}s', + } + + except Exception as e: + return { + 'success': False, + 'returncode': -1, + 'stdout': '', + 'stderr': str(e), + } + + +def format_bytes(bytes_value: int) -> str: + """Format bytes in human readable format.""" + for unit in ['B', 'KB', 'MB', 'GB', 'TB', 'PB']: + if bytes_value < 1024.0: + return f"{bytes_value:.1f} {unit}" + bytes_value /= 1024.0 + return f"{bytes_value:.1f} EB" + + +def format_duration(seconds: float) -> str: + """Format duration in human readable format.""" + if seconds < 60: + return f"{seconds:.1f}s" + elif seconds < 3600: + minutes = seconds / 60 + return f"{minutes:.1f}m" + elif seconds < 86400: + hours = seconds / 3600 + return f"{hours:.1f}h" + else: + days = seconds / 86400 + return f"{days:.1f}d" + + +def safe_path_join(base: Path, *parts: str) -> Path: + """Safely join path parts, preventing directory traversal.""" + result = base + for part in parts: + # Remove any directory traversal attempts + clean_part = part.replace('..', '').replace('/', '').replace('\\', '') + if clean_part: + result = result / clean_part + return result + + +def ensure_directory(path: Path) -> bool: + """Ensure directory exists, create if necessary.""" + try: + path.mkdir(parents=True, exist_ok=True) + return True + except Exception as e: + logger.error(f"Error creating directory {path}: {e}") + return False + + +def get_file_icon(file_path: Path) -> str: + """Get appropriate icon for file type.""" + if file_path.is_dir(): + return "πŸ“" + + suffix = file_path.suffix.lower() + icon_map = { + '.py': '🐍', '.js': 'πŸ“œ', '.html': '🌐', '.css': '🎨', + '.json': 'πŸ“‹', '.yaml': 'βš™οΈ', '.yml': 'βš™οΈ', '.xml': 'πŸ“„', + '.txt': 'πŸ“', '.md': 'πŸ“–', '.rst': 'πŸ“–', + '.jpg': 'πŸ–ΌοΈ', '.jpeg': 'πŸ–ΌοΈ', '.png': 'πŸ–ΌοΈ', '.gif': 'πŸ–ΌοΈ', + '.mp3': '🎡', '.wav': '🎡', '.mp4': '🎬', '.avi': '🎬', + '.pdf': 'πŸ“•', '.doc': 'πŸ“˜', '.docx': 'πŸ“˜', + '.zip': 'πŸ“¦', '.tar': 'πŸ“¦', '.gz': 'πŸ“¦', + } + + return icon_map.get(suffix, 'πŸ“„') + + +def get_syntax_language(file_path: Path) -> Optional[str]: + """Get syntax highlighting language for file.""" + suffix = file_path.suffix.lower() + language_map = { + '.py': 'python', + '.js': 'javascript', + '.ts': 'typescript', + '.html': 'html', + '.css': 'css', + '.json': 'json', + '.yaml': 'yaml', + '.yml': 'yaml', + '.xml': 'xml', + '.md': 'markdown', + '.sh': 'bash', + '.bash': 'bash', + '.zsh': 'zsh', + '.fish': 'fish', + '.c': 'c', + '.cpp': 'cpp', + '.h': 'c', + '.hpp': 'cpp', + '.java': 'java', + '.go': 'go', + '.rs': 'rust', + '.php': 'php', + '.rb': 'ruby', + '.sql': 'sql', + } + + return language_map.get(suffix) + + +def is_binary_file(file_path: Path) -> bool: + """Check if file is binary.""" + try: + with open(file_path, 'rb') as f: + chunk = f.read(1024) + return b'\0' in chunk + except Exception: + return True + + +def get_mime_type(file_path: Path) -> str: + """Get MIME type of file.""" + try: + import mimetypes + mime_type, _ = mimetypes.guess_type(str(file_path)) + return mime_type or 'application/octet-stream' + except Exception: + return 'application/octet-stream' + + +class PerformanceMonitor: + """Monitor performance metrics.""" + + def __init__(self): + self.start_time = None + self.metrics = {} + + def start(self, operation: str): + """Start timing an operation.""" + import time + self.start_time = time.time() + self.current_operation = operation + + def end(self) -> float: + """End timing and return duration.""" + if self.start_time is None: + return 0.0 + + import time + duration = time.time() - self.start_time + + if hasattr(self, 'current_operation'): + self.metrics[self.current_operation] = duration + + self.start_time = None + return duration + + def get_metrics(self) -> Dict[str, float]: + """Get all recorded metrics.""" + return self.metrics.copy() \ No newline at end of file diff --git a/submissions/terminal-os/terminalos/utils/logger.py b/submissions/terminal-os/terminalos/utils/logger.py new file mode 100644 index 00000000..f4b0320b --- /dev/null +++ b/submissions/terminal-os/terminalos/utils/logger.py @@ -0,0 +1,23 @@ +"""Simple logging utility for TerminalOS.""" + +import logging +import sys +from pathlib import Path + +def get_logger(name): + """Get a simple logger.""" + logger = logging.getLogger(name) + if not logger.handlers: + handler = logging.StreamHandler(sys.stdout) + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + handler.setFormatter(formatter) + logger.addHandler(handler) + logger.setLevel(logging.INFO) + return logger + +def setup_logger(level='INFO'): + """Setup basic logging.""" + logging.basicConfig( + level=getattr(logging, level.upper()), + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' + ) \ No newline at end of file