Dual Screen Mirroring for the AYN Thor
  • Python 92.8%
  • Shell 7.2%
Find a file
SkyfaR e64f078631
Fix flatpak runner registration: run register from the runner home
forgejo-runner register writes .runner into the current working
directory, which was root's cwd and not writable for the runner user.
2026-07-07 22:50:36 +02:00
.forgejo/workflows Add Flatpak build for immutable distros (Bazzite, Silverblue) 2026-07-07 16:20:55 +02:00
assets Track all v1.0 README assets (wordmark + file transfer / device profile screenshots); fix gitignore to not ignore assets/screenshots 2026-06-01 23:38:32 +02:00
bin ThorCPY Linux v0.3.0 — Linux fork of ThorCPY 2026-03-19 20:17:32 +01:00
config ThorCPY Linux v0.3.0 — Linux fork of ThorCPY 2026-03-19 20:17:32 +01:00
logs ThorCPY Linux v0.3.0 — Linux fork of ThorCPY 2026-03-19 20:17:32 +01:00
scripts Fix flatpak runner registration: run register from the runner home 2026-07-07 22:50:36 +02:00
src Fix Python 3.11 compatibility and log dir fallback for read-only installs 2026-07-07 12:12:20 +02:00
tests Migrate UI to customtkinter; add device profiles, FPS config, scrcpy 0.4 tuning 2026-06-01 08:58:12 +02:00
.gitignore Clean up .gitignore by removing unnecessary entries 2026-06-02 00:02:26 +02:00
build.py Rebrand to DualCPY-Linux (v0.4.0); adopt upstream File Transfer window + gamepad 2026-06-01 23:08:35 +02:00
CHANGELOG.md Credit v1.0.0 contributors (DrSkyfaR, theswest, tommywaaf) and testers, matching the upstream release 2026-06-01 23:45:09 +02:00
LICENSE ThorCPY Linux v0.3.0 — Linux fork of ThorCPY 2026-03-19 20:17:32 +01:00
main.py Add RPM package and portable tarball builds; release 1.0.5 2026-07-07 14:31:15 +02:00
README.md Add Flatpak build for immutable distros (Bazzite, Silverblue) 2026-07-07 16:20:55 +02:00
requirements.txt Migrate UI to customtkinter; add device profiles, FPS config, scrcpy 0.4 tuning 2026-06-01 08:58:12 +02:00
RPM-GPG-KEY-dualcpy Sign RPM packages in CI 2026-07-07 14:46:13 +02:00
verify_linux_port.py Migrate UI to customtkinter; add device profiles, FPS config, scrcpy 0.4 tuning 2026-06-01 08:58:12 +02:00

DualCPY-Linux logo    DualCPY-Linux

Note

DualCPY-Linux is the Linux port of DualCPY (previously ThorCPY). As of v1.0.0 the project supports all dual-screen Android handhelds — not just the AYN Thor — and has been renamed to DualCPY.

DualCPY-Linux (pronounced "Dual Copy") is a Linux multi-window scrcpy launcher, designed specifically for dual-screen Android handhelds. It features a layout editor, window docking, screenshots, file transfer, device profiles, and real-time window positioning.

It launches two scrcpy windows (one per display) and embeds them into a single container window on X11 — ideal for screensharing, recording, or livestreaming. On Wayland it runs in floating mode via XWayland.

DualCPY-Linux targets Linux with X11 (recommended) or Wayland (floating mode).

For the Windows version, see the upstream project: https://github.com/theswest/DualCPY

Please report Linux-specific issues at https://github.com/DrSkyfaR/DualCPY-Linux/issues

Screenshots

Control Panel Dual-Screen Capture
Control Panel Dual-screen capture
File Transfer Device Profiles
File transfer Device profiles

What's New in 1.0.0

  • Rebranded from ThorCPY-Linux to DualCPY-Linux, with a brand-new logo
  • Complete UI rewrite in customtkinter, with a cleaner, more modern design language (replaces the previous pygame control panel)
  • Multi-device support with automatic device detection and built-in profiles for many handhelds
  • Profile editor for custom devices, screen sizes, internal monitors, and per-profile scrcpy commands
  • File Transfer window for two-way file management over ADB (@DrSkyfaR & @theswest)
  • Gamepad passthrough to the device
  • FPS selector and Restart button in the control panel (@tommywaaf)
  • Undocked windows keep their window-manager title bars for easy resizing and moving
  • Tuned scrcpy launch for low latency; requires scrcpy v4.0+

See the full CHANGELOG for details.

