Skip to content

Commit 95eb088

Browse files
committed
Fixed eject feature on Windows
1 parent 18b01f1 commit 95eb088

File tree

2 files changed

+149
-102
lines changed

2 files changed

+149
-102
lines changed

apps/sd_flasher/fresh_install.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
from apps.sd_flasher.update import get_latest_release_link
55
from apps.sd_flasher.format import start_formatting
66

7-
from lib.sd_card import format_sd_card, get_volume_by_letter, eject_sd
8-
97
def start_installing(sd_selector, display, dropped_file):
108
# Start download on a different thread
119

lib/sd_card.py

Lines changed: 149 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import os
2-
import platform
3-
import subprocess
1+
import os, platform, subprocess, tempfile
42
import tkinter as tk
53
import ctypes
64
from tkinter import simpledialog, messagebox
@@ -12,20 +10,44 @@ def detect_sd_card():
1210
"""Detect connected SD Cards."""
1311
devices = []
1412
if platform.system() == "Windows":
15-
# Windows specific logic
1613
bitmask = ctypes.windll.kernel32.GetLogicalDrives()
1714
for letter in range(26): # Check from A to Z
1815
if bitmask & (1 << letter):
1916
drive_letter = f"{chr(65 + letter)}:\\"
2017
drive_type = ctypes.windll.kernel32.GetDriveTypeW(drive_letter)
2118
if drive_type == 2: # DRIVE_REMOVABLE == 2
22-
devices.append(drive_letter)
19+
try:
20+
volume_name_buffer = ctypes.create_unicode_buffer(1024)
21+
volume_serial = ctypes.c_ulong(0)
22+
max_component_length = ctypes.c_ulong(0)
23+
file_system_flags = ctypes.c_ulong(0)
24+
file_system_name_buffer = ctypes.create_unicode_buffer(1024)
25+
26+
if ctypes.windll.kernel32.GetVolumeInformationW(
27+
drive_letter,
28+
volume_name_buffer,
29+
ctypes.sizeof(volume_name_buffer),
30+
ctypes.byref(volume_serial),
31+
ctypes.byref(max_component_length),
32+
ctypes.byref(file_system_flags),
33+
file_system_name_buffer,
34+
ctypes.sizeof(file_system_name_buffer)
35+
):
36+
label = volume_name_buffer.value
37+
# If there is a label, show both letter and label
38+
if label:
39+
devices.append(f"{drive_letter[0]}:\\ ({label})")
40+
else:
41+
devices.append(drive_letter)
42+
else:
43+
devices.append(drive_letter)
44+
except Exception as e:
45+
print(f"Error getting volume name: {e}")
46+
devices.append(drive_letter)
2347
elif platform.system() == "Darwin": # macOS
24-
# macOS specific logic
2548
volumes = [os.path.join("/Volumes", d) for d in os.listdir("/Volumes") if os.path.ismount(os.path.join("/Volumes", d))]
2649
devices.extend(volumes)
2750
elif platform.system() == "Linux":
28-
# Linux specific logic
2951
user = os.getenv("USER", "default_user")
3052
media_path = f"/media/{user}"
3153
if os.path.exists(media_path):
@@ -36,72 +58,40 @@ def get_disk_identifier(volume_path):
3658
"""Returns disk identifier for given volume path."""
3759
system = platform.system()
3860

