# Advanced Paste – Python Scripts Advanced Paste supports user-defined Python scripts that transform clipboard content. Scripts are discovered automatically from a configurable folder and appear as actions in the Advanced Paste UI. ## Quick start 1. Open the scripts folder — by default `%LOCALAPPDATA%\Microsoft\PowerToys\AdvancedPaste\Scripts`. You can change this in **Settings → Advanced Paste → Python scripts → Scripts folder**. 2. Drop a `.py` file into the folder. 3. Define one `advanced_paste_from__to_` function (see [Writing a script](#writing-a-script)). 4. Open the Advanced Paste UI (`Win+Shift+V`) — your script will appear in the action list. > **Important:** Each `.py` file must define exactly one `advanced_paste_from__to_` > function. Scripts with zero or multiple such functions are ignored. ## Writing a script You write a single Python function whose **name** declares both what clipboard input it accepts and what output type it produces. No imports from PowerToys are needed — zero setup, zero dependencies on our side. ### Function naming convention The function name follows the pattern: ``` advanced_paste_from__to_() ``` **Input types** (what the function receives): | Input | Parameter | When it runs | |-------|-----------|--------------| | `text` | `str` — clipboard text | Clipboard has text | | `html` | `str` — clipboard HTML | Clipboard has HTML | | `image` | `str` — path to temp image file | Clipboard has an image | | `audio` | `str` — path to audio file | Clipboard has an audio file | | `video` | `str` — path to video file | Clipboard has a video file | | `files` | `list[str]` — file paths | Clipboard has files | **Output types** (what the function produces — declared via `_to_` suffix): | Output | Effect | |--------|--------| | `text` | Sets clipboard to text | | `html` | Sets clipboard to HTML | | `image` | Sets clipboard to image | | `audio` | Sets clipboard to audio file | | `video` | Sets clipboard to video file | | `file` | Sets clipboard to a file | | `files` | Sets clipboard to multiple files | ### Return value The return value is interpreted according to the declared output type: | Output type | Expected return value | |-------------|---------------------| | `text` | `str` (or any value — will be converted via `str()`) | | `html` | `str` containing HTML | | `image` | `str` or `pathlib.Path` pointing to an image file | | `file` | `str` or `pathlib.Path` pointing to a file | | `files` | `list` of `str`/`pathlib.Path` file paths | Returning `None` produces an empty result (no-op). ## Examples ### Minimal — uppercase text ```python def advanced_paste_from_text_to_text(text): return text.upper() ``` That's it. No headers required, no imports from PowerToys. ### With optional metadata ```python # @advancedpaste:name Reverse Text # @advancedpaste:desc Reverses clipboard text character by character def advanced_paste_from_text_to_text(text): return text[::-1] ``` ### Text to HTML ```python # @advancedpaste:name Markdown Table to HTML # @advancedpaste:desc Convert a markdown table to an HTML table def advanced_paste_from_text_to_html(text): headers = text.splitlines()[0].split("|") return f"{''.join(f'' for h in headers if h.strip())}
{h.strip()}
" ``` ### Image to text (OCR) ```python # @advancedpaste:requires pytesseract def advanced_paste_from_image_to_text(image_path): import pytesseract return pytesseract.image_to_string(image_path).strip() ``` ### Save text as file ```python import os from pathlib import Path import tempfile def advanced_paste_from_text_to_file(text): # Use ADVANCED_PASTE_WORK_DIR for WSL compatibility; falls back to temp dir on Windows. out_dir = os.environ.get("ADVANCED_PASTE_WORK_DIR") or tempfile.gettempdir() out = Path(out_dir) / "clipboard.txt" out.write_text(text, encoding="utf-8") return out ``` ### Image processing (image → image) ```python import os from PIL import Image from pathlib import Path import tempfile def advanced_paste_from_image_to_image(image_path): """Convert image to grayscale.""" img = Image.open(image_path).convert("L") out_dir = os.environ.get("ADVANCED_PASTE_WORK_DIR") or tempfile.gettempdir() out = Path(out_dir) / "gray.png" img.save(out) return out ``` ### File listing (files → text) ```python import os def advanced_paste_from_files_to_text(file_paths): lines = [] for p in file_paths: size = os.path.getsize(p) lines.append(f"{os.path.basename(p)} ({size} bytes)") return "\n".join(lines) ``` ## Header tags All header tags are **optional**. Tags are placed in comment lines at the top of the script. | Tag | Description | |-----|-------------| | `name` | Display name in the Advanced Paste UI. If omitted, the filename is used. | | `desc` | Short description / tooltip. | | `disabled` | Presence of this tag disables the script (it won't appear in the UI). | | `requires` | Declare Python package dependencies (see [Dependencies](#declaring-dependencies)). | ### Example header ```python # @advancedpaste:name My Formatter # @advancedpaste:desc Formats clipboard text as markdown table ``` To disable a script without deleting it, add: ```python # @advancedpaste:disabled ``` Remove the line to re-enable. ## Declaring dependencies Use `requires` to declare Python packages the script needs: ```python # @advancedpaste:requires PIL=Pillow # @advancedpaste:requires cv2=opencv-python-headless numpy requests ``` Each token is either: - **`import_name`** — the pip package is assumed to have the same name (e.g. `requests`). - **`import_name=pip_package`** — when the import name differs from the pip package (e.g. `cv2=opencv-python-headless`, `PIL=Pillow`). ### Automatic import detection Advanced Paste also scans the script body for `import` and `from ... import` statements and cross-references them against the Python standard library. Any non-stdlib import that is not already installed triggers a prompt to install it automatically. ## Security — script trust The first time a script is executed (or after it has been modified), Advanced Paste shows a confirmation dialog. Upon approval the SHA-256 hash of the script is stored. Subsequent runs of the unchanged file skip the dialog. ## Error handling When a script fails, Advanced Paste extracts the Python traceback from stderr and displays a user-friendly summary in the UI: - **ModuleNotFoundError** — identifies the missing module and suggests installing it. - **SyntaxError** — shows the file and line number. - **Timeout** — shows the configured timeout value (default 30 s; configurable in Settings). - **Other errors** — shows the last line of the traceback as a summary, with the full traceback available in the expandable *Details* section. ## Settings The Python scripts feature uses a **mode selector** (dropdown) with three options: | Mode | Description | |------|-------------| | **Disabled** | Python scripts are not active. | | **Windows** | Scripts run using a native Windows Python interpreter. | | **WSL** | Scripts run inside Windows Subsystem for Linux. | Each mode maintains its own independent settings (scripts folder, interpreter path, etc.), so switching between Windows and WSL does not lose your previous configuration. ### Windows mode settings | Setting | Description | Default | |---------|-------------|---------| | Scripts folder | Folder to scan for `.py` scripts. | `%LOCALAPPDATA%\Microsoft\PowerToys\AdvancedPaste\Scripts` | | Python interpreter | Path to the Python executable. Leave blank for auto-detection. | *(auto-detect)* | ### WSL mode settings | Setting | Description | Default | |---------|-------------|---------| | Scripts folder | Folder to scan for `.py` scripts (Windows path — auto-translated to `/mnt/...`). | `%LOCALAPPDATA%\Microsoft\PowerToys\AdvancedPaste\Scripts` | | WSL distribution | Which WSL distro to use (e.g. `Ubuntu`). Leave blank for the default distribution. | *(default)* | ### Scripts list The Settings page shows a read-only list of discovered scripts. For each script you can see: - **Name** — from `@advancedpaste:name` tag, or the filename if not set. - **Description** — from `@advancedpaste:desc` tag. - **Conversion** — the input → output types detected from the function name (e.g. "text → image"). The list is **not editable** from Settings. To change a script's name, description, enabled state, or any other metadata, open the script file directly (click the "Open in editor" button) and edit the `# @advancedpaste:...` header tags. After saving, click **Refresh** in Settings to reload. ### WSL mode details When **WSL** mode is selected: - Scripts are executed via `wsl.exe bash -l -c "python3 ..."` using the configured distribution. - The scripts folder remains on the Windows filesystem; paths are automatically translated to `/mnt/c/...` format for WSL access. - Package installation uses `pip3 install` inside the WSL environment. - Output files from scripts must be written under `/mnt/` (the Windows-mounted filesystem) so they can be accessed from Windows. The runner sets the `ADVANCED_PASTE_WORK_DIR` environment variable to a temp directory under `/mnt/c/...` — use it instead of `tempfile.gettempdir()` when producing file output for cross-platform compatibility. > **Tip:** If you have Python installed only in WSL (not on Windows), select WSL mode > to use your existing WSL Python environment with all its packages. ## Tips - Each `.py` file must contain exactly one `advanced_paste_from__to_` function. If you need to handle multiple input types, create separate script files for each. - A `.py` file without any matching function is ignored — use this for helper modules that other scripts can import. - Scripts can be tested from the command line: ``` echo {"format":["text"],"text":"hello"} | python _runner.py my_script.py ``` - The script's directory is added to `sys.path` at runtime, so you can import sibling `.py` files as helper modules.