Features

  • Multi-device support with built-in profiles for many dual-screen handhelds (AYN Thor, RG DS, Pocket DS, AYN Odin 3 / Odin 2 / 2 Portal / 2 Mini + RDS, Retroid Pocket 6 / G2 / 5 / 4 Pro + RDS, and more), plus custom user-defined profiles
  • Automatic device detection over ADB, with a device selector on launch and smart selection of the connected device
  • "Last used profile" is remembered per-device and auto-booted on launch
  • Both wired (USB) and wireless (ADB over WiFi, Android 11+ pairing) connection, plus a network scanner that auto-discovers devices on your subnet
  • X11 docking — embed both screens into one container window, or undock them into independent, resizable, title-barred windows for individual capture (e.g. streaming)
  • Wayland support — floating window mode via XWayland (docking not possible on pure Wayland)
  • Layout presets to position the screens precisely how you want
  • Screenshot capture grabs both screens together (saved as a PNG via mss)
  • File transfer to and from the device over ADB, with image previews, file metadata, and quick-nav shortcuts on both the local-PC and device sides
  • Profile editor for custom screen sizes, internal-monitor layouts, and per-profile scrcpy launch commands
  • Gamepad passthrough to the device (--gamepad=uhid on the top screen)
  • FPS selector and restart controls in the panel
  • Real-time positioning to move the screens into any arrangement
  • Linux extra: optional Discord audio routing via PipeWire/PulseAudio so game audio is captured automatically during a screen-share

Installation

Important

To use DualCPY-Linux, you must have USB Debugging enabled.

  1. On the device, go to Settings > About device.
  2. Tap the Build number seven times to unlock Settings > Developer options.
  3. Enable USB Debugging in Developer options.

Then connect your device via USB, or just launch DualCPY-Linux to start the wireless connection dialog.

System dependencies

Install git, adb, and scrcpy ≥ 4.0 plus the X11 dev headers from your distro. DualCPY-Linux can also attempt to install adb/scrcpy automatically on first launch via pkexec (pacman and apt-get supported).

Arch / Manjaro / CachyOS

sudo pacman -S git base-devel android-tools scrcpy python-xlib tk

Debian / Ubuntu

sudo apt install git adb scrcpy python3-dev python3-xlib python3-venv build-essential tk

On older Debian, scrcpy may be available via backports.

Option 1: Install from the AUR (Arch / CachyOS / Manjaro)

DualCPY-Linux is on the AUR. With an AUR helper:

paru -S dualcpy-linux
# or
yay -S dualcpy-linux

This installs DualCPY system-wide and adds it to your application launcher. All dependencies (including scrcpy, tk, and python-customtkinter) are pulled in automatically. Launch it from your menu as DualCPY, or run dualcpy-linux from a terminal.

The AUR package is maintained by @theswest. Report packaging issues on the AUR page; report app bugs here.

Option 2: Install from the pacman repository (Arch / CachyOS / Manjaro)

Prefer a prebuilt binary package with updates via pacman -Syu? Add the DualCPY pacman repository (hosted on Forgejo) once:

sudo curl -fsSL -o /tmp/dualcpy-forgejo.gpg https://forgejo.skyfar.de/api/packages/SkyfaR/arch/repository.key
sudo pacman-key --add /tmp/dualcpy-forgejo.gpg
sudo pacman-key --lsign-key 'SkyfaR@noreply.forgejo.skyfar.de'

Append to /etc/pacman.conf:

[SkyfaR.stable.forgejo.skyfar.de]
SigLevel = Required
Server = https://forgejo.skyfar.de/api/packages/SkyfaR/arch/stable/x86_64

Then install:

sudo pacman -Sy dualcpy

scrcpy and android-tools come from the regular Arch repos as dependencies.

Option 3: Install the .deb package (Debian / Ubuntu)

Add the DualCPY apt repository (hosted on Forgejo) once:

sudo curl -fsSL --create-dirs -o /etc/apt/keyrings/dualcpy-forgejo.asc \
  https://forgejo.skyfar.de/api/packages/SkyfaR/debian/repository.key
echo "deb [signed-by=/etc/apt/keyrings/dualcpy-forgejo.asc] https://forgejo.skyfar.de/api/packages/SkyfaR/debian stable main" \
  | sudo tee /etc/apt/sources.list.d/dualcpy.list

Then install (and later update) like any other package:

sudo apt update
sudo apt install dualcpy

Unlike the other install options, the .deb bundles scrcpy and adb (the official portable scrcpy release, like the Windows build), so no extra system dependencies are needed. It adds DualCPY to your application launcher (dualcpy from a terminal). Built on Debian 12, so it requires Debian 12+ or a comparably recent Ubuntu.

Note

Binaries you place in ~/.local/share/dualcpy/bin take precedence over the bundled scrcpy/adb, if you ever want to use your own versions.