39-
if system == "Darwin":
40-
result = subprocess.run(['diskutil', 'info', volume_path], capture_output=True, text=True)
41-
for line in result.stdout.splitlines():
42-
if "Device Identifier" in line:
43-
return line.split(":")[1].strip()
44-
elif system == "Windows":
61+
if system == "Windows":
4562
try:
46-
# PowerShell command to get device ID
47-
command = f"powershell -Command (Get-WmiObject Win32_LogicalDisk | Where-Object {{$_.DeviceID -eq '{volume_path}'}}).PNPDeviceID"
48-
result = subprocess.run(command, capture_output=True, text=True, shell=True)
49-
output = result.stdout.strip()
50-
51-
if output:
52-
return output
53-
else:
54-
print(f"Error: No PNPDeviceID found for {volume_path}")
63+
drive_letter = volume_path.split(" ")[0]
64+
command = f"""
65+
$driveLetter = '{drive_letter[0]}'
66+
$removableDrives = Get-WmiObject -Query "SELECT * FROM Win32_DiskDrive WHERE InterfaceType='USB'"
67+
foreach ($drive in $removableDrives) {{
68+
$partitions = Get-WmiObject -Query "ASSOCIATORS OF {{Win32_DiskDrive.DeviceID='$($drive.DeviceID)'}} WHERE AssocClass=Win32_DiskDriveToDiskPartition"
69+
foreach ($partition in $partitions) {{
70+
$logicalDisks = Get-WmiObject -Query "ASSOCIATORS OF {{Win32_DiskPartition.DeviceID='$($partition.DeviceID)'}} WHERE AssocClass=Win32_LogicalDiskToPartition"
71+
foreach ($logicalDisk in $logicalDisks) {{
72+
if ($logicalDisk.DeviceID -eq '{drive_letter[0]}') {{
73+
return $drive.PNPDeviceID
74+
}}
75+
}}
76+
}}
77+
}}
78+
"""
79+
result = subprocess.run(["powershell", "-Command", command], capture_output=True, text=True, shell=True)
80+
81+
if result.returncode != 0:
82+
print(f"Error in PowerShell command: {result.stderr}")
5583
return None
84+
85+
return result.stdout.strip()
5686
except Exception as e:
5787
print(f"Error during retrieving device identifier: {e}")
5888
return None
5989
return None
6090

6191

62-
def get_volume_by_letter(letter):
63-
try:
64-
# Esegui il comando DiskPart per elencare i volumi
65-
diskpart_script = "list volume"
66-
67-
process = subprocess.Popen(
68-
["diskpart"],
69-
stdin=subprocess.PIPE,
70-
stdout=subprocess.PIPE,
71-
stderr=subprocess.PIPE,
72-
text=True
73-
)
74-
75-
# Invia il comando al processo
76-
stdout, stderr = process.communicate(input=diskpart_script)
77-
78-
if stderr:
79-
print(f"Errore: {stderr}")
80-
return None
81-
82-
# Filtriamo l'output per ignorare intestazioni e altre righe non pertinenti
83-
volume_lines = []
84-
for line in stdout.splitlines():
85-
line = line.strip()
86-
if line and line.startswith("Volume"): # Solo righe con informazioni sui volumi
87-
volume_lines.append(line)
88-
89-
# Cerca la lettera specificata nei volumi
90-
for line in volume_lines:
91-
if letter in line:
92-
print(f"Volume trovato per {letter}: {line}")
93-
return line
94-
95-
print(f"Volume con lettera {letter} non trovato.")
96-
return None
97-
98-
except Exception as e:
99-
print(f"Errore: {str(e)}")
100-
return None
101-
10292
def refresh_sd_devices(sd_select, sd_dropdown, identifier):
10393
sd_devices = detect_sd_card() or ["Click to refresh"]
104-
if sd_devices[0] == "Click to refresh":
94+
if sd_devices[0] == "Click to refresh" or sd_devices[0] == "None" :
10595
selected_sd = "Click to refresh"
10696
else:
10797
selected_sd = get_volume_name(identifier)
@@ -115,53 +105,55 @@ def refresh_sd_devices(sd_select, sd_dropdown, identifier):
115105

116106
def eject_sd(sd_device, sd_select, sd_dropdown, terminal):
117107
system_os = platform.system()
118-
identifier = get_disk_identifier(sd_device)
119-
120-
if not identifier:
121-
print(f"Error: Can't find {sd_device} disk identifier.")
122-
terminal.message(f"Error: Can't find {sd_device} disk identifier.")
123-
return False
108+
109+
if system_os == "Windows" and "(" in sd_device:
110+
drive_letter = sd_device.split(" ")[0]
111+
else:
112+
drive_letter = sd_device
124113

