added function for uploading a screenshot via Drag and Drop
This commit is contained in:
97
README.md
97
README.md
@@ -1,24 +1,23 @@
|
|||||||
# ADB Unlock, Screenshot & Scrcpy Tool
|
# ADB Unlock, Screenshot & Scrcpy Tool
|
||||||
|
|
||||||
A simple Python GUI application that uses:
|
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.
|
||||||
- **ADB** to unlock an Android device and take a screenshot
|
|
||||||
- **Scrcpy** to display and control an Android device (non-blocking)
|
## Features
|
||||||
- Tkinter for the GUI
|
- **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
|
## Requirements
|
||||||
|
|
||||||
|
### Software
|
||||||
- **Python 3** (with Tkinter installed):
|
- **Python 3** (with Tkinter installed):
|
||||||
- On most systems, Python 3 will already include the `tkinter` module.
|
- Tkinter comes bundled with most Python installations. If not, install it using the instructions below.
|
||||||
- If you need to install it separately:
|
- **ADB** (Android Debug Bridge): Ensure it is installed and added to your system PATH.
|
||||||
- Debian/Ubuntu: `sudo apt-get install python3-tk`
|
- **Scrcpy**: Install and add it to your system PATH for screen mirroring.
|
||||||
- macOS (Homebrew): `brew install python-tk` (though often included by default)
|
- **Devices Configuration**: Create a `devices.json` file in the same directory as the script to store device serials.
|
||||||
- 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`:
|
|
||||||
|
|
||||||
|
#### Example `devices.json`:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"devices": [
|
"devices": [
|
||||||
@@ -28,3 +27,73 @@ Example `devices.json`:
|
|||||||
"device_serial_2"
|
"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.
|
||||||
|
|
||||||
|
|||||||
123
adb-gui.py
123
adb-gui.py
@@ -3,19 +3,9 @@ import subprocess
|
|||||||
import os
|
import os
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from tkinterdnd2 import DND_FILES, TkinterDnD # Import DnD support
|
||||||
|
|
||||||
def load_devices(config_file):
|
def load_devices(config_file):
|
||||||
"""
|
|
||||||
Load device serials from a JSON config file.
|
|
||||||
Expects a structure like:
|
|
||||||
{
|
|
||||||
"devices": [
|
|
||||||
"emulator-5554",
|
|
||||||
"my_device_serial"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
with open(config_file, 'r', encoding='utf-8') as f:
|
with open(config_file, 'r', encoding='utf-8') as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
@@ -23,35 +13,16 @@ def load_devices(config_file):
|
|||||||
except (FileNotFoundError, json.JSONDecodeError):
|
except (FileNotFoundError, json.JSONDecodeError):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def unlock_device():
|
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()
|
device_serial = selected_device.get()
|
||||||
if not device_serial:
|
if not device_serial:
|
||||||
status_label.config(text="Error: No device selected.")
|
status_label.config(text="Error: No device selected.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Wake device (KEYCODE_POWER = 26)
|
|
||||||
subprocess.run(["adb", "-s", device_serial, "shell", "input", "keyevent", "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"])
|
subprocess.run(["adb", "-s", device_serial, "shell", "input", "keyevent", "82"])
|
||||||
|
|
||||||
status_label.config(text=f"Device {device_serial} unlocked successfully.")
|
status_label.config(text=f"Device {device_serial} unlocked successfully.")
|
||||||
|
|
||||||
|
|
||||||
def take_screenshot():
|
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/<filename> on the device.
|
|
||||||
2) Pull it from the device to /Users/justinschwegmann/Downloads/<filename>.
|
|
||||||
"""
|
|
||||||
device_serial = selected_device.get()
|
device_serial = selected_device.get()
|
||||||
if not device_serial:
|
if not device_serial:
|
||||||
status_label.config(text="Error: No device selected.")
|
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.")
|
status_label.config(text="Error: Screenshot name cannot be empty.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Ensure the screenshot file has a .png extension
|
|
||||||
if not screenshot_name.lower().endswith(".png"):
|
if not screenshot_name.lower().endswith(".png"):
|
||||||
screenshot_name += ".png"
|
screenshot_name += ".png"
|
||||||
|
|
||||||
# Remote path on device
|
|
||||||
remote_screenshot_path = f"/sdcard/{screenshot_name}"
|
remote_screenshot_path = f"/sdcard/{screenshot_name}"
|
||||||
|
|
||||||
# Local folder path
|
|
||||||
local_screenshot_folder = "/Users/justinschwegmann/Downloads/"
|
local_screenshot_folder = "/Users/justinschwegmann/Downloads/"
|
||||||
|
|
||||||
# Combine folder + file for local path
|
|
||||||
local_screenshot_path = os.path.join(local_screenshot_folder, screenshot_name)
|
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])
|
result = subprocess.run(["adb", "-s", device_serial, "shell", "screencap", "-p", remote_screenshot_path])
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
status_label.config(text=f"Error capturing screenshot on device {device_serial}.")
|
status_label.config(text=f"Error capturing screenshot on device {device_serial}.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# 2) Pull screenshot to local folder
|
|
||||||
result = subprocess.run(["adb", "-s", device_serial, "pull", remote_screenshot_path, local_screenshot_path])
|
result = subprocess.run(["adb", "-s", device_serial, "pull", remote_screenshot_path, local_screenshot_path])
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
status_label.config(text=f"Error pulling screenshot from device {device_serial}.")
|
status_label.config(text=f"Error pulling screenshot from device {device_serial}.")
|
||||||
return
|
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}.")
|
status_label.config(text=f"Screenshot saved to {local_screenshot_path}.")
|
||||||
|
|
||||||
|
|
||||||
def start_scrcpy():
|
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 <device_serial>
|
|
||||||
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()
|
device_serial = selected_device.get()
|
||||||
if not device_serial:
|
if not device_serial:
|
||||||
status_label.config(text="Error: No device selected.")
|
status_label.config(text="Error: No device selected.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get desired FPS from the user (default to 60 if not valid)
|
|
||||||
fps_value_str = scrcpy_fps_entry.get().strip()
|
fps_value_str = scrcpy_fps_entry.get().strip()
|
||||||
if not fps_value_str.isdigit():
|
if not fps_value_str.isdigit():
|
||||||
fps_value_str = "60" # fallback default
|
fps_value_str = "60"
|
||||||
|
|
||||||
command = [
|
command = [
|
||||||
"scrcpy",
|
"scrcpy",
|
||||||
@@ -122,46 +73,68 @@ def start_scrcpy():
|
|||||||
]
|
]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Launch scrcpy without blocking the GUI
|
|
||||||
subprocess.Popen(command)
|
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}.")
|
status_label.config(text=f"scrcpy started with FPS={fps_value_str}.")
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
status_label.config(text="Error: scrcpy not found in PATH.")
|
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 --------------
|
# -------------- Main GUI code --------------
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
CONFIG_FILE = "devices.json"
|
CONFIG_FILE = "devices.json"
|
||||||
devices_list = load_devices(CONFIG_FILE)
|
devices_list = load_devices(CONFIG_FILE)
|
||||||
|
|
||||||
root = tk.Tk()
|
# Use TkinterDnD's Tk class for drag-and-drop support
|
||||||
root.title("ADB Unlock, Screenshot & Scrcpy Tool")
|
root = TkinterDnD.Tk()
|
||||||
root.geometry("450x350")
|
root.title("ADB Unlock, Screenshot, Scrcpy & Photo Push Tool")
|
||||||
|
root.geometry("450x550")
|
||||||
|
|
||||||
main_frame = ttk.Frame(root, padding="10")
|
main_frame = ttk.Frame(root, padding="10")
|
||||||
main_frame.pack(fill="both", expand=True)
|
main_frame.pack(fill="both", expand=True)
|
||||||
|
|
||||||
# -- Device selection --
|
# Device selection
|
||||||
device_label = ttk.Label(main_frame, text="Select Device:")
|
device_label = ttk.Label(main_frame, text="Select Device:")
|
||||||
device_label.pack(pady=5)
|
device_label.pack(pady=5)
|
||||||
|
|
||||||
selected_device = tk.StringVar()
|
selected_device = tk.StringVar()
|
||||||
device_combobox = ttk.Combobox(
|
device_combobox = ttk.Combobox(main_frame, textvariable=selected_device,
|
||||||
main_frame, textvariable=selected_device,
|
values=devices_list, state="readonly")
|
||||||
values=devices_list, state="readonly"
|
|
||||||
)
|
|
||||||
device_combobox.pack(pady=5)
|
device_combobox.pack(pady=5)
|
||||||
if devices_list:
|
if devices_list:
|
||||||
device_combobox.current(0)
|
device_combobox.current(0)
|
||||||
|
|
||||||
# -- Unlock button --
|
|
||||||
unlock_button = ttk.Button(main_frame, text="Unlock Device", command=unlock_device)
|
unlock_button = ttk.Button(main_frame, text="Unlock Device", command=unlock_device)
|
||||||
unlock_button.pack(pady=5)
|
unlock_button.pack(pady=5)
|
||||||
|
|
||||||
# -- Screenshot name label/entry/button --
|
# Screenshot section
|
||||||
screenshot_label = ttk.Label(main_frame, text="Screenshot Name (.png):")
|
screenshot_label = ttk.Label(main_frame, text="Screenshot Name (.png):")
|
||||||
screenshot_label.pack(pady=5)
|
screenshot_label.pack(pady=5)
|
||||||
|
|
||||||
@@ -171,18 +144,32 @@ if __name__ == "__main__":
|
|||||||
screenshot_button = ttk.Button(main_frame, text="Take Screenshot", command=take_screenshot)
|
screenshot_button = ttk.Button(main_frame, text="Take Screenshot", command=take_screenshot)
|
||||||
screenshot_button.pack(pady=5)
|
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 = ttk.Label(main_frame, text="scrcpy Max FPS:")
|
||||||
scrcpy_fps_label.pack(pady=5)
|
scrcpy_fps_label.pack(pady=5)
|
||||||
|
|
||||||
scrcpy_fps_entry = ttk.Entry(main_frame, width=10)
|
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_fps_entry.pack(pady=5)
|
||||||
|
|
||||||
scrcpy_button = ttk.Button(main_frame, text="Start scrcpy", command=start_scrcpy)
|
scrcpy_button = ttk.Button(main_frame, text="Start scrcpy", command=start_scrcpy)
|
||||||
scrcpy_button.pack(pady=5)
|
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('<<Drop>>', 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 = ttk.Label(main_frame, text="Status: Ready", foreground="blue")
|
||||||
status_label.pack(pady=10)
|
status_label.pack(pady=10)
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
# ------------------------------------
|
# Requirements for ADB Unlock, Screenshot & Scrcpy Tool
|
||||||
# Python dependencies for this project
|
|
||||||
# ------------------------------------
|
# Python dependencies:
|
||||||
# This project only uses Python's standard library modules:
|
# - tkinterdnd2 (external package for drag-and-drop support)
|
||||||
# - json
|
tkinterdnd2
|
||||||
# - subprocess
|
|
||||||
# - os
|
# The rest of the required software is non-Python:
|
||||||
# - tkinter (comes bundled with Python on most systems)
|
# 1. Android Debug Bridge (ADB)
|
||||||
#
|
# - Download from: https://developer.android.com/studio/releases/platform-tools
|
||||||
# Therefore, there are no external Python packages needed
|
# 2. Scrcpy
|
||||||
# to be installed via pip.
|
# - Installation guide: https://github.com/Genymobile/scrcpy
|
||||||
#
|
|
||||||
# You do, however, need "scrcpy" and "adb" installed on your system PATH.
|
# Note: If tkinter is missing (rare):
|
||||||
#
|
|
||||||
# If you are missing tkinter (rare on some systems), you may need to install:
|
|
||||||
# - On Debian/Ubuntu: sudo apt-get install python3-tk
|
# - On Debian/Ubuntu: sudo apt-get install python3-tk
|
||||||
# - On macOS (Homebrew): brew install python-tk (usually tkinter is pre-included)
|
# - On macOS (Homebrew): brew install python-tk
|
||||||
# - On Windows: usually included with official Python installer
|
# - On Windows: Included in the Python installer
|
||||||
Reference in New Issue
Block a user