1+ import json
2+
13from PySide6 .QtWidgets import (
2- QWidget , QVBoxLayout , QPushButton , QLabel , QTreeWidget , QTreeWidgetItem , QFileDialog , QHBoxLayout
4+ QWidget , QVBoxLayout , QPushButton , QLabel ,
5+ QTreeWidget , QTreeWidgetItem , QFileDialog , QHBoxLayout
36)
47
8+ from core .concurrency .subprocess import Subprocess
59from core .scan import MachOScanner
610from core .utils import is_macho_file
711
812
13+ def run_scan_process (file_path : str , verbose : bool = True ):
14+ scanner = MachOScanner ()
15+ results = scanner .analyze (file_path , verbose = verbose , out_path = None )
16+ return json .dumps (results )
17+
18+
919class ScannerTab (QWidget ):
1020 def __init__ (self ):
1121 super ().__init__ ()
@@ -25,6 +35,8 @@ def __init__(self):
2535 self .tree .setColumnWidth (0 , 220 )
2636 self .tree .setAlternatingRowColors (True )
2737
38+ # todo add spinner
39+
2840 self .scan_button = QPushButton ("Run Scan" )
2941 self .scan_button .clicked .connect (self ._on_scan_clicked )
3042 self .scan_button .setEnabled (False )
@@ -36,7 +48,11 @@ def __init__(self):
3648 self .setAcceptDrops (True )
3749
3850 self .file_path = None
39- self .scanner = MachOScanner ()
51+ self .worker = Subprocess ()
52+
53+ def _toggle_button_states (self , on ):
54+ self .scan_button .setEnabled (on )
55+ self .open_button .setEnabled (on )
4056
4157 def dragEnterEvent (self , event ):
4258 if event .mimeData ().hasUrls ():
@@ -49,6 +65,7 @@ def dragEnterEvent(self, event):
4965 event .ignore ()
5066
5167 def dropEvent (self , event ):
68+ # todo forbid drop when already scanning
5269 if event .mimeData ().hasUrls ():
5370 file_path = event .mimeData ().urls ()[0 ].toLocalFile ()
5471 if file_path and is_macho_file (file_path ):
@@ -80,22 +97,26 @@ def _run_scan(self):
8097 if not self .file_path :
8198 return
8299
83- self .scan_button . setEnabled (False )
100+ self ._toggle_button_states (False )
84101 self .tree .clear ()
85102 status_root = QTreeWidgetItem (["Status" , "Scanning..." ])
86103 self .tree .addTopLevelItem (status_root )
87104
88- # TODO: This is a blocking call, UI will freeze here for large files!
89- results = self .scanner .analyze (self .file_path , verbose = True )
90-
91- print (results )
105+ self .worker .submit (
106+ run_scan_process ,
107+ self .file_path ,
108+ True ,
109+ on_done = self ._display_results ,
110+ on_error = self ._display_error
111+ )
92112
113+ def _display_results (self , jsonResults ):
93114 self .tree .clear ()
94115 root = QTreeWidgetItem (["Scan Results" , self .file_path ])
95116 self .tree .addTopLevelItem (root )
96117
97- for i , result in enumerate (results , 1 ):
98- result_item = QTreeWidgetItem ([f "Binary" ])
118+ for i , result in enumerate (json . loads ( jsonResults ) , 1 ):
119+ result_item = QTreeWidgetItem (["Binary" ])
99120 root .addChild (result_item )
100121
101122 for category , data in result .items ():
@@ -109,15 +130,6 @@ def _run_scan(self):
109130 f"section: { s .get ('section' , '' )} , offset: { s .get ('offset' , '' )} "
110131 ])
111132 cat_item .addChild (syscall_item )
112-
113- # context = s.get("context", [])
114- # for ins in context[-12:]: # last 12 instructions
115- # ctx_item = QTreeWidgetItem([
116- # f"0x{ins['address']:x} {ins['mnemonic']}",
117- # ins.get("op_str", "")
118- # ])
119- # syscall_item.addChild(ctx_item)
120-
121133 elif isinstance (data , dict ):
122134 for k , v in data .items ():
123135 sub_item = QTreeWidgetItem ([str (k ), str (v )])
@@ -131,4 +143,13 @@ def _run_scan(self):
131143
132144 root .setExpanded (True )
133145 self .tree .expandAll ()
134- self .scan_button .setEnabled (True )
146+ self ._toggle_button_states (True )
147+
148+ def _display_error (self , error ):
149+ self .tree .clear ()
150+ self .tree .addTopLevelItem (QTreeWidgetItem (["Error" , str (error )]))
151+ self ._toggle_button_states (True )
152+
153+ def closeEvent (self , event ):
154+ self .worker .shutdown (wait = False , cancel_futures = True )
155+ super ().closeEvent (event )
0 commit comments