125114
if sd_device != "Click to refresh":
126115
try:
127-
if system_os == "Linux":
116+
if system_os == "Windows":
117+
# Modified PowerShell command to properly exit after ejection
118+
script = f"""
119+
(New-Object -comObject Shell.Application).Namespace(17).ParseName(\"{drive_letter[0]}:\").InvokeVerb(\"Eject\")
120+
Start-Sleep -Seconds 1
121+
exit
122+
"""
123+
124+
# Use CREATE_NO_WINDOW to avoid GUI freezing
125+
result = subprocess.run(
126+
["powershell", "-Command", script],
127+
capture_output=True,
128+
text=True,
129+
creationflags=subprocess.CREATE_NO_WINDOW
130+
)
131+
132+
if result.returncode == 0:
133+
terminal.message(f"{drive_letter} successfully ejected.")
134+
else:
135+
terminal.message(f"Error ejecting {drive_letter}: {result.stderr}")
136+
137+
elif system_os == "Linux":
128138
os.system(f"umount {sd_device}")
129139
print(f"{sd_device} correctly ejected.")
130140
terminal.message(f"{sd_device} correctly ejected.")
131141
elif system_os == "Darwin":
132142
os.system(f"diskutil unmount {sd_device}")
133143
print(f"{sd_device} correctly ejected.")
134144
terminal.message(f"{sd_device} correctly ejected.")
135-
elif system_os == "Windows":
136-
# Prima smontiamo il volume
137-
os.system(f"mountvol {sd_device} /p")
138-
print(f"{sd_device} correctly ejected.")
139-
terminal.message(f"{sd_device} correctly unmounted.")
140-
141-
# Ora rimuoviamo il dispositivo usando pnputil
142-
try:
143-
# Comando pnputil per rimuovere il dispositivo
144-
pnputil_command = f"pnputil /remove-device \"{identifier}\""
145-
subprocess.run(pnputil_command, check=True, shell=True)
146-
print(f"{sd_device} correctly ejected.")
147-
terminal.message(f"{sd_device} correctly ejected.")
148-
except subprocess.CalledProcessError as e:
149-
print(f"Error trying to eject {sd_device}: {e}")
150-
terminal.message(f"Error trying to eject {sd_device}: {e}")
151-
152145
else:
153146
print(f"{system_os} OS not supported for disk ejection.")
154147
return False
148+
149+
refresh_sd_devices(sd_select, sd_dropdown, get_disk_identifier(sd_device))
155150
except Exception as e:
156151
print(f"Error trying to eject {sd_device}: {e}")
157-
return False
152+
terminal.message(f"Error trying to eject {sd_device}: {e}")
158153
else:
159154
print("No SD found to eject.")
160155
terminal.message("No SD found to eject.")
161156

162-
refresh_sd_devices(sd_select, sd_dropdown, identifier)
163-
164-
165157
def get_volume_name(disk_identifier):
166158
"""
167159
RETURN NAME OF VOLUME ASSOCIED WITH DISK.
@@ -170,7 +162,7 @@ def get_volume_name(disk_identifier):
170162
disk_identifier (str): identifier disk (es. '/dev/disk4', '/dev/sdb', 'D:')
171163
172164
Returns:
173-
str: name of volume, or None is not found.
165+
str: name of volume, or None if not found.
174166
"""
175167
system = platform.system()
176168

@@ -202,6 +194,7 @@ def get_volume_name(disk_identifier):
202194

