# Electron Angle Patcher

Patch Electron and Chrome applications on macOS to use OpenGL rendering through the `use-angle@1` setting.

## Features

- **macOS Optimized**: Designed specifically for macOS Electron applications
- **Auto-Discovery**: Automatically finds apps in standard locations
- **Deep Scanning**: Recursively searches directories comprehensively
- **Special App Support**: Handles complex apps (Logitech G Hub, Teams, Brave)
- **Executable Wrapping**: Modifies executables to pass `--use-angle=gl`
- **Network Mount Protection**: Avoids scanning network-mounted volumes

## Installation

### Quick Start (Recommended)

**Using uv** (fast, 10-100x faster than pip):

```bash
# Install uv
curl -LsSf https://astral.sh/uv/install.sh | sh

# Clone and setup
git clone https://github.com/martin-juul/electron-angle-patcher.git
cd electron-angle-patcher
make init
```

**Using venv** (standard, built-in):

```bash
git clone https://github.com/martin-juul/electron-angle-patcher.git
cd electron-angle-patcher
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
```

**Using the setup script** (interactive):

```bash
git clone https://github.com/martin-juul/electron-angle-patcher.git
cd electron-angle-patcher
./scripts/setup.sh
```

The setup script will:
1. Check Python version (requires 3.10+)
2. Optionally install uv for faster setup
3. Create virtual environment
4. Install dependencies
5. Offer to run tests

### From Source (Manual)

```bash
git clone https://github.com/martin-juul/electron-angle-patcher.git
cd electron-angle-patcher

# Option 1: With uv (fast)
uv venv
uv pip install -e ".[dev]"

# Option 2: With venv (standard)
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
```

### Using pip (when published)

```bash
pip install electron-angle-patcher
```

## Usage

### Basic Usage

```bash
# Patch all detected applications
electron-angle-patcher

# List applications without patching
electron-angle-patcher --list-only

# Dry run: show what would be patched without making changes
electron-angle-patcher --dry-run

# Perform deep scan of home directory
electron-angle-patcher --deep-scan

# Enable verbose output
electron-angle-patcher -v
```

### Advanced Usage

```bash
# Patch a specific application
electron-angle-patcher --app-path ~/Library/Application\ Support/MyApp/Local\ State --app-name "My App"

# Scan a custom directory
electron-angle-patcher --scan-dir ~/path/to/apps

# Specify custom Brave Browser path
electron-angle-patcher --brave-path ~/Applications/Brave\ Browser.app

# Specify custom lghub path
electron-angle-patcher --lghub-path ~/Applications/lghub.app
```

### Running as Module

```bash
python -m electron_angle_patcher.cli --list-only
```

## Development

### Environment Management

This project supports **uv** (fast, recommended) or **venv** (standard).

**Why uv?**
- 10-100x faster than pip
- Better dependency resolution
- Built-in lockfile support
- From the creators of Ruff (which we also use)

**Using uv (recommended):**

```bash
# Install uv
curl -LsSf https://astral.sh/uv/install.sh | sh

# All make commands auto-detect and use uv if available
make init          # Set up environment with uv
make test          # Run tests with uv
make fmt           # Format with uv run ruff
```

**Using venv (fallback):**

```bash
# Standard setup
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"

# Make commands auto-detect and use venv if uv is not available
make test
make fmt
```

### Project Structure

```
electron-angle-patcher/
├── src/
│   └── electron_angle_patcher/
│       ├── __init__.py           # Public API exports
│       ├── app.py                # Main orchestrator (ElectronAnglePatcher)
│       ├── cli.py                # Command-line interface
│       ├── config.py             # Configuration constants
│       ├── models.py             # Data models (enums, dataclasses)
│       ├── utils.py              # Utility functions
│       ├── executable_patcher.py # Executable wrapping logic
│       ├── settings_patcher.py   # Local State file patching
│       └── scanner.py            # Directory scanning logic
├── tests/
│   ├── conftest.py               # Pytest configuration
│   ├── unit/                     # Unit tests
│   └── sandbox/                  # Test fixtures
├── scripts/
│   └── setup.sh                  # Interactive setup script
├── pyproject.toml                # Project configuration
├── Makefile                      # Common tasks (auto-detects uv)
└── README.md
```

**Module Overview:**
- `__init__.py` - Public API exports, maintains backward compatibility
- `app.py` - `ElectronAnglePatcher` main application class
- `cli.py` - Command-line interface using argparse
- `config.py` - `Config` class with all constants and settings
- `models.py` - `AngleVariant`, `PatchResult` enums; `AppInfo`, `PatchSummary` dataclasses
- `utils.py` - Helper functions (logging, permissions, file operations)
- `executable_patcher.py` - `ExecutableWrapper`, `BraveExecutablePatcher`, `LghubPatcher`
- `settings_patcher.py` - `ChromiumSettingsPatcher` for Local State files
- `scanner.py` - `LocalStateScanner` for finding applications

### Running Tests