Option 4: Install the RPM package (Fedora / openSUSE / RHEL 9+)

Import the package signing key and add the DualCPY RPM repository (hosted on Forgejo) once:

sudo rpm --import https://forgejo.skyfar.de/SkyfaR/DualCPY-Linux/raw/branch/master/RPM-GPG-KEY-dualcpy
# Fedora
sudo dnf config-manager addrepo --from-repofile=https://forgejo.skyfar.de/api/packages/SkyfaR/rpm.repo
# openSUSE
sudo zypper addrepo https://forgejo.skyfar.de/api/packages/SkyfaR/rpm.repo

Then install (and later update) like any other package:

sudo dnf install dualcpy      # or: sudo zypper install dualcpy

Like the .deb, the RPM bundles scrcpy and adb — no extra dependencies needed. Built on Rocky Linux 9, so it runs on Fedora, openSUSE and RHEL 9+.

Option 5: Flatpak (Bazzite / Silverblue / immutable distros)

Grab DualCPY-<version>-x86_64.flatpak from the latest release and install it (the freedesktop runtime comes from Flathub, which Bazzite and friends have preconfigured):

flatpak install --user ./DualCPY-*-x86_64.flatpak
flatpak run de.skyfar.DualCPY   # or launch DualCPY from your app menu

scrcpy and adb are bundled; USB device access for adb is part of the sandbox permissions.

Option 6: AppImage or portable tarball (any distro)

Grab DualCPY-<version>-x86_64.AppImage from the latest release, make it executable and run it — scrcpy and adb are bundled:

chmod +x DualCPY-*-x86_64.AppImage
./DualCPY-*-x86_64.AppImage

Prefer a plain archive? Each release also ships DualCPY-<version>-linux-x86_64.tar.gz — extract it anywhere and run dualcpy.sh from the extracted folder (config and logs stay inside it, like the Windows zip).

Option 7: Run from Source

git clone https://github.com/DrSkyfaR/DualCPY-Linux.git
cd DualCPY-Linux
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
python main.py

Using fish or csh? Use the following activation script instead: source venv/bin/activate.fish (fish) or activate.csh (csh).

Re-activate the venv (source venv/bin/activate) before running in later sessions.

Option 8: Build a Standalone Executable

source venv/bin/activate
pip install pyinstaller
python build.py
# Find your build in dist/DualCPY/

Note: unlike the Windows build, scrcpy and ADB are not bundled on Linux — they come from your system (or are auto-installed via pkexec).

Requirements

System

  • OS: Linux with X11 (recommended) or Wayland (floating mode via XWayland)
  • Python: 3.9 or higher (tested on 3.14)
  • scrcpy: v4.0 or higher
  • Device: a dual-screen Android handheld with USB Debugging enabled

Python Dependencies

Installed with pip install -r requirements.txt:

  • customtkinter — control panel / dialog UI
  • pillow — icons and image previews
  • mss — cross-platform screenshots
  • darkdetect — appearance-mode detection
  • python-xlib — X11 window management (Linux only)
  • pyinstaller — only needed to build a standalone executable

Usage

Connection

  • You can connect via USB (charging, offline, more stable) or wirelessly (no tethers).
  • USB: ensure USB Debugging is enabled, plug in your device, and launch DualCPY-Linux.
  • Wireless:
    • Launch DualCPY-Linux without a USB device connected and open the Wireless dialog.
    • On the device, enable Wireless debugging and open Pair device with pairing code.
    • Enter the IP address, port, and pairing code shown.
    • Once paired, copy the device's IP and port into the Connect by IP field.
    • Close the dialog — DualCPY-Linux connects and starts mirroring.

Device Selection

  • On launch, DualCPY-Linux detects connected devices over ADB and auto-matches the best profile (with an AYN Thor fallback), remembering the last used profile per device.
  • Don't see your device matched? Use the Edit Device Profiles editor to add a custom profile (screen sizes, internal-monitor layout, and per-profile scrcpy launch command).

Main Controls

The control panel appears on the right-hand side of your screen:

  • Global Scale — adjust the scale of the scrcpy outputs (requires restart)
  • FPS — select the target framerate (top window; bottom capped to ≤60)
  • Restart — restart the mirroring session
  • Layout: Top X / Top Y and Bottom X / Bottom Y position each screen
  • Window controls:
    • Undock — separate into independent, title-barred floating windows (for individual capture)
    • Dock — bring undocked windows back into one unified container (X11 only)
    • Screenshot — capture the docked view to a PNG in screenshots/
  • File Transfer — open the file browser to move files between your PC and device
  • Presets — name a layout and Save; Load / Del next to a saved preset