203195
elif system == "Windows":
204196
# Usa PowerShell per ottenere informazioni sul disco
197+
print(f"disk_identifier[0]: {disk_identifier}")
205198
result = subprocess.run(
206199
[
207200
"powershell", "-Command",
@@ -211,6 +204,7 @@ def get_volume_name(disk_identifier):
211204
stderr=subprocess.PIPE,
212205
text=True
213206
)
207+
214208
return result.stdout.strip()
215209

216210
else:
@@ -243,43 +237,58 @@ def format_sd_card(sd_path, display, callback, sd_selector):
243237
os_type = platform.system()
244238
# print(sd_path)
245239
if sd_selector[0].get() == "Click to refresh":
246-
display.message("Formatting Click to refresh to FAT32… did you plug-in an sd?")
240+
display.message("Formatting ??? to FAT32… did you plug-in an sd?")
247241
return False
248242
try:
249243
if os_type == "Windows":
250-
# Ask user volume name
244+
# Chiedi all'utente il nome del volume
251245
def on_volume_name_enter(volume_name):
252246
if not volume_name or len(volume_name.strip()) == 0:
253247
messagebox.showerror("Error", "Volume name cannot be empty!")
254248
return
255-
volume_name = volume_name.upper() # convert uppercase to avoid errors
249+
volume_name = volume_name.upper() # Converti il nome in maiuscolo per evitare errori
256250
identifier = get_disk_identifier(volume_name)
257251

258-
# create formatting script
252+
if not identifier:
253+
messagebox.showerror("Error", "Unable to get disk identifier.")
254+
return
255+
256+
# Crea lo script di formattazione
259257
script = f"""
260258
select volume {sd_path[0]}
261259
format fs=fat32 quick label={volume_name}
262260
exit
263261
"""
262+
264263
try:
265-
# Scrivi e esegui il comando DiskPart
264+
# Scrivi ed esegui lo script diskpart
266265
with open("format_script.txt", "w") as script_file:
267266
script_file.write(script)
268-
subprocess.run("diskpart /s format_script.txt", check=True, shell=True)
267+
268+
# Esegui diskpart
269+
result = subprocess.run("diskpart /s format_script.txt", check=True, shell=True, capture_output=True, text=True)
270+
271+
# Debug: stampa l'output di diskpart
272+
print(f"Diskpart Output (stdout): {result.stdout}")
273+
print(f"Diskpart Output (stderr): {result.stderr}")
274+
269275
os.remove("format_script.txt")
270-
# refresh_sd_devices(sd_selector[0], sd_selector[1], identifier)
271276
display.message(f"Formatting completed!\nYour SD card has been formatted with the name '{volume_name}'!")
277+
278+
# Chiamata al callback
272279
try:
273280
callback_thread = threading.Thread(target=callback)
274281
callback_thread.start()
275282
except:
276283
traceback.print_exc()
277284
return True
285+
278286
except subprocess.CalledProcessError as e:
279287
messagebox.showerror("Error", f"Error while formatting: {e}")
288+
print(f"Error while formatting: {e}")
280289
return False
281290

282-
# Chiedi il nome del volume
291+
# Chiedi all'utente di inserire un nome valido per il volume
283292
display.user_input("Enter the name for your new volume:", on_volume_name_enter)
284293
elif os_type == "Darwin":
285294
# Get disk identifier for macOS
@@ -334,3 +343,43 @@ def on_volume_name_enter(volume_name):
334343
messagebox.showerror("Error", f"Error while formatting: {e}")
335344
print(e.stderr) # Show detailed error
336345
return False
346+
347+
def run_powershell_script(command):
348+
"""Esegui uno script PowerShell in background senza aprire la finestra PowerShell."""
349+
350+
# Crea un file temporaneo per il comando PowerShell
351+
with tempfile.NamedTemporaryFile(suffix=".ps1", delete=False) as temp_file:
352+
temp_file.write(command.encode("utf-8"))
353+
temp_file.close() # Chiudiamo il file temporaneo per poterlo eseguire
354+
355+
# Imposta la politica di esecuzione per consentire l'esecuzione di script non firmati
356+
set_policy_command = "Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Force"
357+
358+
try:
359+
# Esegui il comando PowerShell
360+
subprocess.run(
361+
["powershell", "-Command", set_policy_command],
362+
capture_output=True,
363+
text=True,
364+
creationflags=subprocess.CREATE_NO_WINDOW
365+
)
366+
367+
# Esegui lo script PowerShell in background senza finestra
368+
result = subprocess.run(
369+
["powershell.exe", "-ExecutionPolicy", "Unrestricted", "-File", temp_file.name],
370+
capture_output=True,
371+
text=True,
372+
creationflags=subprocess.CREATE_NO_WINDOW
373+
)
374+
375+
# Stampa l'output e gli errori (se ci sono)
376+
if result.returncode != 0:
377+
print(f"PowerShell Error: {result.stderr}")
378+
raise Exception(f"Error executing PowerShell script: {result.stderr}")
379+
380+
except Exception as e:
381+
print(f"Error during script execution: {e}")
382+
finally:
383+
# Rimuovi il file temporaneo dopo l'esecuzione
384+
os.remove(temp_file.name)
385+

0 commit comments

Comments
 (0)