Skip to content

Commit a9dcdb6

Browse files
authored
Add GUI support for selecting files to collect
1 parent 63c88a3 commit a9dcdb6

File tree

1 file changed

+113
-5
lines changed

1 file changed

+113
-5
lines changed

acquire/gui/win32.py

Lines changed: 113 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
c_void_p,
1616
cast,
1717
create_string_buffer,
18+
create_unicode_buffer,
1819
get_last_error,
1920
sizeof,
2021
string_at,
22+
wstring_at
2123
)
2224
from ctypes import wintypes as w
2325
from pathlib import Path
@@ -144,6 +146,37 @@ class ITEMIDLIST(Structure):
144146
_fields_ = (("mkid", SHITEMID),)
145147

146148

149+
class OPENFILENAME(Structure):
150+
_fields_ = (
151+
("lStructSize", w.DWORD),
152+
("hwndOwner", w.HWND),
153+
("hInstance", w.HINSTANCE),
154+
("lpstrFilter", w.LPWSTR),
155+
("lpstrCustomFilter", w.LPWSTR),
156+
("nMaxCustFilter", w.DWORD),
157+
("nFilterIndex", w.DWORD),
158+
("lpstrFile", w.LPWSTR),
159+
("nMaxFile", w.DWORD),
160+
("lpstrFileTitle", w.LPWSTR),
161+
("nMaxFileTitle", w.DWORD),
162+
("lpstrInitialDir", w.LPWSTR),
163+
("lpstrTitle", w.LPWSTR),
164+
("Flags", w.DWORD),
165+
("nFileOffset", w.WORD),
166+
("nFileExtension", w.WORD),
167+
("lpstrDefExt", w.LPWSTR),
168+
("lCustData", w.LPARAM),
169+
("lpfnHook", w.LPVOID),
170+
("lpTemplateName", w.LPWSTR),
171+
("pvReserved", w.LPVOID),
172+
("dwReserved", w.DWORD),
173+
("FlagsEx", w.DWORD),
174+
)
175+
176+
comdlg32 = WinDLL("comdlg32", use_last_error=True)
177+
comdlg32.GetOpenFileNameW.argtypes = (POINTER(OPENFILENAME),)
178+
comdlg32.GetOpenFileNameW.restype = w.BOOL
179+
147180
kernel32 = WinDLL("kernel32", use_last_error=True)
148181
kernel32.GetModuleHandleW.argtypes = (w.LPCWSTR,)
149182
kernel32.GetModuleHandleW.restype = w.HMODULE
@@ -244,13 +277,16 @@ class Win32(GUI):
244277

245278
start_button = None
246279
choose_folder_button = None
280+
choose_files_button = None
247281

248282
input_field = None
249283
checkbox = None
250284
reveal_text = None
251285
label = None
252-
info = None
253286
upload_label = None
287+
info = None
288+
file_info = None
289+
file_label = None
254290
progress_bar = None
255291
image = None
256292

@@ -293,6 +329,14 @@ def message(self, message: str) -> None:
293329
def choose_folder(self) -> None:
294330
if self._closed:
295331
return
332+
333+
if self.folder:
334+
self.folder = None
335+
user32.SetWindowTextA(self.label, b"No path selected...")
336+
user32.SetWindowTextA(self.choose_folder_button, b"Choose folder")
337+
if not self.files:
338+
user32.EnableWindow(self.start_button, False)
339+
return
296340

297341
browseinfo = BROWSEINFOA()
298342
browseinfo.hwndOwner = self.hwnd
@@ -310,11 +354,54 @@ def choose_folder(self) -> None:
310354
if pathstr:
311355
self.folder = Path(pathstr)
312356
user32.SetWindowTextA(self.label, string_at(path))
357+
user32.SetWindowTextA(self.choose_folder_button, b"Clear folder")
313358
user32.EnableWindow(self.start_button, True)
314359

315360
# Caller is responsible for freeing this memory.
316361
ole32.CoTaskMemFree(choice)
317362

