├── README.md ├── .gitignore └── py_inspect.py /README.md: -------------------------------------------------------------------------------- 1 | ## PyInspect 2 | 3 | ### NOTE: this repo is not supported any more. Please submit patches and issues to officially supported repo: https://github.com/pywinauto/py_inspect. Thanks! 4 | 5 | **Dmitry Vodopyanov, Alexander Smirnov** 6 | *Lobachevsky University, Nizhny Novgorod, Russia* 7 | 8 | Simple Inspect.exe analogue for Windows using Python 3.5, pywinauto and PyQt. 9 | 10 | #### Requirements 11 | 12 | - Windows OS (Windows 10 is preferable) 13 | - [Python 3.5](https://www.python.org/downloads/release/python-350/) 14 | - pywinauto 15 | ```pip3 install pywinauto``` 16 | - PyQt5 17 | ```pip3 install PyQt5``` 18 | 19 | #### Run 20 | ```python3 py_inspect.py``` 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | .venv/ 85 | 86 | # Spyder project settings 87 | .spyderproject 88 | 89 | # Rope project settings 90 | .ropeproject 91 | 92 | # Visual Studio Code 93 | .vscode/ 94 | -------------------------------------------------------------------------------- /py_inspect.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import warnings 3 | 4 | warnings.simplefilter("ignore", UserWarning) 5 | sys.coinit_flags = 2 6 | 7 | from pywinauto import backend 8 | 9 | from PyQt5.QtCore import * 10 | from PyQt5.QtGui import * 11 | from PyQt5.QtWidgets import * 12 | 13 | 14 | def main(): 15 | app = QApplication(sys.argv) 16 | app.setStyle('Fusion') 17 | 18 | w = MyWindow() 19 | w.show() 20 | sys.exit(app.exec_()) 21 | 22 | 23 | class MyWindow(QWidget): 24 | def __init__(self): 25 | super(MyWindow, self).__init__() 26 | 27 | self.setMinimumSize(1000,1000) 28 | self.setLocale(QLocale(QLocale.English, QLocale.UnitedStates)) 29 | self.setWindowTitle(QCoreApplication.translate("MainWindow", "PyInspect")) 30 | 31 | self.settings = QSettings('py_inspect', 'MainWindow') 32 | 33 | # Main layout 34 | self.mainLayout = QGridLayout() 35 | 36 | # Backend label 37 | self.backendLabel = QLabel("Backend Type") 38 | 39 | # Backend combobox 40 | self.comboBox = QComboBox() 41 | self.comboBox.setMouseTracking(False) 42 | self.comboBox.setMaxVisibleItems(5) 43 | self.comboBox.setObjectName("comboBox") 44 | 45 | for _backend in backend.registry.backends.keys(): 46 | self.comboBox.addItem(_backend) 47 | 48 | # Add top widgets to main window 49 | self.mainLayout.addWidget(self.backendLabel, 0, 0, 1, 1) 50 | self.mainLayout.addWidget(self.comboBox, 0, 1, 1, 1) 51 | 52 | self.tree_view = QTreeView() 53 | self.tree_view.setColumnWidth(0, 150) 54 | 55 | self.comboBox.setCurrentText('uia') 56 | self.__initialize_calc() 57 | 58 | self.table_view = QTableView() 59 | 60 | self.comboBox.activated[str].connect(self.__show_tree) 61 | 62 | # Add center widgets to main window 63 | self.mainLayout.addWidget(self.tree_view, 1, 0, 1, 1) 64 | self.mainLayout.addWidget(self.table_view, 1, 1, 1, 1) 65 | 66 | self.setLayout(self.mainLayout) 67 | geometry = self.settings.value('Geometry', bytes('', 'utf-8')) 68 | self.restoreGeometry(geometry) 69 | 70 | 71 | def __initialize_calc(self, _backend='uia'): 72 | self.element_info = backend.registry.backends[_backend].element_info_class() 73 | self.tree_model = MyTreeModel(self.element_info, _backend) 74 | self.tree_model.setHeaderData(0, Qt.Horizontal, 'Controls') 75 | self.tree_view.setModel(self.tree_model) 76 | self.tree_view.clicked.connect(self.__show_property) 77 | 78 | def __show_tree(self, text): 79 | backend = text 80 | self.__initialize_calc(backend) 81 | 82 | def __show_property(self, index=None): 83 | data = index.data() 84 | self.table_model = MyTableModel(self.tree_model.props_dict.get(data), self) 85 | self.table_view.wordWrap() 86 | self.table_view.setModel(self.table_model) 87 | self.table_view.setColumnWidth(1, 320) 88 | 89 | def closeEvent(self, event): 90 | geometry = self.saveGeometry() 91 | self.settings.setValue('Geometry', geometry) 92 | super(MyWindow, self).closeEvent(event) 93 | 94 | class MyTreeModel(QStandardItemModel): 95 | def __init__(self, element_info, backend): 96 | QStandardItemModel.__init__(self) 97 | root_node = self.invisibleRootItem() 98 | self.props_dict = {} 99 | self.backend = backend 100 | self.branch = QStandardItem(self.__node_name(element_info)) 101 | self.branch.setEditable(False) 102 | root_node.appendRow(self.branch) 103 | self.__generate_props_dict(element_info) 104 | self.__get_next(element_info, self.branch) 105 | 106 | def __get_next(self, element_info, parent): 107 | for child in element_info.children(): 108 | self.__generate_props_dict(child) 109 | child_item = QStandardItem(self.__node_name(child)) 110 | child_item.setEditable(False) 111 | parent.appendRow(child_item) 112 | self.__get_next(child, child_item) 113 | 114 | def __node_name(self, element_info): 115 | if 'uia' == self.backend: 116 | return '%s "%s" (%s)' % (str(element_info.control_type), str(element_info.name), id(element_info)) 117 | return '"%s" (%s)' % (str(element_info.name), id(element_info)) 118 | 119 | def __generate_props_dict(self, element_info): 120 | props = [ 121 | ['control_id', str(element_info.control_id)], 122 | ['class_name', str(element_info.class_name)], 123 | ['enabled', str(element_info.enabled)], 124 | ['handle', str(element_info.handle)], 125 | ['name', str(element_info.name)], 126 | ['process_id', str(element_info.process_id)], 127 | ['rectangle', str(element_info.rectangle)], 128 | ['rich_text', str(element_info.rich_text)], 129 | ['visible', str(element_info.visible)] 130 | ] 131 | 132 | props_win32 = [ 133 | ] if (self.backend == 'win32') else [] 134 | 135 | props_uia = [ 136 | ['automation_id', str(element_info.automation_id)], 137 | ['control_type', str(element_info.control_type)], 138 | ['element', str(element_info.element)], 139 | ['framework_id', str(element_info.framework_id)], 140 | ['runtime_id', str(element_info.runtime_id)] 141 | ] if (self.backend == 'uia') else [] 142 | 143 | props.extend(props_uia) 144 | props.extend(props_win32) 145 | node_dict = {self.__node_name(element_info): props} 146 | self.props_dict.update(node_dict) 147 | 148 | 149 | class MyTableModel(QAbstractTableModel): 150 | def __init__(self, datain, parent=None, *args): 151 | QAbstractTableModel.__init__(self, parent, *args) 152 | self.arraydata = datain 153 | self.header_labels = ['Property', 'Value'] 154 | 155 | def rowCount(self, parent): 156 | return len(self.arraydata) 157 | 158 | def columnCount(self, parent): 159 | return len(self.arraydata[0]) 160 | 161 | def data(self, index, role): 162 | if not index.isValid(): 163 | return QVariant() 164 | elif role != Qt.DisplayRole: 165 | return QVariant() 166 | return QVariant(self.arraydata[index.row()][index.column()]) 167 | 168 | def headerData(self, section, orientation, role=Qt.DisplayRole): 169 | if role == Qt.DisplayRole and orientation == Qt.Horizontal: 170 | return self.header_labels[section] 171 | return QAbstractTableModel.headerData(self, section, orientation, role) 172 | 173 | 174 | if __name__ == "__main__": 175 | main() 176 | --------------------------------------------------------------------------------