class DirectoryWidget(QToolButton): Sg_double_clicked = Signal(str) def __init__(self): super(DirectoryWidget, self).__init__() self.timer = QTimer() self.timer.setSingleShot(True) self.clicked.connect(self.Sl_check_double_click) self.setAccessibleName('Directory') self.setIcon(QIcon(resource_path("icons/Cartella.png"))) self.setIconSize(QSize(45, 45)) self.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) @Slot() def Sl_check_double_click(self): success = False if self.timer.isActive(): time = self.timer.remainingTime() if time > 0: self.double_clicked_action() success = True self.timer.stop() if time <= 0: self.timer.start(250) if not self.timer.isActive() and not success: self.timer.start(250) def double_clicked_action(self) -> None: pass
class FileWidget(QToolButton): Sg_double_clicked = Signal() def __init__(self, file: File): super(FileWidget, self).__init__() self.timer = QTimer() self.timer.setSingleShot(True) self.clicked.connect(self.check_double_click) self.Sg_double_clicked.connect(self.Sl_on_double_click) self.setAccessibleName('File') self.name = file.get_name() self.creation_date = file.get_creation_date() self.last_modified_date = file.get_last_modified_date() self.extension = self.get_extension() self.set_icon() self.setText(self.name) def get_extension(self) -> str: if self.name.find('.') != -1: e = self.name.split(".") return e[-1] else: return "no" def set_icon(self): self.setIconSize(QSize(45, 45)) self.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) if self.extension in ["txt", "xml", "json", "docx", "xlsx"]: self.setIcon(QIcon(resource_path('icons/Txt.png'))) elif self.extension in ["mp4", "avi", "mpeg", "wmv"]: self.setIcon(QIcon(resource_path('icons/Video.png'))) elif self.extension in ["jpg", "png", "gif"]: self.setIcon(QIcon(resource_path('icons/Immagine.png'))) elif self.extension in ["mp3", "wav", "ogg"]: self.setIcon(QIcon(resource_path('icons/Audio.png'))) else: self.setIcon(QIcon(resource_path('icons/DocGenerico.png'))) def check_double_click(self): if self.timer.isActive(): time = self.timer.remainingTime() if time > 0: self.Sg_double_clicked.emit() self.timer.stop() if time <= 0: self.timer.start(250) if self.timer.isActive() is False: self.timer.start(250) @Slot() def Sl_on_double_click(self): pass
def test_clustering_analyzer(self): dataset = random_dataset(**SIMPLE_PRESET, n_samples=200) main = ClusteringAnalyzer() main.show() main.on_dataset_loaded(dataset) timer = QTimer() timer.setSingleShot(True) timer.timeout.connect(main.save_result) timer.start(2000) app.exec()
class EntropyWidget(QWidget): def __init__(self, parent, view, data): super(EntropyWidget, self).__init__(parent) self.view = view self.data = data self.raw_data = data.file.raw self.block_size = (len(self.raw_data) / 4096) + 1 if self.block_size < 1024: self.block_size = 1024 self.width = int(len(self.raw_data) / self.block_size) self.image = QImage(self.width, 1, QImage.Format_ARGB32) self.image.fill(QColor(0, 0, 0, 0)) self.thread = EntropyThread(self.raw_data, self.image, self.block_size) self.started = False self.timer = QTimer() self.timer.timeout.connect(self.timerEvent) self.timer.setInterval(100) self.timer.setSingleShot(False) self.timer.start() self.setMinimumHeight(UIContext.getScaledWindowSize(32, 32).height()) def paintEvent(self, event): p = QPainter(self) p.drawImage(self.rect(), self.image) p.drawRect(self.rect()) def sizeHint(self): return QSize(640, 32) def timerEvent(self): if not self.started: self.thread.start() self.started = True if self.thread.updated: self.thread.updated = False self.update() def mousePressEvent(self, event): if event.button() != Qt.LeftButton: return frac = float(event.x()) / self.rect().width() offset = int(frac * self.width * self.block_size) self.view.navigateToFileOffset(offset)
class TimerQT(TimerBase): def __init__(self, *args, **kwargs): self._timer = QTimer() self._timer.timeout.connect(self._on_timer) TimerBase.__init__(self, *args, **kwargs) def __del__(self): self._timer_stop() def _timer_set_single_shot(self): self._timer.setSingleShot(self._single) def _timer_set_interval(self): self._timer.setInterval(self._interval) def _timer_start(self): self._timer.start() def _timer_stop(self): self._timer.stop()
class ModList(QTableView): def __init__(self, parent: QWidget, model: Model) -> None: super().__init__(parent) settings = QSettings() self.hoverIndexRow = -1 self.modmodel = model self.installLock = asyncio.Lock() self.setMouseTracking(True) self.setSelectionMode(QAbstractItemView.ExtendedSelection) self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setWordWrap(False) self.setSortingEnabled(True) self.setFocusPolicy(Qt.StrongFocus) self.setAcceptDrops(True) self.setEditTriggers(QTableView.EditKeyPressed | QTableView.DoubleClicked) self.setShowGrid(False) self.setStyleSheet(''' QTableView { gridline-color: rgba(255,255,255,1); } QTableView::item:!selected:hover { background-color: rgb(217, 235, 249); } ''') self.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel) self.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel) self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.showContextMenu) self.verticalHeader().hide() self.verticalHeader().setVisible(False) self.setSectionSize(settings.value('compactMode', 'False') == 'True') self.setCornerButtonEnabled(False) self.horizontalHeader().setHighlightSections(False) self.horizontalHeader().setStretchLastSection(True) self.horizontalHeader().setSectionsMovable(True) self.listmodel = ModListModel(self, model) self.filtermodel = ModListFilterModel(self, self.listmodel) self.setModel(self.filtermodel) self.setItemDelegate(ModListItemDelegate(self)) self.setSelectionModel(ModListSelectionModel(self, self.filtermodel)) if len(model): self.modCountLastUpdate = len(model) self.resizeColumnsToContents() else: self.modCountLastUpdate = -1 if settings.value('modlistHorizontalHeaderState'): self.horizontalHeader().restoreState( settings.value('modlistHorizontalHeaderState')) # type: ignore self.horizontalHeader().sectionMoved.connect( lambda: self.headerChangedEvent()) self.horizontalHeader().sectionResized.connect( lambda: self.headerChangedEvent()) self.setFocus() self.sortByColumn(3, Qt.AscendingOrder, False) self.sortByColumn(2, Qt.AscendingOrder, False) self.sortByColumn(1, Qt.AscendingOrder, False) if settings.value('modlistSortColumn') is not None and \ settings.value('modlistSortOrder') is not None: try: self.sortByColumn( cast(int, settings.value('modlistSortColumn', 1, int)), Qt.DescendingOrder if cast(int, settings.value('modlistSortOrder', 1, int)) else Qt.AscendingOrder, False) except Exception as e: logger.exception(f'could not restore sort order: {e}') self.horizontalHeader().sortIndicatorChanged.connect(self.sortByColumn) self.doubleClicked.connect(self.doubleClickEvent) model.updateCallbacks.append(self.modelUpdateEvent) # setup viewport caching to counter slow resizing with many table elements self.resizeTimer = QTimer(self) self.resizeTimer.setSingleShot(True) self.resizeTimer.setInterval(250) self.resizeTimer.timeout.connect(lambda: [ self.resizeTimer.stop(), self.viewport().repaint(), ]) self.viewportCache = QPixmap() self.viewportCacheSize = QSize(0, 0) # TODO: enhancement: offer option to read readme and other additional text files def setSectionSize(self, compact: bool) -> None: if compact: self.verticalHeader().setDefaultSectionSize(25) else: self.verticalHeader().setDefaultSectionSize(30) @debounce(200) async def headerChangedEvent(self) -> None: settings = QSettings() state = self.horizontalHeader().saveState() # call later to work around pyqt5 StopIteration exception asyncio.get_running_loop().call_later( 25 / 1000.0, lambda: settings.setValue('modlistHorizontalHeaderState', state)) def modelUpdateEvent(self, model: Model) -> None: if not self.modCountLastUpdate and len(self.modmodel): # if list was empty before, auto resize columns self.resizeColumnsToContents() self.modCountLastUpdate = len(self.modmodel) def mouseMoveEvent(self, event: QMouseEvent) -> None: self.hoverIndexRow = self.indexAt(event.pos()).row() return super().mouseMoveEvent(event) def wheelEvent(self, event: QWheelEvent) -> None: result = super().wheelEvent(event) # repaint previously hovered row on scroll avoid repaint artifacts index = self.model().index(self.hoverIndexRow, 0) self.hoverIndexRow = self.indexAt(event.position().toPoint()).row() rect = self.visualRect(index) rect.setLeft(0) rect.setRight(self.viewport().width()) self.viewport().repaint(rect) return result def leaveEvent(self, event: QEvent) -> None: index = self.model().index(self.hoverIndexRow, 0) # unset row hover state and repaint previously hovered row self.hoverIndexRow = -1 rect = self.visualRect(index) rect.setLeft(0) rect.setRight(self.viewport().width()) self.viewport().repaint(rect) return super().leaveEvent(event) def doubleClickEvent(self, index: QModelIndex) -> None: if self.filtermodel.mapToSource(index).column() == 0: mod = self.modmodel[self.filtermodel.mapToSource(index).row()] if mod.enabled: asyncio.create_task(self.modmodel.disable(mod)) else: asyncio.create_task(self.modmodel.enable(mod)) def resizeEvent(self, event: QResizeEvent) -> None: super().resizeEvent(event) if not self.resizeTimer.isActive( ) and event.size() != self.viewportCacheSize: self.viewportCacheSize = event.size() self.viewportCache = self.viewport().grab() self.resizeTimer.start() def paintEvent(self, event: QPaintEvent) -> None: if self.resizeTimer.isActive(): painter = QPainter(self.viewport()) painter.drawPixmap(0, 0, self.viewportCache) else: super().paintEvent(event) def selectionChanged(self, selected: QItemSelection, deselected: QItemSelection) -> None: super().selectionChanged(selected, deselected) def eventFilter(self, obj: QObject, event: QEvent) -> bool: return super().eventFilter(obj, event) def sortByColumn(self, col: int, order: Qt.SortOrder, save: bool = True) -> None: # type: ignore if save and col is not None and order is not None: settings = QSettings() settings.setValue('modlistSortColumn', col) settings.setValue('modlistSortOrder', 0 if order == Qt.AscendingOrder else 1) super().sortByColumn(col, order) def showContextMenu(self, pos: QPoint) -> None: mods = self.getSelectedMods() if not mods: return menu = QMenu(self) actionOpen = menu.addAction('&Open Directory') actionOpen.setIcon( QIcon(str(getRuntimePath('resources/icons/open-folder.ico')))) actionOpen.triggered.connect(lambda: [ util.openDirectory(self.modmodel.getModPath(mod)) # type: ignore for mod in mods ]) menu.addSeparator() actionEnable = menu.addAction('&Enable') actionEnable.triggered.connect( lambda: [asyncio.create_task(self.enableSelectedMods(True))]) actionEnable.setEnabled(not all(mod.enabled for mod in mods)) actionDisable = menu.addAction('&Disable') actionDisable.triggered.connect( lambda: [asyncio.create_task(self.enableSelectedMods(False))]) actionDisable.setEnabled(not all(not mod.enabled for mod in mods)) menu.addSeparator() actionUninstall = menu.addAction('&Uninstall') actionUninstall.triggered.connect( lambda: [asyncio.create_task(self.deleteSelectedMods())]) menu.addSeparator() actionOpenNexus = menu.addAction('Open &Nexus Mods page') actionOpenNexus.setIcon( QIcon(str(getRuntimePath('resources/icons/browse.ico')))) actionOpenNexus.triggered.connect(lambda: [ QDesktopServices.openUrl( QUrl(f'https://www.nexusmods.com/witcher3/mods/{modid}')) for modid in {mod.modid for mod in mods if mod.modid > 0} ]) actionOpenNexus.setEnabled(not all(mod.modid <= 0 for mod in mods)) menu.popup(self.viewport().mapToGlobal(pos)) def selectRowChecked(self, row: int) -> None: nums: int = self.filtermodel.rowCount() if row < nums and row >= 0: self.selectRow(row) elif nums > 0: self.selectRow(nums - 1) def getSelectedMods(self) -> List[Mod]: return [ self.modmodel[self.filtermodel.mapToSource(index).row()] for index in self.selectionModel().selectedRows() ] async def enableSelectedMods(self, enable: bool = True) -> None: if not self.selectionModel().hasSelection(): return mods = self.getSelectedMods() self.setDisabled(True) for mod in mods: try: if enable: await self.modmodel.enable(mod) else: await self.modmodel.disable(mod) except Exception as e: logger.bind(name=mod.filename).exception( f'Could not enable/disable mod: {e}') self.setDisabled(False) self.setFocus() async def deleteSelectedMods(self) -> None: if not self.selectionModel().hasSelection(): return self.setDisabled(True) mods = self.getSelectedMods() # TODO: incomplete: ask if selected mods should really be removed inds = self.selectedIndexes() self.selectionModel().clear() for mod in mods: try: await self.modmodel.remove(mod) except Exception as e: logger.bind( name=mod.filename).exception(f'Could not delete mod: {e}') asyncio.get_running_loop().call_later( 100 / 1000.0, partial(self.selectRowChecked, inds[0].row())) self.setDisabled(False) self.setFocus() async def updateModDetails(self, mod: Mod) -> bool: logger.bind(name=mod.filename, dots=True).debug('Requesting details for mod') if not mod.md5hash: logger.bind(name=mod.filename).warning( 'Could not get details for mod not installed from archive') return False try: details = await getModInformation(mod.md5hash) except Exception as e: logger.bind(name=mod.filename).warning(f'{e}') return False try: package = str(details[0]['mod']['name']) summary = str(details[0]['mod']['summary']) modid = int(details[0]['mod']['mod_id']) category = int(details[0]['mod']['category_id']) version = str(details[0]['file_details']['version']) fileid = int(details[0]['file_details']['file_id']) uploadname = str(details[0]['file_details']['name']) uploadtime = str(details[0]['file_details']['uploaded_time']) mod.package = package mod.summary = summary mod.modid = modid mod.category = getCategoryName(category) mod.version = version mod.fileid = fileid mod.uploadname = uploadname uploaddate = dateparser.parse(uploadtime) if uploaddate: mod.uploaddate = uploaddate.astimezone(tz=timezone.utc) else: logger.bind(name=mod.filename).debug( f'Could not parse date {uploadtime} in mod information response' ) except KeyError as e: logger.bind(name=mod.filename).exception( f'Could not find key "{str(e)}" in mod information response') return False try: await self.modmodel.update(mod) except Exception as e: logger.bind( name=mod.filename).exception(f'Could not update mod: {e}') return False return True async def updateSelectedModsDetails(self) -> None: if not self.selectionModel().hasSelection(): return self.setDisabled(True) updatetime = datetime.now(tz=timezone.utc) mods = self.getSelectedMods() logger.bind( newline=True, output=False).debug(f'Requesting details for {len(mods)} mods') results = await asyncio.gather( *[self.updateModDetails(mod) for mod in mods], loop=asyncio.get_running_loop(), return_exceptions=True) successes = sum(results) errors = len(results) - successes message = 'Updated details for {0} mods{1}'.format( successes, f' ({errors} errors)' if errors else '') if errors: logger.warning(message) else: logger.success(message) self.modmodel.setLastUpdateTime(updatetime) self.setDisabled(False) self.setFocus() async def changeSelectedModsPriority(self, delta: int) -> None: mods = self.getSelectedMods() await asyncio.gather(*[ self.modmodel.setPriority( mod, max(-1, min(9999, int(mod.priority + delta)))) for mod in mods if mod.datatype in ( 'mod', 'udf', ) ], loop=asyncio.get_running_loop()) self.modmodel.setLastUpdateTime(datetime.now(tz=timezone.utc)) def keyPressEvent(self, event: QKeyEvent) -> None: if event.key() == Qt.Key_Escape: self.selectionModel().clear() elif event.matches(QKeySequence.Delete): asyncio.create_task(self.deleteSelectedMods()) elif event.modifiers( ) & Qt.ControlModifier == Qt.ControlModifier and event.key( ) == Qt.Key_Up: asyncio.create_task(self.changeSelectedModsPriority(1)) elif event.modifiers( ) & Qt.ControlModifier == Qt.ControlModifier and event.key( ) == Qt.Key_Down: asyncio.create_task(self.changeSelectedModsPriority(-1)) elif event.modifiers( ) & Qt.ControlModifier == Qt.ControlModifier and event.key( ) == Qt.Key_P: index = self.selectionModel().selectedRows()[0] index = index.sibling(index.row(), 5) if index.flags() & Qt.ItemIsEditable: self.setCurrentIndex(index) self.edit(index) else: super().keyPressEvent(event) def setFilter(self, search: str) -> None: self.filtermodel.setFilterRegularExpression( QRegularExpression(search, QRegularExpression.CaseInsensitiveOption)) async def checkInstallFromURLs(self, paths: List[Union[str, QUrl]], local: bool = True, web: bool = True) -> None: await self.installLock.acquire() installed = 0 errors = 0 installtime = datetime.now(tz=timezone.utc) # remove duplicate paths paths = list(set(paths)) logger.bind(newline=True, output=False).debug('Starting install from URLs') try: results = await asyncio.gather(*[ self.installFromURL(path, local, web, installtime) for path in paths ], loop=asyncio.get_running_loop()) for result in results: installed += result[0] errors += result[1] except Exception as e: # we should never land here, but don't lock up the UI if it happens logger.exception(str(e)) errors += 1 if installed > 0 or errors > 0: log = logger.bind(modlist=bool(installed)) message = 'Installed {0} mods{1}'.format( installed, f' ({errors} errors)' if errors else '') if installed > 0 and errors > 0: log.warning(message) elif installed > 0: log.success(message) else: log.error(message) self.setDisabled(False) self.setFocus() self.installLock.release() async def installFromURL( self, path: Union[str, QUrl], local: bool = True, web: bool = True, installtime: Optional[datetime] = None) -> Tuple[int, int]: installed = 0 errors = 0 if not installtime: installtime = datetime.now(tz=timezone.utc) if isinstance(path, QUrl): path = path.toString() if web and isValidModDownloadUrl(path): self.setDisabled(True) logger.bind(dots=True, path=path).info(f'Installing mods from') i, e = await self.installFromFileDownload(path, installtime) installed += i errors += e elif local and isValidFileUrl(path): self.setDisabled(True) path = QUrl(path) logger.bind(dots=True, path=Path( path.toLocalFile())).info(f'Installing mods from') i, e = await self.installFromFile(Path(path.toLocalFile()), installtime) installed += i errors += e else: logger.bind(path=path).error('Could not install mods from') return installed, errors async def installFromFileDownload( self, url: str, installtime: Optional[datetime] = None) -> Tuple[int, int]: installed = 0 errors = 0 if not installtime: installtime = datetime.now(tz=timezone.utc) try: target = Path(urlparse(url).path) filename = re.sub(r'[^\w\-_\. ]', '_', unquote(target.name)) target = Path(tempfile.gettempdir()).joinpath( 'w3modmanager/download').joinpath(f'{filename}') except ValueError: logger.bind(name=url).exception('Wrong request URL') return 0, 1 try: target.parent.mkdir(parents=True, exist_ok=True) logger.bind(name=url).info('Starting to download file') await downloadFile(url, target) installed, errors = await self.installFromFile(target, installtime) except (RequestError, ResponseError, Exception) as e: logger.bind(name=url).exception(f'Failed to download file: {e}') return 0, 1 except Exception as e: logger.exception(str(e)) return 0, 1 finally: if target.is_file(): target.unlink() return installed, errors async def installFromFile( self, path: Path, installtime: Optional[datetime] = None) -> Tuple[int, int]: originalpath = path installed = 0 errors = 0 archive = path.is_file() source = None md5hash = '' details = None detailsrequest: Optional[asyncio.Task] = None if not installtime: installtime = datetime.now(tz=timezone.utc) try: if archive: # unpack archive, set source and request details md5hash = getMD5Hash(path) source = path settings = QSettings() if settings.value('nexusGetInfo', 'False') == 'True': logger.bind( path=str(path), dots=True).debug('Requesting details for archive') detailsrequest = asyncio.create_task( getModInformation(md5hash)) logger.bind(path=str(path), dots=True).debug('Unpacking archive') path = await extractMod(source) # validate and read mod valid, exhausted = containsValidMod(path, searchlimit=8) if not valid: if not exhausted and self.showContinueSearchDialog( searchlimit=8): if not containsValidMod(path): raise InvalidPathError(path, 'Invalid mod') elif not exhausted: raise InvalidPathError(path, 'Stopped searching for mod') else: raise InvalidPathError(path, 'Invalid mod') mods = await Mod.fromDirectory(path, searchCommonRoot=not archive) installedMods = [] # update mod details and add mods to the model for mod in mods: mod.md5hash = md5hash try: # TODO: incomplete: check if mod is installed, ask if replace await self.modmodel.add(mod) installedMods.append(mod) installed += 1 except ModExistsError: logger.bind(path=source if source else mod.source, name=mod.filename).error(f'Mod exists') errors += 1 continue # wait for details response if requested if detailsrequest: try: details = await detailsrequest except (RequestError, ResponseError, Exception) as e: logger.warning( f'Could not get information for {source.name if source else path.name}: {e}' ) # update mod with additional information if source or details: for mod in installedMods: if source: # set source if it differs from the scan directory, e.g. an archive mod.source = source if details: # set additional details if requested and available try: package = str(details[0]['mod']['name']) summary = str(details[0]['mod']['summary']) modid = int(details[0]['mod']['mod_id']) category = int(details[0]['mod']['category_id']) version = str( details[0]['file_details']['version']) fileid = int(details[0]['file_details']['file_id']) uploadname = str( details[0]['file_details']['name']) uploadtime = str( details[0]['file_details']['uploaded_time']) mod.package = package mod.summary = summary mod.modid = modid mod.category = getCategoryName(category) mod.version = version mod.fileid = fileid mod.uploadname = uploadname uploaddate = dateparser.parse(uploadtime) if uploaddate: mod.uploaddate = uploaddate.astimezone( tz=timezone.utc) else: logger.bind(name=mod.filename).debug( f'Could not parse date {uploadtime} in mod information response' ) except KeyError as e: logger.bind(name=mod.filename).exception( f'Could not find key "{str(e)}" in mod information response' ) try: await self.modmodel.update(mod) except Exception: logger.bind(name=mod.filename).warning( 'Could not update mod details') except ModelError as e: logger.bind(path=e.path).error(e.message) errors += 1 except InvalidPathError as e: # TODO: enhancement: better install error message logger.bind(path=e.path).error(e.message) errors += 1 except FileNotFoundError as e: logger.bind( path=e.filename).error(e.strerror if e.strerror else str(e)) errors += 1 except OSError as e: logger.bind( path=e.filename).error(e.strerror if e.strerror else str(e)) errors += 1 except Exception as e: logger.exception(str(e)) errors += 1 finally: if detailsrequest and not detailsrequest.done(): detailsrequest.cancel() if archive and not path == originalpath: try: util.removeDirectory(path) except Exception: logger.bind(path=path).warning( 'Could not remove temporary directory') self.modmodel.setLastUpdateTime(installtime) self.repaint() return installed, errors def showContinueSearchDialog(self, searchlimit: int) -> bool: messagebox = QMessageBox(self) messagebox.setWindowTitle('Unusual search depth') messagebox.setText(f''' <p>No mod detected after searching through {searchlimit} directories.</p> <p>Are you sure this is a valid mod?</p> ''') messagebox.setTextFormat(Qt.RichText) messagebox.setStandardButtons(QMessageBox.Cancel) yes: QPushButton = QPushButton(' Yes, continue searching ', messagebox) yes.setAutoDefault(True) yes.setDefault(True) messagebox.addButton(yes, QMessageBox.YesRole) messagebox.exec_() return messagebox.clickedButton() == yes def dropEvent(self, event: QDropEvent) -> None: event.accept() self.setDisabled(True) self.repaint() asyncio.create_task(self.checkInstallFromURLs(event.mimeData().urls())) def dragEnterEvent(self, event: QDragEnterEvent) -> None: self.setDisabled(True) self.repaint() urls = event.mimeData().urls() if not urls: self.setDisabled(False) self.setFocus() event.ignore() return for url in urls: try: parse = urlparse(url.toString()) if parse.scheme not in ['file']: self.setDisabled(False) event.ignore() return filepath = Path(url.toLocalFile()) if isArchive(filepath) or containsValidMod(filepath, searchlimit=8)[0]: self.setDisabled(False) event.accept() return except Exception as e: logger.debug(str(e)) self.setDisabled(False) self.setFocus() event.ignore() def dragMoveEvent(self, event: QDragMoveEvent) -> None: event.accept() def dragLeaveEvent(self, event: QDragLeaveEvent) -> None: event.accept()
class GameConnection(QObject, ConnectionBackend): Updated = Signal() _dt: float = 2.5 _last_status: Any = None _permanent_pickups: List[Tuple[str, PickupEntry]] def __init__(self, executor: MemoryOperationExecutor): super().__init__() ConnectionBackend.__init__(self, executor) self._permanent_pickups = [] self._timer = QTimer(self) self._timer.timeout.connect(self._auto_update) self._timer.setInterval(self._dt * 1000) self._timer.setSingleShot(True) self._notify_status() def set_executor(self, executor: MemoryOperationExecutor): self.executor = executor self._notify_status() async def start(self): self._timer.start() async def stop(self): self._timer.stop() @asyncSlot() async def _auto_update(self): try: await self.update(self._dt) self._notify_status() finally: self._timer.start() def _notify_status(self): new_status = self.current_status inventory = self.get_current_inventory() if self._last_status != (new_status, self.executor, inventory): self._last_status = (new_status, self.executor, copy.copy(inventory)) self.Updated.emit() @property def pretty_current_status(self) -> str: return f"{self.backend_choice.pretty_text}: {self.current_status.pretty_text}" @property def current_game_name(self) -> Optional[str]: if self.connector is not None: return self.connector.game_enum.long_name @property def name(self) -> str: raise ValueError("bleh") def set_location_collected_listener(self, listener: Optional[LocationListener]): super(ConnectionBackend, self).set_location_collected_listener(listener) self.checking_for_collected_index = listener is not None
class MainWindow(QMainWindow): """Voice Changer main window.""" def __init__(self, parent=None): super(MainWindow, self).__init__() self.statusBar().showMessage("Move Dial to Deform Microphone Voice !.") self.setWindowTitle(__doc__) self.setMinimumSize(240, 240) self.setMaximumSize(480, 480) self.resize(self.minimumSize()) self.setWindowIcon(QIcon.fromTheme("audio-input-microphone")) self.tray = QSystemTrayIcon(self) self.center() QShortcut("Ctrl+q", self, activated=lambda: self.close()) self.menuBar().addMenu("&File").addAction("Quit", lambda: exit()) self.menuBar().addMenu("Sound").addAction( "STOP !", lambda: call('killall rec', shell=True)) windowMenu = self.menuBar().addMenu("&Window") windowMenu.addAction("Hide", lambda: self.hide()) windowMenu.addAction("Minimize", lambda: self.showMinimized()) windowMenu.addAction("Maximize", lambda: self.showMaximized()) windowMenu.addAction("Restore", lambda: self.showNormal()) windowMenu.addAction("FullScreen", lambda: self.showFullScreen()) windowMenu.addAction("Center", lambda: self.center()) windowMenu.addAction("Top-Left", lambda: self.move(0, 0)) windowMenu.addAction("To Mouse", lambda: self.move_to_mouse_position()) # widgets group0 = QGroupBox("Voice Deformation") self.setCentralWidget(group0) self.process = QProcess(self) self.process.error.connect( lambda: self.statusBar().showMessage("Info: Process Killed", 5000)) self.control = QDial() self.control.setRange(-10, 20) self.control.setSingleStep(5) self.control.setValue(0) self.control.setCursor(QCursor(Qt.OpenHandCursor)) self.control.sliderPressed.connect( lambda: self.control.setCursor(QCursor(Qt.ClosedHandCursor))) self.control.sliderReleased.connect( lambda: self.control.setCursor(QCursor(Qt.OpenHandCursor))) self.control.valueChanged.connect( lambda: self.control.setToolTip(f"<b>{self.control.value()}")) self.control.valueChanged.connect(lambda: self.statusBar().showMessage( f"Voice deformation: {self.control.value()}", 5000)) self.control.valueChanged.connect(self.run) self.control.valueChanged.connect(lambda: self.process.kill()) # Graphic effect self.glow = QGraphicsDropShadowEffect(self) self.glow.setOffset(0) self.glow.setBlurRadius(99) self.glow.setColor(QColor(99, 255, 255)) self.control.setGraphicsEffect(self.glow) self.glow.setEnabled(False) # Timer to start self.slider_timer = QTimer(self) self.slider_timer.setSingleShot(True) self.slider_timer.timeout.connect(self.on_slider_timer_timeout) # an icon and set focus QLabel(self.control).setPixmap( QIcon.fromTheme("audio-input-microphone").pixmap(32)) self.control.setFocus() QVBoxLayout(group0).addWidget(self.control) self.menu = QMenu(__doc__) self.menu.addAction(__doc__).setDisabled(True) self.menu.setIcon(self.windowIcon()) self.menu.addSeparator() self.menu.addAction( "Show / Hide", lambda: self.hide() if self.isVisible() else self.showNormal()) self.menu.addAction("STOP !", lambda: call('killall rec', shell=True)) self.menu.addSeparator() self.menu.addAction("Quit", lambda: exit()) self.tray.setContextMenu(self.menu) self.make_trayicon() def run(self): """Run/Stop the QTimer.""" if self.slider_timer.isActive(): self.slider_timer.stop() self.glow.setEnabled(True) call('killall rec ; killall play', shell=True) self.slider_timer.start(3000) def on_slider_timer_timeout(self): """Run subprocess to deform voice.""" self.glow.setEnabled(False) value = int(self.control.value()) * 100 command = f'play -q -V0 "|rec -q -V0 -n -d -R riaa bend pitch {value} "' print(f"Voice Deformation Value: {value}") print(f"Voice Deformation Command: {command}") self.process.start(command) if self.isVisible(): self.statusBar().showMessage("Minimizing to System TrayIcon", 3000) print("Minimizing Main Window to System TrayIcon now...") sleep(3) self.hide() def center(self): """Center Window on the Current Screen,with Multi-Monitor support.""" window_geometry = self.frameGeometry() mousepointer_position = QApplication.desktop().cursor().pos() screen = QApplication.desktop().screenNumber(mousepointer_position) centerPoint = QApplication.desktop().screenGeometry(screen).center() window_geometry.moveCenter(centerPoint) self.move(window_geometry.topLeft()) def move_to_mouse_position(self): """Center the Window on the Current Mouse position.""" window_geometry = self.frameGeometry() window_geometry.moveCenter(QApplication.desktop().cursor().pos()) self.move(window_geometry.topLeft()) def make_trayicon(self): """Make a Tray Icon.""" if self.windowIcon() and __doc__: self.tray.setIcon(self.windowIcon()) self.tray.setToolTip(__doc__) self.tray.activated.connect( lambda: self.hide() if self.isVisible() else self.showNormal()) return self.tray.show()
class ByteView(QAbstractScrollArea, View): def __init__(self, parent, data): QAbstractScrollArea.__init__(self, parent) View.__init__(self) View.setBinaryDataNavigable(self, True) self.setupView(self) self.data = data self.byte_mapping = [ u' ', u'☺', u'☻', u'♥', u'♦', u'♣', u'♠', u'•', u'◘', u'○', u'◙', u'♂', u'♀', u'♪', u'♫', u'☼', u'▸', u'◂', u'↕', u'‼', u'¶', u'§', u'▬', u'↨', u'↑', u'↓', u'→', u'←', u'∟', u'↔', u'▴', u'▾', u' ', u'!', u'"', u'#', u'$', u'%', u'&', u'\'', u'(', u')', u'*', u'+', u',', u'-', u'.', u'/', u'0', u'1', u'2', u'3', u'4', u'5', u'6', u'7', u'8', u'9', u':', u';', u'<', u'=', u'>', u'?', u'@', u'A', u'B', u'C', u'D', u'E', u'F', u'G', u'H', u'I', u'J', u'K', u'L', u'M', u'N', u'O', u'P', u'Q', u'R', u'S', u'T', u'U', u'V', u'W', u'X', u'Y', u'Z', u'[', u'\\', u']', u'^', u'_', u'`', u'a', u'b', u'c', u'd', u'e', u'f', u'g', u'h', u'i', u'j', u'k', u'l', u'm', u'n', u'o', u'p', u'q', u'r', u's', u't', u'u', u'v', u'w', u'x', u'y', u'z', u'{', u'|', u'}', u'~', u'⌂', u'Ç', u'ü', u'é', u'â', u'ä', u'à', u'å', u'ç', u'ê', u'ë', u'è', u'ï', u'î', u'ì', u'Ä', u'Å', u'É', u'æ', u'Æ', u'ô', u'ö', u'ò', u'û', u'ù', u'ÿ', u'Ö', u'Ü', u'¢', u'£', u'¥', u'₧', u'ƒ', u'á', u'í', u'ó', u'ú', u'ñ', u'Ñ', u'ª', u'º', u'¿', u'⌐', u'¬', u'½', u'¼', u'¡', u'«', u'»', u'░', u'▒', u'▓', u'│', u'┤', u'╡', u'╢', u'╖', u'╕', u'╣', u'║', u'╗', u'╝', u'╜', u'╛', u'┐', u'└', u'┴', u'┬', u'├', u'─', u'┼', u'╞', u'╟', u'╚', u'╔', u'╩', u'╦', u'╠', u'═', u'╬', u'╧', u'╨', u'╤', u'╥', u'╙', u'╘', u'╒', u'╓', u'╫', u'╪', u'┘', u'┌', u'█', u'▄', u'▌', u'▐', u'▀', u'α', u'ß', u'Γ', u'π', u'Σ', u'σ', u'µ', u'τ', u'Φ', u'Θ', u'Ω', u'δ', u'∞', u'φ', u'ε', u'∩', u'≡', u'±', u'≥', u'≤', u'⌠', u'⌡', u'÷', u'≈', u'°', u'∙', u'·', u'√', u'ⁿ', u'²', u'■', u' ' ] self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self.setFocusPolicy(Qt.StrongFocus) self.cursorAddr = self.data.start self.prevCursorAddr = self.cursorAddr self.selectionStartAddr = self.cursorAddr self.topAddr = self.cursorAddr self.topLine = 0 self.selectionVisible = False self.caretVisible = False self.caretBlink = True self.leftButtonDown = False self.cols = 128 self.updatesRequired = False self.visibleRows = 1 self.lines = [] self.updateRanges() areaSize = self.viewport().size() self.adjustSize(areaSize.width(), areaSize.height()) if self.allocatedLength > 0x7fffffff: self.scrollBarMultiplier = (self.allocatedLength // 0x7fffffff) + 1 else: self.scrollBarMultiplier = 1 self.wheelDelta = 0 self.updatingScrollBar = False self.verticalScrollBar().setRange(0, (self.allocatedLength - 1) // self.scrollBarMultiplier) self.verticalScrollBar().sliderMoved.connect(self.scrollBarMoved) self.verticalScrollBar().actionTriggered.connect(self.scrollBarAction) self.cursorTimer = QTimer(self) self.cursorTimer.setInterval(500) self.cursorTimer.setSingleShot(False) self.cursorTimer.timeout.connect(self.cursorTimerEvent) self.cursorTimer.start() self.updateTimer = QTimer(self) self.updateTimer.setInterval(200) self.updateTimer.setSingleShot(False) #self.updateTimer.timeout.connect(self.updateTimerEvent) self.actionHandler().bindAction("Move Cursor Up", UIAction(lambda ctxt: self.up(False))) self.actionHandler().bindAction( "Move Cursor Down", UIAction(lambda ctxt: self.down(False))) self.actionHandler().bindAction( "Move Cursor Left", UIAction(lambda ctxt: self.left(1, False))) self.actionHandler().bindAction( "Move Cursor Right", UIAction(lambda ctxt: self.right(1, False))) self.actionHandler().bindAction( "Move Cursor Word Left", UIAction(lambda ctxt: self.left(8, False))) self.actionHandler().bindAction( "Move Cursor Word Right", UIAction(lambda ctxt: self.right(8, False))) self.actionHandler().bindAction("Extend Selection Up", UIAction(lambda ctxt: self.up(True))) self.actionHandler().bindAction("Extend Selection Down", UIAction(lambda ctxt: self.down(True))) self.actionHandler().bindAction( "Extend Selection Left", UIAction(lambda ctxt: self.left(1, True))) self.actionHandler().bindAction( "Extend Selection Right", UIAction(lambda ctxt: self.right(1, True))) self.actionHandler().bindAction( "Extend Selection Word Left", UIAction(lambda ctxt: self.left(8, True))) self.actionHandler().bindAction( "Extend Selection Word Right", UIAction(lambda ctxt: self.right(8, True))) self.actionHandler().bindAction( "Page Up", UIAction(lambda ctxt: self.pageUp(False))) self.actionHandler().bindAction( "Page Down", UIAction(lambda ctxt: self.pageDown(False))) self.actionHandler().bindAction( "Extend Selection Page Up", UIAction(lambda ctxt: self.pageUp(True))) self.actionHandler().bindAction( "Extend Selection Page Down", UIAction(lambda ctxt: self.pageDown(True))) self.actionHandler().bindAction( "Move Cursor to Start of Line", UIAction(lambda ctxt: self.moveToStartOfLine(False))) self.actionHandler().bindAction( "Move Cursor to End of Line", UIAction(lambda ctxt: self.moveToEndOfLine(False))) self.actionHandler().bindAction( "Move Cursor to Start of View", UIAction(lambda ctxt: self.moveToStartOfView(False))) self.actionHandler().bindAction( "Move Cursor to End of View", UIAction(lambda ctxt: self.moveToEndOfView(False))) self.actionHandler().bindAction( "Extend Selection to Start of Line", UIAction(lambda ctxt: self.moveToStartOfLine(True))) self.actionHandler().bindAction( "Extend Selection to End of Line", UIAction(lambda ctxt: self.moveToEndOfLine(True))) self.actionHandler().bindAction( "Extend Selection to Start of View", UIAction(lambda ctxt: self.moveToStartOfView(True))) self.actionHandler().bindAction( "Extend Selection to End of View", UIAction(lambda ctxt: self.moveToEndOfView(True))) def getData(self): return self.data def getStart(self): return self.data.start def getEnd(self): return self.data.end def getLength(self): return self.getEnd() - self.getStart() def getCurrentOffset(self): return self.cursorAddr def getSelectionOffsets(self): start = self.selectionStartAddr end = self.cursorAddr if end < start: t = start start = end end = t return (start, end) def updateRanges(self): self.ranges = self.data.allocated_ranges # Remove regions not backed by the file for i in self.data.segments: if i.data_length < len(i): self.removeRange(i.start + i.data_length, i.end) self.allocatedLength = 0 for i in self.ranges: self.allocatedLength += i.end - i.start def removeRange(self, begin, end): newRanges = [] for i in self.ranges: if (end <= i.start) or (begin >= i.end): newRanges.append(i) elif (begin <= i.start) and (end >= i.end): continue elif (begin <= i.start) and (end < i.end): newRanges.append(AddressRange(end, i.end)) elif (begin > i.start) and (end >= i.end): newRanges.append(AddressRange(i.start, begin)) else: newRanges.append(AddressRange(i.start, begin)) newRanges.append(AddressRange(end, i.end)) self.ranges = newRanges def setTopToAddress(self, addr): for i in self.ranges: if (addr >= i.start) and (addr <= i.end): self.topAddr = addr - ((addr - i.start) % self.cols) if self.topAddr < i.start: self.topAddr = i.start return if i.start > addr: self.topAddr = i.start return self.topAddr = self.data.end def navigate(self, addr): if addr < self.getStart(): return False if addr > self.getEnd(): return False self.cursorAddr = self.getStart() for i in self.ranges: if i.start > addr: break if addr > i.end: self.cursorAddr = i.end elif addr >= i.start: self.cursorAddr = addr else: self.cursorAddr = i.start self.setTopToAddress(self.cursorAddr) self.refreshLines() self.showContextAroundTop() self.selectNone() self.repositionCaret() return True def updateFonts(self): areaSize = self.viewport().size() self.adjustSize(areaSize.width(), areaSize.height()) def getFont(self): userFont = binaryninjaui.getMonospaceFont(self) if sys.platform == "darwin": # Some fonts aren't fixed width across all characters, use a known good one font = QFont("Menlo", userFont.pointSize()) font.setKerning(False) else: font = userFont return font def createRenderContext(self): render = RenderContext(self) render.setFont(self.getFont()) return render def adjustSize(self, width, height): self.addrWidth = max(len("%x" % self.data.end), 8) render = self.createRenderContext() cols = ((width - 4) // render.getFontWidth()) - (self.addrWidth + 2) if cols < 1: cols = 1 if cols != self.cols: self.cols = cols if self.topLine < len(self.lines): self.setTopToAddress(self.lines[self.topLine].address) else: self.setTopToAddress(self.cursorAddr) self.refreshLines() self.visibleRows = (height - 4) // render.getFontHeight() self.verticalScrollBar().setPageStep(self.visibleRows * self.cols // self.scrollBarMultiplier) self.refreshAtCurrentLocation() self.viewport().update() def getContiguousOffsetForAddress(self, addr): offset = 0 for i in self.ranges: if (addr >= i.start) and (addr <= i.end): offset += addr - i.start break offset += i.end - i.start return offset def getAddressForContiguousOffset(self, offset): cur = 0 for i in self.ranges: if offset < (cur + (i.end - i.start)): return i.start + (offset - cur) cur += i.end - i.start return self.data.end def refreshLines(self): addr = self.topAddr self.lines = [] self.topLine = 0 self.bottomAddr = self.topAddr self.updateRanges() if self.allocatedLength > 0x7fffffff: self.scrollBarMultiplier = (self.allocatedLength // 0x7fffffff) + 1 else: self.scrollBarMultiplier = 1 self.updatingScrollBar = True self.verticalScrollBar().setRange(0, (self.allocatedLength - 1) // self.scrollBarMultiplier) self.verticalScrollBar().setValue( self.getContiguousOffsetForAddress(addr) // self.scrollBarMultiplier) self.updatingScrollBar = False self.updateCache() self.viewport().update() UIContext.updateStatus() def refreshAtCurrentLocation(self): if self.topLine < len(self.lines): self.topAddr = self.lines[self.topLine].address self.refreshLines() def createLine(self, addr, length, separator): if separator: return ByteViewLine(addr, length, u'', True) else: data = self.data.read(addr, length) text = u''.join([self.byte_mapping[value] for value in data]) return ByteViewLine(addr, length, text, False) def cachePreviousLines(self): prevEnd = None for i in self.ranges: if (self.topAddr > i.start) and (self.topAddr <= i.end): startLine = self.topAddr - ( (self.topAddr - i.start) % self.cols) if startLine == self.topAddr: startLine -= self.cols if startLine < i.start: startLine = i.start line = self.createLine(startLine, self.topAddr - startLine, False) self.lines.insert(0, line) self.topLine += 1 self.topAddr = startLine return True elif i.start >= self.topAddr: if prevEnd is None: return False line = self.createLine(prevEnd, i.start - prevEnd, True) self.lines.insert(0, line) self.topLine += 1 self.topAddr = prevEnd return True prevEnd = i.end if prevEnd is None: return False line = self.createLine(prevEnd, self.topAddr - prevEnd, True) self.lines.insert(0, line) self.topLine += 1 self.topAddr = prevEnd return True def cacheNextLines(self): lastAddr = self.data.start for i in self.ranges: if (self.bottomAddr >= i.start) and (self.bottomAddr < i.end): endLine = self.bottomAddr + self.cols if endLine > i.end: endLine = i.end line = self.createLine(self.bottomAddr, endLine - self.bottomAddr, False) self.lines.append(line) self.bottomAddr = endLine return True elif i.start > self.bottomAddr: line = self.createLine(self.bottomAddr, i.start - self.bottomAddr, True) self.lines.append(line) self.bottomAddr = i.start return True lastAddr = i.end if self.bottomAddr == lastAddr: # Ensure there is a place for the cursor at the end of the file if (len(self.lines) > 0) and (self.lines[-1].length != self.cols): return False line = self.createLine(lastAddr, 0, False) self.lines.append(line) self.bottomAddr += 1 return True return False def updateCache(self): # Cache enough for the current page and the next page while (len(self.lines) - self.topLine) <= (self.visibleRows * 2): if not self.cacheNextLines(): break # Cache enough for the previous page while self.topLine <= self.visibleRows: if not self.cachePreviousLines(): break # Trim cache if self.topLine > (self.visibleRows * 4): self.lines = self.lines[self.topLine - (self.visibleRows * 4):] self.topLine = self.visibleRows * 4 self.topAddr = self.lines[0].address if (len(self.lines) - self.topLine) > (self.visibleRows * 5): self.bottomAddr = self.lines[self.topLine + (self.visibleRows * 5)].address self.lines = self.lines[0:self.topLine + (self.visibleRows * 5)] def scrollLines(self, count): newOffset = self.topLine + count if newOffset < 0: self.topLine = 0 elif newOffset >= len(self.lines): self.topLine = len(self.lines) - 1 else: self.topLine = newOffset self.updateCache() self.viewport().update() if self.topLine < len(self.lines): self.updatingScrollBar = True addr = self.lines[self.topLine].address self.verticalScrollBar().setValue( self.getContiguousOffsetForAddress(addr) // self.scrollBarMultiplier) self.updatingScrollBar = False def showContextAroundTop(self): scroll = self.visibleRows // 4 if scroll > self.topLine: self.topLine = 0 else: self.topLine -= scroll if self.topLine < len(self.lines): self.updatingScrollBar = True addr = self.lines[self.topLine].address self.verticalScrollBar().setValue( self.getContiguousOffsetForAddress(addr) // self.scrollBarMultiplier) self.updatingScrollBar = False self.updateCache() def repositionCaret(self): self.updateCache() found = False for i in range(0, len(self.lines)): if (((self.cursorAddr >= self.lines[i].address) and (self.cursorAddr < (self.lines[i].address + self.lines[i].length))) or (((i + 1) == len(self.lines)) and (self.cursorAddr == (self.lines[i].address + self.lines[i].length)))): if i < self.topLine: self.topLine = i elif i > (self.topLine + self.visibleRows - 1): self.topLine = i - (self.visibleRows - 1) self.updatingScrollBar = True addr = self.lines[self.topLine].address self.verticalScrollBar().setValue( self.getContiguousOffsetForAddress(addr) // self.scrollBarMultiplier) self.updatingScrollBar = False self.updateCache() self.viewport().update() found = True break if not found: self.setTopToAddress(self.cursorAddr) self.refreshLines() self.showContextAroundTop() # Force caret to be visible and repaint self.caretBlink = True self.cursorTimer.stop() self.cursorTimer.start() self.updateCaret() UIContext.updateStatus() def updateCaret(self): # Rerender both the old caret position and the new caret position render = self.createRenderContext() for i in range(self.topLine, min(len(self.lines), self.topLine + self.visibleRows)): if (((self.prevCursorAddr >= self.lines[i].address) and (self.prevCursorAddr <= (self.lines[i].address + self.lines[i].length))) or ((self.cursorAddr >= self.lines[i].address) and (self.cursorAddr <= (self.lines[i].address + self.lines[i].length)))): self.viewport().update(0, (i - self.topLine) * render.getFontHeight(), self.viewport().size().width(), render.getFontHeight() + 3) def resizeEvent(self, event): self.adjustSize(event.size().width(), event.size().height()) def paintEvent(self, event): p = QPainter(self.viewport()) render = self.createRenderContext() render.init(p) charWidth = render.getFontWidth() charHeight = render.getFontHeight() # Compute range that needs to be updated topY = event.rect().y() botY = topY + event.rect().height() topY = (topY - 2) // charHeight botY = ((botY - 2) // charHeight) + 1 # Compute selection range selection = False selStart, selEnd = self.getSelectionOffsets() if selStart != selEnd: selection = True # Draw selection if selection: startY = None endY = None startX = None endX = None for i in range(0, len(self.lines)): if selStart >= self.lines[i].address: startY = i - self.topLine startX = selStart - self.lines[i].address if startX > self.cols: startX = self.cols if selEnd >= self.lines[i].address: endY = i - self.topLine endX = selEnd - self.lines[i].address if endX > self.cols: endX = self.cols if startY is not None and endY is not None: p.setPen(binaryninjaui.getThemeColor( ThemeColor.SelectionColor)) p.setBrush( binaryninjaui.getThemeColor(ThemeColor.SelectionColor)) if startY == endY: p.drawRect(2 + (self.addrWidth + 2 + startX) * charWidth, 2 + startY * charHeight, (endX - startX) * charWidth, charHeight + 1) else: p.drawRect(2 + (self.addrWidth + 2 + startX) * charWidth, 2 + startY * charHeight, (self.cols - startX) * charWidth, charHeight + 1) if endX > 0: p.drawRect(2 + (self.addrWidth + 2) * charWidth, 2 + endY * charHeight, endX * charWidth, charHeight + 1) if (endY - startY) > 1: p.drawRect(2 + (self.addrWidth + 2) * charWidth, 2 + (startY + 1) * charHeight, self.cols * charWidth, ((endY - startY) - 1) * charHeight + 1) # Paint each line color = self.palette().color(QPalette.WindowText) for y in range(topY, botY): if (y + self.topLine) < 0: continue if (y + self.topLine) >= len(self.lines): break if self.lines[y + self.topLine].separator: render.drawLinearDisassemblyLineBackground( p, LinearDisassemblyLineType.NonContiguousSeparatorLineType, QRect(0, 2 + y * charHeight, event.rect().width(), charHeight), 0) continue lineStartAddr = self.lines[y + self.topLine].address addrStr = "%.8x" % lineStartAddr length = self.lines[y + self.topLine].length text = self.lines[y + self.topLine].text cursorCol = None if (((self.cursorAddr >= lineStartAddr) and (self.cursorAddr < (lineStartAddr + length))) or (((y + self.topLine + 1) >= len(self.lines)) and (self.cursorAddr == (lineStartAddr + length)))): cursorCol = self.cursorAddr - lineStartAddr render.drawText( p, 2, 2 + y * charHeight, binaryninjaui.getThemeColor(ThemeColor.AddressColor), addrStr) render.drawText(p, 2 + (self.addrWidth + 2) * charWidth, 2 + y * charHeight, color, text) if self.caretVisible and self.caretBlink and not selection and cursorCol is not None: p.setPen(Qt.NoPen) p.setBrush(self.palette().color(QPalette.WindowText)) p.drawRect(2 + (self.addrWidth + 2 + cursorCol) * charWidth, 2 + y * charHeight, charWidth, charHeight + 1) caretTextColor = self.palette().color(QPalette.Base) byteValue = self.data.read(lineStartAddr + cursorCol, 1) if len(byteValue) == 1: byteStr = self.byte_mapping[byteValue[0]] render.drawText( p, 2 + (self.addrWidth + 2 + cursorCol) * charWidth, 2 + y * charHeight, caretTextColor, byteStr) def wheelEvent(self, event): if event.orientation() == Qt.Horizontal: return self.wheelDelta -= event.delta() if (self.wheelDelta <= -40) or (self.wheelDelta >= 40): lines = self.wheelDelta // 40 self.wheelDelta -= lines * 40 self.scrollLines(lines) def scrollBarMoved(self, value): if self.updatingScrollBar: return self.wheelDelta = 0 addr = self.getAddressForContiguousOffset(value * self.scrollBarMultiplier) self.setTopToAddress(addr) self.refreshLines() for i in range(1, len(self.lines)): if (self.lines[i].address + self.lines[i].length) > addr: self.topLine = i - 1 break self.updateCache() def scrollBarAction(self, action): if action == QAbstractSlider.SliderSingleStepAdd: self.wheelDelta = 0 self.scrollLines(1) elif action == QAbstractSlider.SliderSingleStepSub: self.wheelDelta = 0 self.scrollLines(-1) elif action == QAbstractSlider.SliderPageStepAdd: self.wheelDelta = 0 self.scrollLines(self.visibleRows) elif action == QAbstractSlider.SliderPageStepSub: self.wheelDelta = 0 self.scrollLines(-self.visibleRows) elif action == QAbstractSlider.SliderToMinimum: self.wheelDelta = 0 self.setTopToAddress(self.getStart()) self.verticalScrollBar().setValue( self.getContiguousOffsetForAddress(self.topAddr) // self.scrollBarMultiplier) self.refreshLines() elif action == QAbstractSlider.SliderToMaximum: self.wheelDelta = 0 self.setTopToAddress(self.getEnd()) self.verticalScrollBar().setValue( self.getContiguousOffsetForAddress(self.topAddr) // self.scrollBarMultiplier) self.refreshLines() def cursorTimerEvent(self): self.caretBlink = not self.caretBlink self.updateCaret() def focusInEvent(self, event): self.caretVisible = True self.updateCaret() def focusOutEvent(self, event): self.caretVisible = False self.leftButtonDown = False self.updateCaret() def selectNone(self): for i in self.lines: if (self.cursorAddr >= i.address) and (self.cursorAddr < (i.address + i.length)) and i.separator: self.cursorAddr = i.address + i.length break self.selectionStartAddr = self.cursorAddr if self.selectionVisible: self.viewport().update() self.repositionCaret() UIContext.updateStatus() def selectAll(self): self.selectionStartAddr = self.getStart() self.cursorAddr = self.getEnd() self.viewport().update() UIContext.updateStatus() def adjustAddressAfterBackwardMovement(self): lastAddr = self.getStart() for i in self.ranges: if (self.cursorAddr >= i.start) and (self.cursorAddr < i.end): break if i.start > self.cursorAddr: self.cursorAddr = lastAddr break lastAddr = i.end - 1 def adjustAddressAfterForwardMovement(self): for i in self.ranges: if (self.cursorAddr >= i.start) and (self.cursorAddr < i.end): break if i.start > self.cursorAddr: self.cursorAddr = i.start break def left(self, count, selecting): if self.cursorAddr > (self.getStart() + count): self.cursorAddr -= count else: self.cursorAddr = self.getStart() self.adjustAddressAfterBackwardMovement() if not selecting: self.selectNone() self.repositionCaret() if self.selectionVisible or selecting: self.viewport().update() def right(self, count, selecting): if self.cursorAddr <= (self.getEnd() - count): self.cursorAddr += count else: self.cursorAddr = self.getEnd() self.adjustAddressAfterForwardMovement() if not selecting: self.selectNone() self.repositionCaret() if self.selectionVisible or selecting: self.viewport().update() def up(self, selecting): self.left(self.cols, selecting) def down(self, selecting): self.right(self.cols, selecting) def pageUp(self, selecting): for i in range(0, len(self.lines)): if (((self.cursorAddr >= self.lines[i].address) and (self.cursorAddr < (self.lines[i].address + self.lines[i].length))) or (((i + 1) == len(self.lines)) and (self.cursorAddr == (self.lines[i].address + self.lines[i].length)))): if i < self.visibleRows: self.cursorAddr = self.getStart() else: lineOfs = self.cursorAddr - self.lines[i].address self.cursorAddr = self.lines[ i - self.visibleRows].address + lineOfs if self.cursorAddr < self.lines[i - self.visibleRows].address: self.cursorAddr = self.lines[i - self.visibleRows].address elif self.cursorAddr >= ( self.lines[i - self.visibleRows].address + self.lines[i - self.visibleRows].length): self.cursorAddr = self.lines[ i - self.visibleRows].address + self.lines[ i - self.visibleRows].length - 1 break self.adjustAddressAfterBackwardMovement() if self.topLine > self.visibleRows: self.topLine -= self.visibleRows else: self.topLine = 0 if self.topLine < len(self.lines): self.updatingScrollBar = True addr = self.lines[self.topLine].address self.verticalScrollBar().setValue( self.getContiguousOffsetForAddress(addr) // self.scrollBarMultiplier) self.updatingScrollBar = False if not selecting: self.selectNone() self.repositionCaret() self.viewport().update() def pageDown(self, selecting): for i in range(0, len(self.lines)): if (((self.cursorAddr >= self.lines[i].address) and (self.cursorAddr < (self.lines[i].address + self.lines[i].length))) or (((i + 1) == len(self.lines)) and (self.cursorAddr == (self.lines[i].address + self.lines[i].length)))): if i >= (len(self.lines) - self.visibleRows): self.cursorAddr = self.getEnd() else: lineOfs = self.cursorAddr - self.lines[i].address self.cursorAddr = self.lines[ i + self.visibleRows].address + lineOfs if self.cursorAddr < self.lines[i + self.visibleRows].address: self.cursorAddr = self.lines[i + self.visibleRows].address elif self.cursorAddr >= ( self.lines[i + self.visibleRows].address + self.lines[i + self.visibleRows].length): self.cursorAddr = self.lines[ i + self.visibleRows].address + self.lines[ i + self.visibleRows].length - 1 break self.adjustAddressAfterForwardMovement() if (self.topLine + self.visibleRows) < len(self.lines): self.topLine += self.visibleRows elif len(self.lines) > 0: self.topLine = len(self.lines) - 1 if self.topLine < len(self.lines): self.updatingScrollBar = True addr = self.lines[self.topLine].address self.verticalScrollBar().setValue( self.getContiguousOffsetForAddress(addr) // self.scrollBarMultiplier) self.updatingScrollBar = False if not selecting: self.selectNone() self.repositionCaret() self.viewport().update() def moveToStartOfLine(self, selecting): for i in self.lines: if (self.cursorAddr >= i.address) and (self.cursorAddr < (i.address + i.length)): self.cursorAddr = i.address break if not selecting: self.selectNone() self.repositionCaret() if self.selectionVisible or selecting: self.viewport().update() def moveToEndOfLine(self, selecting): for i in self.lines: if (self.cursorAddr >= i.address) and (self.cursorAddr < (i.address + i.length)): self.cursorAddr = i.address + i.length - 1 break if not selecting: self.selectNone() self.repositionCaret() if self.selectionVisible or selecting: self.viewport().update() def moveToStartOfView(self, selecting): self.cursorAddr = self.getStart() if not selecting: self.selectNone() self.repositionCaret() if self.selectionVisible or selecting: self.viewport().update() def moveToEndOfView(self, selecting): self.cursorAddr = self.getEnd() if not selecting: self.selectNone() self.repositionCaret() if self.selectionVisible or selecting: self.viewport().update() def addressFromLocation(self, x, y): if y < 0: y = 0 if x < 0: x = 0 if x > self.cols: x = self.cols if (y + self.topLine) >= len(self.lines): return self.getEnd() if self.lines[y + self.topLine].separator: return self.lines[y + self.topLine].address - 1 result = self.lines[y + self.topLine].address + x if result >= (self.lines[y + self.topLine].address + self.lines[y + self.topLine].length): if (y + self.topLine) == (len(self.lines) - 1): return self.getEnd() else: return self.lines[y + self.topLine].address + self.lines[ y + self.topLine].length - 1 return result def mousePressEvent(self, event): if event.button() != Qt.LeftButton: return render = self.createRenderContext() x = (event.x() - 2) // render.getFontWidth() - (self.addrWidth + 2) y = (event.y() - 2) // render.getFontHeight() self.lastMouseX = x self.lastMouseY = y self.cursorAddr = self.addressFromLocation(x, y) if (event.modifiers() & Qt.ShiftModifier) == 0: self.selectNone() self.repositionCaret() if (event.modifiers() & Qt.ShiftModifier) != 0: self.viewport().update() self.leftButtonDown = True def mouseMoveEvent(self, event): if not self.leftButtonDown: return render = self.createRenderContext() x = (event.x() - 2) // render.getFontWidth() - (self.addrWidth + 2) y = (event.y() - 2) // render.getFontHeight() if (x == self.lastMouseX) and (y == self.lastMouseY): return self.lastMouseX = x self.lastMouseY = y self.cursorAddr = self.addressFromLocation(x, y) self.repositionCaret() self.viewport().update() def mouseReleaseEvent(self, event): if event.button() != Qt.LeftButton: return self.leftButtonDown = False
class PomoTimer(QWidget, BaseTimer): """ PomoTimer: Pomodoro タイマーを提供する。 1 pomodoro: 25min short break: 5min long break: 15min long break after 4pomodoro. """ paused = Signal() def __init__(self): super().__init__() self.simple_timer = SimpleTimer(self) self.settings = { 'pomo': 25, 'short_break': 5, 'long_break': 15, 'long_break_after': 4 } self.state_dict = { 'work': 'Work!', 'break': 'Break.', 'pause': 'Pause', 'wait': "Let's start." } self.work_timer = QTimer(self) self.work_timer.setSingleShot(True) self.break_timer = QTimer(self) self.break_timer.setSingleShot(True) self.pomo_count = 0 self.pomo_count_lbl = QLabel(self) self.pomo_count_lbl.setText(f'{self.pomo_count} pomodoro finished.') self.state_lbl = QLabel(self) self.state_lbl.setText(self.state_dict['wait']) self.estimate_pomo = 0 self.estimate_label = QLabel(self) self.estimate_label.setText('Estimate: ') self.estimate_pomo_widget = QSpinBox(self) self.estimate_pomo_widget.setValue(4) self.estimate_pomo_widget.setSuffix(' pomo') self.estimate_pomo_widget.setRange(1, 20) self.set_ui() self.set_connection() def set_ui(self): layout = self.simple_timer.layout() i = layout.indexOf(self.simple_timer.timer_edit) layout.takeAt(i) hlayout = QHBoxLayout() hlayout.addWidget(self.estimate_label) hlayout.addWidget(self.estimate_pomo_widget) vlayout = QVBoxLayout() vlayout.addLayout(hlayout) vlayout.addWidget(self.pomo_count_lbl) vlayout.addWidget(self.state_lbl) vlayout.addWidget(self.simple_timer) self.setLayout(vlayout) def set_connection(self): self.work_timer.timeout.connect(self.timeout) self.break_timer.timeout.connect(self.start_work) def start(self): self.estimate_pomo = self.estimate_pomo_widget.value() self.start_work() def start_work(self): """ Start timer for working on the task. :return: """ self.state_lbl.setText(self.state_dict['work']) self.simple_timer.timer = self.work_timer self.simple_timer.timer_edit.setValue(self.settings['pomo']) self.simple_timer.start() self.started.emit() def start_break(self): """ Start timer for working on the rest. Short break is normal break, long break comes every some tasks(default 4). :return: """ self.state_lbl.setText(self.state_dict['break']) self.simple_timer.timer = self.break_timer if self.pomo_count % self.settings['long_break_after']: self.simple_timer.timer_edit.setValue(self.settings['short_break']) else: self.simple_timer.timer_edit.setValue(self.settings['long_break']) self.simple_timer.start() self.started.emit() def abort(self): self.reset() self.aborted.emit() def pause(self): self.state_lbl.setText(self.state_dict['pause']) self.simple_timer.pause() self.paused.emit() def resume(self): self.state_lbl.setText(self.state_dict['work']) self.simple_timer.resume() self.started.emit() def timeout(self): self.pomo_count += 1 if self.pomo_count >= self.estimate_pomo: self.reset() self.finished.emit() else: self.start_break() def reset(self): self.pomo_count = 0 self.simple_timer.reset() self.work_timer.stop() self.break_timer.stop() self.state_lbl.setText(self.state_dict['wait']) def get_notify_message(self): return '' @property def name(self): return 'Pomo Timer' def fake_start(self): self.simple_timer.setting_time = self.simple_timer.timer_edit.value() self.simple_timer.timer.start(self.simple_timer.setting_time * 1000) self.simple_timer.set_remain_update() self.simple_timer.started.emit()
class EvelynDesktop(QStackedWidget): INTERVAL_SECS = 30 ALERT_SECS = 5 signal_get_ping = Signal() signal_post_history = Signal(int, QDateTime) def __init__( self, config_file: str ) -> None: super().__init__() # load config try: self.config = Config(config_file) except Exception as e: QMessageBox.critical(self, 'Config error', str(e)) QTimer.singleShot(0, self.close) return # load settings self.settings = Settings() # state self.state_key: Optional[int] = None # label widget self.label_ping = ClickableLabel('Loading ...', self.post_history) self.label_ping.setTextFormat(Qt.RichText) self.label_ping.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) layout_ping = QGridLayout() layout_ping.setContentsMargins(0, 0, 0, 0) layout_ping.addWidget(self.label_ping) self.widget_ping = QWidget() self.widget_ping.setLayout(layout_ping) self.addWidget(self.widget_ping) # alert widget self.label_alert = QLabel() self.label_alert.setWordWrap(True) self.label_alert.setAlignment(Qt.AlignCenter) self.label_alert.setStyleSheet(f'background: #dddddd;') self.addWidget(self.label_alert) # context menu self.action_report_done = QAction('Report done ...') self.action_report_done.triggered.connect(self.report_done) self.action_exit = QAction('Exit') self.action_exit.triggered.connect(self.close) self.action_frameless = QAction('Frameless window') self.action_frameless.setCheckable(True) self.action_frameless.triggered.connect(self.set_frameless_window) self.action_homepage = QAction('Open homepage') self.action_homepage.triggered.connect(self.open_homepage) self.context_menu = QMenu() self.context_menu.addAction(self.action_report_done) self.context_menu.addAction(self.action_exit) self.context_menu.addAction(self.action_frameless) self.context_menu.addAction(self.action_homepage) # threads self.thread_communication = QThread() self.thread_communication.start() # workers self.worker_communication = CommunicationWorker( netloc=self.config.netloc, base_path=self.config.base_path, api_key=self.config.api_key, guild=self.config.guild, member=self.config.member) self.worker_communication.moveToThread(self.thread_communication) # signals self.worker_communication.signal_get_ping_done.connect(self.get_ping_done) self.worker_communication.signal_post_history_done.connect(self.post_history_done) self.signal_get_ping.connect(self.worker_communication.get_ping) self.signal_post_history.connect(self.worker_communication.post_history) # get ping timer QTimer.singleShot(0, self.get_ping) self.timer_ping = QTimer() self.timer_ping.timeout.connect(self.get_ping) self.timer_ping.setTimerType(Qt.VeryCoarseTimer) self.timer_ping.start(self.INTERVAL_SECS * 1000) # switch label timer self.timer_label = QTimer() self.timer_label.timeout.connect(lambda: self.setCurrentWidget(self.widget_ping)) self.timer_label.setSingleShot(True) self.timer_label.setTimerType(Qt.CoarseTimer) # window attributes size = self.settings.get('window', 'size', type_=QSize) if size is not None: self.resize(size) pos = self.settings.get('window', 'pos', type_=QPoint) if pos is not None: self.move(pos) frameless = self.settings.get('window', 'frameless', type_=bool) if frameless is not None and frameless: QTimer.singleShot(100, self.action_frameless.trigger) self.setWindowFlag(Qt.WindowStaysOnTopHint, self.config.window_stays_on_top) self.setAttribute(Qt.WA_TranslucentBackground) self.setWindowTitle('Evelyn Reminder') def closeEvent( self, event: QCloseEvent ) -> None: # save settings with suppress_and_log_exception(): self.settings.set('window', 'size', self.size()) self.settings.set('window', 'pos', self.pos()) self.settings.set('window', 'frameless', bool(self.windowFlags() & Qt.FramelessWindowHint)) # stop communication thread with suppress_and_log_exception(): self.thread_communication.quit() self.thread_communication.wait() # done super().closeEvent(event) def contextMenuEvent( self, event: QContextMenuEvent ) -> None: self.context_menu.exec_(event.globalPos()) @Slot() def get_ping(self) -> None: logging.info('Get ping ...') self.signal_get_ping.emit() @Slot(int, str, str) def get_ping_done( self, key: int, text: str, color: str ) -> None: logging.info('Get ping done') if key == -1: self.state_key = None self.label_ping.setWordWrap(True) else: self.state_key = key self.label_ping.setWordWrap(False) self.label_ping.setText(text) self.widget_ping.setStyleSheet(f'background : {color}; ') @Slot() def post_history( self, date_time: QDateTime = QDateTime() ) -> None: # this method is called as Slot by ClickableLabel.mouseReleaseEvent() without arguments # this method is called directly by EvelynDesktop.report_done() with a date_time if self.state_key is None: return logging.info('Post history ...') self.label_alert.setText('Sending ...') self.label_alert.setStyleSheet(f'background: #dddddd;') self.setCurrentWidget(self.label_alert) self.signal_post_history.emit(self.state_key, date_time) @Slot(str, bool) def post_history_done( self, text: str, error: bool ) -> None: logging.info('Post history done') self.label_alert.setText(text) if error: self.label_alert.setStyleSheet(f'background: #dd4b4b;') self.timer_label.start(self.ALERT_SECS * 1000) # trigger instant ping update to avoid outdated info self.timer_ping.stop() self.timer_ping.start(self.INTERVAL_SECS * 1000) self.get_ping() @Slot() def report_done(self) -> None: self.timer_ping.stop() # stop ping update while dialog is open report_done_dialog = ReportDoneDialog(self) response = report_done_dialog.exec() if response != QDialog.Accepted: self.timer_ping.start(self.INTERVAL_SECS * 1000) self.get_ping() return date_time = report_done_dialog.get_date_time() self.post_history(date_time) @Slot(bool) def set_frameless_window( self, value: bool ) -> None: pos = self.pos() self.setWindowFlag(Qt.FramelessWindowHint, value) # workaround: window goes invisible otherwise self.setVisible(True) # workaround: window would move up otherwise if value: QTimer.singleShot(100, lambda: self.move(pos)) @Slot() def open_homepage(self) -> None: webbrowser.open('https://github.com/stefs/evelyn-reminder')
class SimpleTimer(QWidget, BaseTimer): paused = Signal() def __init__(self, parent=None): super().__init__(parent) self.timer = QTimer(self) self.timer.setSingleShot(True) self.update_timer = QTimer(self) self.setting_time = 0 self.remaining = 0 self.remain_label = QLabel('00:00', self) self.remain_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.remain_label.setScaledContents(True) font = QFont() font.setPointSize(86) self.remain_label.setFont(font) self.timer_edit = QSpinBox(self) self.timer_edit.setRange(1, 99) self.timer_edit.setValue(25) self.set_ui() self.set_connection() self.show() def set_ui(self): vlayout = QVBoxLayout() vlayout.addWidget(self.remain_label, alignment=Qt.AlignHCenter) vlayout.addWidget(self.timer_edit) self.setLayout(vlayout) def set_connection(self): self.timer.timeout.connect(self.stop) self.update_timer.timeout.connect(self.remain_update) def start(self): self.setting_time = self.timer_edit.value() self.timer.start(self.setting_time * 60 * 1000) self.set_remain_update() self.started.emit() def set_remain_update(self): self.remain_update() self.update_timer.start(250) def stop(self): self.reset() self.finished.emit() def abort(self): self.reset() self.timer.stop() self.aborted.emit() def pause(self): self.update_timer.stop() self.remaining = self.timer.remainingTime() self.timer.stop() self.paused.emit() def resume(self): self.timer.start(self.remaining) self.set_remain_update() self.started.emit() def reset(self): self.timer.stop() self.update_timer.stop() self.remain_label.setText('00:00') self.remaining = 0 def get_notify_message(self): remaining = self.timer.remainingTime() / 1000 return '{0} minutes have passed.'.format(self.setting_time - int(remaining/60)) def remain_update(self): remaining = self.timer.remainingTime() / 1000 self.remain_label.setText('{min:02}:{sec:02}'.format( min=int(remaining / 60), sec=int(remaining % 60))) @property def name(self): return 'Simple Timer'
class MainWindow(QMainWindow): def __init__(self): super(MainWindow, self).__init__() self.ui = Ui_MainWindow() self.ui.setupUi(self) self.art_scene = QGraphicsScene(self) self.art_scene.addText('Open an image file.') self.ui.art_view.setScene(self.art_scene) self.symbols_scene = QGraphicsScene(self) self.ui.symbols_view.setScene(self.symbols_scene) self.ui.action_exit.triggered.connect(self.close) self.ui.action_open_art.triggered.connect(self.open_image) self.ui.action_open_words.triggered.connect(self.open_words) self.ui.action_save.triggered.connect(self.save_pdf) self.ui.action_save_png.triggered.connect(self.save_png) self.ui.action_shuffle.triggered.connect(self.shuffle) self.ui.action_sort.triggered.connect(self.sort) self.ui.rows.valueChanged.connect(self.on_options_changed) self.ui.columns.valueChanged.connect(self.on_options_changed) self.ui.word_clues_radio.toggled.connect(self.on_options_changed) self.ui.symbol_clues_radio.toggled.connect(self.on_options_changed) self.word_layout = QGridLayout(self.ui.word_content) self.ui.word_scroll.setWidgetResizable(True) self.word_labels: typing.Dict[str, QLabel] = {} self.word_shuffler = WordShuffler([]) self.clues = None self.pixmap = self.scaled_pixmap = self.mini_pixmap = None self.sliced_pixmap_item: typing.Optional[QGraphicsPixmapItem] = None self.sliced_image: typing.Optional[QImage] = None self.selection_grid: typing.Optional[SelectionGrid] = None self.cells = [] self.art_shuffler: typing.Optional[ArtShuffler] = None self.symbols_source_pixmap_item: typing.Optional[ QGraphicsPixmapItem] = None self.symbols_pixmap_item: typing.Optional[QGraphicsPixmapItem] = None self.symbols_image: typing.Optional[QImage] = None self.symbols_shuffler: typing.Optional[ArtShuffler] = None self.selected_row: typing.Optional[int] = None self.selected_column: typing.Optional[int] = None self.settings = QSettings() self.image_path: typing.Optional[str] = self.settings.value( 'image_path') self.words_path: typing.Optional[str] = self.settings.value( 'words_path') self.dirty_letters = set() self.timer = QTimer() self.timer.setInterval(500) self.timer.setSingleShot(True) # noinspection PyUnresolvedReferences self.timer.timeout.connect(self.on_dirty) self.row_count = self.column_count = 0 self.clue_type = ClueType.words self.ui.rows.setValue(self.settings.value('row_count', 6, int)) self.ui.columns.setValue(self.settings.value('column_count', 4, int)) clue_type_name = self.settings.value('clue_type', ClueType.words.name) try: clue_type = ClueType[clue_type_name] except KeyError: clue_type = ClueType.words if clue_type == ClueType.words: self.ui.word_clues_radio.setChecked(True) else: self.ui.symbol_clues_radio.setChecked(True) self.row_clues: typing.List[QPixmap] = [] self.column_clues: typing.List[QPixmap] = [] self.on_options_changed() def on_dirty(self): for letter in self.dirty_letters: self.word_labels[letter].setText( self.word_shuffler.make_display(letter)) self.settings.setValue(f'word_{letter}', self.word_shuffler[letter]) if self.dirty_letters: self.clues = self.word_shuffler.make_clues() self.art_shuffler.clues = dict(self.clues) self.dirty_letters.clear() self.on_selection_moved() if self.pixmap is not None: x, y, width, height = self.get_selected_fraction() self.settings.setValue('x', x) self.settings.setValue('y', y) self.settings.setValue('width', width) self.settings.setValue('height', height) new_rows = self.ui.rows.value() new_columns = self.ui.columns.value() if self.ui.word_clues_radio.isChecked(): new_clue_type = ClueType.words else: new_clue_type = ClueType.symbols if (new_rows, new_columns, new_clue_type) == (self.row_count, self.column_count, self.clue_type): return self.settings.setValue('row_count', new_rows) self.settings.setValue('column_count', new_columns) self.settings.setValue('clue_type', new_clue_type.name) self.row_count, self.column_count = new_rows, new_columns self.clue_type = new_clue_type word_count = (self.row_count * self.column_count) while self.word_layout.count(): layout_item = self.word_layout.takeAt(0) layout_item.widget().deleteLater() self.word_labels.clear() self.row_clues.clear() self.column_clues.clear() if self.image_path is not None: self.load_image(self.image_path) if self.words_path is not None: self.load_words(self.words_path) letters = [chr(65 + i) for i in range(word_count)] if self.word_shuffler.needs_blank: letters.insert(0, '') word_fields = {} for i, letter in enumerate(letters): word_field = QLineEdit() self.word_layout.addWidget(word_field, i, 0) # noinspection PyUnresolvedReferences word_field.textEdited.connect(partial(self.on_word_edited, letter)) word_label = QLabel() self.word_layout.addWidget(word_label, i, 1) self.word_labels[letter] = word_label word_fields[letter] = word_field for i, letter in enumerate(letters): word = self.settings.value(f'word_{letter}', '') self.word_shuffler[letter] = word self.dirty_letters.add(letter) word_fields[letter].setText(word) def on_options_changed(self, *_): self.timer.start() def shuffle(self): self.clues = self.word_shuffler.make_clues() if self.art_shuffler is not None: self.art_shuffler.shuffle() self.on_selection_moved() def sort(self): if self.art_shuffler is not None: self.art_shuffler.sort() self.on_selection_moved() def open_words(self): word_filter = 'Text files (*.txt)' if self.words_path is None: words_folder = None else: words_folder = str(Path(self.words_path).parent) file_name, _ = QFileDialog.getOpenFileName(self, "Open a words file.", dir=words_folder, filter=word_filter) if not file_name: return self.settings.setValue('words_path', file_name) self.load_words(file_name) def load_words(self, words_path): with open(words_path) as f: choice = 0 if choice == 0: self.word_shuffler = WordShuffler(f) else: self.word_shuffler = WordStripper(f) def open_image(self): formats = QImageReader.supportedImageFormats() patterns = (f'*.{fmt.data().decode()}' for fmt in formats) image_filter = f'Images ({" ".join(patterns)})' if self.image_path is None: image_folder = None else: image_folder = str(Path(self.image_path).parent) file_name, _ = QFileDialog.getOpenFileName(self, "Open an image file.", dir=image_folder, filter=image_filter) if not file_name: return self.settings.setValue('image_path', file_name) self.load_image(file_name) def load_image(self, image_path): self.pixmap = QPixmap(image_path) if self.pixmap.isNull(): self.pixmap = None self.image_path = image_path self.scale_image() def scale_image(self): if self.pixmap is None: return if self.selection_grid is None: x = self.settings.value('x', 0.0, float) y = self.settings.value('y', 0.0, float) width = self.settings.value('width', 1.0, float) height = self.settings.value('height', 1.0, float) else: x, y, width, height = self.get_selected_fraction() self.art_scene.clear() self.cells.clear() view_size = self.ui.art_view.maximumViewportSize() if view_size.width() == 0: return self.art_scene.setSceneRect(0, 0, view_size.width(), view_size.height()) display_size = QSize(view_size.width() * 0.99 / 2, view_size.height() * 0.99) self.scaled_pixmap = self.pixmap.scaled( display_size, aspectMode=Qt.AspectRatioMode.KeepAspectRatio) self.art_scene.addPixmap(self.scaled_pixmap) scaled_size = self.scaled_pixmap.size() self.selection_grid = SelectionGrid(scaled_size.width() * x, scaled_size.height() * y, scaled_size.width() * width, scaled_size.height() * height, row_count=self.row_count, column_count=self.column_count) self.selection_grid.on_moved = self.on_selection_moved self.art_scene.addItem(self.selection_grid) self.sliced_image = QImage(display_size, QImage.Format.Format_ARGB32_Premultiplied) self.check_clues() self.art_shuffler = ArtShuffler(self.selection_grid.row_count, self.selection_grid.column_count, self.sliced_image, QRect(0, 0, display_size.width(), display_size.height()), clues=self.clues, row_clues=self.row_clues, column_clues=self.column_clues) self.sliced_pixmap_item = self.art_scene.addPixmap( QPixmap.fromImage(self.sliced_image)) self.sliced_pixmap_item.setPos(display_size.width(), 0) self.symbols_scene.clear() self.symbols_source_pixmap_item = self.symbols_scene.addPixmap( self.scaled_pixmap) self.symbols_image = QImage(display_size, QImage.Format.Format_ARGB32_Premultiplied) if self.symbols_shuffler is not None: selected_row = self.symbols_shuffler.selected_row selected_column = self.symbols_shuffler.selected_column else: selected_row = 0 selected_column = None self.symbols_shuffler = ArtShuffler(self.selection_grid.row_count, self.selection_grid.column_count, self.symbols_image, QRect(0, 0, display_size.width(), display_size.height()), row_clues=self.row_clues, column_clues=self.column_clues) self.symbols_shuffler.selected_row = selected_row self.symbols_shuffler.selected_column = selected_column self.symbols_pixmap_item = ClickablePixmapItem( QPixmap.fromImage(self.symbols_image)) self.symbols_pixmap_item.on_click = self.on_symbols_clicked self.symbols_scene.addItem(self.symbols_pixmap_item) self.symbols_pixmap_item.setPos(display_size.width(), 0) self.on_selection_moved() def on_symbols_clicked(self, event: QGraphicsSceneMouseEvent): self.symbols_scene.clearSelection() self.symbols_shuffler.select_clue(event.pos().toPoint()) self.on_selection_moved() def on_word_edited(self, letter, word): self.word_shuffler[letter] = word self.dirty_letters.add(letter) self.timer.start() def get_selected_fraction(self): selection_rect = self.selection_grid.rect() selection_pos = self.selection_grid.pos() size = self.scaled_pixmap.size() x = (selection_pos.x() + selection_rect.x()) / size.width() width = selection_rect.width() / size.width() y = (selection_pos.y() + selection_rect.y()) / size.height() height = selection_rect.height() / size.height() return x, y, width, height def on_selection_moved(self): selected_pixmap = self.get_selected_pixmap() self.art_shuffler.draw(selected_pixmap) self.sliced_pixmap_item.setPixmap(QPixmap.fromImage(self.sliced_image)) selected_pixmap = self.get_selected_pixmap() cell_width = (selected_pixmap.width() / self.selection_grid.column_count) cell_height = (selected_pixmap.height() / self.selection_grid.row_count) self.row_clues.clear() self.column_clues.clear() for i in range(self.selection_grid.row_count): clue_image = selected_pixmap.copy(0, i * cell_height, cell_width, cell_height) self.row_clues.append(clue_image) for j in range(self.selection_grid.column_count): clue_image = selected_pixmap.copy(j * cell_width, 0, cell_width, cell_height) self.column_clues.append(clue_image) self.symbols_shuffler.row_clues = self.row_clues self.symbols_shuffler.column_clues = self.column_clues self.symbols_shuffler.draw_grid(selected_pixmap) self.symbols_pixmap_item.setPixmap( QPixmap.fromImage(self.symbols_image)) self.timer.start() def get_selected_pixmap(self) -> QPixmap: x, y, width, height = self.get_selected_fraction() original_size = self.pixmap.size() selected_pixmap = self.pixmap.copy(x * original_size.width(), y * original_size.height(), width * original_size.width(), height * original_size.height()) return selected_pixmap def resizeEvent(self, event: QResizeEvent): super().resizeEvent(event) self.scale_image() def save_pdf(self): pdf_folder = self.settings.value('pdf_folder') file_name, _ = QFileDialog.getSaveFileName(self, "Save a PDF file.", dir=pdf_folder, filter='Documents (*.pdf)') if not file_name: return self.settings.setValue('pdf_folder', os.path.dirname(file_name)) writer = QPdfWriter(file_name) writer.setPageSize(QPageSize(QPageSize.Letter)) writer.setTitle('Sliced Art Puzzle') writer.setCreator('Don Kirkby') self.paint_puzzle(writer) def save_png(self): pdf_folder = self.settings.value('pdf_folder') file_name, _ = QFileDialog.getSaveFileName(self, "Save an image file.", dir=pdf_folder, filter='Images (*.png)') if not file_name: return writer = QPixmap(1000, 2000) self.paint_puzzle(writer) writer.save(file_name) self.settings.setValue('pdf_folder', os.path.dirname(file_name)) def paint_puzzle(self, writer: QPaintDevice): self.check_clues() painter = QPainter(writer) try: print_shuffler = ArtShuffler(self.art_shuffler.rows, self.art_shuffler.cols, writer, QRect(0, 0, writer.width(), round(writer.height() / 2)), clues=self.clues, row_clues=self.row_clues, column_clues=self.column_clues) print_shuffler.cells = self.art_shuffler.cells[:] print_shuffler.is_shuffled = self.art_shuffler.is_shuffled selected_pixmap = self.get_selected_pixmap() print_shuffler.draw(selected_pixmap, painter) print_shuffler.rect.moveTop(writer.height() / 2) print_shuffler.draw_grid(selected_pixmap, painter) finally: painter.end() def check_clues(self): if self.clue_type == ClueType.words: if self.clues is None: self.clues = self.word_shuffler.make_clues() self.row_clues.clear() self.column_clues.clear() else: self.clues = None
class NotepadDockWidget(QWidget, DockContextHandler): """ NotepadDockWidget is the - you guessed it - notepad dock widget. It is responsible for saving and restoring the user's notes, and providing the user an interface to edit their notes from. """ # Tab container tab_container: QTabWidget # The actual editor widget. editor: JMarkdownEditor # Viewer/content widget viewer: JMarkdownViewer # Timer for auto-saving. save_timer: QTimer # The currently focused BinaryView. bv: Optional[BinaryView] = None def __init__(self, parent: QWidget, name: str, bv: Optional[BinaryView]): """ Initialize a new NotepadDockWidget. :param parent: the QWidget to parent this NotepadDockWidget to :param name: the name to register the dock widget under :param bv: the currently focused BinaryView (may be None) """ self.bv = bv QWidget.__init__(self, parent) DockContextHandler.__init__(self, self, name) # Set up the save timer to save the current notepad content on timeout. self.save_timer = QTimer(self) self.save_timer.setSingleShot(True) self.save_timer.timeout.connect(lambda: self.store_notes()) # Initialize the editor and set up the text changed callback. self.editor = JMarkdownEditor(self) self.editor.textChanged.connect(self.on_editor_text_changed) # Create the viewer self.viewer = JMarkdownViewer(self, self.editor) # Add both widgets to a tab container self.tab_container = QTabWidget() self.tab_container.addTab(self.viewer, "View") self.tab_container.addTab(self.editor, "Edit") # Create a simple layout for the editor and set it as the root layout. layout = QVBoxLayout() layout.addWidget(self.tab_container) layout.setContentsMargins(0, 0, 0, 0) self.setLayout(layout) # Probably does nothing since there should be no BV when the program # starts, but needs confirmation, so here it will stay. self.query_notes() def on_editor_text_changed(self): """ Callback triggered when the editor's text changes. Used is to start the save timer, which will automatically save the user's notes after 1 second of typing inactivity. """ self.save_timer.start(1000) def on_binary_view_changed(self, new_bv: BinaryView): """ Callback triggered when the focused BinaryView changes. Used to save the notes of the old BinaryView and load the stored notes from the new BinaryView, if there are any. """ # Do nothing if the new view is the old view. if self.bv == new_bv: return # Do nothing if we have no BinaryView, which might happen at startup? if self.bv is not None: self.store_notes() # Update the internal BinaryView reference to point to the new # BinaryView, and attempt to load the notes stored in it. If there is no # new BinaryView, remove the reference to the old one. if new_bv is None: self.bv = None else: self.bv = new_bv self.query_notes() def query_notes(self): """ Attempt to retrieve the saved notes from the database. Will do nothing if there is no active BinaryView. """ # Do nothing if we have no BinaryView, which might happen at startup? if self.bv is None: return # Attempt to access the notes stored in the database metadata. Will # throw a KeyError if this is a brand new database. Block signals to # avoid accidentally triggering the save event. self.editor.blockSignals(True) try: notes = self.bv.query_metadata(METADATA_KEY) self.editor.setPlainText(notes) # Manually trigger the text changed event since we disabled signals self.viewer.on_editor_text_changed() except KeyError: self.editor.setPlainText("") self.editor.blockSignals(False) def store_notes(self): """ Attempt to store the current notes in the database. Will do nothing if there is no active BinaryView. """ # Do nothing if we have no BinaryView, which might happen at startup? if self.bv is None: pass # Store the notes in the metadata of the database. notes = self.editor.toPlainText() self.bv.store_metadata(METADATA_KEY, notes) self.bv.modified = True # -- Binary Ninja UI callbacks (camelCase shenanigans warning) -- # Should the notepad be visible? Helps with automatically hiding the notepad # when it is not needed. def shouldBeVisible(self, vf): return vf is not None # Triggered when a view changes, vf is a ViewFrame. def notifyViewChanged(self, vf): self.on_binary_view_changed(vf.getCurrentViewInterface().getData())
class AlarmClock(QWidget): started = Signal() aborted = Signal() finished = Signal() def __init__(self, parent=None): super().__init__(parent) self.alarm = QTime() self.timer = QTimer(self) self.timer.setSingleShot(True) self.update_timer = QTimer(self) self.update_timer.setInterval(1000) self.current_label = QLabel('00:00:00') font = QFont() font.setPointSize(50) self.current_label.setFont(font) self.time_edit = QTimeEdit(self) self.time_edit.setDisplayFormat('H:mm') self.set_around_btn = QPushButton('Set around time', self) self.set_ui() self.set_connection() self.reset() self.update_timer.start() self.update_label() self.set_around_time() def set_ui(self): self.vlayout = QVBoxLayout(self) self.vlayout.addWidget(self.current_label) self.vlayout.addWidget(self.time_edit) self.vlayout.addWidget(self.set_around_btn) def set_connection(self): self.timer.timeout.connect(self.stop) self.update_timer.timeout.connect(self.update_label) self.set_around_btn.clicked.connect(self.set_around_time) def set_around_time(self): current = QTime.currentTime() if current.minute() < 29: self.time_edit.setTime(QTime(current.hour(), 30)) else: self.time_edit.setTime(QTime(current.hour() + 1, 0)) def update_label(self): current = QTime.currentTime() self.current_label.setText('{hour:02}:{min:02}:{sec:02}'.format( hour=current.hour(), min=current.minute(), sec=current.second())) def start(self): self.time_edit.setEnabled(False) self.alarm = self.time_edit.time() self.timer.start(QTime.currentTime().msecsTo(self.alarm)) self.started.emit() def abort(self): self.reset() self.aborted.emit() def stop(self): self.reset() self.finished.emit() def reset(self): self.timer.stop() self.time_edit.setEnabled(True) def get_notify_message(self): return '' @property def name(self): return 'Alarm Clock'
class DebugView(QWidget, View): class DebugViewHistoryEntry(HistoryEntry): def __init__(self, memory_addr, address, is_raw): HistoryEntry.__init__(self) self.memory_addr = memory_addr self.address = address self.is_raw = is_raw def __repr__(self): if self.is_raw: return "<raw history: {}+{:0x} (memory: {:0x})>".format(self.address['module'], self.address['offset'], self.memory_addr) return "<code history: {:0x} (memory: {:0x})>".format(self.address, self.memory_addr) def __init__(self, parent, data): if not type(data) == BinaryView: raise Exception('expected widget data to be a BinaryView') self.bv = data self.debug_state = binjaplug.get_state(data) memory_view = self.debug_state.memory_view self.debug_state.ui.debug_view = self QWidget.__init__(self, parent) self.controls = ControlsWidget.DebugControlsWidget(self, "Controls", data, self.debug_state) View.__init__(self) self.setupView(self) self.current_offset = 0 self.splitter = QSplitter(Qt.Orientation.Horizontal, self) frame = ViewFrame.viewFrameForWidget(self) self.memory_editor = LinearView(memory_view, frame) self.binary_editor = DisassemblyContainer(frame, data, frame) self.binary_text = TokenizedTextView(self, memory_view) self.is_raw_disassembly = False self.raw_address = 0 self.is_navigating_history = False self.memory_history_addr = 0 # TODO: Handle these and change views accordingly # Currently they are just disabled as the DisassemblyContainer gets confused # about where to go and just shows a bad view self.binary_editor.getDisassembly().actionHandler().bindAction("View in Hex Editor", UIAction()) self.binary_editor.getDisassembly().actionHandler().bindAction("View in Linear Disassembly", UIAction()) self.binary_editor.getDisassembly().actionHandler().bindAction("View in Types View", UIAction()) self.memory_editor.actionHandler().bindAction("View in Hex Editor", UIAction()) self.memory_editor.actionHandler().bindAction("View in Disassembly Graph", UIAction()) self.memory_editor.actionHandler().bindAction("View in Types View", UIAction()) small_font = QApplication.font() small_font.setPointSize(11) bv_layout = QVBoxLayout() bv_layout.setSpacing(0) bv_layout.setContentsMargins(0, 0, 0, 0) bv_label = QLabel("Loaded File") bv_label.setFont(small_font) bv_layout.addWidget(bv_label) bv_layout.addWidget(self.binary_editor) self.bv_widget = QWidget() self.bv_widget.setLayout(bv_layout) disasm_layout = QVBoxLayout() disasm_layout.setSpacing(0) disasm_layout.setContentsMargins(0, 0, 0, 0) disasm_label = QLabel("Raw Disassembly at PC") disasm_label.setFont(small_font) disasm_layout.addWidget(disasm_label) disasm_layout.addWidget(self.binary_text) self.disasm_widget = QWidget() self.disasm_widget.setLayout(disasm_layout) memory_layout = QVBoxLayout() memory_layout.setSpacing(0) memory_layout.setContentsMargins(0, 0, 0, 0) memory_label = QLabel("Debugged Process") memory_label.setFont(small_font) memory_layout.addWidget(memory_label) memory_layout.addWidget(self.memory_editor) self.memory_widget = QWidget() self.memory_widget.setLayout(memory_layout) self.splitter.addWidget(self.bv_widget) self.splitter.addWidget(self.memory_widget) # Equally sized self.splitter.setSizes([0x7fffffff, 0x7fffffff]) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self.controls) layout.addWidget(self.splitter, 100) self.setLayout(layout) self.needs_update = True self.update_timer = QTimer(self) self.update_timer.setInterval(200) self.update_timer.setSingleShot(False) self.update_timer.timeout.connect(lambda: self.updateTimerEvent()) self.add_scripting_ref() # set initial breakpoint when view is switched if self.debug_state.bv and self.debug_state.bv.entry_point: local_entry_offset = self.debug_state.bv.entry_point - self.debug_state.bv.start if not self.debug_state.breakpoints.contains_offset(self.debug_state.bv.file.original_filename, local_entry_offset): self.debug_state.breakpoints.add_offset(self.debug_state.bv.file.original_filename, local_entry_offset) if self.debug_state.ui is not None: self.debug_state.ui.breakpoint_tag_add(self.debug_state.bv.entry_point) self.debug_state.ui.update_highlights() self.debug_state.ui.update_breakpoints() def add_scripting_ref(self): # Hack: The interpreter is just a thread, so look through all threads # and assign our state to the interpreter's locals for thread in threading.enumerate(): if type(thread) == PythonScriptingInstance.InterpreterThread: thread.locals["dbg"] = self.debug_state def getData(self): return self.bv def getFont(self): return binaryninjaui.getMonospaceFont(self) def getCurrentOffset(self): if not self.is_raw_disassembly: return self.binary_editor.getDisassembly().getCurrentOffset() return self.raw_address def getSelectionOffsets(self): if not self.is_raw_disassembly: return self.binary_editor.getDisassembly().getSelectionOffsets() return (self.raw_address, self.raw_address) def getCurrentFunction(self): if not self.is_raw_disassembly: return self.binary_editor.getDisassembly().getCurrentFunction() return None def getCurrentBasicBlock(self): if not self.is_raw_disassembly: return self.binary_editor.getDisassembly().getCurrentBasicBlock() return None def getCurrentArchitecture(self): if not self.is_raw_disassembly: return self.binary_editor.getDisassembly().getCurrentArchitecture() return None def getCurrentLowLevelILFunction(self): if not self.is_raw_disassembly: return self.binary_editor.getDisassembly().getCurrentLowLevelILFunction() return None def getCurrentMediumLevelILFunction(self): if not self.is_raw_disassembly: return self.binary_editor.getDisassembly().getCurrentMediumLevelILFunction() return None def getHistoryEntry(self): if self.is_navigating_history: return None memory_addr = self.memory_editor.getCurrentOffset() if memory_addr != self.memory_history_addr: self.memory_history_addr = memory_addr if self.is_raw_disassembly and self.debug_state.connected: rel_addr = self.debug_state.modules.absolute_addr_to_relative(self.raw_address) return DebugView.DebugViewHistoryEntry(memory_addr, rel_addr, True) else: address = self.binary_editor.getDisassembly().getCurrentOffset() return DebugView.DebugViewHistoryEntry(memory_addr, address, False) def navigateToFunction(self, func, offset): return self.navigate(offset) def navigateToHistoryEntry(self, entry): self.is_navigating_history = True if hasattr(entry, 'is_raw'): self.memory_editor.navigate(entry.memory_addr) if entry.is_raw: if self.debug_state.connected: address = self.debug_state.modules.relative_addr_to_absolute(entry.address) self.navigate_raw(address) else: self.navigate_live(entry.address) View.navigateToHistoryEntry(self, entry) self.is_navigating_history = False def navigate(self, addr): # If we're not connected we cannot even check if the address is remote if not self.debug_state.connected: return self.navigate_live(addr) if self.debug_state.memory_view.is_local_addr(addr): local_addr = self.debug_state.memory_view.remote_addr_to_local(addr) if self.debug_state.bv.read(local_addr, 1) and len(self.debug_state.bv.get_functions_containing(local_addr)) > 0: return self.navigate_live(local_addr) # This runs into conflicts if some other address space is mapped over # where the local BV is currently loaded, but this is was less likely # than the user navigating to a function from the UI if self.debug_state.bv.read(addr, 1) and len(self.debug_state.bv.get_functions_containing(addr)) > 0: return self.navigate_live(addr) return self.navigate_raw(addr) def navigate_live(self, addr): self.show_raw_disassembly(False) return self.binary_editor.getDisassembly().navigate(addr) def navigate_raw(self, addr): if not self.debug_state.connected: # Can't navigate to remote addr when disconnected return False self.raw_address = addr self.show_raw_disassembly(True) self.load_raw_disassembly(addr) return True def notifyMemoryChanged(self): self.needs_update = True def updateTimerEvent(self): if self.needs_update: self.needs_update = False # Refresh the editor if not self.debug_state.connected: self.memory_editor.navigate(0) return # self.memory_editor.navigate(self.debug_state.stack_pointer) def showEvent(self, event): if not event.spontaneous(): self.update_timer.start() self.add_scripting_ref() def hideEvent(self, event): if not event.spontaneous(): self.update_timer.stop() def shouldBeVisible(self, view_frame): if view_frame is None: return False else: return True def load_raw_disassembly(self, start_ip): # Read a few instructions from rip and disassemble them inst_count = 50 arch_dis = self.debug_state.remote_arch rip = self.debug_state.ip # Assume the worst, just in case read_length = arch_dis.max_instr_length * inst_count data = self.debug_state.memory_view.read(start_ip, read_length) lines = [] # Append header line tokens = [InstructionTextToken(InstructionTextTokenType.TextToken, "(Code not backed by loaded file, showing only raw disassembly)")] contents = DisassemblyTextLine(tokens, start_ip) line = LinearDisassemblyLine(LinearDisassemblyLineType.BasicLineType, None, None, contents) lines.append(line) total_read = 0 for i in range(inst_count): line_addr = start_ip + total_read (insn_tokens, length) = arch_dis.get_instruction_text(data[total_read:], line_addr) if insn_tokens is None: insn_tokens = [InstructionTextToken(InstructionTextTokenType.TextToken, "??")] length = arch_dis.instr_alignment if length == 0: length = 1 # terrible libshiboken workaround, see #101 for tok in insn_tokens: if tok.value.bit_length() == 64: tok.value ^= 0x8000000000000000 tokens = [] color = HighlightStandardColor.NoHighlightColor if line_addr == rip: if self.debug_state.breakpoints.contains_absolute(start_ip + total_read): # Breakpoint & pc tokens.append(InstructionTextToken(InstructionTextTokenType.TagToken, self.debug_state.ui.get_breakpoint_tag_type().icon + ">", width=5)) color = HighlightStandardColor.RedHighlightColor else: # PC tokens.append(InstructionTextToken(InstructionTextTokenType.TextToken, " ==> ")) color = HighlightStandardColor.BlueHighlightColor else: if self.debug_state.breakpoints.contains_absolute(start_ip + total_read): # Breakpoint tokens.append(InstructionTextToken(InstructionTextTokenType.TagToken, self.debug_state.ui.get_breakpoint_tag_type().icon, width=5)) color = HighlightStandardColor.RedHighlightColor else: # Regular line tokens.append(InstructionTextToken(InstructionTextTokenType.TextToken, " ")) # Address tokens.append(InstructionTextToken(InstructionTextTokenType.AddressDisplayToken, hex(line_addr)[2:], line_addr)) tokens.append(InstructionTextToken(InstructionTextTokenType.TextToken, " ")) tokens.extend(insn_tokens) # Convert to linear disassembly line contents = DisassemblyTextLine(tokens, line_addr, color=color) line = LinearDisassemblyLine(LinearDisassemblyLineType.CodeDisassemblyLineType, None, None, contents) lines.append(line) total_read += length self.binary_text.setLines(lines) def show_raw_disassembly(self, raw): if raw != self.is_raw_disassembly: self.splitter.replaceWidget(0, self.disasm_widget if raw else self.bv_widget) self.is_raw_disassembly = raw def refresh_raw_disassembly(self): if not self.debug_state.connected: # Can't navigate to remote addr when disconnected return if self.is_raw_disassembly: self.load_raw_disassembly(self.getCurrentOffset())
class StatusBar(QWidget): def __init__(self, parent=None) -> None: super().__init__() self.parent = parent # self.message_label = QLabel(self) self.sep1 = QLabel("|", self) self.curr_pos_label = QLabel(self) self.sep2 = QLabel("|", self) self.file_name_label = QLabel(self) self.sep3 = QLabel("|", self) self.resolution_label = QLabel(self) self.sep4 = QLabel("|", self) # self.memory_label = QLabel(self) # self.sep5 = QLabel("|", self) self.mode_label = QLabel(self) self.sep5 = QLabel("|", self) self.progressbar = QProgressBar(self) self.progressbar.setMaximumWidth(150) self.progressbar.setMaximumHeight(15) self.progressbar.hide() # layout = QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) self.setLayout(layout) self.layout().addWidget(self.message_label) self.layout().addWidget(self.sep1) self.layout().addWidget(self.curr_pos_label) self.layout().addWidget(self.sep2) self.layout().addWidget(self.resolution_label) self.layout().addWidget(self.sep3) self.layout().addWidget(self.file_name_label) self.layout().addWidget(self.sep4) # self.layout().addWidget(self.memory_label) # self.layout().addWidget(self.sep5) self.layout().addWidget(self.mode_label) self.layout().addWidget(self.sep5) self.layout().addWidget(self.progressbar) # self.message_timer = None def reset(self) -> None: self.message_label.setText("") self.curr_pos_label.setText("") self.file_name_label.setText("") self.resolution_label.setText("") def flash_message(self, msg: str, wait: int = cfg.MESSAGE_FLASH_TIME_2) -> None: self.message_label.setText(msg) if self.message_timer: self.message_timer.stop() self.message_timer.deleteLater() self.message_timer = QTimer() self.message_timer.timeout.connect( self.delete_flashed_message) # type: ignore self.message_timer.setSingleShot(True) # type: ignore self.message_timer.start(wait) # type: ignore def delete_flashed_message(self) -> None: self.message_label.setText("")