363+
def choose_files(self) -> None:
364+
if self._closed:
365+
return
366+
367+
if self.files:
368+
self.files = None
369+
user32.SetWindowTextA(self.file_label, b"No file(s) selected...")
370+
user32.SetWindowTextA(self.choose_files_button, b"Choose files")
371+
if not self.folder:
372+
user32.EnableWindow(self.start_button, False)
373+
return
374+
375+
buffer_size = 4096
376+
buffer = create_unicode_buffer(buffer_size)
377+
378+
ofn = OPENFILENAME()
379+
ofn.lStructSize = sizeof(OPENFILENAME)
380+
ofn.hwndOwner = self.hwnd
381+
ofn.lpstrFile = cast(buffer, w.LPWSTR)
382+
ofn.nMaxFile = sizeof(buffer)
383+
ofn.lpstrFilter = "All Files\0*.*\0Text Files\0*.txt\0\0"
384+
ofn.nFilterIndex = 1
385+
ofn.Flags = 0x00002000 | 0x00080000 | 0x00000200
386+
387+
if comdlg32.GetOpenFileNameW(byref(ofn)):
388+
raw_data = string_at(buffer, buffer_size * 2).decode("utf-16le").strip("\0")
389+
parts = raw_data.split("\0")
390+
391+
if len(parts) > 1:
392+
dir_path, *file_list = parts
393+
selected_paths = [f"{dir_path}\\{file}" for file in file_list]
394+
else:
395+
selected_paths = [parts[0]]
396+
397+
if selected_paths:
398+
self.files = [path for path in selected_paths if path]
399+
user32.SetWindowTextA(self.file_label, f"{len(self.files)} file(s) selected".encode("utf-8"))
400+
user32.SetWindowTextA(self.choose_files_button, b"Clear file(s)")
401+
user32.EnableWindow(self.start_button, True)
402+
else:
403+
print("User cancelled file selection")
404+
318405
def show(self) -> None:
319406
if self._closed:
320407
return
@@ -389,31 +476,48 @@ def show(self) -> None:
389476
0, "static", "Acquire output folder:", WS_CHILD | WS_VISIBLE, 20, 20, 200, 20, hwnd, 0, 0, 0
390477
)
391478
self.label = user32.CreateWindowExW(
392-
0, "static", "No path selected...", WS_CHILD | WS_VISIBLE, 20, 40, 400, 25, hwnd, 0, 0, 0
479+
0, "static", "No path selected...", WS_CHILD | WS_VISIBLE, 20, 40, 300, 25, hwnd, 0, 0, 0
480+
)
481+
482+
self.file_info = user32.CreateWindowExW(
483+
0, "static", "Select files(s) to collect:", WS_CHILD | WS_VISIBLE, 20, 130, 250, 20, hwnd, 0, 0, 0
393484
)
485+
394486
self.choose_folder_button = user32.CreateWindowExW(
395487
0, "Button", "Choose folder", WS_CHILD | WS_VISIBLE | WS_BORDER | BS_FLAT, 450, 35, 120, 32, hwnd, 0, 0, 0
396488
)
489+
self.choose_files_button = user32.CreateWindowExW(
490+
0, "Button", "Choose files", WS_CHILD | WS_VISIBLE | WS_BORDER | BS_FLAT, 450, 145, 120, 32, hwnd, 0, 0, 0
491+
)
492+
493+
self.file_label = user32.CreateWindowExW(
494+
0, "static", "No file(s) selected...", WS_CHILD | WS_VISIBLE, 20, 150, 300, 25, hwnd, 0, 0, 0
495+
)
496+
397497
self.start_button = user32.CreateWindowExW(
398498
0,
399499
"Button",
400500
"Start",
401501
WS_CHILD | WS_VISIBLE | WS_BORDER | WS_DISABLED | BS_FLAT,
402502
250,
403-
100,
503+
250,
404504
100,
405505
32,
406506
hwnd,
407507
0,
408508
0,
409509
0,
410510
)
511+
411512
if hFont:
412513
SendMessage(self.info, WM_SETFONT, hFont, 1)
514+
SendMessage(self.file_info, WM_SETFONT, hFont, 1)
413515
SendMessage(self.start_button, WM_SETFONT, hFont, 1)
414516
SendMessage(self.choose_folder_button, WM_SETFONT, hFont, 1)
517+
SendMessage(self.choose_files_button, WM_SETFONT, hFont, 1)
415518
SendMessage(self.label, WM_SETFONT, hFont, 1)
416-
519+
SendMessage(self.file_label, WM_SETFONT, hFont, 1)
520+
417521
msg = w.MSG()
418522
while user32.GetMessageW(byref(msg), None, 0, 0) != 0:
419523
user32.TranslateMessage(byref(msg))
@@ -427,6 +531,10 @@ def _message(self, hwnd: w.HWND, message: w.UINT, wParam: w.WPARAM, lParam: w.LP
427531
event = HIWORD(wParam)
428532
if event == BN_CLICKED:
429533
self.choose_folder()
534+
elif lParam == self.choose_files_button:
535+
event = HIWORD(wParam)
536+
if event == BN_CLICKED:
537+
self.choose_files()
430538
elif lParam == self.start_button:
431539
user32.EnableWindow(self.start_button, False)
432540
user32.EnableWindow(self.choose_folder_button, False)
@@ -447,7 +555,7 @@ def _message(self, hwnd: w.HWND, message: w.UINT, wParam: w.WPARAM, lParam: w.LP
447555
user32.DestroyWindow(hwnd)
448556
return 0
449557

450-
if message == WM_CTLCOLORSTATIC and lParam in [self.upload_label, self.info]:
558+
if message == WM_CTLCOLORSTATIC and lParam in [self.upload_label, self.info, self.file_info]:
451559
return gdi32.GetStockObject(WHITE_BRUSH)
452560

453561
if message == WM_DESTROY:

0 commit comments

Comments
 (0)