added function for uploading a screenshot via Drag and Drop

This commit is contained in:
2025-01-19 12:31:36 +01:00
parent 0af1a27dcd
commit af4b377bb6
3 changed files with 155 additions and 101 deletions

View File

@@ -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/<filename> on the device.
2) Pull it from the device to /Users/justinschwegmann/Downloads/<filename>.
"""
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 <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()
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('<<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.pack(pady=10)
root.mainloop()
root.mainloop()