diff --git a/README.md b/README.md index 9fb6872..bb4b9e6 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,23 @@ # ADB Unlock, Screenshot & Scrcpy Tool -A simple Python GUI application that uses: -- **ADB** to unlock an Android device and take a screenshot -- **Scrcpy** to display and control an Android device (non-blocking) -- Tkinter for the GUI +A user-friendly Python GUI application for managing Android devices. This tool allows you to unlock devices, take screenshots, mirror the device screen, and transfer photos with drag-and-drop functionality. + +## Features +- **Unlock Devices**: Easily unlock an Android device using ADB. +- **Take Screenshots**: Capture and save screenshots from the connected Android device. +- **Screen Mirroring**: Use Scrcpy for real-time screen mirroring and control. +- **Photo Transfer**: Drag and drop photos to transfer them to the connected device. ## Requirements +### Software - **Python 3** (with Tkinter installed): - - On most systems, Python 3 will already include the `tkinter` module. - - If you need to install it separately: - - Debian/Ubuntu: `sudo apt-get install python3-tk` - - macOS (Homebrew): `brew install python-tk` (though often included by default) - - Windows: `tkinter` is usually included in the official Python installer. -- **ADB** (Android SDK Platform Tools) available on your PATH. -- **Scrcpy** installed and on your PATH (for screen mirroring). -- A **`devices.json`** file in the same directory, containing your device serials. - -Example `devices.json`: + - Tkinter comes bundled with most Python installations. If not, install it using the instructions below. +- **ADB** (Android Debug Bridge): Ensure it is installed and added to your system PATH. +- **Scrcpy**: Install and add it to your system PATH for screen mirroring. +- **Devices Configuration**: Create a `devices.json` file in the same directory as the script to store device serials. +#### Example `devices.json`: ```json { "devices": [ @@ -28,3 +27,73 @@ Example `devices.json`: "device_serial_2" ] } +``` + +### Tkinter Installation (if not already installed) +- **Debian/Ubuntu**: `sudo apt-get install python3-tk` +- **macOS** (Homebrew): `brew install python-tk` (Tkinter is usually included by default). +- **Windows**: Tkinter is typically included in the official Python installer. + +### Installation of ADB and Scrcpy +- **ADB**: + - Download the Android SDK Platform Tools from [Google](https://developer.android.com/studio/releases/platform-tools). + - Add the folder containing `adb` to your system PATH. +- **Scrcpy**: + - Installation instructions for various platforms can be found on the [Scrcpy GitHub page](https://github.com/Genymobile/scrcpy). + - Add the Scrcpy binary to your system PATH. + +## How to Run +1. Clone or download the repository containing this script. +2. Ensure `devices.json` is in the same directory. +3. Run the script: + ```bash + python script_name.py + ``` +4. Follow the on-screen instructions to select a device and perform actions. + +## How to Use + +1. **Device Selection**: + - Select a device from the dropdown menu populated using `devices.json`. + +2. **Unlock Device**: + - Click the "Unlock Device" button to unlock the selected device. + +3. **Take Screenshot**: + - Enter a name for the screenshot (e.g., `screenshot.png`). + - Click the "Take Screenshot" button to save the screenshot to the default folder. + +4. **Screen Mirroring**: + - Set the maximum FPS for Scrcpy (optional). + - Click "Start scrcpy" to mirror and control the device screen. + +5. **Photo Transfer**: + - Drag and drop a photo file into the input field or manually provide its path. + - Click "Push Photo" to transfer the photo to the device. + +## Dependencies +This project relies only on Python’s standard library: +- `json` +- `subprocess` +- `os` +- `tkinter` + +### No External Python Packages Required +All required modules come bundled with Python. However, ensure: +- `adb` is installed and on your PATH. +- `scrcpy` is installed and on your PATH. + +## Troubleshooting +- **ADB or Scrcpy Commands Not Found**: + - Ensure ADB and Scrcpy are installed and added to your system PATH. +- **Tkinter Module Not Found**: + - Follow the installation instructions for your operating system to install Tkinter. +- **Device Not Listed**: + - Ensure the device is connected and `adb devices` lists it. + +## Contributing +Feel free to submit issues or contribute improvements to the project. Fork the repository and submit a pull request with your changes. + +## License +This project is licensed under the MIT License. See the `LICENSE` file for details. + diff --git a/adb-gui.py b/adb-gui.py index 91f2d93..64a98a1 100644 --- a/adb-gui.py +++ b/adb-gui.py @@ -3,19 +3,9 @@ import subprocess import os import tkinter as tk from tkinter import ttk - +from tkinterdnd2 import DND_FILES, TkinterDnD # Import DnD support def load_devices(config_file): - """ - Load device serials from a JSON config file. - Expects a structure like: - { - "devices": [ - "emulator-5554", - "my_device_serial" - ] - } - """ try: with open(config_file, 'r', encoding='utf-8') as f: data = json.load(f) @@ -23,35 +13,16 @@ def load_devices(config_file): except (FileNotFoundError, json.JSONDecodeError): return [] - def unlock_device(): - """ - Unlocks the selected device by sending ADB commands: - 1) Keyevent 26 (power) to wake up screen - 2) Keyevent 82 (menu) to unlock (simple locks) - """ device_serial = selected_device.get() if not device_serial: status_label.config(text="Error: No device selected.") return - - # Wake device (KEYCODE_POWER = 26) subprocess.run(["adb", "-s", device_serial, "shell", "input", "keyevent", "26"]) - - # Send MENU key to unlock (KEYCODE_MENU = 82) subprocess.run(["adb", "-s", device_serial, "shell", "input", "keyevent", "82"]) - status_label.config(text=f"Device {device_serial} unlocked successfully.") - def take_screenshot(): - """ - Takes a screenshot from the selected device. - The screenshot file name is taken from the screenshot_name_entry widget. - Steps: - 1) Store the screenshot in /sdcard/ on the device. - 2) Pull it from the device to /Users/justinschwegmann/Downloads/. - """ device_serial = selected_device.get() if not device_serial: status_label.config(text="Error: No device selected.") @@ -62,54 +33,34 @@ def take_screenshot(): status_label.config(text="Error: Screenshot name cannot be empty.") return - # Ensure the screenshot file has a .png extension if not screenshot_name.lower().endswith(".png"): screenshot_name += ".png" - # Remote path on device remote_screenshot_path = f"/sdcard/{screenshot_name}" - - # Local folder path local_screenshot_folder = "/Users/justinschwegmann/Downloads/" - - # Combine folder + file for local path local_screenshot_path = os.path.join(local_screenshot_folder, screenshot_name) - # 1) Capture screenshot on the device result = subprocess.run(["adb", "-s", device_serial, "shell", "screencap", "-p", remote_screenshot_path]) if result.returncode != 0: status_label.config(text=f"Error capturing screenshot on device {device_serial}.") return - # 2) Pull screenshot to local folder result = subprocess.run(["adb", "-s", device_serial, "pull", remote_screenshot_path, local_screenshot_path]) if result.returncode != 0: status_label.config(text=f"Error pulling screenshot from device {device_serial}.") return - # (Optional) Remove screenshot from device after pulling - # subprocess.run(["adb", "-s", device_serial, "shell", "rm", remote_screenshot_path]) - status_label.config(text=f"Screenshot saved to {local_screenshot_path}.") - def start_scrcpy(): - """ - Runs the scrcpy command with specified parameters in a non-blocking way: - scrcpy --video-codec=h265 -m1920 --max-fps=FPS --no-audio -K -s - The FPS is taken from the scrcpy_fps_entry widget (default: 60). - - Using subprocess.Popen() so the GUI remains responsive. - """ device_serial = selected_device.get() if not device_serial: status_label.config(text="Error: No device selected.") return - # Get desired FPS from the user (default to 60 if not valid) fps_value_str = scrcpy_fps_entry.get().strip() if not fps_value_str.isdigit(): - fps_value_str = "60" # fallback default + fps_value_str = "60" command = [ "scrcpy", @@ -122,46 +73,68 @@ def start_scrcpy(): ] try: - # Launch scrcpy without blocking the GUI subprocess.Popen(command) - - # We won't know when scrcpy closes (without additional logic), - # but the GUI remains usable now. status_label.config(text=f"scrcpy started with FPS={fps_value_str}.") except FileNotFoundError: status_label.config(text="Error: scrcpy not found in PATH.") +def push_photo(): + device_serial = selected_device.get() + if not device_serial: + status_label.config(text="Error: No device selected.") + return + + local_photo_path = photo_path_entry.get().strip() + if not local_photo_path or not os.path.isfile(local_photo_path): + status_label.config(text="Error: Invalid local photo path.") + return + + destination_path = "/sdcard/DCIM/" + result = subprocess.run(["adb", "-s", device_serial, "push", local_photo_path, destination_path]) + if result.returncode != 0: + status_label.config(text=f"Error pushing photo to device {device_serial}.") + else: + status_label.config(text=f"Photo pushed to device {device_serial} successfully.") + +def handle_drop(event): + """ + Handle file drop event. Extracts file path from the event data + and inserts it into the photo_path_entry widget. + """ + # event.data can contain one or more file paths; we'll use the first one + files = root.tk.splitlist(event.data) + if files: + photo_path_entry.delete(0, tk.END) + photo_path_entry.insert(0, files[0]) # -------------- Main GUI code -------------- if __name__ == "__main__": CONFIG_FILE = "devices.json" devices_list = load_devices(CONFIG_FILE) - root = tk.Tk() - root.title("ADB Unlock, Screenshot & Scrcpy Tool") - root.geometry("450x350") + # Use TkinterDnD's Tk class for drag-and-drop support + root = TkinterDnD.Tk() + root.title("ADB Unlock, Screenshot, Scrcpy & Photo Push Tool") + root.geometry("450x550") main_frame = ttk.Frame(root, padding="10") main_frame.pack(fill="both", expand=True) - # -- Device selection -- + # Device selection device_label = ttk.Label(main_frame, text="Select Device:") device_label.pack(pady=5) selected_device = tk.StringVar() - device_combobox = ttk.Combobox( - main_frame, textvariable=selected_device, - values=devices_list, state="readonly" - ) + device_combobox = ttk.Combobox(main_frame, textvariable=selected_device, + values=devices_list, state="readonly") device_combobox.pack(pady=5) if devices_list: device_combobox.current(0) - # -- Unlock button -- unlock_button = ttk.Button(main_frame, text="Unlock Device", command=unlock_device) unlock_button.pack(pady=5) - # -- Screenshot name label/entry/button -- + # Screenshot section screenshot_label = ttk.Label(main_frame, text="Screenshot Name (.png):") screenshot_label.pack(pady=5) @@ -171,19 +144,33 @@ if __name__ == "__main__": screenshot_button = ttk.Button(main_frame, text="Take Screenshot", command=take_screenshot) screenshot_button.pack(pady=5) - # -- scrcpy FPS label/entry/button -- + # scrcpy section scrcpy_fps_label = ttk.Label(main_frame, text="scrcpy Max FPS:") scrcpy_fps_label.pack(pady=5) scrcpy_fps_entry = ttk.Entry(main_frame, width=10) - scrcpy_fps_entry.insert(0, "60") # default to 60 + scrcpy_fps_entry.insert(0, "60") scrcpy_fps_entry.pack(pady=5) scrcpy_button = ttk.Button(main_frame, text="Start scrcpy", command=start_scrcpy) scrcpy_button.pack(pady=5) - # -- Status label -- + # Photo push section with drag-and-drop enabled entry + photo_label = ttk.Label(main_frame, text="Local Photo Path (drag & drop a file below):") + photo_label.pack(pady=5) + + # Use a standard tk.Entry for drag-and-drop compatibility + photo_path_entry = tk.Entry(main_frame, width=40) + photo_path_entry.pack(pady=5) + + # Register the entry as a drop target and bind the drop event + photo_path_entry.drop_target_register(DND_FILES) + photo_path_entry.dnd_bind('<>', handle_drop) + + push_photo_button = ttk.Button(main_frame, text="Push Photo", command=push_photo) + push_photo_button.pack(pady=5) + status_label = ttk.Label(main_frame, text="Status: Ready", foreground="blue") status_label.pack(pady=10) - root.mainloop() + root.mainloop() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 40833ca..edc1710 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,18 +1,16 @@ -# ------------------------------------ -# Python dependencies for this project -# ------------------------------------ -# This project only uses Python's standard library modules: -# - json -# - subprocess -# - os -# - tkinter (comes bundled with Python on most systems) -# -# Therefore, there are no external Python packages needed -# to be installed via pip. -# -# You do, however, need "scrcpy" and "adb" installed on your system PATH. -# -# If you are missing tkinter (rare on some systems), you may need to install: -# - On Debian/Ubuntu: sudo apt-get install python3-tk -# - On macOS (Homebrew): brew install python-tk (usually tkinter is pre-included) -# - On Windows: usually included with official Python installer +# Requirements for ADB Unlock, Screenshot & Scrcpy Tool + +# Python dependencies: +# - tkinterdnd2 (external package for drag-and-drop support) +tkinterdnd2 + +# The rest of the required software is non-Python: +# 1. Android Debug Bridge (ADB) +# - Download from: https://developer.android.com/studio/releases/platform-tools +# 2. Scrcpy +# - Installation guide: https://github.com/Genymobile/scrcpy + +# Note: If tkinter is missing (rare): +# - On Debian/Ubuntu: sudo apt-get install python3-tk +# - On macOS (Homebrew): brew install python-tk +# - On Windows: Included in the Python installer \ No newline at end of file