File Transfer

  • Transfer files in both directions (local PC ↔ device) over ADB.
  • Create folders, rename, and delete on either side.
  • Local quick-nav: Home, Desktop, Downloads, Documents, Pictures
  • Device quick-nav: Internal, Download, DCIM, Pictures, Music, Documents
  • Automatic SD-card detection with quick-nav pills
  • Inline image previews with file metadata

Configuration

Layouts / Presets — config/layout.json

{
    "Default":   { "tx": 0,   "ty": 0,  "bx": 251, "by": 648, "global_scale": 0.6 },
    "Streaming": { "tx": 100, "ty": 50, "bx": 300, "by": 700, "global_scale": 0.3 }
}

General Config — config/config.json

{
    "tx": 0, "ty": 0, "bx": 250, "by": 648, "global_scale": 0.6,
    "max_fps": 120,
    "device_profiles": { "78ab8b8f": "ayn_thor" },
    "last_profile": "AYN Thor",
    "discord_audio_routing": true
}

Custom Profiles — config/custom_profiles.json

User-defined device profiles created in the profile editor are stored here.

Logging — logs/

  • dualcpy_YYYYMMDD.log — main application log
  • scrcpy_top_YYYYMMDD_HHMMSS.log / scrcpy_bottom_YYYYMMDD_HHMMSS.log — per-window scrcpy output

To adjust verbosity, change the logging level in main.py:

logging.basicConfig(
    level=logging.INFO,  # Change to DEBUG for detailed logs
    ...
)

Troubleshooting

Layout issues

  • Load a preset at 0.6 global scale and save it.
  • Delete config/layout.json and config/config.json so they are regenerated.

Device not found

  • Ensure USB debugging is enabled — try a different (data, not charging-only) cable.
  • Revoke USB-debugging authorizations and reconnect (Developer Options).
  • Check that ADB sees your device: adb devices
  • Restart the ADB server: adb kill-server && adb start-server

scrcpy won't start

  • Ensure scrcpy (≥ 4.0) is installed and on your PATH.
  • Check the per-window logs in logs/ for the exact error.
  • Try running scrcpy manually: scrcpy -s YOUR_DEVICE_SERIAL
  • Ensure your device exposes the display IDs expected by your profile.

Windows won't dock (X11)

  • Wait a few seconds for the windows to initialise, then toggle dock/undock.
  • Make sure you are on an X11 session (echo $XDG_SESSION_TYPE).
  • Restart the application and check the logs.

Running on Wayland

  • Docking requires X11. With XWayland present, DualCPY-Linux forces the X11 backend automatically; on pure Wayland only floating mode is available.

Graphical glitches

  • Toggle dock/undock a few times, or restart the application.
  • Try a wireless connection to rule out USB issues.
  • Check the logs for errors.

Performance / stuttering

  • Reduce the global scale or lower the FPS in the control panel.
  • Close other resource-intensive applications; prefer a USB 3 port.
  • Increase the per-profile screen-launch delay for lower-powered devices.

Gamepad not detected

  • You may need to reconnect your controller while DualCPY-Linux is running — this is an Android limitation with --gamepad=uhid.

Missing module / import errors

  • Activate the venv and reinstall: pip install -r requirements.txt --force-reinstall
  • Ensure Python 3.9+ and the python-xlib system package are installed.

Running in a Distrobox container (Bazzite / immutable systems)

DualCPY-Linux runs inside a Distrobox container on immutable systems. Install the system dependencies inside the container, create a venv, and run as above. Make sure the container can reach the host display (DISPLAY/WAYLAND_DISPLAY) and that adb can see your device.

Licenses

  • This project is licensed under the GNU General Public License v3.0 — see LICENSE. You may modify and redistribute it under the same terms.
  • scrcpy is used as-is from your system under the Apache License 2.0.
  • The in-app font is Cal Sans, under the SIL Open Font License 1.1 — see assets/fonts/OFL.txt.

Contributing

  • For Linux-specific bugs or features: open an issue in this repository on GitHub.
  • For general DualCPY issues (Windows / upstream): see the upstream repository.

Pull requests are welcome — for major changes, please open an issue first.

Supporting

Support the original author: https://ko-fi.com/theswest

Acknowledgements

  • the_swest — original DualCPY (ThorCPY) author
  • DrSkyfaR — File Transfer logic and the Linux port
  • tommywaaf — backend performance work, FPS/restart controls, title-barred undocked windows, and more
  • eldermonkey — project logo
  • scrcpy by Romain Vimont — the backend
  • Cal Sans by Cal.com Inc. — UI typography (OFL 1.1)
  • customtkinter — modern UI toolkit
  • python-xlib — X11 window docking
  • All other contributors and testers, especially dd, splain, and everyone else who helped!