# noVNC Client

[![PyPI version](https://img.shields.io/pypi/v/noVNC-client.svg)](https://pypi.org/project/noVNC-client/)
[![Python Version](https://img.shields.io/pypi/pyversions/noVNC-client.svg)](https://pypi.org/project/noVNC-client/)
[![License](https://img.shields.io/github/license/Haskely/noVNC-client.svg)](https://github.com/Haskely/noVNC-client/blob/main/LICENSE)
[![Downloads](https://static.pepy.tech/badge/noVNC-client)](https://pepy.tech/project/noVNC-client)

A Python library for programmatically controlling noVNC WebSocket servers. Provides asynchronous remote desktop automation capabilities including mouse control, keyboard input, and screenshot capture.

## Features

- **Mouse Control**: Move, click, scroll with precise coordinate control
- **Keyboard Input**: Send keystrokes, text input, and key combinations
- **Screenshot Capture**: Take screenshots in PNG format
- **Async/Await Support**: Built for modern Python async applications
- **Type Safety**: Full type annotations and comprehensive exception handling
- **Context Manager**: Automatic connection management with `async with`

## Installation

```bash
pip install noVNC-client
```

## Quick Start

```python
import asyncio
from novnc_client import NoVNCClient

async def main():
    # Connect to noVNC server
    async with NoVNCClient('localhost', 6080) as client:
        # Take a screenshot
        await client.take_screenshot('desktop.png')

        # Mouse operations
        await client.mouse_click(100, 100)
        await client.mouse_scroll(200, 200, 'up', steps=3)

        # Keyboard input
        await client.type_text('Hello, World!')
        await client.key_combination('ctrl', 'enter')

        # Get screen information
        width, height = client.screen_size
        print(f"Screen size: {width}x{height}")

asyncio.run(main())
```

## API Reference

### Connection

```python
# Context manager (recommended)
async with NoVNCClient('localhost', 6080) as client:
    # Your code here
    pass

# Manual connection
client = NoVNCClient('localhost', 6080, timeout=15.0)
await client.connect()
try:
    # Your code here
    pass
finally:
    await client.disconnect()
```

### Mouse Operations

```python
# Move mouse to coordinates
await client.mouse_move(x, y)

# Click at coordinates
await client.mouse_click(x, y, button='left')  # 'left', 'right', 'middle'

# Scroll at coordinates
await client.mouse_scroll(x, y, direction='up', steps=3)  # 'up', 'down'

# Get current mouse position
x, y = client.mouse_position
```

### Keyboard Operations

```python
# Send individual keys
await client.key_tap('enter')
await client.key_down('shift')
await client.key_up('shift')

# Type text strings
await client.type_text('Hello, World!', delay=0.01)

# Send key combinations
await client.key_combination('ctrl', 'c')
await client.key_combination('ctrl', 'shift', 'n')
await client.key_combination('alt', 'f4')
```

### Special Keys

Supported special keys include:
- **Navigation**: `enter`, `tab`, `backspace`, `escape`, `delete`, `home`, `end`, `insert`
- **Arrows**: `left`, `up`, `right`, `down`, `page_up`, `page_down`
- **Function Keys**: `f1`, `f2`, ..., `f12`
- **Modifiers**: `ctrl`, `alt`, `shift`, `win`

### Screenshot

```python
# Save to file
await client.take_screenshot('screenshot.png')

# Get PIL Image object
image = await client.take_screenshot()
print(f"Image size: {image.width}x{image.height}")
print(f"Image mode: {image.mode}")

# Save the PIL Image to a file
image.save('screenshot.png')

# Convert to bytes if needed
from io import BytesIO
buffer = BytesIO()
image.save(buffer, format='PNG')
image_bytes = buffer.getvalue()
```

### Server Information

```python
# Get screen dimensions
width, height = client.screen_size

# Get detailed server info
info = client.server_info
if info:
    print(f"Server: {info.server_name}")
    print(f"Resolution: {info.width}x{info.height}")
    print(f"Pixel format: {info.pixel_format.bits_per_pixel} bits")
```

## Error Handling

```python
from novnc_client import (
    NoVNCClientError,
    ConnectionError,
    ScreenshotError,
    # ... other exceptions
)

try:
    async with NoVNCClient('localhost', 6080) as client:
        await client.take_screenshot('test.png')
except ConnectionError as e:
    print(f"Connection failed: {e}")
except ScreenshotError as e:
    print(f"Screenshot failed: {e}")
except NoVNCClientError as e:
    print(f"Client error: {e}")
```

## Advanced Usage

### Automation Example

```python
async def automate_notepad():
    async with NoVNCClient('localhost', 6080) as client:
        # Open Run dialog
        await client.key_combination('win', 'r')
        await asyncio.sleep(0.5)

        # Launch Notepad
        await client.type_text('notepad')
        await client.key_tap('enter')
        await asyncio.sleep(1)

        # Type content
        await client.type_text('Automated text input via noVNC!')

        # Select all and copy
        await client.key_combination('ctrl', 'a')
        await client.key_combination('ctrl', 'c')
```

### Mouse Position Tracking

```python
async with NoVNCClient('localhost', 6080) as client:
    # Mouse position is automatically tracked
    await client.mouse_move(100, 100)
    x, y = client.mouse_position  # Returns (100, 100)

    await client.mouse_click(200, 200)
    x, y = client.mouse_position  # Returns (200, 200)
```

## Development

```bash
# Install dependencies
uv sync

# Run tests
uv run pytest

# Format code
pre-commit install

# Commit changes
cz commit

# Publish to PyPI
cz bump
git push
git push --tags
```

## Contributing

1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests for new functionality
5. Submit a pull request