```bash
# Run all tests
make test

# Run with coverage
make test-cov

# Run specific test file
pytest tests/unit/test_config.py

# Run with verbose output
pytest -v

# Run only unit tests
pytest -m unit

# Skip slow tests
pytest -m "not slow"
```

### Code Quality

```bash
# Format code with ruff
make fmt

# Check linting
make lint

# Run type checking
make typecheck

# Run all checks
make check
```

### Building and Installing

```bash
# Build Python package (wheel/source)
make build

# Build standalone binary (single executable)
make build-binary

# Build single-file binary (smaller, slower startup)
make build-binary-onefile

# Install locally
make install

# Install in development mode
make dev-install

# Generate/update lockfile (uv only)
make lock

# Update all dependencies (uv only)
make update-deps
```

### CI Pipeline

For continuous integration or automated builds, use the CI target:

```bash
# Run complete CI pipeline:
# 1. Install dependencies (creates venv if needed)
# 2. Run all tests
# 3. Build standalone binary
make ci
```

The CI target:
- Auto-detects and uses `uv` if available, otherwise falls back to `venv`
- Creates a virtual environment if it doesn't exist
- Installs all dependencies
- Runs the test suite
- Builds the standalone binary
- Reports success/failure at each step

### Building Standalone Binaries

This project can be compiled into a standalone binary that doesn't require Python installation:

```bash
# Option 1: Directory-based binary (faster startup)
make build-binary
# Output: dist/electron-angle-patcher

# Option 2: Single-file binary (smaller, slower startup)
make build-binary-onefile
# Output: dist/electron-angle-patcher

# Run the binary directly
./dist/electron-angle-patcher --help
./dist/electron-angle-patcher --list-only
```

**Binary Features:**
- Self-contained: No Python or dependencies required
- macOS optimized: Creates native macOS executables
- Code signed: Uses ad-hoc signature for macOS
- Smaller size: Only includes required dependencies

**Building Manually with PyInstaller:**

```bash
# Install PyInstaller
pip install pyinstaller

# Build using spec file (recommended)
pyinstaller electron_angle_patcher.spec

# Or build single-file directly
pyinstaller --onefile --name electron-angle-patcher \
  --console src/electron_angle_patcher/cli.py
```

### Make Targets

| Target                      | Description                                  |
|-----------------------------|----------------------------------------------|
| `make help`                 | Show all available commands                  |
| `make init`                 | Initialize dev environment (auto-detects uv) |
| `make install-uv`           | Install uv package manager                   |
| `make ci`                   | CI pipeline: install deps, run tests, build  |
| `make build`                | Build Python package                         |
| `make build-binary`         | Build standalone binary                      |
| `make build-binary-onefile` | Build single-file binary                     |
| `make test`                 | Run all tests                                |
| `make test-cov`             | Run with coverage report                     |
| `make fmt`                  | Format code with ruff                        |
| `make lint`                 | Check code with ruff                         |
| `make typecheck`            | Run mypy type checking                       |
| `make check`                | Run all quality checks                       |
| `make run`                  | Run the patcher                              |
| `make run-list`             | Run in list-only mode                        |

## Configuration

The patcher searches for Chromium/Electron applications in the following locations:

### Standard Directories

- `~/Library/Application Support`
- `~/Library/Caches`
- `~/Library/Preferences`
- `~/Library/Containers`

### Specific Apps

The following apps are handled specifically:

- Logitech G Hub (Standard, Main Container, Helper)
- Microsoft Teams (Classic and New)
- Microsoft Edge
- Brave Browser (executable wrapping)

### Skipped Directories

The following directories are skipped during scanning:

- `.Trash`
- `node_modules`
- `Library/Developer`
- `Library/Caches/Homebrew`
- `Library/Logs`
- Network-mounted volumes

## How It Works

### Settings Patching

For most apps, the patcher modifies the `Local State` file to add:

```json
{
  "browser": {
    "enabled_labs_experiments": [
      "use-angle@1"
    ]
  }
}
```

### Executable Wrapping

For apps like Brave Browser and lghub, the patcher wraps the executable to pass the `--use-angle=gl` argument at runtime.

The original executable is backed up as `<name>.original` and replaced with a shell script:

```bash
#!/bin/bash
DIR="$(cd "$(dirname "$0")" && pwd)"
exec "$DIR/<name>.original" --use-angle=gl "$@"
```

## Requirements

- Python 3.10 or higher
- macOS (Darwin)
- `codesign` command (for re-signing apps, optional)
- `xattr` command (for stripping attributes, optional)

## License

MIT License

## Contributing

Contributions are welcome! Please:

1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests
5. Run `make check` to ensure quality
6. Submit a pull request

## Troubleshooting

### "Cannot write to app" Error

Run the script with sudo:

```bash
sudo python -m electron_angle_patcher.cli
```

### App Not Detected

Use `--list-only` to see what apps are detected, or use `--app-path` to target a specific app.

### Changes Not Taking Effect

Some apps may need to be restarted for changes to take effect. Try:
1. Quit the app completely
2. Re-launch the app
3. Check the flag is active (use `chrome://flags` in Chrome-based apps)
