-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathcontroller.py
More file actions
1707 lines (1429 loc) · 73.7 KB
/
Copy pathcontroller.py
File metadata and controls
1707 lines (1429 loc) · 73.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
# SPDX-License-Identifier: BSD-3-Clause-Clear
"""
@file controller.py
This file contains the primary controller for the DTGUI application.
The main class in this file is the DTGUIController, which holds instances of the various other classes; given that
most classes are actually windows/views, the controller is the only component that actually knows whether components
are visible and has a variable representing them (i.e., is able to notify them of events). As a result, it serves as
the "proxy" (or handler) for all events that occur in the application. Since the DTGUIController holds the instance of
the DTWrapper (dtwrapper.py), it is the only class permitted to make changes to the DeviceTree. The EditView, TreeView,
etc., all simply take in the user's intention to make modifications to the DeviceTree and notify the controller of this
intention, so that the controller can pass the intent on to the DTWrapper.
Another example of the controller's proxying is when the user requests to highlight a path using the TreeView. The
TreeView will notify the controller of this intent and the controller will then propagate that information to the
HexWindow (if it is open) and the TreeView itself; note that the TreeView highlight event handler never directly calls
a TreeView function to highlight the path, and that all of this passes through the controller first.
There are probably better ways to structure this system, but I am not highly familiar with GUI programming models, so
this simple view/controller system was chosen since it seemed easiest to implement with Tkinter and seemed to make sense
in my mind.
"""
# python core libraries
import os
import re
import math
import multiprocessing as mp
import traceback
import time
import threading
import subprocess
import shutil
import json
# Main tkinter library
import sys
import tkinter as tk
import tkinter.filedialog
import tkinter.messagebox
import tkinter.simpledialog
# Windows/Views we use
from findview import FindWindow
from treeview import TreeView
from editview import EditDialog
from hexview import HexWindow
# configuration, debug, etc.
from flags import flags as gf
from flags import global_info as gl_info
from settings import Json_Operate
import dbgutil as dbg
import settings
import dtlogger
# packaging help
import package
# fdt interface
import dtwrapper as dt
import xblcfgint as xbl
from pyfdt import pyfdt
import Autocmd as cmd
#nhlos parser lib
import non_hlos_parser
import get_qsahara_files
QUTS_STATE = None
quts_path = None
if os.path.exists(gl_info["quts"]):
quts_path = gl_info["quts"]
elif os.path.exists(gl_info["quts2"]):
quts_path = gl_info["quts2"]
if quts_path and os.path.exists(os.path.join(quts_path,'Common','ttypes.py'))\
and os.path.exists(os.path.join(quts_path,'ImageManagementService','ImageManagementService.py'))\
and os.path.exists(os.path.join(quts_path,'ImageManagementService','ttypes.py')):
sys.path.append(quts_path)
try:
import QutsAtom.Atom_ImageManagementService
import QutsClient
import Common.ttypes
import ImageManagementService.ImageManagementService
import ImageManagementService.ttypes
except Exception as e:
dtlogger.info(e)
QUTS_STATE='old'
else:
QUTS_STATE="import"
# title and version
DTGUI_TITLE = 'QDTE (Qualcomm Device Tree Editor)'
DTGUI_VERSION = 'V1.5.7'
QDTE_ICON = "QDTE.png"
About_Info="""%s %s
Features Supported
1. Load and Parse DTB files.
2. Edit DTB Properties (double-click) and Save DTB.
3. Add and remove nodes and properties.
4. Read/Write DTB elf file from Local/Device.
5. Edit Diassemble/Reassemble DTB elf.
Support: qdte.support@qti.qualcomm.com
Copyright(c) 2020-2024 Qualcomm Technologies, Inc.
""" %(DTGUI_TITLE,DTGUI_VERSION)
class DTGUIController(tk.Frame,non_hlos_parser.nhlos_Operator):
hexView = None
hexViewShowing = None
viewStyleHex = None
viewStyleDec = None
viewStyleIgnTrace = False
findView = None
knownHighlights = []
editMenu = None
lastFindOpts = None
fdtModified = False
xblDialog = None
userFirstTimeXbl = True
readDeviceDtbElf = False
Nonhlosdtb = False
def __init__(self, root, initial_file=None):
"""Initialize the DTGUIController
This function initializes all of the variables and views in the DTGUIController and calls various other internal
helper functions to initialize the menu bar and catch key bindings.
:param root: The root tk.Tk() object
:param initial_file: The initial file to edit. Defaults to None, in which case the editor will be blank.
"""
super().__init__()
self._root = root
icon_path = None
if os.path.exists(os.path.join(os.path.dirname(os.path.realpath(__file__)),QDTE_ICON)):
icon_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),QDTE_ICON)
elif os.path.exists(os.path.join(gl_info["sign_json_path"],QDTE_ICON)):
icon_path = os.path.join(gl_info["sign_json_path"],QDTE_ICON)
if icon_path!= None:
self._root.icon = tk.PhotoImage(file = icon_path)
self._root.iconphoto(True,self._root.icon)
screenwidth = self.winfo_screenwidth()
screenheight = self.winfo_screenheight()
self._root.geometry("+%d+%d" % ((screenwidth-1100)/2, (screenheight-700)/2))
# qdte logger initialization
logger_file = os.path.join(gl_info['log'],"log.txt")
dtlogger.logger_init(log_file=logger_file)
# DTWrapper initialization
self.dtw = dt.DTWrapper()
# initialize components of the main controller
self.treeView = TreeView(self._root, self)
self._init_menus()
self._init_key_bindings()
self.treeView.pack(fill='both', expand=True)
# open the initial file, if given
self._update_title()
self._root.geometry("+%d+%d" % ((screenwidth-1100)/2, (screenheight-700)/2))
if initial_file is not None:
ext = initial_file.rsplit('.elf', 1)
if initial_file.lower().endswith('.elf'):
self.open_xblfile(xbl_fn=initial_file)
pass
else:
self._update_view_file(initial_file)
# catch window close
self._root.protocol('WM_DELETE_WINDOW', self.on_close)
def _init_menus(self):
"""Initialize menu bar items"""
# top level items
menu_bar = tk.Menu(self._root)
file_menu = tk.Menu(menu_bar, tearoff=0)
edit_menu = tk.Menu(menu_bar, tearoff=0)
view_menu = tk.Menu(menu_bar, tearoff=0)
help_menu = tk.Menu(menu_bar, tearoff=0)
menu_bar.add_cascade(label='File', menu=file_menu)
menu_bar.add_cascade(label='Edit', menu=edit_menu)
menu_bar.add_cascade(label='View', menu=view_menu)
menu_bar.add_command(label='Setting', command=self.Settings_gui)
menu_bar.add_cascade(label='Help', menu=help_menu)
self._root.config(menu=menu_bar)
# help menu items
help_menu.add_command(label='About...', compound='left', underline=0,
command=self.show_about)
help_menu.add_command(label='User Manual...', compound='left', underline=0, command=self.open_manual)
# view menu items
self.hexViewShowing = tk.BooleanVar()
if gf['test']:
self.hexViewShowing.set(True)
self.show_hexview()
else:
self.hexViewShowing.set(False)
self.hexViewShowing.trace('w', self.show_hexview)
view_menu.add_checkbutton(label='Raw', variable=self.hexViewShowing)
view_menu.add_separator()
# default to show hex value
self.viewStyleHex = tk.BooleanVar()
self.viewStyleHex.set(gf['viewAsHex'])
self.viewStyleHex.trace('w', self.change_viewstyle)
self.viewStyleDec = tk.BooleanVar()
self.viewStyleDec.set(not gf['viewAsHex'])
self.viewStyleDec.trace('w', self.change_viewstyle)
view_style_menu = tk.Menu(view_menu, tearoff=0)
view_style_menu.add_checkbutton(label='Hexadecimal', onvalue=1, offvalue=0, variable=self.viewStyleHex)
view_style_menu.add_checkbutton(label='Decimal', onvalue=1, offvalue=0, variable=self.viewStyleDec)
view_menu.add_cascade(label='Values As...', menu=view_style_menu, underline=0)
view_menu.add_separator()
view_menu.add_command(label='Clear Highlights', command=self.clear_highlights)
view_menu.add_separator()
view_menu.add_command(label='Expand All Nodes', accelerator='Ctrl+E', command=self.treeView.expand_all_items)
view_menu.add_command(label='Collapse All Nodes', accelerator='Ctrl+D',
command=self.treeView.collapse_all_items)
# edit menu items
# NB: will need to adjust the update_undoredo() function if the order/position of undo/redo is changed here
edit_menu.add_command(label='Undo', accelerator='Ctrl+Z', command=self.undo)
edit_menu.add_command(label='Redo', accelerator='Ctrl+Shift+Z', command=self.redo)
edit_menu.add_separator()
edit_menu.add_command(label='Find...', accelerator='Ctrl+F', command=self.find_popup)
edit_menu.add_command(label='Find Next', accelerator='F3', command=self.find_next)
# edit_menu.add_separator()
# edit_menu.add_command(label='Preferences...')
self.editMenu = edit_menu
self.update_undoredo()
# file menu items
if gf['test']:
file_menu.add_command(label='New Window...', command=self.open_file_new_win)
file_menu.add_separator()
file_menu.add_command(label='Open DTB...', accelerator='Ctrl+O', compound='left', underline=0,
command=self.open_file)
xbl_cfg_menu = tk.Menu(file_menu, tearoff=0)
file_menu.add_cascade(label='Open DTB Elf', menu=xbl_cfg_menu, underline=0)
xbl_cfg_menu.add_command(label='From Build', command=self.open_build_dtb_elf)
xbl_cfg_menu.add_separator()
xbl_cfg_menu.add_command(label='From Device', command=self.open_device_dtb_elf)
file_menu.add_command(label='Write DTB Elf', compound='left', underline=0, command=self.write_dtb_elf_file)
# file_menu.add_command(label='Open XBLConfig...', accelerator='Ctrl+Shift+O', compound='left', underline=0,
# command=self.open_build_dtb_elf)
file_menu.add_command(label='Reload File', command=self.reload_file)
file_menu.add_separator()
file_menu.add_command(label='Save', accelerator='Ctrl+S', command=self.save_file)
file_menu.add_command(label='Save Copy As...', accelerator='Shift+Ctrl+S', command=self.save_as)
file_menu.add_separator()
file_menu.add_command(label='Export Change Report...', command=self.change_report)
file_menu.add_command(label='Export to DTS...', compound='left', underline=0, command=self.export_dts)
file_menu.add_separator()
file_menu.add_command(label='Exit', command=self.on_close)
# debug menu items
self.viewReadOnly = tk.BooleanVar()
self.viewReadOnly.set(False)
self.viewReadOnly.trace('w', self.update_readonly)
if gf['debug']:
debug_menu = tk.Menu(menu_bar, tearoff=0)
menu_bar.add_cascade(label='Debug', menu=debug_menu)
debug_menu.add_checkbutton(label='Read Only', variable=self.viewReadOnly)
debug_menu.add_command(label='Import Journal....', command=self.import_cr)
if gf['profileMem']:
debug_menu.add_command(label='Trace memory usage', accelerator='Shift+Ctrl+M',
command=lambda _=None: dbg.display_mem_usage())
def Settings_gui(self):
self.settings = settings.Settings(self._root, settings.ITEMS_ALL)
def _init_key_bindings(self):
"""Initialize various key bindings for the controller"""
# file menu
# self._root.bind('<Control-o>', self.open_file)
# self._root.bind('<Control-O>', self.open_build_dtb_elf)
# self._root.bind('<Control-s>', self.save_file)
# self._root.bind('<Control-S>', self.save_as)
# edit menu
self._root.bind('<Control-z>', self.undo)
self._root.bind('<Control-Z>', self.redo)
self._root.bind('<Control-f>', self.find_popup)
self._root.bind('<F3>', self.find_next)
# view menu
self._root.bind('<Control-e>', self.treeView.expand_all_items)
self._root.bind('<Control-d>', self.treeView.collapse_all_items)
if gf['debug']:
# debug menu
if gf['profileMem']:
self._root.bind('<Control-M>', lambda _=None: dbg.display_mem_usage())
def _update_views(self, path=None, fdtModified=True):
"""Internal helper function to update the TreeView and HexView when changes have occurred.
This function is called whenever changes have been made to the DeviceTree and the TreeView, HexView, and window
title need to be updated. A path, or multiple, can be specified, which will constrain the scope of updates and
can help the program run more efficiently and avoid losing state information. This function calls the TreeView
update_fdt() function and the Hexview update_view() functions, and also updates the window title and undo/redo
stack information.
:param path: The paths that were updated. Not specifying this will result in the default value of None, which
will refresh the entire view, which may be undesirable in some cases. This parameter can be a
string of a single path that was modified, or it can also be a list of strings of paths that have
been modified.
:param fdtModified: Whether or not the fdt has been modified since the last save. If this value is True, then
an asterisk will appear next to the window title. Defaults to True.
:return:
"""
# update the title
self.fdtModified = fdtModified
self._update_title()
self.update_undoredo()
if isinstance(path, list):
# support updating a list of paths if necessary
for p in path:
self.treeView.update_fdt(p)
else:
self.treeView.update_fdt(path)
# tell the hexview to update
if self.hexViewShowing.get():
self.hexView.update_view()
def _update_view_file(self, new_filename=None):
"""Update the file that is currently being edited in the DTGUIController
This file calls into the DTWrapper to apply a new Load DTOperation and open a given file.
:param new_filename: The new filename to open, or None to close the existing file without opening a new one
:return: whether the new file was successfully opened
"""
if new_filename and not os.path.exists(new_filename):
tk.messagebox.showerror('File not found', 'Could not open file ' + new_filename)
return False
# read and parse the bytes
self._root.config(cursor='watch')
self._root.update()
try:
if new_filename:
self.dtw.apply(dt.DTOperation.make(dt.DTOperationType.LOAD, new_filename))
else:
self.dtw.reset()
except Exception as ex:
if gf['debug']:
traceback.print_exc()
# throw an error if there is an invalid file
tk.messagebox.showerror('File read error', 'Could not open file ' + new_filename +
':\n' + getattr(ex, 'message', repr(ex)))
self._root.config(cursor='')
return False
self._root.config(cursor='')
# Everything worked!
self.knownHighlights = []
self._update_views(fdtModified=False)
return True
def _update_title(self):
"""Update the window title with the currently open filename and asterisk if changes have been made"""
title_str = ''
if self.dtw.fdt_name is not None:
title_str += '*' if (not self.xblDialog) and self.fdtModified else ''
title_str += os.path.basename(self.dtw.fdt_name)
title_str += ' - '
#title_str += 'devicetree DTB viewer/editor '
title_str += DTGUI_TITLE
title_str += DTGUI_VERSION
self._root.title(title_str)
def check_python(self):
try:
proc = subprocess.Popen(["python",
'--version'
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
#universal_newlines=True,
#shell=True)
while True:
outs, errs = proc.communicate()
output=None
dtlogger.debug("out:{} errs:{}".format(outs,errs))
if len(outs) == 0 and len(errs) != 0:
output = errs
else:
output = outs
if output!=None:
if isinstance(output,bytes):
output=output.decode()
for line in output.split('\n'):
if isinstance(line,bytes):
line = line.decode()
line = line.strip("\r\n")
line = line.strip("\r")
dtlogger.debug("out line:{}".format(line))
if line.lower().find('Python was not found'.lower())!=-1 or r"'python' is not recognized" in line:
dtlogger.debug("Error: Python is not installed. Please install python v3.7.x or later!")
tkinter.messagebox.showerror("Python is not installed","Please install python v3.7.x or later!\n")
return False
else:
if 'Python' in line or "python" in line:
python_version = line.split(' ')[1]
if int(python_version.split('.')[0]) >= 3 and int(python_version.split('.')[1]) >= 7:
return True
else:
tkinter.messagebox.showerror("Python Error", "Please install python v3.7 or later!\n")
return False
return_value = proc.poll()
if return_value!=None and return_value!=9009:
dtlogger.debug("sub process has been terminate:{}".format(proc.poll()))
break
elif return_value == 9009:
tkinter.messagebox.showerror("Python is not installed","Please install python v3.7.x or later!\n")
return False
except Exception as Error:
dtlogger.debug("Errors1:{}".format(Error))
return False
return True
def check_quts(self):
if QUTS_STATE == 'old':
tkinter.messagebox.showerror(title="QUTS Error", message="Please install the latest QUTS from QPM\n")
return False
elif QUTS_STATE == None:
tkinter.messagebox.showerror('Warning', "Please install QUTS to enable this function")
return False
else:
return True
def on_close(self, _=None):
"""Handler to prompt the user to save changes before closing the DTGUI application.
:param _: ignored
"""
if not self.xblDialog and self.fdtModified:
r = tk.messagebox.askyesnocancel('Save changes', 'Save changes before quitting?', icon='warning')
if r:
# yes = save changes, then continue
self.save_file()
if r is None:
# cancel = do not do anything
return
if self.xblDialog:
# cleanup & close existing xbl file
if not self.xblDialog.cleanup_and_close():
# user cancelled closing
return
self.xblDialog = None
self._root.destroy()
def open_file(self, _=None, new_filename=None):
"""Callback for when the user expresses an intent to open a DTB file
This function ensures that the user has saved all changes and then calls _update_view_file() to open a new file.
:param _: ignored (sometimes it is a tkinter event)
:param new_filename: New filename to open. Defaults to None, in which case the user will be prompted with a
dialog box to pick the path to the new file to open.
:return: Whether or not opening the new file was successful
"""
if not self.xblDialog and self.fdtModified:
# prompt user if they have unsaved changes (only do this if there is no XBLConfig DTB open)
r = tk.messagebox.askyesnocancel('Save changes', 'Save changes before opening new file?', icon='warning')
if r:
# yes = save changes, then continue
self.save_file()
if r is None:
# cancel = do not do anything
return False
# get the new filename
if not new_filename:
if self.xblDialog:
# cleanup & close existing
if not self.xblDialog.cleanup_and_close():
# user cancelled closing
return False
self.xblDialog = None
new_filename = tk.filedialog.askopenfilename(defaultextension='.dtb',
filetypes=[('Device Tree Blob', '*.dtb'),
('Device Tree Blob', '*.dtbo'),
('All Files', '*.*')])
if new_filename:
return self._update_view_file(new_filename)
return False
def open_file_new_win(self, _=None):
"""Open a file in a new instance of the DTGUI. Experimental and probably doesn't work.
:param _: ignored
"""
_ = self
# get the new filename
new_filename = tk.filedialog.askopenfilename(defaultextension='.dtb',
filetypes=[('Device Tree Blob', '*.dtb'),
('All Files', '*.*')])
if new_filename:
# start a new process
p = mp.Process(target=run, kwargs={'initial_file': new_filename})
p.start()
def open_build_dtb_elf(self):
"""Callback when the user expresses intent to open an XBLConfig file.
This function validates the XBLConfig setup to ensure that XBLConfig integration is enabled, prompts the user to
pick an XBLConfig file to open (if none is specified), and then calls into the xblcfgint.py XblCfgGUI helper
class/window to disassemble the XBLConfig, etc.
:param _: ignored (sometimes it is a Tkinter event)
:param xbl_fn: filename/path to the XBLConfig file. Defaults to None, in which case the user will be prompted
for the filename to open.
"""
if self.check_python() == False:
return False
if not gf['xblEnabled']:
tk.messagebox.showerror('XBLConfig integration disabled', 'XBLConfig integration has been disabled. Please '
're-run the program and ensure that the '
'--xbltools_dir path has been specified and that '
'either --sectools_dir has been given, or '
'--allow_unsigned is enabled.\n'
'For more help, please run the program with the '
'--help flag.')
return
if self.xblDialog:
# cleanup & close existing
if not self.xblDialog.cleanup_and_close():
# user cancelled closing
return
self.xblDialog = None
elif self.fdtModified:
# prompt user if they have unsaved changes
r = tk.messagebox.askyesnocancel('Save changes', 'Save changes before opening new file?', icon='warning')
if r:
# yes = save changes, then continue
self.save_file()
if r is None:
# cancel = do not do anything
return
self._update_view_file(None)
tmp_thread = threading.Thread(target=self.call_tmp_thread, name="Tmp Thread")
tmp_thread.setDaemon(True)
tmp_thread.start()
def call_tmp_thread(self):
gf['XBLConfigFlag'] = 0
if self.readDeviceDtbElf == False:
xbl.XblDirGUI(self._root)
while(gf['XBLConfigFlag'] == 0):
time.sleep(0.2)
if (gf['XBLConfigFlag'] == 1 and gf['inputFile']) or (self.readDeviceDtbElf == True):
xbl_fn = gf['inputFile']
elif gf['XBLConfigFlag'] == 255:
return
self.readDeviceDtbElf = False
# open the new dialog with the filename
self.xblDialog = xbl.XblCfgGUI(self._root, self, xbl_fn)
def write_dtb_elf_file(self):
if self.check_quts() == False:
return False
tmp_thread = threading.Thread(target=self.call_write_dtb_elf_thread, name="Write Thread")
tmp_thread.setDaemon(True)
tmp_thread.start()
def write_parition(self,partition_info):
dataChunkOptionList = []
dataChunkOptionList.append(ImageManagementService.ttypes.DataChunkOptions(
partition_info["lun"], partition_info["s_lba"], partition_info["cnt"], partition_info["image"]))
resu = QutsAtom.Atom_ImageManagementService.writePartitionData(self.dev_handle, dataChunkOptionList)
return resu
def call_write_dtb_elf_thread(self):
self.dt_dict = {}
self.dev_handle = None
# get partition info from device
gf['devprg'] = ""
gf['setting_flag'] = 0
settings.Settings(self._root,settings.ITEMS_DEVPRG)
while(gf['setting_flag'] == 0):
time.sleep(0.2)
if gf['setting_flag'] == 255:
return False
while self.get_dev_partitioninfo() == 2:
time.sleep(0.1)
if not self.dt_dict:
return False
# write partition image
flash_info = {}
gf['PartitionName'] = ""
gf['PartitionImage'] = ""
gf['PartitionImage_Flag'] = 0
xbl.DT_Write_GUI(self._root, self.dt_dict)
while(gf['PartitionImage_Flag'] == 0):
time.sleep(0.2)
if gf['PartitionImage_Flag'] == 255:
return False
prog_win = xbl.DT_ProgressBar(self._root)
time.sleep(0.5)
dtlogger.debug(gf['PartitionName'][:-2])
dtlogger.debug(gf['PartitionName'].split('_'))
dtlogger.debug(gf['PartitionABCheck'])
A_B_POSTFIX=["_a","_b"]
BACKUP_POSTFIX=["","_BACKUP"]
partition_postfix = ''
# POSTFIX_list=[]
# write_partititon_list=[]
partition_postfix_regex="(_((BACKUP){1}|(a|b){1})){1}$"
m = re.search(partition_postfix_regex,gf['PartitionName'],re.I)
if m:
partition_postfix = m.group(0)
# write subsystem DTB elfs into non-hlos.bin
#if partition_postfix in BACKUP_POSTFIX:
if self.dt_dict[gf['PartitionName']]["container"] !=None:
self.write_dtb_elf_into_nhlos_file(self.dt_dict[gf['PartitionName']]["name"],
os.path.join(gl_info["tmp_xblcfg"],self.dt_dict[gf['PartitionName']]["container"]+".bin"),
gf['PartitionImage'],
gl_info["tmp_xblcfg"])
#write dtb elfs or non-hlos.bin into corresponding paritons.
flash_partition_list = [gf['PartitionName']]
# check if needs to write into A&B partition.
if gf['PartitionABCheck'] == 1:
partition_list = [item for item in self.dt_dict]
partition_root_name = self.dt_dict[gf['PartitionName']]["name"].rstrip(partition_postfix)
flash_partition_list = [x for x in partition_list if (partition_root_name in x)]
#write dtb elfs or non-hlos.bin into paritons.
for Partition_name in flash_partition_list:
if Partition_name not in self.dt_dict:
dtlogger.debug("partition can't be found!")
#if container exists, that means dtb elf is in nhlos binary
if self.dt_dict[Partition_name]["container"]!=None:
flash_info["partition"] = self.dt_dict[Partition_name]["container"]
flash_info["image"] = os.path.join(gl_info["tmp_xblcfg"],self.dt_dict[gf['PartitionName']]["container"]+".bin")
else:
flash_info["partition"] = Partition_name
flash_info["image"] = gf['PartitionImage']
flash_info["lun"] = self.dt_dict[Partition_name]["lun"]
flash_info["s_lba"] = self.dt_dict[Partition_name]["s_lba"]
flash_info["cnt"] = self.dt_dict[Partition_name]["cnt"]
resu = self.write_parition(flash_info)
if resu:
prog_win.call_popup_close()
dtlogger.debug("Can't flash image %s to device." % flash_info["partition"])
tkinter.messagebox.showerror('Error', "Can't flash image %s to device" % flash_info["partition"])
return False
prog_win.call_popup_close()
if len(flash_partition_list)==2:
dtlogger.debug("Flash image into {} & {} Successfully".format(flash_partition_list[0],flash_partition_list[1]))
tkinter.messagebox.showinfo('Success', "Flashed {} to A&B partitions!".format(gf['PartitionName']))
else:
dtlogger.debug("Flash image into {} Successfully".format(flash_partition_list[0]))
tkinter.messagebox.showinfo('Success', "Flashed {} to device!".format(gf['PartitionName']))
def open_device_dtb_elf(self):
if self.check_python() == False:
return False
if self.check_quts() == False:
return False
self.tmp_thread = threading.Thread(target=self.call_device_dtb_elf_thread, name="Dev XblCfg Thread")
self.tmp_thread.setDaemon(True)
self.tmp_thread.start()
# read dtb elf file from partition of device
def read_partition_from_device(self,partition_inf,output_file):
dataChunkOptions = []
dataChunkOptions.append(ImageManagementService.ttypes.DataChunkOptions(
partition_inf["lun"], partition_inf["s_lba"], partition_inf["cnt"], output_file))
resu = QutsAtom.Atom_ImageManagementService.readPartitionData(self.dev_handle, dataChunkOptions)
if resu or not os.path.exists(output_file):
dtlogger.debug("Can't get partition image from device.")
tkinter.messagebox.showerror('Error', "Can't get partition image from device")
return False
def call_device_dtb_elf_thread(self):
self.dt_dict = {}
self.dev_handle = None
# get partition info from device
gf['devprg'] = ""
gf['setting_flag'] = 0
settings.Settings(self._root,settings.ITEMS_DEVPRG)
while(gf['setting_flag'] == 0):
time.sleep(0.2)
if gf['setting_flag'] == 255:
return False
while self.get_dev_partitioninfo() == 2:
time.sleep(0.1)
if not self.dt_dict:
return False
# delete the old xbl cfg file
#shutil.rmtree(gl_info["tmp_xblcfg"])
# get partition image from device
gf['partition_name'] = ""
gf['partition_name_flag'] = 0
xbl.DT_Select_GUI(self._root, self.dt_dict)
while(gf['partition_name_flag'] == 0):
time.sleep(0.2)
if gf['partition_name_flag'] == 255:
return False
readDir = gl_info["tmp_xblcfg"]
parti_name = gf['partition_name']
image_name = self.dt_dict[parti_name]["name"]+".elf"
if self.dt_dict[parti_name]['container'] == None:
if self.read_partition_from_device(self.dt_dict[parti_name],os.path.join(readDir, image_name)) == False:
return False
else:
self.extract_dtb_elf_file(self.dt_dict[parti_name]["name"],os.path.join(gl_info["tmp_xblcfg"],self.dt_dict[parti_name]["container"]+".bin"),readDir)
gf["inputFile"] = os.path.join(readDir, image_name)
self.jsonx = Json_Operate()
self.jsonx.update_json_cfg_data()
# open and edit xbl cfg file
self.readDeviceDtbElf = True
self.open_build_dtb_elf()
def get_dev_partitioninfo(self):
if self.check_quts() == False:
return False
prog_win = xbl.DT_ProgressBar(self._root)
memoryTypes = {
"EMMC" :0,
"UFS" :1,
"NAND" :2,
"NVME" :3,
"SPINOR":4
}
# get xbl cfg partition
try:
client = QutsClient.QutsClient("ImageInfo")
devMgr = client.getDeviceManager()
deviceList = devMgr.getDeviceList()
dtlogger.debug(deviceList)
self.dev_handle = None
for device in deviceList:
for protocol in device.protocols:
dtlogger.debug("Protocol:{}{}".format( protocol.description, protocol.protocolType))
#if(Common.ttypes.ProtocolType.PROT_SAHARA == protocol.protocolType):
if '9008' in protocol.description:
self.dev_handle = device.deviceHandle
dtlogger.debug("Protocol:{}".format( protocol.description))
dtlogger.debug("DeviceHandle: {}".format(self.dev_handle))
break
if self.dev_handle != None:
break
except Exception as e:
dtlogger.debug(e)
if not self.dev_handle:
dtlogger.debug("Can't get the device handle. Please boot to EDL mode first.")
prog_win.call_popup_close()
rst = tkinter.messagebox.askretrycancel(
title="Retry or Cancel", message="Can't get the device handle.\n\nPlease ensure device is in EDL mode \n QPCAT/QUT is closed and retry.")
if rst:
return 2
else:
return False
buildOption = None
Qsahara_files = get_qsahara_files.get_all_sahara_files(gf["devprg"],storage=gf['flashtype'].lower())
devprg_file = None
Qsahara_files_list = {}
if Qsahara_files == None:
prog_win.call_popup_close()
tkinter.messagebox.showerror('Error', "Can't get dev programmer file!")
return False
if len(Qsahara_files) == 1:
if isinstance(Qsahara_files, list):
devprg_file = Qsahara_files[0]
elif isinstance(Qsahara_files, dict):
for key,val in Qsahara_files.items():
devprg_file = Qsahara_files[key]
else:
dtlogger.debug("Qsahara file return value is incorrect with {}".format(type(Qsahara_files)))
return False
buildOption = ImageManagementService.ttypes.DownloadBuildOptions(
memoryType=memoryTypes[gf['flashtype']], firehoseProgPath=devprg_file)
else:
for key, values in Qsahara_files.items():
Qsahara_files_list[int(key)]=Qsahara_files[key][0]
buildOption = ImageManagementService.ttypes.DownloadBuildOptions(
memoryType=memoryTypes[gf['flashtype']], saharaImageList=Qsahara_files_list)
buildOption.partitionIndexList = []
buildOption.readImages = True
buildOption.readImagesPath = gl_info["tmp_xblcfg"]
partitionTable = []
try:
partitionTable = QutsAtom.Atom_ImageManagementService.initPartitionTable(self.dev_handle, buildOption)
except Exception as e:
dtlogger.debug("Exception of getting partition table:{}".format(e))
dtlogger.info("Exception of getting partition table:{}".format(e))
prog_win.call_popup_close()
tkinter.messagebox.showerror('Error', "Exception of getting partition table")
return False
if not partitionTable:
dtlogger.debug("Can't get the partition table. Please boot to EDL mode, select correct flash type and try again.")
prog_win.call_popup_close()
rst = tkinter.messagebox.askretrycancel(
title="Retry or Cancel", message="Can't get the partition table.\n\nPlease boot to EDL mode and retry.")
if rst:
return 2
else:
return False
self.dt_dict.clear()
partitionName = {}
partitionPath = './partition_name.json'
partitionJson = xbl.Json_Partition_Operate(partitionPath)
partitionName = partitionJson.read_json_partition_data()
if partitionName:
dt_pattern = re.compile(r"%s" % (partitionName["partition"]), flags=re.I)
if partitionName["partitionBan"] != "":
dt_pattern_ban = re.compile(r"%s" % (partitionName["partitionBan"]), flags=re.I)
else:
prog_win.call_popup_close()
# tkinter.messagebox.showerror(title="Error", message="Can't get the partition format.\n\nPlease generate info in json file and retry.")
return False
#dtlogger.info("all the partitionTable: ",partitionTable)
#NHLOS_name_regex = "^(NHLOS|modem|BOOT_FW1|BOOT_FW2){1}(_(BACKUP{1}|(a|b|A|B){1}){1})?$"
NHLOS_name_regex = "^(NHLOS|modem){1}(_(BACKUP{1}|(a|b|A|B){1}){1})?$"
for partition in partitionTable:
q6_dtb_name = None
#if it's NHLOS , read out NHLOS.bin from device.
m = re.search(NHLOS_name_regex,partition.name)
if m:
partition_info={}
partition_info["name"]= partition.name
partition_info["lun"] = partition.lun
partition_info["s_lba"] = str(partition.startingLba)
partition_info["cnt"] = str(partition.endingLba-partition.startingLba+1)
# read NHLOS.bin from device
if self.read_partition_from_device(partition_info, os.path.join(gl_info["tmp_xblcfg"],partition.name+".bin"))==False:
return False
# fetch DTB_elf files name from nhlos binary file
self.get_dtb_elf_names(os.path.join(gl_info["tmp_xblcfg"],partition.name+".bin"))
# get partition postfix, like _A/_B or _BACKUP
parition_index = ""
if m.group(2):
parition_index = m.group(2)
dtlogger.debug("NHLOS dtb elf:{}".format(self.dtb_files_name))
if self.dtb_files_name:
for q6_dtb_name in self.dtb_files_name:
q6_dtb=q6_dtb_name+parition_index
self.dt_dict[q6_dtb] = {}
self.dt_dict[q6_dtb]["name"] = q6_dtb_name
self.dt_dict[q6_dtb]["lun"] = partition.lun
self.dt_dict[q6_dtb]["s_lba"] = str(partition.startingLba)
self.dt_dict[q6_dtb]["cnt"] = str(partition.endingLba-partition.startingLba+1)
self.dt_dict[q6_dtb]["container"] = partition.name
dt_list = dt_pattern.findall(partition.name)
dt_list_ban = []
if partitionName["partitionBan"] != "":
dt_list_ban = dt_pattern_ban.findall(partition.name)
if dt_list and not dt_list_ban:
self.dt_dict[partition.name] = {}
self.dt_dict[partition.name]["name"] = partition.name
self.dt_dict[partition.name]["lun"] = partition.lun
self.dt_dict[partition.name]["s_lba"] = str(partition.startingLba)
self.dt_dict[partition.name]["cnt"] = str(partition.endingLba-partition.startingLba+1)
self.dt_dict[partition.name]["container"] = None
if not self.dt_dict:
dtlogger.debug("Can't get partition info from device.")
prog_win.call_popup_close()
tkinter.messagebox.showerror(title="Error", message="Can't get partition info from device\n")
return False
prog_win.call_popup_close()
dtlogger.debug("dt partition info: {}".format( str(self.dt_dict)))
def on_xbldialog_close(self):
"""Callback when the XBLConfig dialog is closed that will clear the currently edited file since the handle to it
is not longer valid (the file is being edited from a temporary directory that only exists while the XBLConfig
window is open)."""
self.xblDialog = None
self._update_view_file(None)
def reload_file(self, _=None):
"""Reload the file currently in the view
This function re-reads the file currently displayed in the GUI from the disk. Undo history and the state of the
tree view are reset.
:param _: ignored
"""
# disabled if we are in XBLConfig integration mode
if self.xblDialog:
tk.messagebox.showerror('Cannot reload DTB in XBLConfig', 'Sorry, it is not possible to reload a DTB file '
'inside of an XBLConfig ELF.')
return
# just call update view again
if self.fdtModified:
if not tk.messagebox.askokcancel('Unsaved changes', 'You have unsaved changes, and reloading the file will '
'lose them. Proceed?', icon='warning'):
return
self.fdtModified = False
self._update_view_file(self.dtw.fdt_name)
def show_about(self, _=None):
"""Show a dialog with information about the program
:param _: ignored
"""
# msg = 'devicetree DTB viewer/editor ' + DTGUI_VERSION + '\n'
# if self.dtw.fdt_name is not None:
# msg += 'Currently editing ' + self.dtw.fdt_name + '\n'
# msg += ('Core Platform Boot go/vtechstudy Assignment: \n\nMar-Apr 2020 : TKinter Internals to'
# ' create quick cross platform python GUI front-end \n\n devicetree DTB '
# 'viewer/editor'
# '\n By Dhamim P\n\n'
# '\n Features Supported \n1. Load and Parse DTB files \n2. Edit DTB Properties '
# '(double-click) and Save DTB\n'
# '3. Add and remove nodes and properties\n'
# '\nMay-Aug 2020 : Extended by Mason Xiao'
# )
tk.messagebox.showinfo('About', About_Info)
def open_manual(self):
"""Display the user manual PDF"""
_ = self
user_manual = package.fetch_resource('UserManual.pdf')
if user_manual is None:
tk.messagebox.showerror('User Manual Not Found', 'The user manual has not been bundled with this version of'
' the QDTE.')
return
if sys.platform == 'win32':
os.startfile(user_manual)
elif sys.platform == 'darwin':
import subprocess
subprocess.Popen(['open', user_manual])
else:
try:
import subprocess