174 lines
6.6 KiB
Plaintext
174 lines
6.6 KiB
Plaintext
import sys
|
|
import subprocess
|
|
import ctypes
|
|
from PyQt6.QtWidgets import (
|
|
QApplication, QWidget, QVBoxLayout, QPushButton, QComboBox,
|
|
QLabel, QLineEdit, QFileDialog
|
|
)
|
|
from PyQt6.QtCore import Qt
|
|
|
|
def get_adb_mounts():
|
|
"""Return a list of device mounts with SD card and internal storage at top."""
|
|
try:
|
|
# Check if any devices are connected
|
|
devices_result = subprocess.run(
|
|
["adb", "devices"],
|
|
capture_output=True, text=True, check=True
|
|
)
|
|
lines = devices_result.stdout.strip().splitlines()[1:] # skip header
|
|
connected = [line for line in lines if "device" in line]
|
|
if not connected:
|
|
return ["No device connected or unauthorized"]
|
|
|
|
# Get mounts from the first connected device
|
|
mount_result = subprocess.run(
|
|
["adb", "shell", "mount"],
|
|
capture_output=True, text=True, check=True
|
|
)
|
|
|
|
mounts = []
|
|
sd_card_mount = None
|
|
internal_storage = None
|
|
other_mounts = []
|
|
|
|
for line in mount_result.stdout.splitlines():
|
|
parts = line.split()
|
|
if len(parts) >= 3:
|
|
device, mount_point = parts[0], parts[2]
|
|
mount_entry = f"{device} -> {mount_point}"
|
|
|
|
# Check for SD card patterns (xxxx-xxxx format in device or common SD mount points)
|
|
if any(pattern in mount_point.lower() for pattern in ['/mnt/media_rw/', '/storage/', '/sdcard', '/external_sd']) \
|
|
or '-' in device and len(device.split('-')[0]) == 4:
|
|
sd_card_mount = mount_entry
|
|
# Check for internal storage
|
|
elif '/storage/emulated/0' in mount_point or '/data/media' in mount_point:
|
|
internal_storage = mount_entry
|
|
else:
|
|
other_mounts.append(mount_entry)
|
|
|
|
# Add SD card at top if found (try to get specific format)
|
|
if sd_card_mount:
|
|
# Try to extract the xxxx-xxxx pattern for better display
|
|
device_part = sd_card_mount.split('->')[0].strip()
|
|
mount_part = sd_card_mount.split('->')[1].strip()
|
|
if '-' in device_part and len(device_part) >= 9:
|
|
mounts.append(f"{device_part} -> {mount_part} (micro SD card)")
|
|
else:
|
|
mounts.append(f"{sd_card_mount} (micro SD card)")
|
|
else:
|
|
# Try alternative method to find SD card
|
|
try:
|
|
sd_result = subprocess.run(
|
|
["adb", "shell", "ls", "/storage/"],
|
|
capture_output=True, text=True, check=True
|
|
)
|
|
for item in sd_result.stdout.split():
|
|
if item and item not in ['emulated', 'self']:
|
|
mounts.append(f"SD Card -> /storage/{item}")
|
|
break
|
|
except:
|
|
pass
|
|
|
|
# Add internal storage second
|
|
if internal_storage:
|
|
mounts.append(f"{internal_storage} (Internal Storage)")
|
|
else:
|
|
# Try to find internal storage via other means
|
|
mounts.append("/data/media/0 -> /storage/emulated/0 (Internal Storage)")
|
|
|
|
# Add all other mounts
|
|
mounts.extend(other_mounts)
|
|
|
|
if not mounts:
|
|
mounts.append("No mounts found")
|
|
return mounts
|
|
except subprocess.CalledProcessError as e:
|
|
return [f"ADB error: {e.stderr.strip() or e}"]
|
|
except FileNotFoundError:
|
|
return ["ADB not found. Make sure it is installed and in PATH."]
|
|
|
|
def get_volume_id(drive_letter):
|
|
"""Return the volume serial number for a given drive letter."""
|
|
volume_name_buf = ctypes.create_unicode_buffer(1024)
|
|
file_system_name_buf = ctypes.create_unicode_buffer(1024)
|
|
serial_number = ctypes.c_uint()
|
|
max_component_length = ctypes.c_uint()
|
|
file_system_flags = ctypes.c_uint()
|
|
ret = ctypes.windll.kernel32.GetVolumeInformationW(
|
|
ctypes.c_wchar_p(drive_letter + "\\"),
|
|
volume_name_buf,
|
|
ctypes.sizeof(volume_name_buf),
|
|
ctypes.byref(serial_number),
|
|
ctypes.byref(max_component_length),
|
|
ctypes.byref(file_system_flags),
|
|
file_system_name_buf,
|
|
ctypes.sizeof(file_system_name_buf)
|
|
)
|
|
if ret:
|
|
return f"{serial_number.value:X}"
|
|
return "Unavailable"
|
|
|
|
class AdbStorageApp(QWidget):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.setWindowTitle("ADB Storage & Drive Info")
|
|
self.setGeometry(300, 300, 450, 220)
|
|
layout = QVBoxLayout()
|
|
|
|
# ADB Mounts Picker
|
|
self.mount_label = QLabel("Device Storage Mounts:")
|
|
self.mount_combo = QComboBox()
|
|
self.refresh_btn = QPushButton("Refresh Mounts")
|
|
self.refresh_btn.clicked.connect(self.load_mounts)
|
|
|
|
# Drive input
|
|
self.drive_label = QLabel("Drive:")
|
|
self.drive_input = QLineEdit("F:")
|
|
self.browse_btn = QPushButton("Browse")
|
|
self.browse_btn.clicked.connect(self.browse_drive)
|
|
|
|
# Volume ID display
|
|
self.volume_label = QLabel("Volume ID:")
|
|
self.volume_display = QLabel("")
|
|
|
|
# Connect drive input change
|
|
self.drive_input.textChanged.connect(self.update_volume_id)
|
|
|
|
layout.addWidget(self.mount_label)
|
|
layout.addWidget(self.mount_combo)
|
|
layout.addWidget(self.refresh_btn)
|
|
layout.addWidget(self.drive_label)
|
|
layout.addWidget(self.drive_input)
|
|
layout.addWidget(self.browse_btn)
|
|
layout.addWidget(self.volume_label)
|
|
layout.addWidget(self.volume_display)
|
|
|
|
self.setLayout(layout)
|
|
self.load_mounts()
|
|
self.update_volume_id()
|
|
|
|
def load_mounts(self):
|
|
self.mount_combo.clear()
|
|
mounts = get_adb_mounts()
|
|
self.mount_combo.addItems(mounts)
|
|
|
|
def browse_drive(self):
|
|
folder = QFileDialog.getExistingDirectory(self, "Select Drive")
|
|
if folder:
|
|
drive = folder.split(":")[0] + ":"
|
|
self.drive_input.setText(drive)
|
|
|
|
def update_volume_id(self):
|
|
drive = self.drive_input.text().strip()
|
|
if drive and len(drive) == 2 and drive[1] == ":":
|
|
vid = get_volume_id(drive)
|
|
self.volume_display.setText(vid)
|
|
else:
|
|
self.volume_display.setText("Invalid Drive")
|
|
|
|
if __name__ == "__main__":
|
|
app = QApplication(sys.argv)
|
|
window = AdbStorageApp()
|
|
window.show()
|
|
sys.exit(app.exec()) |