created basic gui for interacting with an android device via adb
This commit is contained in:
30
README.md
Normal file
30
README.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# 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
|
||||
|
||||
## Requirements
|
||||
|
||||
- **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`:
|
||||
|
||||
```json
|
||||
{
|
||||
"devices": [
|
||||
"emulator-5554",
|
||||
"emulator-5556",
|
||||
"device_serial_1",
|
||||
"device_serial_2"
|
||||
]
|
||||
}
|
||||
189
adb-gui.py
Normal file
189
adb-gui.py
Normal file
@@ -0,0 +1,189 @@
|
||||
import json
|
||||
import subprocess
|
||||
import os
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
|
||||
|
||||
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)
|
||||
return data.get("devices", [])
|
||||
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.")
|
||||
return
|
||||
|
||||
screenshot_name = screenshot_name_entry.get().strip()
|
||||
if not screenshot_name:
|
||||
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
|
||||
|
||||
command = [
|
||||
"scrcpy",
|
||||
"--video-codec=h265",
|
||||
"-m", "1920",
|
||||
f"--max-fps={fps_value_str}",
|
||||
"--no-audio",
|
||||
"-K",
|
||||
"-s", device_serial
|
||||
]
|
||||
|
||||
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.")
|
||||
|
||||
|
||||
# -------------- 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")
|
||||
|
||||
main_frame = ttk.Frame(root, padding="10")
|
||||
main_frame.pack(fill="both", expand=True)
|
||||
|
||||
# -- 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.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_label = ttk.Label(main_frame, text="Screenshot Name (.png):")
|
||||
screenshot_label.pack(pady=5)
|
||||
|
||||
screenshot_name_entry = ttk.Entry(main_frame, width=30)
|
||||
screenshot_name_entry.pack(pady=5)
|
||||
|
||||
screenshot_button = ttk.Button(main_frame, text="Take Screenshot", command=take_screenshot)
|
||||
screenshot_button.pack(pady=5)
|
||||
|
||||
# -- scrcpy FPS label/entry/button --
|
||||
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.pack(pady=5)
|
||||
|
||||
scrcpy_button = ttk.Button(main_frame, text="Start scrcpy", command=start_scrcpy)
|
||||
scrcpy_button.pack(pady=5)
|
||||
|
||||
# -- Status label --
|
||||
status_label = ttk.Label(main_frame, text="Status: Ready", foreground="blue")
|
||||
status_label.pack(pady=10)
|
||||
|
||||
root.mainloop()
|
||||
8
devices.json
Normal file
8
devices.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"devices": [
|
||||
"emulator-5554",
|
||||
"emulator-5556",
|
||||
"device_serial_1",
|
||||
"device_serial_2"
|
||||
]
|
||||
}
|
||||
18
requirements.txt
Normal file
18
requirements.txt
Normal file
@@ -0,0 +1,18 @@
|
||||
# ------------------------------------
|
||||
# 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
|
||||
17
run_app.command
Executable file
17
run_app.command
Executable file
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# A simple macOS script to launch the ADB Unlock GUI app.
|
||||
# ---------------------------------------------------------
|
||||
|
||||
# 1) Find the directory of this script
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
# 2) Move into that directory
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
# 3) Use python3 (make sure python3 is installed and on your PATH)
|
||||
PYTHON=$(which python3)
|
||||
|
||||
# 4) Run your Python script (adjust the file name if different)
|
||||
"$PYTHON" adb-gui.py
|
||||
Reference in New Issue
Block a user