def addHistoryEntry(self, url): """ Public method to add a history entry. @param url URL to be added (string) """ cleanurl = QUrl(url) if cleanurl.scheme() not in ["eric", "about"]: if cleanurl.password(): # don't save the password in the history cleanurl.setPassword("") if cleanurl.host(): cleanurl.setHost(cleanurl.host().lower()) itm = HistoryEntry(cleanurl.toString(), QDateTime.currentDateTime()) self._addHistoryEntry(itm)
def on_imagesTree_currentItemChanged(self, current, previous): """ Private slot to show a preview of the selected image. @param current current image entry (QTreeWidgetItem) @param previous old current entry (QTreeWidgetItem) """ if current is None: return imageUrl = QUrl(current.text(1)) if not imageUrl.host(): imageUrl.setHost(QUrl(self.siteAddressLabel.text()).host()) imageUrl.setScheme(QUrl(self.siteAddressLabel.text()).scheme()) import Helpviewer.HelpWindow cache = Helpviewer.HelpWindow.HelpWindow.networkAccessManager().cache() if cache: cacheData = cache.data(imageUrl) else: cacheData = None pixmap = QPixmap() invalidPixmap = False scene = QGraphicsScene(self.imagePreview) if not cacheData: invalidPixmap = True else: pixmap.loadFromData(cacheData.readAll()) if pixmap.isNull(): invalidPixmap = True if invalidPixmap: scene.addText(self.tr("Preview not available.")) else: scene.addPixmap(pixmap) self.imagePreview.setScene(scene)
def keyPressEvent(self, evt): """ Protected method to handle key presses. @param evt reference to the key press event (QKeyEvent) """ if evt.key() == Qt.Key_Escape and self.__browser is not None: self.setText( str(self.__browser.url().toEncoded(), encoding="utf-8")) self.selectAll() return currentText = self.text().strip() if evt.key() in [Qt.Key_Enter, Qt.Key_Return] and \ not currentText.lower().startswith("http://"): append = "" if evt.modifiers() == Qt.KeyboardModifiers(Qt.ControlModifier): append = ".com" elif evt.modifiers() == Qt.KeyboardModifiers( Qt.ControlModifier | Qt.ShiftModifier): append = ".org" elif evt.modifiers() == Qt.KeyboardModifiers(Qt.ShiftModifier): append = ".net" if append != "": url = QUrl("http://www." + currentText) host = url.host() if not host.lower().endswith(append): host += append url.setHost(host) self.setText(url.toString()) E5LineEdit.keyPressEvent(self, evt)
def __guessUrlFromPath(self, path): """ Private method to guess an URL given a path string. @param path path string to guess an URL for (string) @return guessed URL (QUrl) """ manager = self.__mainWindow.openSearchManager() path = Utilities.fromNativeSeparators(path) url = manager.convertKeywordSearchToUrl(path) if url.isValid(): return url try: url = QUrl.fromUserInput(path) except AttributeError: url = QUrl(path) if url.scheme() == "about" and \ url.path() == "home": url = QUrl("eric:home") if url.scheme() in ["s", "search"]: url = manager.currentEngine().searchUrl(url.path().strip()) if url.scheme() != "" and \ (url.host() != "" or url.path() != ""): return url urlString = Preferences.getHelp("DefaultScheme") + path.strip() url = QUrl.fromEncoded(urlString.encode("utf-8"), QUrl.TolerantMode) return url
def download_temp(url: QUrl) -> TempDownload: """Download the given URL into a file object. The download is not saved to disk. Returns a ``TempDownload`` object, which triggers a ``finished`` signal when the download has finished:: dl = downloads.download_temp(QUrl("https://www.example.com/")) dl.finished.connect(functools.partial(on_download_finished, dl)) After the download has finished, its ``successful`` attribute can be checked to make sure it finished successfully. If so, its contents can be read by accessing the ``fileobj`` attribute:: def on_download_finished(download: downloads.TempDownload) -> None: if download.successful: print(download.fileobj.read()) download.fileobj.close() """ fobj = io.BytesIO() fobj.name = 'temporary: ' + url.host() target = downloads.FileObjDownloadTarget(fobj) download_manager = objreg.get('qtnetwork-download-manager') return download_manager.get(url, target=target, auto_remove=True)
def __addFeed(self): """ Private slot to add a RSS feed. """ button = self.sender() urlString = button.feed[1] url = QUrl(urlString) if not url.host(): if not urlString.startswith("/"): urlString = "/" + urlString urlString = self.__browser.url().host() + urlString tmpUrl = QUrl(urlString) if not tmpUrl.scheme(): urlString = "http://" + urlString tmpUrl = QUrl(urlString) if not tmpUrl.scheme() or not tmpUrl.host(): return if not url.isValid(): return if button.feed[0]: title = button.feed[0] else: title = self.__browser.url().host() import Helpviewer.HelpWindow feedsManager = Helpviewer.HelpWindow.HelpWindow.feedsManager() if feedsManager.addFeed(urlString, title, self.__browser.icon()): if Helpviewer.HelpWindow.HelpWindow.notificationsEnabled(): Helpviewer.HelpWindow.HelpWindow.showNotification( UI.PixmapCache.getPixmap("rss48.png"), self.tr("Add RSS Feed"), self.tr("""The feed was added successfully.""")) else: E5MessageBox.information( self, self.tr("Add RSS Feed"), self.tr("""The feed was added successfully.""")) else: E5MessageBox.warning( self, self.tr("Add RSS Feed"), self.tr("""The feed was already added before.""")) self.close()
def __showEnginesMenu(self): """ Private slot to handle the display of the engines menu. """ self.__enginesMenu.clear() from .OpenSearch.OpenSearchEngineAction import OpenSearchEngineAction engineNames = self.__openSearchManager.allEnginesNames() for engineName in engineNames: engine = self.__openSearchManager.engine(engineName) action = OpenSearchEngineAction(engine, self.__enginesMenu) action.setData(engineName) action.triggered.connect(self.__changeCurrentEngine) self.__enginesMenu.addAction(action) if self.__openSearchManager.currentEngineName() == engineName: action.setCheckable(True) action.setChecked(True) ct = self.__mw.currentBrowser() linkedResources = ct.linkedResources("search") if len(linkedResources) > 0: self.__enginesMenu.addSeparator() for linkedResource in linkedResources: url = QUrl(linkedResource.href) title = linkedResource.title mimetype = linkedResource.type_ if mimetype != "application/opensearchdescription+xml": continue if url.isEmpty(): continue if url.isRelative(): url = ct.url().resolved(url) if not title: if not ct.title(): title = url.host() else: title = ct.title() action = self.__enginesMenu.addAction( self.tr("Add '{0}'").format(title), self.__addEngineFromUrl) action.setData(url) action.setIcon(ct.icon()) self.__enginesMenu.addSeparator() self.__enginesMenu.addAction(self.__mw.searchEnginesAction()) if self.__recentSearches: self.__enginesMenu.addAction(self.tr("Clear Recent Searches"), self.clear)
def data_for_url(url): """Get the data to show for the given URL. Args: url: The QUrl to show. Return: A (mimetype, data) tuple. """ norm_url = url.adjusted(QUrl.NormalizePathSegments | QUrl.StripTrailingSlash) if norm_url != url: raise Redirect(norm_url) path = url.path() host = url.host() query = urlutils.query_string(url) # A url like "qute:foo" is split as "scheme:path", not "scheme:host". log.misc.debug("url: {}, path: {}, host {}".format( url.toDisplayString(), path, host)) if not path or not host: new_url = QUrl() new_url.setScheme('qute') # When path is absent, e.g. qute://help (with no trailing slash) if host: new_url.setHost(host) # When host is absent, e.g. qute:help else: new_url.setHost(path) new_url.setPath('/') if query: new_url.setQuery(query) if new_url.host(): # path was a valid host raise Redirect(new_url) try: handler = _HANDLERS[host] except KeyError: raise NoHandlerFound(url) try: mimetype, data = handler(url) except OSError as e: # FIXME:qtwebengine how to handle this? raise QuteSchemeOSError(e) except QuteSchemeError as e: raise assert mimetype is not None, url if mimetype == 'text/html' and isinstance(data, str): # We let handlers return HTML as text data = data.encode('utf-8', errors='xmlcharrefreplace') return mimetype, data
def __saveImage(self): """ Private slot to save the selected image to disk. """ act = self.sender() index = act.data() itm = self.imagesTree.topLevelItem(index) if itm is None: return imageUrl = QUrl(itm.text(1)) if not imageUrl.host(): imageUrl.setHost(QUrl(self.siteAddressLabel.text()).host()) imageUrl.setScheme(QUrl(self.siteAddressLabel.text()).scheme()) import Helpviewer.HelpWindow cache = Helpviewer.HelpWindow.HelpWindow.networkAccessManager().cache() if cache: cacheData = cache.data(imageUrl) else: cacheData = None if not cacheData: E5MessageBox.critical( self, self.tr("Save Image"), self.tr("""This image is not available.""")) return downloadDirectory = Helpviewer.HelpWindow.HelpWindow\ .downloadManager().downloadDirectory() fn = os.path.join(downloadDirectory, os.path.basename(itm.text(1))) filename = E5FileDialog.getSaveFileName( self, self.tr("Save Image"), fn, self.tr("All Files (*)"), E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite)) if not filename: return f = QFile(filename) if not f.open(QFile.WriteOnly): E5MessageBox.critical( self, self.tr("Save Image"), self.tr( """<p>Cannot write to file <b>{0}</b>.</p>""") .format(filename)) return f.write(cacheData.readAll()) f.close()
def _is_blocked(self, request_url: QUrl, first_party_url: QUrl = None) -> bool: """Check whether the given request is blocked.""" if first_party_url is not None and not first_party_url.isValid(): first_party_url = None if not config.get('content.host_blocking.enabled', url=first_party_url): return False host = request_url.host() return ((host in self._blocked_hosts or host in self._config_blocked_hosts) and not _is_whitelisted_url(request_url))
def assert_urls(host_blocker, blocked=BLOCKLIST_HOSTS, whitelisted=WHITELISTED_HOSTS, urls_to_check=URLS_TO_CHECK): """Test if Urls to check are blocked or not by HostBlocker. Ensure URLs in 'blocked' and not in 'whitelisted' are blocked. All other URLs must not be blocked. """ whitelisted = list(whitelisted) + list(host_blocker.WHITELISTED) for str_url in urls_to_check: url = QUrl(str_url) host = url.host() if host in blocked and host not in whitelisted: assert host_blocker.is_blocked(url) else: assert not host_blocker.is_blocked(url)
def test_disabled_blocking_per_url(config_stub, host_blocker_factory): example_com = 'https://www.example.com/' config_stub.val.content.host_blocking.lists = [] pattern = urlmatch.UrlPattern(example_com) config_stub.set_obj('content.host_blocking.enabled', False, pattern=pattern) url = QUrl('blocked.example.com') host_blocker = host_blocker_factory() host_blocker._blocked_hosts.add(url.host()) assert host_blocker._is_blocked(url) assert not host_blocker._is_blocked(url, first_party_url=QUrl(example_com))
def __setOkButton(self): """ Private slot to enable or disable the OK button. """ enable = True enable = enable and bool(self.titleEdit.text()) urlString = self.urlEdit.text() enable = enable and bool(urlString) if urlString: url = QUrl(urlString) enable = enable and bool(url.scheme()) enable = enable and bool(url.host()) self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(enable)
def __init__(self, connections=[], parent=None): """ Constructor @param connections list of database connections to add (list of strings) @param parent reference to the parent widget (QWidget) """ super(SqlBrowser, self).__init__(parent) self.setObjectName("SqlBrowser") self.setWindowTitle(self.tr("SQL Browser")) self.setWindowIcon(UI.PixmapCache.getIcon("eric.png")) self.setStyle(Preferences.getUI("Style"), Preferences.getUI("StyleSheet")) from .SqlBrowserWidget import SqlBrowserWidget self.__browser = SqlBrowserWidget(self) self.setCentralWidget(self.__browser) self.__browser.statusMessage.connect(self.statusBar().showMessage) self.__initActions() self.__initMenus() self.__initToolbars() self.resize(self.__browser.size()) self.__warnings = [] for connection in connections: url = QUrl(connection, QUrl.TolerantMode) if not url.isValid(): self.__warnings.append( self.tr("Invalid URL: {0}").format(connection)) continue err = self.__browser.addConnection(url.scheme(), url.path(), url.userName(), url.password(), url.host(), url.port(-1)) if err.type() != QSqlError.NoError: self.__warnings.append( self.tr("Unable to open connection: {0}".format( err.text()))) QTimer.singleShot(0, self.__uiStartUp)
def transform(self, value): if not value: return None elif value == 'system': return SYSTEM_PROXY elif value == 'none': return QNetworkProxy(QNetworkProxy.NoProxy) url = QUrl(value) typ = self.PROXY_TYPES[url.scheme()] proxy = QNetworkProxy(typ, url.host()) if url.port() != -1: proxy.setPort(url.port()) if url.userName(): proxy.setUser(url.userName()) if url.password(): proxy.setPassword(url.password()) return proxy
def assert_urls(host_blocker, blocked=BLOCKLIST_HOSTS, whitelisted=WHITELISTED_HOSTS, urls_to_check=URLS_TO_CHECK): """Test if Urls to check are blocked or not by HostBlocker. Ensure URLs in 'blocked' and not in 'whitelisted' are blocked. All other URLs must not be blocked. localhost is an example of a special case that shouldn't be blocked. """ whitelisted = list(whitelisted) + ['localhost'] for str_url in urls_to_check: url = QUrl(str_url) host = url.host() if host in blocked and host not in whitelisted: assert host_blocker._is_blocked(url) else: assert not host_blocker._is_blocked(url)
def data_for_url(url): """Get the data to show for the given URL. Args: url: The QUrl to show. Return: A (mimetype, data) tuple. """ path = url.path() host = url.host() # A url like "qute:foo" is split as "scheme:path", not "scheme:host". log.misc.debug("url: {}, path: {}, host {}".format( url.toDisplayString(), path, host)) if path and not host: new_url = QUrl() new_url.setScheme('qute') new_url.setHost(path) new_url.setPath('/') if new_url.host(): # path was a valid host raise Redirect(new_url) try: handler = _HANDLERS[host] except KeyError: raise NoHandlerFound(url) try: mimetype, data = handler(url) except OSError as e: # FIXME:qtwebengine how to handle this? raise QuteSchemeOSError(e) except QuteSchemeError as e: raise assert mimetype is not None, url if mimetype == 'text/html' and isinstance(data, str): # We let handlers return HTML as text data = data.encode('utf-8', errors='xmlcharrefreplace') return mimetype, data
def _init_host(self, parsed): """Parse the host from the given URL. Deviation from Chromium: - http://:1234/ is not a valid URL because it has no host. """ if parsed.hostname is None or not parsed.hostname.strip(): if self._scheme not in self._SCHEMES_WITHOUT_HOST: raise ParseError("Pattern without host") assert self._host is None return if parsed.netloc.startswith('['): # Using QUrl parsing to minimize ipv6 addresses url = QUrl() url.setHost(parsed.hostname) if not url.isValid(): raise ParseError(url.errorString()) self._host = url.host() return # FIXME what about multiple dots? host_parts = parsed.hostname.rstrip('.').split('.') if host_parts[0] == '*': host_parts = host_parts[1:] self._match_subdomains = True if not host_parts: self._host = None return self._host = '.'.join(host_parts) if self._host.endswith('.*'): # Special case to have a nicer error raise ParseError("TLD wildcards are not implemented yet") if '*' in self._host: # Only * or *.foo is allowed as host. raise ParseError("Invalid host wildcard")
class App(QMainWindow): def __init__(self): super().__init__() self.title = "OraDocs by littleT" self.left = 10 self.top = 10 self.width = 600 self.height = 400 self.initUI() self.url = QUrl() self.qnam = QNetworkAccessManager() self.reply = None self.outFile = None self.httpGetId = 0 self.httpRequestAborted = False def initUI(self): self.setWindowTitle(self.title) self.setGeometry(self.left, self.top, self.width, self.height) self.statusBar().showMessage('Starting ...') # for the menu first create some actions to be added like # File - Exit action exitAction = QAction(QIcon('exit24.png'), 'Exit', self) exitAction.setShortcut('Ctrl+Q') exitAction.setStatusTip('Exit Application') exitAction.triggered.connect(self.close) # File - open openAction = QAction(QIcon(''), '&Open', self) openAction.setShortcut('Ctrl+O') openAction.setStatusTip('Opens a file') openAction.triggered.connect(self.openCall()) # the help actions helpAction = QAction(QIcon(''), 'Help', self) helpAction.triggered.connect(self.openHelp) mainMenu = self.menuBar() fileMenu = mainMenu.addMenu('&File') fileMenu.addAction(openAction) fileMenu.addAction(exitAction) helpMenu = mainMenu.addMenu('&Help') helpMenu.addAction(helpAction) self.logOutput = QTextEdit(self) self.logOutput.setReadOnly(True) # self.logOutput.setLineWrapMode(QTextEdit.NoWrap) self.logOutput.setGeometry(5, 200, 590, 60) self.statusBar().showMessage('waiting for click ...') self.urlLineEdit = QLineEdit(self) # self.textbox.move(5, 300) # self.textbox.resize(590, 20) self.urlLineEdit.setGeometry(5, 325, 590, 20) self.button = QPushButton('Click Me', self) self.button.move(10, 350) self.button.clicked.connect(self.doRequest) def doRequest(self): self.root = "https://oradocs-corp.documents.us2.oraclecloud.com/documents/" # url = "https://www.google.com" self.url = QUrl(self.root) self.httpRequestAborted = False self.startRequest(self.url) self.downloadFile() self.qnam.authenticationRequired.connect( self.slotAuthenticationRequired) def startRequest(self, url): self.reply = self.qnam.get(QNetworkRequest(url)) self.reply.finished.connect(self.httpFinished) self.reply.readyRead.connect(self.httpReadyRead) def downloadFile(self): self.url = QUrl(self.urlLineEdit.text()) fileInfo = QFileInfo(self.url.path()) fileName = fileInfo.fileName() if not fileName: fileName = 'index.html' if QFile.exists(fileName): ret = QMessageBox.question(self, "HTTP", "There already exists a file called %s in the current " "directory. Overwrite?" % fileName, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if ret == QMessageBox.No: return QFile.remove(fileName) self.outFile = QFile(fileName) if not self.outFile.open(QIODevice.WriteOnly): QMessageBox.information(self, "HTTP", "Unable to save the file %s: %s." % (fileName, self.outFile.errorString())) self.outFile = None return self.progressDialog.setWindowTitle("HTTP") self.progressDialog.setLabelText("Downloading %s." % fileName) self.downloadButton.setEnabled(False) self.httpRequestAborted = False self.startRequest(self.url) def cancelDownload(self): self.statusLabel.setText("Download canceled.") self.httpRequestAborted = True if self.reply is not None: self.reply.abort() self.downloadButton.setEnabled(True) def httpFinished(self): if self.httpRequestAborted: if self.outFile is not None: self.outFile.close() self.outFile.remove() self.outFile = None self.reply.deleteLater() self.reply = None # self.progressDialog.hide() return # self.progressDialog.hide() self.outFile.flush() self.outFile.close() redirectionTarget = self.reply.attribute(QNetworkRequest.RedirectionTargetAttribute) if self.reply.error(): self.outFile.remove() QMessageBox.information(self, "HTTP", "Download failed: %s." % self.reply.errorString()) self.downloadButton.setEnabled(True) elif redirectionTarget is not None: newUrl = self.url.resolved(redirectionTarget) ret = QMessageBox.question(self, "HTTP", "Redirect to %s?" % newUrl.toString(), QMessageBox.Yes | QMessageBox.No) if ret == QMessageBox.Yes: self.url = newUrl self.reply.deleteLater() self.reply = None self.outFile.open(QIODevice.WriteOnly) self.outFile.resize(0) self.startRequest(self.url) return else: fileName = QFileInfo(QUrl(self.urlLineEdit.text()).path()).fileName() self.statusLabel.setText("Downloaded %s to %s." % (fileName, QDir.currentPath())) self.downloadButton.setEnabled(True) self.reply.deleteLater() self.reply = None self.outFile = None def httpReadyRead(self): if self.outFile is not None: self.outFile.write(self.reply.readAll()) def updateDataReadProgress(self, bytesRead, totalBytes): if self.httpRequestAborted: return self.progressDialog.setMaximum(totalBytes) self.progressDialog.setValue(bytesRead) def slotAuthenticationRequired(self, authenticator): import os from PyQt5 import uic ui = os.path.join(os.path.dirname(__file__), 'authenticationdialog.ui') dlg = uic.loadUi(ui) dlg.adjustSize() dlg.siteDescription.setText("%s at %s" % (authenticator.realm(), self.url.host())) dlg.userEdit.setText(self.url.userName()) dlg.passwordEdit.setText(self.url.password()) if dlg.exec_() == QDialog.Accepted: authenticator.setUser(dlg.userEdit.text()) authenticator.setPassword(dlg.passwordEdit.text()) def sslErrors(self, reply, errors): errorString = ", ".join([str(error.errorString()) for error in errors]) ret = QMessageBox.warning(self, "HTTP Example", "One or more SSL errors has occurred: %s" % errorString, QMessageBox.Ignore | QMessageBox.Abort) if ret == QMessageBox.Ignore: self.reply.ignoreSslErrors() def openCall(self): print("Open Call") def openHelp(self): print("Open Help") @pyqtSlot() def onClick(self): # textboxValue = self.textbox.text() # QMessageBox.question(self, 'Hello World!', "Confirm: " + textboxValue, # QMessageBox.Ok, QMessageBox.Ok) # self.textbox.setText("...") myRet = self.myDocs.doRequest("https://www.google.com") self.logOutput.moveCursor(QTextCursor.End) print(myRet) self.logOutput.insertPlainText(myRet) @pyqtSlot() def processReq(self): if self.nam.bytesAvailable(): self.logOutput.moveCursor(QTextCursor.End) self.logOutput.insertPlainText('We are connected\n') bytes_string = self.nam.readAll() self.nam.deleteLater() @pyqtSlot(QNetworkReply.NetworkError) def processErr(self, code): self.msg.critical(None, "Info", "You are not connected to the Internet.") print(code)
class ClickToFlash(QWidget, Ui_ClickToFlash): """ Class implementing the Flash blocker. """ _acceptedUrl = QUrl() _acceptedArgNames = [] _acceptedArgValues = [] def __init__(self, plugin, mimeType, url, argumentNames, argumentValues, parent=None): """ Constructor @param plugin reference to the plug-in (ClickToFlashPlugin) @param mimeType MIME type for the plug-in (string) @param url requested URL (QUrl) @param argumentNames list of argument names (list of strings) @param argumentValues list of argument values (list of strings) @param parent reference to the parent widget (QWidget) """ super(ClickToFlash, self).__init__(parent) # Check AdBlock first import Helpviewer.HelpWindow manager = Helpviewer.HelpWindow.HelpWindow.adBlockManager() if manager.isEnabled(): urlString = bytes(url.toEncoded()).decode() urlDomain = url.host() for subscription in manager.subscriptions(): blockedRule = subscription.match( QNetworkRequest(url), urlDomain, urlString) if blockedRule: QTimer.singleShot(200, self.__hideAdBlocked) return self.setupUi(self) self.__swapping = False self.__element = QWebElement() self.__plugin = plugin self.__url = QUrl(url) self.__argumentNames = argumentNames[:] self.__argumentValues = argumentValues[:] self.__mimeType = mimeType self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.__showContextMenu) self.setToolTip(self.__url.toString()) iconName = plugin.getIconName(mimeType) if iconName: self.loadFlashButton.setIcon(UI.PixmapCache.getIcon(iconName)) else: self.loadFlashButton.setText(self.tr("Load")) @pyqtSlot() def on_loadFlashButton_clicked(self): """ Private slot handling the flash activation. """ self.__load() def __showContextMenu(self): """ Private slot to show the context menu. """ menu = QMenu() act = menu.addAction(self.tr("Object blocked by ClickToFlash")) font = act.font() font.setBold(True) act.setFont(font) menu.addAction( self.tr("Show information about object"), self.__showInfo) menu.addSeparator() menu.addAction(self.tr("Load"), self.__load) menu.addAction(self.tr("Delete object"), self.__hideAdBlocked) menu.addSeparator() host = self.__url.host() add = menu.addAction( self.tr("Add '{0}' to Whitelist").format(host), self.__addToWhitelist) remove = menu.addAction( self.tr("Remove '{0}' from Whitelist").format(host), self.__removeFromWhitelist) onWhitelist = self.__plugin.onWhitelist(host) add.setEnabled(not onWhitelist) remove.setEnabled(onWhitelist) menu.addSeparator() menu.addAction(self.tr("Configure Whitelist"), self.__configure) menu.actions()[0].setEnabled(False) menu.exec_(QCursor.pos()) def swapping(self): """ Public method to check, if the plug-in is swapping. @return flag indicating the swapping status (boolean) """ return self.__swapping def __configure(self): """ Private slot to configure the whitelist. """ self.__plugin.configure() def __addToWhitelist(self): """ Private slot to add the host to the whitelist. """ self.__plugin.addToWhitelist(self.__url.host()) def __removeFromWhitelist(self): """ Private slot to remove the host from the whitelist. """ self.__plugin.removeFromWhitelist(self.__url.host()) def __load(self, all=False): """ Private slot to load the flash content. @param all flag indicating to load all flash players. (boolean) """ self.__findElement() if not self.__element.isNull(): substitute = self.__element.clone() substitute.setAttribute("type", self.__mimeType) self.__element.replace(substitute) ClickToFlash._acceptedUrl = self.__url ClickToFlash._acceptedArgNames = self.__argumentNames ClickToFlash._acceptedArgValues = self.__argumentValues def __findElement(self): """ Private method to find the element belonging to this ClickToFlash instance. """ parent = self.parentWidget() view = None while parent is not None: if isinstance(parent, QWebView): view = parent break parent = parent.parentWidget() if view is None: return objectPos = view.mapFromGlobal(self.loadFlashButton.mapToGlobal( self.loadFlashButton.pos())) objectFrame = view.page().frameAt(objectPos) hitResult = QWebHitTestResult() hitElement = QWebElement() if objectFrame is not None: hitResult = objectFrame.hitTestContent(objectPos) hitElement = hitResult.element() if not hitElement.isNull() and \ hitElement.tagName().lower() in ["embed", "object"]: self.__element = hitElement return # hit test failed, trying to find element by src # attribute in elements of all frames on page (although less accurate frames = [] frames.append(view.page().mainFrame()) while frames: frame = frames.pop(0) if not frame: continue docElement = frame.documentElement() elements = QWebElementCollection() elements.append(docElement.findAll("embed")) elements.append(docElement.findAll("object")) for element in elements: if not self.__checkElement(element) and \ not self.__checkUrlOnElement(element, view): continue self.__element = element return frames.extend(frame.childFrames()) def __checkUrlOnElement(self, element, view): """ Private slot to check the URL of an element. @param element reference to the element to check (QWebElement) @param view reference to the view object (QWebView) @return flag indicating a positive result (boolean) """ checkString = element.attribute("src") if checkString == "": checkString = element.attribute("data") if checkString == "": checkString = element.attribute("value") checkString = view.url().resolved(QUrl(checkString)).toString( QUrl.RemoveQuery) return self.__url.toEncoded().contains( QByteArray(checkString.encode("utf-8"))) def __checkElement(self, element): """ Private slot to check an element against the saved arguments. @param element reference to the element to check (QWebElement) @return flag indicating a positive result (boolean) """ if self.__argumentNames == element.attributeNames(): for name in self.__argumentNames: if element.attribute(name) not in self.__argumentValues: return False return True return False def __hideAdBlocked(self): """ Private slot to hide the object. """ self.__findElement() if not self.__element.isNull(): self.__element.setStyleProperty("display", "none") else: self.hide() def __showInfo(self): """ Private slot to show information about the blocked object. """ dlg = QDialog() dlg.setWindowTitle(self.tr("Flash Object")) dlg.setSizeGripEnabled(True) layout = QFormLayout(dlg) layout.addRow(QLabel(self.tr("<b>Attribute Name</b>")), QLabel(self.tr("<b>Value</b>"))) index = 0 for name in self.__argumentNames: nameLabel = QLabel(self.__elide(name, length=30)) value = self.__argumentValues[index] valueLabel = QLabel(self.__elide(value, length=60)) valueLabel.setTextInteractionFlags( Qt.TextSelectableByMouse | Qt.LinksAccessibleByMouse) layout.addRow(nameLabel, valueLabel) index += 1 if index == 0: layout.addRow(QLabel(self.tr("No information available."))) dlg.setMaximumHeight(500) dlg.setMaximumWidth(500) dlg.exec_() def __elide(self, txt, mode=Qt.ElideMiddle, length=40): """ Private method to elide some text. @param txt text to be elided (string) @keyparam mode elide mode (Qt.TextElideMode) @keyparam length amount of characters to be used (integer) @return the elided text (string) """ if mode == Qt.ElideNone or len(txt) < length: return txt elif mode == Qt.ElideLeft: return "...{0}".format(txt[-length:]) elif mode == Qt.ElideMiddle: return "{0}...{1}".format(txt[:length // 2], txt[-(length // 2):]) elif mode == Qt.ElideRight: return "{0}...".format(txt[:length]) else: # just in case return txt @classmethod def isAlreadyAccepted(cls, url, argumentNames, argumentValues): """ Class method to check, if the given parameter combination is being accepted. @param url URL to be checked for (QUrl) @param argumentNames argument names to be checked for (list of strings) @param argumentValues argument values to be checked for (list of strings) @return flag indicating that this was already accepted (boolean) """ return url == cls._acceptedUrl and \ argumentNames == cls._acceptedArgNames and \ argumentValues == cls._acceptedArgValues
def url_watcher(self, url: QtCore.QUrl): """Watches the QWebEngineView for url changes.""" if url.host() not in [ 'twitch.tv', 'localhost', self.ENDPOINT.host(), 'passport.twitch.tv' ] and url.host() != '': self.LOGGER.warning( f'Redirected to an unsupported host! ({url.host()})') self.LOGGER.debug('Creating informative dialog...') _m = QtWidgets.QMessageBox( QtWidgets.QMessageBox.Critical, 'Token Generator', f'You were redirected to an unsupported host. ({url.host()})', QtWidgets.QMessageBox.Ok, self) self.LOGGER.debug('Displaying dialog...') self.hide() _m.exec() self.LOGGER.debug('Ensuring dialog is deleted properly...') if not sip.isdeleted(_m): _m.deleteLater() self.browser.setUrl(QtCore.QUrl('about:blank')) return self.reject() if url.hasFragment(): query = QtCore.QUrlQuery(url.fragment()) else: query = QtCore.QUrlQuery(url.query()) if not query.hasQueryItem('state') and url.path() != '/two_factor/new': self.LOGGER.warning('No state sent!') self.LOGGER.debug('Creating informative dialog...') _m = QtWidgets.QMessageBox(QtWidgets.QMessageBox.Critical, 'Token Generator', "The state parameter wasn't passed.", QtWidgets.QMessageBox.Ok, self) self.LOGGER.debug('Displaying dialog...') self.hide() _m.exec() self.LOGGER.debug('Ensuring dialog is deleted properly...') if not sip.isdeleted(_m): _m.deleteLater() self.browser.setUrl(QtCore.QUrl('about:blank')) return self.reject() if query.hasQueryItem( 'state') and self._state != query.queryItemValue('state'): self.LOGGER.warning('Sent state is not our state!') self.LOGGER.debug('Creating informative dialog...') _m = QtWidgets.QMessageBox(QtWidgets.QMessageBox.Critical, 'Token Generator', 'Sent state is not our state.', QtWidgets.QMessageBox.Ok, self) self.LOGGER.debug('Displaying dialog...') _m.exec() self.hide() self.LOGGER.debug('Ensuring dialog is deleted properly...') if not sip.isdeleted(_m): _m.deleteLater() self.browser.setUrl(QtCore.QUrl('about:blank')) return self.reject() if query.hasQueryItem('access_token'): # Declarations app: QtWidgets.QApplication = QtWidgets.QApplication.instance() client_id = app.client.settings['extensions']['twitch'][ 'client_id'].value scopes: typing.List[Scopes] = [ Scopes(s) for s in parse.unquote(query.queryItemValue( 'scope')).split('+') ] self.GENERATED.emit( token.Token(client_id, query.queryItemValue('access_token'), scopes)) self.accept()
class E5SslInfoWidget(QMenu): """ Class implementing a widget to show SSL certificate infos. """ def __init__(self, url, configuration, parent=None): """ Constructor @param url URL to show SSL info for (QUrl) @param configuration SSL configuration (QSslConfiguration) @param parent reference to the parent widget (QWidget) """ super(E5SslInfoWidget, self).__init__(parent) self.__url = QUrl(url) self.__configuration = QSslConfiguration(configuration) self.setMinimumWidth(400) certList = self.__configuration.peerCertificateChain() if certList: cert = certList[0] else: cert = QSslCertificate() layout = QGridLayout(self) rows = 0 ########################################## ## Identity Information ########################################## imageLabel = QLabel(self) layout.addWidget(imageLabel, rows, 0, Qt.AlignCenter) label = QLabel(self) label.setWordWrap(True) label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) label.setText(self.tr("Identity")) font = label.font() font.setBold(True) label.setFont(font) layout.addWidget(label, rows, 1) rows += 1 label = QLabel(self) label.setWordWrap(True) if cert.isNull(): label.setText(self.tr( "Warning: this site is NOT carrying a certificate.")) imageLabel.setPixmap(UI.PixmapCache.getPixmap("securityLow32.png")) else: if qVersion() >= "5.0.0": valid = not cert.isBlacklisted() else: valid = cert.isValid() if valid: if qVersion() >= "5.0.0": txt = ", ".join( cert.issuerInfo(QSslCertificate.CommonName)) else: txt = cert.issuerInfo(QSslCertificate.CommonName) label.setText(self.tr( "The certificate for this site is valid" " and has been verified by:\n{0}").format( Utilities.decodeString(txt))) imageLabel.setPixmap( UI.PixmapCache.getPixmap("securityHigh32.png")) else: label.setText(self.tr( "The certificate for this site is NOT valid.")) imageLabel.setPixmap( UI.PixmapCache.getPixmap("securityLow32.png")) layout.addWidget(label, rows, 1) rows += 1 label = QLabel(self) label.setWordWrap(True) label.setText( '<a href="moresslinfos">' + self.tr("Certificate Information") + "</a>") label.linkActivated.connect(self.__showCertificateInfos) layout.addWidget(label, rows, 1) rows += 1 ########################################## ## Identity Information ########################################## imageLabel = QLabel(self) layout.addWidget(imageLabel, rows, 0, Qt.AlignCenter) label = QLabel(self) label.setWordWrap(True) label.setText(self.tr("Encryption")) font = label.font() font.setBold(True) label.setFont(font) layout.addWidget(label, rows, 1) rows += 1 cipher = self.__configuration.sessionCipher() if cipher.isNull(): label = QLabel(self) label.setWordWrap(True) label.setText(self.tr( 'Your connection to "{0}" is NOT encrypted.\n').format( self.__url.host())) layout.addWidget(label, rows, 1) imageLabel.setPixmap(UI.PixmapCache.getPixmap("securityLow32.png")) rows += 1 else: label = QLabel(self) label.setWordWrap(True) label.setText(self.tr( 'Your connection to "{0}" is encrypted.').format( self.__url.host())) layout.addWidget(label, rows, 1) proto = cipher.protocol() if proto == QSsl.SslV3: sslVersion = "SSL 3.0" imageLabel.setPixmap( UI.PixmapCache.getPixmap("securityLow32.png")) elif proto == QSsl.TlsV1SslV3: sslVersion = "TLS 1.0/SSL 3.0" imageLabel.setPixmap( UI.PixmapCache.getPixmap("securityLow32.png")) elif proto == QSsl.SslV2: sslVersion = "SSL 2.0" imageLabel.setPixmap( UI.PixmapCache.getPixmap("securityLow32.png")) else: sslVersion = self.tr("unknown") imageLabel.setPixmap( UI.PixmapCache.getPixmap("securityLow32.png")) if qVersion() >= "5.0.0": if proto == QSsl.TlsV1_0: sslVersion = "TLS 1.0" imageLabel.setPixmap( UI.PixmapCache.getPixmap("securityHigh32.png")) elif proto == QSsl.TlsV1_1: sslVersion = "TLS 1.1" imageLabel.setPixmap( UI.PixmapCache.getPixmap("securityHigh32.png")) elif proto == QSsl.TlsV1_2: sslVersion = "TLS 1.2" imageLabel.setPixmap( UI.PixmapCache.getPixmap("securityHigh32.png")) else: if proto == QSsl.TlsV1: sslVersion = "TLS 1.0" imageLabel.setPixmap( UI.PixmapCache.getPixmap("securityHigh32.png")) rows += 1 label = QLabel(self) label.setWordWrap(True) label.setText(self.tr( "It uses protocol: {0}").format(sslVersion)) layout.addWidget(label, rows, 1) rows += 1 label = QLabel(self) label.setWordWrap(True) label.setText(self.tr( "It is encrypted using {0} at {1} bits, " "with {2} for message authentication and " "{3} as key exchange mechanism.\n\n").format( cipher.encryptionMethod(), cipher.usedBits(), cipher.authenticationMethod(), cipher.keyExchangeMethod())) layout.addWidget(label, rows, 1) rows += 1 def showAt(self, pos): """ Public method to show the widget. @param pos position to show at (QPoint) """ self.adjustSize() xpos = pos.x() - self.width() if xpos < 0: xpos = 10 p = QPoint(xpos, pos.y() + 10) self.move(p) self.show() def __showCertificateInfos(self): """ Private slot to show certificate information. """ from .E5SslCertificatesInfoDialog import E5SslCertificatesInfoDialog dlg = E5SslCertificatesInfoDialog( self.__configuration.peerCertificateChain()) dlg.exec_() def accept(self): """ Public method to accept the widget. """ self.close()
class CoverArtImage: # Indicate if types are provided by the source, ie. CAA or certain file # formats may have types associated with cover art, but some other sources # don't provide such information support_types = False # `is_front` has to be explicitly set, it is used to handle CAA is_front # indicator is_front = None sourceprefix = "URL" def __init__(self, url=None, types=None, comment='', data=None): if types is None: self.types = [] else: self.types = types if url is not None: self.parse_url(url) else: self.url = None self.comment = comment self.datahash = None # thumbnail is used to link to another CoverArtImage, ie. for PDFs self.thumbnail = None self.can_be_saved_to_tags = True self.can_be_saved_to_disk = True self.can_be_saved_to_metadata = True if data is not None: self.set_data(data) def parse_url(self, url): self.url = QUrl(url) self.host = string_(self.url.host()) self.port = self.url.port(80) self.path = string_(self.url.path(QUrl.FullyEncoded)) if self.url.hasQuery(): self.path += '?' + string_(self.url.query(QUrl.FullyEncoded)) @property def source(self): if self.url is not None: return "%s: %s" % (self.sourceprefix, self.url.toString()) else: return "%s" % self.sourceprefix def is_front_image(self): """Indicates if image is considered as a 'front' image. It depends on few things: - if `is_front` was set, it is used over anything else - if `types` was set, search for 'front' in it - if `support_types` is False, default to True for any image - if `support_types` is True, default to False for any image """ if not self.can_be_saved_to_metadata: # ignore thumbnails return False if self.is_front is not None: return self.is_front if 'front' in self.types: return True return (self.support_types is False) def imageinfo_as_string(self): if self.datahash is None: return "" return "w=%d h=%d mime=%s ext=%s datalen=%d file=%s" % (self.width, self.height, self.mimetype, self.extension, self.datalength, self.tempfile_filename) def __repr__(self): p = [] if self.url is not None: p.append("url=%r" % self.url.toString()) if self.types: p.append("types=%r" % self.types) if self.is_front is not None: p.append("is_front=%r" % self.is_front) if self.comment: p.append("comment=%r" % self.comment) return "%s(%s)" % (self.__class__.__name__, ", ".join(p)) def __str__(self): p = ['Image'] if self.url is not None: p.append("from %s" % self.url.toString()) if self.types: p.append("of type %s" % ','.join(self.types)) if self.comment: p.append("and comment '%s'" % self.comment) return ' '.join(p) def __eq__(self, other): if self and other: if self.types and other.types: return (self.datahash, self.types) == (other.datahash, other.types) else: return self.datahash == other.datahash elif not self and not other: return True else: return False def __hash__(self): if self.datahash is None: return 0 return hash(self.datahash.hash()) def set_data(self, data): """Store image data in a file, if data already exists in such file it will be re-used and no file write occurs """ if self.datahash: self.datahash.delete_file() self.datahash = None try: (self.width, self.height, self.mimetype, self.extension, self.datalength) = imageinfo.identify(data) except imageinfo.IdentificationError as e: raise CoverArtImageIdentificationError(e) try: self.datahash = DataHash(data, suffix=self.extension) except (OSError, IOError) as e: raise CoverArtImageIOError(e) @property def maintype(self): """Returns one type only, even for images having more than one type set. This is mostly used when saving cover art to tags because most formats don't support multiple types for one image. Images coming from CAA can have multiple types (ie. 'front, booklet'). """ if self.is_front_image() or not self.types or 'front' in self.types: return 'front' # TODO: do something better than randomly using the first in the list return self.types[0] def _make_image_filename(self, filename, dirname, metadata): filename = ScriptParser().eval(filename, metadata) if config.setting["ascii_filenames"]: if isinstance(filename, str): filename = unaccent(filename) filename = replace_non_ascii(filename) if not filename: filename = "cover" if not os.path.isabs(filename): filename = os.path.join(dirname, filename) # replace incompatible characters if config.setting["windows_compatibility"] or sys.platform == "win32": filename = replace_win32_incompat(filename) # remove null characters if isinstance(filename, bytes): filename = filename.replace(b"\x00", "") return encode_filename(filename) def save(self, dirname, metadata, counters): """Saves this image. :dirname: The name of the directory that contains the audio file :metadata: A metadata object :counters: A dictionary mapping filenames to the amount of how many images with that filename were already saved in `dirname`. """ if not self.can_be_saved_to_disk: return if (config.setting["caa_image_type_as_filename"] and not self.is_front_image()): filename = self.maintype log.debug("Make cover filename from types: %r -> %r", self.types, filename) else: filename = config.setting["cover_image_filename"] log.debug("Using default cover image filename %r", filename) filename = self._make_image_filename(filename, dirname, metadata) overwrite = config.setting["save_images_overwrite"] ext = encode_filename(self.extension) image_filename = self._next_filename(filename, counters) while os.path.exists(image_filename + ext) and not overwrite: if not self._is_write_needed(image_filename + ext): break image_filename = self._next_filename(filename, counters) else: new_filename = image_filename + ext # Even if overwrite is enabled we don't need to write the same # image multiple times if not self._is_write_needed(new_filename): return log.debug("Saving cover image to %r", new_filename) try: new_dirname = os.path.dirname(new_filename) if not os.path.isdir(new_dirname): os.makedirs(new_dirname) shutil.copyfile(self.tempfile_filename, new_filename) except (OSError, IOError) as e: raise CoverArtImageIOError(e) def _next_filename(self, filename, counters): if counters[filename]: new_filename = b"%b (%d)" % (filename, counters[filename]) else: new_filename = filename counters[filename] += 1 return new_filename def _is_write_needed(self, filename): if (os.path.exists(filename) and os.path.getsize(filename) == self.datalength): log.debug("Identical file size, not saving %r", filename) return False return True @property def data(self): """Reads the data from the temporary file created for this image. May raise CoverArtImageIOError """ try: return self.datahash.data except (OSError, IOError) as e: raise CoverArtImageIOError(e) @property def tempfile_filename(self): return self.datahash.filename def types_as_string(self, translate=True, separator=', '): if self.types: types = self.types elif self.is_front_image(): types = ['front'] else: types = ['-'] if translate: types = [translate_caa_type(type) for type in types] return separator.join(types)
def queryProxy(self, query): """ Public method to determine a proxy for a given query. @param query reference to the query object (QNetworkProxyQuery) @return list of proxies in order of preference (list of QNetworkProxy) """ if query.queryType() == QNetworkProxyQuery.UrlRequest and query.protocolTag() in ["http", "https", "ftp"]: # use proxy at all ? if not Preferences.getUI("UseProxy"): return [QNetworkProxy(QNetworkProxy.NoProxy)] # test for exceptions exceptions = Preferences.getUI("ProxyExceptions") if exceptions != self.__exceptions: self.__setExceptions(exceptions) urlHost = query.url().host() for matcher in self.__hostnameMatchers: if matcher.match(urlHost): return [QNetworkProxy(QNetworkProxy.NoProxy)] # determine proxy if Preferences.getUI("UseSystemProxy"): proxyList = QNetworkProxyFactory.systemProxyForQuery(query) if ( not Globals.isWindowsPlatform() and len(proxyList) == 1 and proxyList[0].type() == QNetworkProxy.NoProxy ): # try it the Python way # scan the environment for variables named <scheme>_proxy # scan over whole environment to make this case insensitive for name, value in os.environ.items(): name = name.lower() if value and name[-6:] == "_proxy" and name[:-6] == query.protocolTag().lower(): url = QUrl(value) if url.scheme() == "http": proxyType = QNetworkProxy.HttpProxy elif url.scheme() == "https": proxyType = QNetworkProxy.HttpCachingProxy elif url.scheme() == "ftp": proxyType = QNetworkProxy.FtpCachingProxy else: proxyType = QNetworkProxy.HttpProxy proxy = QNetworkProxy(proxyType, url.host(), url.port(), url.userName(), url.password()) proxyList = [proxy] break if proxyList: scheme = schemeFromProxyType(proxyList[0].type()) if scheme == "": scheme = "Http" if scheme != "NoProxy": proxyList[0].setUser(Preferences.getUI("ProxyUser/{0}".format(scheme))) proxyList[0].setPassword(Preferences.getUI("ProxyPassword/{0}".format(scheme))) return proxyList else: return [QNetworkProxy(QNetworkProxy.NoProxy)] else: if Preferences.getUI("UseHttpProxyForAll"): protocolKey = "Http" else: protocolKey = query.protocolTag().capitalize() host = Preferences.getUI("ProxyHost/{0}".format(protocolKey)) if not host: E5MessageBox.critical( None, QCoreApplication.translate("E5NetworkProxyFactory", "Proxy Configuration Error"), QCoreApplication.translate( "E5NetworkProxyFactory", """Proxy usage was activated""" """ but no proxy host for protocol""" """ '{0}' configured.""", ).format(protocolKey), ) return [QNetworkProxy(QNetworkProxy.DefaultProxy)] else: if protocolKey in ["Http", "Https", "Ftp"]: if query.protocolTag() == "ftp": proxyType = QNetworkProxy.FtpCachingProxy elif query.protocolTag() == "https": proxyType = QNetworkProxy.HttpCachingProxy else: proxyType = QNetworkProxy.HttpProxy proxy = QNetworkProxy( proxyType, host, Preferences.getUI("ProxyPort/" + protocolKey), Preferences.getUI("ProxyUser/" + protocolKey), Preferences.getUI("ProxyPassword/" + protocolKey), ) else: proxy = QNetworkProxy(QNetworkProxy.DefaultProxy) return [proxy, QNetworkProxy(QNetworkProxy.DefaultProxy)] else: return [QNetworkProxy(QNetworkProxy.NoProxy)]
class PageThumbnailer(QObject): """ Class implementing a thumbnail creator for web sites. @signal thumbnailCreated(QPixmap) emitted after the thumbnail has been created """ thumbnailCreated = pyqtSignal(QPixmap) def __init__(self, parent=None): """ Constructor @param parent reference to the parent object (QObject) """ super(PageThumbnailer, self).__init__(parent) self.__size = QSize(231, 130) self.__loadTitle = False self.__title = "" self.__url = QUrl() self.__view = QWebEngineView() self.__view.setAttribute(Qt.WA_DontShowOnScreen) self.__view.resize(1920, 1080) self.__view.show() def setSize(self, size): """ Public method to set the size of the image. @param size size of the image (QSize) """ if size.isValid(): self.__size = QSize(size) def setUrl(self, url): """ Public method to set the URL of the site to be thumbnailed. @param url URL of the web site (QUrl) """ if url.isValid(): self.__url = QUrl(url) def url(self): """ Public method to get the URL of the thumbnail. @return URL of the thumbnail (QUrl) """ return QUrl(self.__url) def loadTitle(self): """ Public method to check, if the title is loaded from the web site. @return flag indicating, that the title is loaded (boolean) """ return self.__loadTitle def setLoadTitle(self, load): """ Public method to set a flag indicating to load the title from the web site. @param load flag indicating to load the title (boolean) """ self.__loadTitle = load def title(self): """ Public method to get the title of the thumbnail. @return title of the thumbnail (string) """ if self.__title: title = self.__title else: title = self.__url.host() if not title: title = self.__url.toString() return title def start(self): """ Public method to start the thumbnailing action. """ self.__view.loadFinished.connect(self.__createThumbnail) self.__view.load(self.__url) def __createThumbnail(self, status): """ Private slot creating the thumbnail of the web site. @param status flag indicating a successful load of the web site (boolean) """ if not status: self.thumbnailCreated.emit(QPixmap()) return QTimer.singleShot(1000, self.__grabThumbnail) def __grabThumbnail(self): """ Private slot to grab the thumbnail image from the view. """ self.__title = self.__view.title() image = QImage(self.__view.size(), QImage.Format_ARGB32) painter = QPainter(image) self.__view.render(painter) painter.end() scaledImage = image.scaled(self.__size, Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation) self.thumbnailCreated.emit(QPixmap.fromImage(scaledImage))
class CoverArtImage: # Indicate if types are provided by the source, ie. CAA or certain file # formats may have types associated with cover art, but some other sources # don't provide such information support_types = False # Indicates that the source supports multiple types per image. support_multi_types = False # `is_front` has to be explicitly set, it is used to handle CAA is_front # indicator is_front = None sourceprefix = "URL" def __init__(self, url=None, types=None, comment='', data=None, support_types=None, support_multi_types=None, id3_type=None): if types is None: self.types = [] else: self.types = types if url is not None: self.parse_url(url) else: self.url = None self.comment = comment self.datahash = None # thumbnail is used to link to another CoverArtImage, ie. for PDFs self.thumbnail = None self.can_be_saved_to_tags = True self.can_be_saved_to_disk = True self.can_be_saved_to_metadata = True self.id3_type = id3_type if support_types is not None: self.support_types = support_types if support_multi_types is not None: self.support_multi_types = support_multi_types if data is not None: self.set_data(data) def parse_url(self, url): self.url = QUrl(url) self.host = self.url.host() self.port = self.url.port(443 if self.url.scheme() == 'https' else 80) self.path = self.url.path(QUrl.ComponentFormattingOption.FullyEncoded) if self.url.hasQuery(): query = QUrlQuery(self.url.query()) self.queryargs = dict(query.queryItems()) else: self.queryargs = None @property def source(self): if self.url is not None: return "%s: %s" % (self.sourceprefix, self.url.toString()) else: return "%s" % self.sourceprefix def is_front_image(self): """Indicates if image is considered as a 'front' image. It depends on few things: - if `is_front` was set, it is used over anything else - if `types` was set, search for 'front' in it - if `support_types` is False, default to True for any image - if `support_types` is True, default to False for any image """ if not self.can_be_saved_to_metadata: # ignore thumbnails return False if self.is_front is not None: return self.is_front if 'front' in self.types: return True return (self.support_types is False) def imageinfo_as_string(self): if self.datahash is None: return "" return "w=%d h=%d mime=%s ext=%s datalen=%d file=%s" % ( self.width, self.height, self.mimetype, self.extension, self.datalength, self.tempfile_filename) def __repr__(self): p = [] if self.url is not None: p.append("url=%r" % self.url.toString()) if self.types: p.append("types=%r" % self.types) p.append('support_types=%r' % self.support_types) p.append('support_multi_types=%r' % self.support_types) if self.is_front is not None: p.append("is_front=%r" % self.is_front) if self.comment: p.append("comment=%r" % self.comment) return "%s(%s)" % (self.__class__.__name__, ", ".join(p)) def __str__(self): p = ['Image'] if self.url is not None: p.append("from %s" % self.url.toString()) if self.types: p.append("of type %s" % ','.join(self.types)) if self.comment: p.append("and comment '%s'" % self.comment) return ' '.join(p) def __eq__(self, other): if self and other: if self.support_types and other.support_types: if self.support_multi_types and other.support_multi_types: return (self.datahash, self.types) == (other.datahash, other.types) else: return (self.datahash, self.maintype) == (other.datahash, other.maintype) else: return self.datahash == other.datahash return not self and not other def __hash__(self): if self.datahash is None: return 0 return hash(self.datahash.hash()) def set_data(self, data): """Store image data in a file, if data already exists in such file it will be re-used and no file write occurs """ if self.datahash: self.datahash.delete_file() self.datahash = None try: (self.width, self.height, self.mimetype, self.extension, self.datalength) = imageinfo.identify(data) except imageinfo.IdentificationError as e: raise CoverArtImageIdentificationError(e) try: self.datahash = DataHash(data, suffix=self.extension) except OSError as e: raise CoverArtImageIOError(e) @property def maintype(self): """Returns one type only, even for images having more than one type set. This is mostly used when saving cover art to tags because most formats don't support multiple types for one image. Images coming from CAA can have multiple types (ie. 'front, booklet'). """ if self.is_front_image() or not self.types or 'front' in self.types: return 'front' # TODO: do something better than randomly using the first in the list return self.types[0] @property def id3_type(self): """Returns the ID3 APIC image type. If explicitly set the type is returned as is, otherwise it is derived from `maintype`. See http://www.id3.org/id3v2.4.0-frames""" if self._id3_type is not None: return self._id3_type else: return image_type_as_id3_num(self.maintype) @id3_type.setter def id3_type(self, type): """Explicitly sets the ID3 APIC image type. If set to None the type will be derived from `maintype`.""" if type is not None: type = Id3ImageType(type) self._id3_type = type def _make_image_filename(self, filename, dirname, _metadata): metadata = Metadata() metadata.copy(_metadata) metadata["coverart_maintype"] = self.maintype metadata["coverart_comment"] = self.comment if self.is_front: metadata.add_unique("coverart_types", "front") for cover_type in self.types: metadata.add_unique("coverart_types", cover_type) filename = script_to_filename(filename, metadata) if not filename: filename = DEFAULT_COVER_IMAGE_FILENAME if not is_absolute_path(filename): filename = os.path.join(dirname, filename) return encode_filename(filename) def save(self, dirname, metadata, counters): """Saves this image. :dirname: The name of the directory that contains the audio file :metadata: A metadata object :counters: A dictionary mapping filenames to the amount of how many images with that filename were already saved in `dirname`. """ if not self.can_be_saved_to_disk: return config = get_config() if config.setting[ "image_type_as_filename"] and not self.is_front_image(): filename = self.maintype log.debug("Make cover filename from types: %r -> %r", self.types, filename) else: filename = config.setting["cover_image_filename"] log.debug("Using default cover image filename %r", filename) filename = self._make_image_filename(filename, dirname, metadata) overwrite = config.setting["save_images_overwrite"] ext = encode_filename(self.extension) image_filename = self._next_filename(filename, counters) while os.path.exists(image_filename + ext) and not overwrite: if not self._is_write_needed(image_filename + ext): break image_filename = self._next_filename(filename, counters) else: new_filename = image_filename + ext # Even if overwrite is enabled we don't need to write the same # image multiple times if not self._is_write_needed(new_filename): return log.debug("Saving cover image to %r", new_filename) try: new_dirname = os.path.dirname(new_filename) if not os.path.isdir(new_dirname): os.makedirs(new_dirname) shutil.copyfile(self.tempfile_filename, new_filename) except OSError as e: raise CoverArtImageIOError(e) @staticmethod def _next_filename(filename, counters): if counters[filename]: new_filename = "%s (%d)" % (decode_filename(filename), counters[filename]) else: new_filename = filename counters[filename] += 1 return encode_filename(new_filename) def _is_write_needed(self, filename): if (os.path.exists(filename) and os.path.getsize(filename) == self.datalength): log.debug("Identical file size, not saving %r", filename) return False return True @property def data(self): """Reads the data from the temporary file created for this image. May raise CoverArtImageIOError """ try: return self.datahash.data except OSError as e: raise CoverArtImageIOError(e) @property def tempfile_filename(self): return self.datahash.filename def normalized_types(self): if self.types: types = sorted(set(self.types)) elif self.is_front_image(): types = ['front'] else: types = ['-'] return types def types_as_string(self, translate=True, separator=', '): types = self.normalized_types() if translate: types = [translate_caa_type(type) for type in types] return separator.join(types)
class HttpWindow(QDialog): def __init__(self, parent=None): super(HttpWindow, self).__init__(parent) self.url = QUrl() self.qnam = QNetworkAccessManager() self.reply = None self.outFile = None self.httpGetId = 0 self.httpRequestAborted = False self.urlLineEdit = QLineEdit('https://www.qt.io') urlLabel = QLabel("&URL:") urlLabel.setBuddy(self.urlLineEdit) self.statusLabel = QLabel( "Please enter the URL of a file you want to download.") self.statusLabel.setWordWrap(True) self.downloadButton = QPushButton("Download") self.downloadButton.setDefault(True) self.quitButton = QPushButton("Quit") self.quitButton.setAutoDefault(False) buttonBox = QDialogButtonBox() buttonBox.addButton(self.downloadButton, QDialogButtonBox.ActionRole) buttonBox.addButton(self.quitButton, QDialogButtonBox.RejectRole) self.progressDialog = QProgressDialog(self) self.urlLineEdit.textChanged.connect(self.enableDownloadButton) self.qnam.authenticationRequired.connect( self.slotAuthenticationRequired) self.qnam.sslErrors.connect(self.sslErrors) self.progressDialog.canceled.connect(self.cancelDownload) self.downloadButton.clicked.connect(self.downloadFile) self.quitButton.clicked.connect(self.close) topLayout = QHBoxLayout() topLayout.addWidget(urlLabel) topLayout.addWidget(self.urlLineEdit) mainLayout = QVBoxLayout() mainLayout.addLayout(topLayout) mainLayout.addWidget(self.statusLabel) mainLayout.addWidget(buttonBox) self.setLayout(mainLayout) self.setWindowTitle("HTTP") self.urlLineEdit.setFocus() def startRequest(self, url): self.reply = self.qnam.get(QNetworkRequest(url)) self.reply.finished.connect(self.httpFinished) self.reply.readyRead.connect(self.httpReadyRead) self.reply.downloadProgress.connect(self.updateDataReadProgress) def downloadFile(self): self.url = QUrl(self.urlLineEdit.text()) fileInfo = QFileInfo(self.url.path()) fileName = fileInfo.fileName() if not fileName: fileName = 'index.html' if QFile.exists(fileName): ret = QMessageBox.question(self, "HTTP", "There already exists a file called %s in the current " "directory. Overwrite?" % fileName, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if ret == QMessageBox.No: return QFile.remove(fileName) self.outFile = QFile(fileName) if not self.outFile.open(QIODevice.WriteOnly): QMessageBox.information(self, "HTTP", "Unable to save the file %s: %s." % (fileName, self.outFile.errorString())) self.outFile = None return self.progressDialog.setWindowTitle("HTTP") self.progressDialog.setLabelText("Downloading %s." % fileName) self.downloadButton.setEnabled(False) self.httpRequestAborted = False self.startRequest(self.url) def cancelDownload(self): self.statusLabel.setText("Download canceled.") self.httpRequestAborted = True if self.reply is not None: self.reply.abort() self.downloadButton.setEnabled(True) def httpFinished(self): if self.httpRequestAborted: if self.outFile is not None: self.outFile.close() self.outFile.remove() self.outFile = None self.reply.deleteLater() self.reply = None self.progressDialog.hide() return self.progressDialog.hide() self.outFile.flush() self.outFile.close() redirectionTarget = self.reply.attribute(QNetworkRequest.RedirectionTargetAttribute) if self.reply.error(): self.outFile.remove() QMessageBox.information(self, "HTTP", "Download failed: %s." % self.reply.errorString()) self.downloadButton.setEnabled(True) elif redirectionTarget is not None: newUrl = self.url.resolved(redirectionTarget) ret = QMessageBox.question(self, "HTTP", "Redirect to %s?" % newUrl.toString(), QMessageBox.Yes | QMessageBox.No) if ret == QMessageBox.Yes: self.url = newUrl self.reply.deleteLater() self.reply = None self.outFile.open(QIODevice.WriteOnly) self.outFile.resize(0) self.startRequest(self.url) return else: fileName = QFileInfo(QUrl(self.urlLineEdit.text()).path()).fileName() self.statusLabel.setText("Downloaded %s to %s." % (fileName, QDir.currentPath())) self.downloadButton.setEnabled(True) self.reply.deleteLater() self.reply = None self.outFile = None def httpReadyRead(self): if self.outFile is not None: self.outFile.write(self.reply.readAll()) def updateDataReadProgress(self, bytesRead, totalBytes): if self.httpRequestAborted: return self.progressDialog.setMaximum(totalBytes) self.progressDialog.setValue(bytesRead) def enableDownloadButton(self): self.downloadButton.setEnabled(self.urlLineEdit.text() != '') def slotAuthenticationRequired(self, authenticator): import os from PyQt5 import uic ui = os.path.join(os.path.dirname(__file__), 'authenticationdialog.ui') dlg = uic.loadUi(ui) dlg.adjustSize() dlg.siteDescription.setText("%s at %s" % (authenticator.realm(), self.url.host())) dlg.userEdit.setText(self.url.userName()) dlg.passwordEdit.setText(self.url.password()) if dlg.exec_() == QDialog.Accepted: authenticator.setUser(dlg.userEdit.text()) authenticator.setPassword(dlg.passwordEdit.text()) def sslErrors(self, reply, errors): errorString = ", ".join([str(error.errorString()) for error in errors]) ret = QMessageBox.warning(self, "HTTP Example", "One or more SSL errors has occurred: %s" % errorString, QMessageBox.Ignore | QMessageBox.Abort) if ret == QMessageBox.Ignore: self.reply.ignoreSslErrors()
class CoverArtImage: # Indicate if types are provided by the source, ie. CAA or certain file # formats may have types associated with cover art, but some other sources # don't provide such information support_types = False # `is_front` has to be explicitly set, it is used to handle CAA is_front # indicator is_front = None sourceprefix = "URL" def __init__(self, url=None, types=None, comment='', data=None): if types is None: self.types = [] else: self.types = types if url is not None: self.parse_url(url) else: self.url = None self.comment = comment self.datahash = None # thumbnail is used to link to another CoverArtImage, ie. for PDFs self.thumbnail = None self.can_be_saved_to_tags = True self.can_be_saved_to_disk = True self.can_be_saved_to_metadata = True if data is not None: self.set_data(data) def parse_url(self, url): self.url = QUrl(url) self.host = string_(self.url.host()) self.port = self.url.port(80) self.path = string_(self.url.path(QUrl.FullyEncoded)) if self.url.hasQuery(): self.path += '?' + string_(self.url.query(QUrl.FullyEncoded)) @property def source(self): if self.url is not None: return "%s: %s" % (self.sourceprefix, self.url.toString()) else: return "%s" % self.sourceprefix def is_front_image(self): """Indicates if image is considered as a 'front' image. It depends on few things: - if `is_front` was set, it is used over anything else - if `types` was set, search for 'front' in it - if `support_types` is False, default to True for any image - if `support_types` is True, default to False for any image """ if not self.can_be_saved_to_metadata: # ignore thumbnails return False if self.is_front is not None: return self.is_front if 'front' in self.types: return True return (self.support_types is False) def imageinfo_as_string(self): if self.datahash is None: return "" return "w=%d h=%d mime=%s ext=%s datalen=%d file=%s" % ( self.width, self.height, self.mimetype, self.extension, self.datalength, self.tempfile_filename) def __repr__(self): p = [] if self.url is not None: p.append("url=%r" % self.url.toString()) if self.types: p.append("types=%r" % self.types) if self.is_front is not None: p.append("is_front=%r" % self.is_front) if self.comment: p.append("comment=%r" % self.comment) return "%s(%s)" % (self.__class__.__name__, ", ".join(p)) def __str__(self): p = ['Image'] if self.url is not None: p.append("from %s" % self.url.toString()) if self.types: p.append("of type %s" % ','.join(self.types)) if self.comment: p.append("and comment '%s'" % self.comment) return ' '.join(p) def __eq__(self, other): if self and other: if self.types and other.types: return (self.datahash, self.types) == (other.datahash, other.types) else: return self.datahash == other.datahash elif not self and not other: return True else: return False def __hash__(self): if self.datahash is None: return 0 return hash(self.datahash.hash()) def set_data(self, data): """Store image data in a file, if data already exists in such file it will be re-used and no file write occurs """ if self.datahash: self.datahash.delete_file() self.datahash = None try: (self.width, self.height, self.mimetype, self.extension, self.datalength) = imageinfo.identify(data) except imageinfo.IdentificationError as e: raise CoverArtImageIdentificationError(e) try: self.datahash = DataHash(data, suffix=self.extension) except (OSError, IOError) as e: raise CoverArtImageIOError(e) @property def maintype(self): """Returns one type only, even for images having more than one type set. This is mostly used when saving cover art to tags because most formats don't support multiple types for one image. Images coming from CAA can have multiple types (ie. 'front, booklet'). """ if self.is_front_image() or not self.types or 'front' in self.types: return 'front' # TODO: do something better than randomly using the first in the list return self.types[0] def _make_image_filename(self, filename, dirname, metadata): filename = ScriptParser().eval(filename, metadata) if config.setting["ascii_filenames"]: if isinstance(filename, str): filename = unaccent(filename) filename = replace_non_ascii(filename) if not filename: filename = "cover" if not os.path.isabs(filename): filename = os.path.join(dirname, filename) # replace incompatible characters if config.setting["windows_compatibility"] or sys.platform == "win32": filename = replace_win32_incompat(filename) # remove null characters if isinstance(filename, bytes): filename = filename.replace(b"\x00", "") return encode_filename(filename) def save(self, dirname, metadata, counters): """Saves this image. :dirname: The name of the directory that contains the audio file :metadata: A metadata object :counters: A dictionary mapping filenames to the amount of how many images with that filename were already saved in `dirname`. """ if not self.can_be_saved_to_disk: return if (config.setting["caa_image_type_as_filename"] and not self.is_front_image()): filename = self.maintype log.debug("Make cover filename from types: %r -> %r", self.types, filename) else: filename = config.setting["cover_image_filename"] log.debug("Using default cover image filename %r", filename) filename = self._make_image_filename(filename, dirname, metadata) overwrite = config.setting["save_images_overwrite"] ext = encode_filename(self.extension) image_filename = self._next_filename(filename, counters) while os.path.exists(image_filename + ext) and not overwrite: if not self._is_write_needed(image_filename + ext): break image_filename = self._next_filename(filename, counters) else: new_filename = image_filename + ext # Even if overwrite is enabled we don't need to write the same # image multiple times if not self._is_write_needed(new_filename): return log.debug("Saving cover image to %r", new_filename) try: new_dirname = os.path.dirname(new_filename) if not os.path.isdir(new_dirname): os.makedirs(new_dirname) shutil.copyfile(self.tempfile_filename, new_filename) except (OSError, IOError) as e: raise CoverArtImageIOError(e) def _next_filename(self, filename, counters): if counters[filename]: new_filename = b"%b (%d)" % (filename, counters[filename]) else: new_filename = filename counters[filename] += 1 return new_filename def _is_write_needed(self, filename): if (os.path.exists(filename) and os.path.getsize(filename) == self.datalength): log.debug("Identical file size, not saving %r", filename) return False return True @property def data(self): """Reads the data from the temporary file created for this image. May raise CoverArtImageIOError """ try: return self.datahash.data except (OSError, IOError) as e: raise CoverArtImageIOError(e) @property def tempfile_filename(self): return self.datahash.filename def types_as_string(self, translate=True, separator=', '): if self.types: types = self.types elif self.is_front_image(): types = ['front'] else: types = ['-'] if translate: types = [translate_caa_type(type) for type in types] return separator.join(types)
class E5SslInfoWidget(QMenu): """ Class implementing a widget to show SSL certificate infos. """ def __init__(self, url, configuration, parent=None): """ Constructor @param url URL to show SSL info for (QUrl) @param configuration SSL configuration (QSslConfiguration) @param parent reference to the parent widget (QWidget) """ super(E5SslInfoWidget, self).__init__(parent) self.__url = QUrl(url) self.__configuration = QSslConfiguration(configuration) self.setMinimumWidth(400) certList = self.__configuration.peerCertificateChain() if certList: cert = certList[0] else: cert = QSslCertificate() layout = QGridLayout(self) rows = 0 ########################################## ## Identity Information ########################################## imageLabel = QLabel(self) layout.addWidget(imageLabel, rows, 0, Qt.AlignCenter) label = QLabel(self) label.setWordWrap(True) label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) label.setText(self.tr("Identity")) font = label.font() font.setBold(True) label.setFont(font) layout.addWidget(label, rows, 1) rows += 1 label = QLabel(self) label.setWordWrap(True) if cert.isNull(): label.setText( self.tr("Warning: this site is NOT carrying a certificate.")) imageLabel.setPixmap(UI.PixmapCache.getPixmap("securityLow32.png")) else: if qVersion() >= "5.0.0": valid = not cert.isBlacklisted() else: valid = cert.isValid() if valid: if qVersion() >= "5.0.0": txt = ", ".join(cert.issuerInfo( QSslCertificate.CommonName)) else: txt = cert.issuerInfo(QSslCertificate.CommonName) label.setText( self.tr("The certificate for this site is valid" " and has been verified by:\n{0}").format( Utilities.decodeString(txt))) imageLabel.setPixmap( UI.PixmapCache.getPixmap("securityHigh32.png")) else: label.setText( self.tr("The certificate for this site is NOT valid.")) imageLabel.setPixmap( UI.PixmapCache.getPixmap("securityLow32.png")) layout.addWidget(label, rows, 1) rows += 1 label = QLabel(self) label.setWordWrap(True) label.setText('<a href="moresslinfos">' + self.tr("Certificate Information") + "</a>") label.linkActivated.connect(self.__showCertificateInfos) layout.addWidget(label, rows, 1) rows += 1 ########################################## ## Identity Information ########################################## imageLabel = QLabel(self) layout.addWidget(imageLabel, rows, 0, Qt.AlignCenter) label = QLabel(self) label.setWordWrap(True) label.setText(self.tr("Encryption")) font = label.font() font.setBold(True) label.setFont(font) layout.addWidget(label, rows, 1) rows += 1 cipher = self.__configuration.sessionCipher() if cipher.isNull(): label = QLabel(self) label.setWordWrap(True) label.setText( self.tr('Your connection to "{0}" is NOT encrypted.\n').format( self.__url.host())) layout.addWidget(label, rows, 1) imageLabel.setPixmap(UI.PixmapCache.getPixmap("securityLow32.png")) rows += 1 else: label = QLabel(self) label.setWordWrap(True) label.setText( self.tr('Your connection to "{0}" is encrypted.').format( self.__url.host())) layout.addWidget(label, rows, 1) proto = cipher.protocol() if proto == QSsl.SslV3: sslVersion = "SSL 3.0" imageLabel.setPixmap( UI.PixmapCache.getPixmap("securityHigh32.png")) elif proto == QSsl.TlsV1SslV3: sslVersion = "TLS 1.0/SSL 3.0" imageLabel.setPixmap( UI.PixmapCache.getPixmap("securityHigh32.png")) elif proto == QSsl.SslV2: sslVersion = "SSL 2.0" imageLabel.setPixmap( UI.PixmapCache.getPixmap("securityLow32.png")) else: sslVersion = self.tr("unknown") imageLabel.setPixmap( UI.PixmapCache.getPixmap("securityLow32.png")) if qVersion() >= "5.0.0": if proto == QSsl.TlsV1_0: sslVersion = "TLS 1.0" imageLabel.setPixmap( UI.PixmapCache.getPixmap("securityHigh32.png")) elif proto == QSsl.TlsV1_1: sslVersion = "TLS 1.1" imageLabel.setPixmap( UI.PixmapCache.getPixmap("securityHigh32.png")) elif proto == QSsl.TlsV1_2: sslVersion = "TLS 1.2" imageLabel.setPixmap( UI.PixmapCache.getPixmap("securityHigh32.png")) else: if proto == QSsl.TlsV1: sslVersion = "TLS 1.0" imageLabel.setPixmap( UI.PixmapCache.getPixmap("securityHigh32.png")) rows += 1 label = QLabel(self) label.setWordWrap(True) label.setText(self.tr("It uses protocol: {0}").format(sslVersion)) layout.addWidget(label, rows, 1) rows += 1 label = QLabel(self) label.setWordWrap(True) label.setText( self.tr("It is encrypted using {0} at {1} bits, " "with {2} for message authentication and " "{3} as key exchange mechanism.\n\n").format( cipher.encryptionMethod(), cipher.usedBits(), cipher.authenticationMethod(), cipher.keyExchangeMethod())) layout.addWidget(label, rows, 1) rows += 1 def showAt(self, pos): """ Public method to show the widget. @param pos position to show at (QPoint) """ self.adjustSize() xpos = pos.x() - self.width() if xpos < 0: xpos = 10 p = QPoint(xpos, pos.y() + 10) self.move(p) self.show() def __showCertificateInfos(self): """ Private slot to show certificate information. """ from .E5SslCertificatesInfoDialog import E5SslCertificatesInfoDialog dlg = E5SslCertificatesInfoDialog( self.__configuration.peerCertificateChain()) dlg.exec_() def accept(self): """ Public method to accept the widget. """ self.close()
class HttpWindow(QDialog): def __init__(self, parent=None): super(HttpWindow, self).__init__(parent) self.url = QUrl() self.qnam = QNetworkAccessManager() self.reply = None self.outFile = None self.httpGetId = 0 self.httpRequestAborted = False self.urlLineEdit = QLineEdit("https://www.qt.io") urlLabel = QLabel("&URL:") urlLabel.setBuddy(self.urlLineEdit) self.statusLabel = QLabel( "Please enter the URL of a file you want to download." ) self.statusLabel.setWordWrap(True) self.downloadButton = QPushButton("Download") self.downloadButton.setDefault(True) self.quitButton = QPushButton("Quit") self.quitButton.setAutoDefault(False) buttonBox = QDialogButtonBox() buttonBox.addButton(self.downloadButton, QDialogButtonBox.ActionRole) buttonBox.addButton(self.quitButton, QDialogButtonBox.RejectRole) self.progressDialog = QProgressDialog(self) self.urlLineEdit.textChanged.connect(self.enableDownloadButton) self.qnam.authenticationRequired.connect(self.slotAuthenticationRequired) self.qnam.sslErrors.connect(self.sslErrors) self.progressDialog.canceled.connect(self.cancelDownload) self.downloadButton.clicked.connect(self.downloadFile) self.quitButton.clicked.connect(self.close) topLayout = QHBoxLayout() topLayout.addWidget(urlLabel) topLayout.addWidget(self.urlLineEdit) mainLayout = QVBoxLayout() mainLayout.addLayout(topLayout) mainLayout.addWidget(self.statusLabel) mainLayout.addWidget(buttonBox) self.setLayout(mainLayout) self.setWindowTitle("HTTP") self.urlLineEdit.setFocus() def startRequest(self, url): self.reply = self.qnam.get(QNetworkRequest(url)) self.reply.finished.connect(self.httpFinished) self.reply.readyRead.connect(self.httpReadyRead) self.reply.downloadProgress.connect(self.updateDataReadProgress) def downloadFile(self): self.url = QUrl(self.urlLineEdit.text()) fileInfo = QFileInfo(self.url.path()) fileName = fileInfo.fileName() if not fileName: fileName = "index.html" if QFile.exists(fileName): ret = QMessageBox.question( self, "HTTP", "There already exists a file called %s in the current " "directory. Overwrite?" % fileName, QMessageBox.Yes | QMessageBox.No, QMessageBox.No, ) if ret == QMessageBox.No: return QFile.remove(fileName) self.outFile = QFile(fileName) if not self.outFile.open(QIODevice.WriteOnly): QMessageBox.information( self, "HTTP", "Unable to save the file %s: %s." % (fileName, self.outFile.errorString()), ) self.outFile = None return self.progressDialog.setWindowTitle("HTTP") self.progressDialog.setLabelText("Downloading %s." % fileName) self.downloadButton.setEnabled(False) self.httpRequestAborted = False self.startRequest(self.url) def cancelDownload(self): self.statusLabel.setText("Download canceled.") self.httpRequestAborted = True if self.reply is not None: self.reply.abort() self.downloadButton.setEnabled(True) def httpFinished(self): if self.httpRequestAborted: if self.outFile is not None: self.outFile.close() self.outFile.remove() self.outFile = None self.reply.deleteLater() self.reply = None self.progressDialog.hide() return self.progressDialog.hide() self.outFile.flush() self.outFile.close() redirectionTarget = self.reply.attribute( QNetworkRequest.RedirectionTargetAttribute ) if self.reply.error(): self.outFile.remove() QMessageBox.information( self, "HTTP", "Download failed: %s." % self.reply.errorString() ) self.downloadButton.setEnabled(True) elif redirectionTarget is not None: newUrl = self.url.resolved(redirectionTarget) ret = QMessageBox.question( self, "HTTP", "Redirect to %s?" % newUrl.toString(), QMessageBox.Yes | QMessageBox.No, ) if ret == QMessageBox.Yes: self.url = newUrl self.reply.deleteLater() self.reply = None self.outFile.open(QIODevice.WriteOnly) self.outFile.resize(0) self.startRequest(self.url) return else: fileName = QFileInfo(QUrl(self.urlLineEdit.text()).path()).fileName() self.statusLabel.setText( "Downloaded %s to %s." % (fileName, QDir.currentPath()) ) self.downloadButton.setEnabled(True) self.reply.deleteLater() self.reply = None self.outFile = None def httpReadyRead(self): if self.outFile is not None: self.outFile.write(self.reply.readAll()) def updateDataReadProgress(self, bytesRead, totalBytes): if self.httpRequestAborted: return self.progressDialog.setMaximum(totalBytes) self.progressDialog.setValue(bytesRead) def enableDownloadButton(self): self.downloadButton.setEnabled(self.urlLineEdit.text() != "") def slotAuthenticationRequired(self, authenticator): import os from PyQt5 import uic ui = os.path.join(os.path.dirname(__file__), "authenticationdialog.ui") dlg = uic.loadUi(ui) dlg.adjustSize() dlg.siteDescription.setText( "%s at %s" % (authenticator.realm(), self.url.host()) ) dlg.userEdit.setText(self.url.userName()) dlg.passwordEdit.setText(self.url.password()) if dlg.exec_() == QDialog.Accepted: authenticator.setUser(dlg.userEdit.text()) authenticator.setPassword(dlg.passwordEdit.text()) def sslErrors(self, reply, errors): errorString = ", ".join([str(error.errorString()) for error in errors]) ret = QMessageBox.warning( self, "HTTP Example", "One or more SSL errors has occurred: %s" % errorString, QMessageBox.Ignore | QMessageBox.Abort, ) if ret == QMessageBox.Ignore: self.reply.ignoreSslErrors()
def accept(self): section = CFG_SECTION_IPFSD if self.ui.groupBoxDaemon.isChecked(): self.sManager.setTrue(section, CFG_KEY_ENABLED) else: self.sManager.setFalse(section, CFG_KEY_ENABLED) self.setS(section, CFG_KEY_SWARMPORT, self.ui.ipfsdSwarmPort.text()) self.setS(section, CFG_KEY_SWARMPORT_QUIC, self.ui.ipfsdSwarmPortQuic.text()) self.sManager.setBoolFrom(section, CFG_KEY_SWARM_QUIC, self.isChecked(self.ui.checkBoxQuic)) self.setS(section, CFG_KEY_APIPORT, self.ui.ipfsdApiPort.text()) self.setS(section, CFG_KEY_HTTPGWPORT, self.ui.ipfsdGwPort.text()) self.setS(section, CFG_KEY_SWARMLOWWATER, self.ui.swarmMinConns.text()) self.setS( section, CFG_KEY_SWARMHIGHWATER, self.ui.swarmMaxConns.text()) self.setS(section, CFG_KEY_STORAGEMAX, self.ui.storageMax.text()) self.setS(section, CFG_KEY_ROUTINGMODE, self.ui.routingMode.currentText()) self.setS(section, CFG_KEY_PUBSUB_ROUTER, self.ui.pubsubRoutingMode.currentText()) self.sManager.setBoolFrom(section, CFG_KEY_HTTPGWWRITABLE, self.isChecked(self.ui.writableHttpGw)) self.sManager.setBoolFrom(section, CFG_KEY_NAMESYS_PUBSUB, self.isChecked(self.ui.namesysPubsub)) self.sManager.setBoolFrom(section, CFG_KEY_FILESTORE, self.isChecked(self.ui.fileStore)) self.sManager.setBoolFrom(section, CFG_KEY_IPFSD_DETACHED, self.isChecked(self.ui.keepDaemonRunning)) section = CFG_SECTION_IPFSCONN1 self.setS(section, CFG_KEY_HOST, self.ui.customIpfsHost.text()) self.setS(section, CFG_KEY_APIPORT, self.ui.customIpfsApiPort.text()) self.setS(section, CFG_KEY_HTTPGWPORT, self.ui.customIpfsGwPort.text()) section = CFG_SECTION_BROWSER self.setS(section, CFG_KEY_HOMEURL, self.ui.home.text()) cSet('defaultWebProfile', self.ui.comboDefaultWebProfile.currentText(), mod='galacteek.browser.webprofiles') section = CFG_SECTION_HISTORY cSet('enabled', self.isChecked(self.ui.urlHistoryEnable), mod='galacteek.ui.history') cSet('zoom.default', self.ui.webEngineDefaultZoom.value(), mod='galacteek.ui.browser') idx = self.ui.language.currentIndex() langCode = self.ui.language.itemData(idx) curLang = cGet('language', mod='galacteek.application') if langCode != curLang: cSet('language', langCode, mod='galacteek.application') self.app.setupTranslator() section = CFG_SECTION_ETHEREUM if self.ui.groupBoxEth.isChecked(): self.sManager.setTrue(section, CFG_KEY_ENABLED) else: self.sManager.setFalse(section, CFG_KEY_ENABLED) rpcUrl = QUrl(self.ui.ethRpcUrl.text()) if not rpcUrl.isValid() or not rpcUrl.scheme() in [ 'http', 'https', 'wss'] or not rpcUrl.host(): return messageBox( 'Invalid Ethereum RPC URL (scheme should be http or wss)' ) self.setS(section, CFG_KEY_PROVIDERTYPE, self.ui.ethProvType.currentText()) self.setS(section, CFG_KEY_RPCURL, rpcUrl.toString()) self.app.urlHistory.historyConfigChanged.emit( self.sManager.urlHistoryEnabled) self.sManager.sync() self.sManager.changed = True self.done(1)
class ClickToFlash(QWidget, Ui_ClickToFlash): """ Class implementing the Flash blocker. """ _acceptedUrl = QUrl() _acceptedArgNames = [] _acceptedArgValues = [] def __init__(self, plugin, mimeType, url, argumentNames, argumentValues, parent=None): """ Constructor @param plugin reference to the plug-in (ClickToFlashPlugin) @param mimeType MIME type for the plug-in (string) @param url requested URL (QUrl) @param argumentNames list of argument names (list of strings) @param argumentValues list of argument values (list of strings) @param parent reference to the parent widget (QWidget) """ super(ClickToFlash, self).__init__(parent) # Check AdBlock first import Helpviewer.HelpWindow manager = Helpviewer.HelpWindow.HelpWindow.adBlockManager() if manager.isEnabled(): urlString = bytes(url.toEncoded()).decode() urlDomain = url.host() for subscription in manager.subscriptions(): blockedRule = subscription.match(QNetworkRequest(url), urlDomain, urlString) if blockedRule: QTimer.singleShot(200, self.__hideAdBlocked) return self.setupUi(self) self.__swapping = False self.__element = QWebElement() self.__plugin = plugin self.__url = QUrl(url) self.__argumentNames = argumentNames[:] self.__argumentValues = argumentValues[:] self.__mimeType = mimeType self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.__showContextMenu) self.setToolTip(self.__url.toString()) iconName = plugin.getIconName(mimeType) if iconName: self.loadFlashButton.setIcon(UI.PixmapCache.getIcon(iconName)) else: self.loadFlashButton.setText(self.tr("Load")) @pyqtSlot() def on_loadFlashButton_clicked(self): """ Private slot handling the flash activation. """ self.__load() def __showContextMenu(self): """ Private slot to show the context menu. """ menu = QMenu() act = menu.addAction(self.tr("Object blocked by ClickToFlash")) font = act.font() font.setBold(True) act.setFont(font) menu.addAction(self.tr("Show information about object"), self.__showInfo) menu.addSeparator() menu.addAction(self.tr("Load"), self.__load) menu.addAction(self.tr("Delete object"), self.__hideAdBlocked) menu.addSeparator() host = self.__url.host() add = menu.addAction( self.tr("Add '{0}' to Whitelist").format(host), self.__addToWhitelist) remove = menu.addAction( self.tr("Remove '{0}' from Whitelist").format(host), self.__removeFromWhitelist) onWhitelist = self.__plugin.onWhitelist(host) add.setEnabled(not onWhitelist) remove.setEnabled(onWhitelist) menu.addSeparator() menu.addAction(self.tr("Configure Whitelist"), self.__configure) menu.actions()[0].setEnabled(False) menu.exec_(QCursor.pos()) def swapping(self): """ Public method to check, if the plug-in is swapping. @return flag indicating the swapping status (boolean) """ return self.__swapping def __configure(self): """ Private slot to configure the whitelist. """ self.__plugin.configure() def __addToWhitelist(self): """ Private slot to add the host to the whitelist. """ self.__plugin.addToWhitelist(self.__url.host()) def __removeFromWhitelist(self): """ Private slot to remove the host from the whitelist. """ self.__plugin.removeFromWhitelist(self.__url.host()) def __load(self, all=False): """ Private slot to load the flash content. @param all flag indicating to load all flash players. (boolean) """ self.__findElement() if not self.__element.isNull(): substitute = self.__element.clone() substitute.setAttribute("type", self.__mimeType) self.__element.replace(substitute) ClickToFlash._acceptedUrl = self.__url ClickToFlash._acceptedArgNames = self.__argumentNames ClickToFlash._acceptedArgValues = self.__argumentValues def __findElement(self): """ Private method to find the element belonging to this ClickToFlash instance. """ parent = self.parentWidget() view = None while parent is not None: if isinstance(parent, QWebView): view = parent break parent = parent.parentWidget() if view is None: return objectPos = view.mapFromGlobal( self.loadFlashButton.mapToGlobal(self.loadFlashButton.pos())) objectFrame = view.page().frameAt(objectPos) hitResult = QWebHitTestResult() hitElement = QWebElement() if objectFrame is not None: hitResult = objectFrame.hitTestContent(objectPos) hitElement = hitResult.element() if not hitElement.isNull() and \ hitElement.tagName().lower() in ["embed", "object"]: self.__element = hitElement return # hit test failed, trying to find element by src # attribute in elements of all frames on page (although less accurate frames = [] frames.append(view.page().mainFrame()) while frames: frame = frames.pop(0) if not frame: continue docElement = frame.documentElement() elements = QWebElementCollection() elements.append(docElement.findAll("embed")) elements.append(docElement.findAll("object")) for element in elements: if not self.__checkElement(element) and \ not self.__checkUrlOnElement(element, view): continue self.__element = element return frames.extend(frame.childFrames()) def __checkUrlOnElement(self, element, view): """ Private slot to check the URL of an element. @param element reference to the element to check (QWebElement) @param view reference to the view object (QWebView) @return flag indicating a positive result (boolean) """ checkString = element.attribute("src") if checkString == "": checkString = element.attribute("data") if checkString == "": checkString = element.attribute("value") checkString = view.url().resolved(QUrl(checkString)).toString( QUrl.RemoveQuery) return self.__url.toEncoded().contains( QByteArray(checkString.encode("utf-8"))) def __checkElement(self, element): """ Private slot to check an element against the saved arguments. @param element reference to the element to check (QWebElement) @return flag indicating a positive result (boolean) """ if self.__argumentNames == element.attributeNames(): for name in self.__argumentNames: if element.attribute(name) not in self.__argumentValues: return False return True return False def __hideAdBlocked(self): """ Private slot to hide the object. """ self.__findElement() if not self.__element.isNull(): self.__element.setStyleProperty("display", "none") else: self.hide() def __showInfo(self): """ Private slot to show information about the blocked object. """ dlg = QDialog() dlg.setWindowTitle(self.tr("Flash Object")) dlg.setSizeGripEnabled(True) layout = QFormLayout(dlg) layout.addRow(QLabel(self.tr("<b>Attribute Name</b>")), QLabel(self.tr("<b>Value</b>"))) index = 0 for name in self.__argumentNames: nameLabel = QLabel(self.__elide(name, length=30)) value = self.__argumentValues[index] valueLabel = QLabel(self.__elide(value, length=60)) valueLabel.setTextInteractionFlags(Qt.TextSelectableByMouse | Qt.LinksAccessibleByMouse) layout.addRow(nameLabel, valueLabel) index += 1 if index == 0: layout.addRow(QLabel(self.tr("No information available."))) dlg.setMaximumHeight(500) dlg.setMaximumWidth(500) dlg.exec_() def __elide(self, txt, mode=Qt.ElideMiddle, length=40): """ Private method to elide some text. @param txt text to be elided (string) @keyparam mode elide mode (Qt.TextElideMode) @keyparam length amount of characters to be used (integer) @return the elided text (string) """ if mode == Qt.ElideNone or len(txt) < length: return txt elif mode == Qt.ElideLeft: return "...{0}".format(txt[-length:]) elif mode == Qt.ElideMiddle: return "{0}...{1}".format(txt[:length // 2], txt[-(length // 2):]) elif mode == Qt.ElideRight: return "{0}...".format(txt[:length]) else: # just in case return txt @classmethod def isAlreadyAccepted(cls, url, argumentNames, argumentValues): """ Class method to check, if the given parameter combination is being accepted. @param url URL to be checked for (QUrl) @param argumentNames argument names to be checked for (list of strings) @param argumentValues argument values to be checked for (list of strings) @return flag indicating that this was already accepted (boolean) """ return url == cls._acceptedUrl and \ argumentNames == cls._acceptedArgNames and \ argumentValues == cls._acceptedArgValues
class WebView(QWebView): """One browser tab in TabbedBrowser. Our own subclass of a QWebView with some added bells and whistles. Attributes: hintmanager: The HintManager instance for this view. progress: loading progress of this page. scroll_pos: The current scroll position as (x%, y%) tuple. statusbar_message: The current javascript statusbar message. inspector: The QWebInspector used for this webview. load_status: loading status of this page (index into LoadStatus) viewing_source: Whether the webview is currently displaying source code. keep_icon: Whether the (e.g. cloned) icon should not be cleared on page load. registry: The ObjectRegistry associated with this tab. tab_id: The tab ID of the view. win_id: The window ID of the view. search_text: The text of the last search. search_flags: The search flags of the last search. _has_ssl_errors: Whether SSL errors occurred during loading. _zoom: A NeighborList with the zoom levels. _old_scroll_pos: The old scroll position. _check_insertmode: If True, in mouseReleaseEvent we should check if we need to enter/leave insert mode. _default_zoom_changed: Whether the zoom was changed from the default. _ignore_wheel_event: Ignore the next wheel event. See https://github.com/The-Compiler/qutebrowser/issues/395 Signals: scroll_pos_changed: Scroll percentage of current tab changed. arg 1: x-position in %. arg 2: y-position in %. linkHovered: QWebPages linkHovered signal exposed. load_status_changed: The loading status changed url_text_changed: Current URL string changed. shutting_down: Emitted when the view is shutting down. """ scroll_pos_changed = pyqtSignal(int, int) linkHovered = pyqtSignal(str, str, str) load_status_changed = pyqtSignal(str) url_text_changed = pyqtSignal(str) shutting_down = pyqtSignal() def __init__(self, win_id, parent=None): super().__init__(parent) if sys.platform == 'darwin' and qtutils.version_check('5.4'): # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-42948 # See https://github.com/The-Compiler/qutebrowser/issues/462 self.setStyle(QStyleFactory.create('Fusion')) self.win_id = win_id self.load_status = LoadStatus.none self._check_insertmode = False self.inspector = None self.scroll_pos = (-1, -1) self.statusbar_message = '' self._old_scroll_pos = (-1, -1) self._zoom = None self._has_ssl_errors = False self._ignore_wheel_event = False self.keep_icon = False self.search_text = None self.search_flags = 0 self.selection_enabled = False self.init_neighborlist() self._set_bg_color() cfg = objreg.get('config') cfg.changed.connect(self.init_neighborlist) # For some reason, this signal doesn't get disconnected automatically # when the WebView is destroyed on older PyQt versions. # See https://github.com/The-Compiler/qutebrowser/issues/390 self.destroyed.connect(functools.partial( cfg.changed.disconnect, self.init_neighborlist)) self.cur_url = QUrl() self.progress = 0 self.registry = objreg.ObjectRegistry() self.tab_id = next(tab_id_gen) tab_registry = objreg.get('tab-registry', scope='window', window=win_id) tab_registry[self.tab_id] = self objreg.register('webview', self, registry=self.registry) page = self._init_page() hintmanager = hints.HintManager(win_id, self.tab_id, self) hintmanager.mouse_event.connect(self.on_mouse_event) hintmanager.start_hinting.connect(page.on_start_hinting) hintmanager.stop_hinting.connect(page.on_stop_hinting) objreg.register('hintmanager', hintmanager, registry=self.registry) mode_manager = objreg.get('mode-manager', scope='window', window=win_id) mode_manager.entered.connect(self.on_mode_entered) mode_manager.left.connect(self.on_mode_left) self.viewing_source = False self.setZoomFactor(float(config.get('ui', 'default-zoom')) / 100) self._default_zoom_changed = False if config.get('input', 'rocker-gestures'): self.setContextMenuPolicy(Qt.PreventContextMenu) self.urlChanged.connect(self.on_url_changed) self.loadProgress.connect(lambda p: setattr(self, 'progress', p)) objreg.get('config').changed.connect(self.on_config_changed) def _init_page(self): """Initialize the QWebPage used by this view.""" page = webpage.BrowserPage(self.win_id, self.tab_id, self) self.setPage(page) page.linkHovered.connect(self.linkHovered) page.mainFrame().loadStarted.connect(self.on_load_started) page.mainFrame().loadFinished.connect(self.on_load_finished) page.statusBarMessage.connect( lambda msg: setattr(self, 'statusbar_message', msg)) page.networkAccessManager().sslErrors.connect( lambda *args: setattr(self, '_has_ssl_errors', True)) return page def __repr__(self): url = utils.elide(self.url().toDisplayString(), 50) return utils.get_repr(self, tab_id=self.tab_id, url=url) def __del__(self): # Explicitly releasing the page here seems to prevent some segfaults # when quitting. # Copied from: # https://code.google.com/p/webscraping/source/browse/webkit.py#325 try: self.setPage(None) except RuntimeError: # It seems sometimes Qt has already deleted the QWebView and we # get: RuntimeError: wrapped C/C++ object of type WebView has been # deleted pass def _set_load_status(self, val): """Setter for load_status.""" if not isinstance(val, LoadStatus): raise TypeError("Type {} is no LoadStatus member!".format(val)) log.webview.debug("load status for {}: {}".format(repr(self), val)) self.load_status = val self.load_status_changed.emit(val.name) def _set_bg_color(self): """Set the webpage background color as configured.""" col = config.get('colors', 'webpage.bg') palette = self.palette() if col is None: col = self.style().standardPalette().color(QPalette.Base) palette.setColor(QPalette.Base, col) self.setPalette(palette) @pyqtSlot(str, str) def on_config_changed(self, section, option): """Reinitialize the zoom neighborlist if related config changed.""" if section == 'ui' and option in ('zoom-levels', 'default-zoom'): if not self._default_zoom_changed: self.setZoomFactor(float(config.get('ui', 'default-zoom')) / 100) self._default_zoom_changed = False self.init_neighborlist() elif section == 'input' and option == 'rocker-gestures': if config.get('input', 'rocker-gestures'): self.setContextMenuPolicy(Qt.PreventContextMenu) else: self.setContextMenuPolicy(Qt.DefaultContextMenu) elif section == 'colors' and option == 'webpage.bg': self._set_bg_color() def init_neighborlist(self): """Initialize the _zoom neighborlist.""" levels = config.get('ui', 'zoom-levels') self._zoom = usertypes.NeighborList( levels, mode=usertypes.NeighborList.Modes.block) self._zoom.fuzzyval = config.get('ui', 'default-zoom') def _mousepress_backforward(self, e): """Handle back/forward mouse button presses. Args: e: The QMouseEvent. """ if e.button() in (Qt.XButton1, Qt.LeftButton): # Back button on mice which have it, or rocker gesture if self.page().history().canGoBack(): self.back() else: message.error(self.win_id, "At beginning of history.", immediately=True) elif e.button() in (Qt.XButton2, Qt.RightButton): # Forward button on mice which have it, or rocker gesture if self.page().history().canGoForward(): self.forward() else: message.error(self.win_id, "At end of history.", immediately=True) def _mousepress_insertmode(self, e): """Switch to insert mode when an editable element was clicked. Args: e: The QMouseEvent. """ pos = e.pos() frame = self.page().frameAt(pos) if frame is None: # This happens when we click inside the webview, but not actually # on the QWebPage - for example when clicking the scrollbar # sometimes. log.mouse.debug("Clicked at {} but frame is None!".format(pos)) return # You'd think we have to subtract frame.geometry().topLeft() from the # position, but it seems QWebFrame::hitTestContent wants a position # relative to the QWebView, not to the frame. This makes no sense to # me, but it works this way. hitresult = frame.hitTestContent(pos) if hitresult.isNull(): # For some reason, the whole hit result can be null sometimes (e.g. # on doodle menu links). If this is the case, we schedule a check # later (in mouseReleaseEvent) which uses webelem.focus_elem. log.mouse.debug("Hitresult is null!") self._check_insertmode = True return try: elem = webelem.WebElementWrapper(hitresult.element()) except webelem.IsNullError: # For some reason, the hit result element can be a null element # sometimes (e.g. when clicking the timetable fields on # http://www.sbb.ch/ ). If this is the case, we schedule a check # later (in mouseReleaseEvent) which uses webelem.focus_elem. log.mouse.debug("Hitresult element is null!") self._check_insertmode = True return if ((hitresult.isContentEditable() and elem.is_writable()) or elem.is_editable()): log.mouse.debug("Clicked editable element!") modeman.enter(self.win_id, usertypes.KeyMode.insert, 'click', only_if_normal=True) else: log.mouse.debug("Clicked non-editable element!") if config.get('input', 'auto-leave-insert-mode'): modeman.maybe_leave(self.win_id, usertypes.KeyMode.insert, 'click') def mouserelease_insertmode(self): """If we have an insertmode check scheduled, handle it.""" if not self._check_insertmode: return self._check_insertmode = False try: elem = webelem.focus_elem(self.page().currentFrame()) except (webelem.IsNullError, RuntimeError): log.mouse.warning("Element/page vanished!") return if elem.is_editable(): log.mouse.debug("Clicked editable element (delayed)!") modeman.enter(self.win_id, usertypes.KeyMode.insert, 'click-delayed', only_if_normal=True) else: log.mouse.debug("Clicked non-editable element (delayed)!") if config.get('input', 'auto-leave-insert-mode'): modeman.maybe_leave(self.win_id, usertypes.KeyMode.insert, 'click-delayed') def _mousepress_opentarget(self, e): """Set the open target when something was clicked. Args: e: The QMouseEvent. """ if e.button() == Qt.MidButton or e.modifiers() & Qt.ControlModifier: background_tabs = config.get('tabs', 'background-tabs') if e.modifiers() & Qt.ShiftModifier: background_tabs = not background_tabs if background_tabs: target = usertypes.ClickTarget.tab_bg else: target = usertypes.ClickTarget.tab self.page().open_target = target log.mouse.debug("Middle click, setting target: {}".format(target)) else: self.page().open_target = usertypes.ClickTarget.normal log.mouse.debug("Normal click, setting normal target") def shutdown(self): """Shut down the webview.""" self.shutting_down.emit() # We disable javascript because that prevents some segfaults when # quitting it seems. log.destroy.debug("Shutting down {!r}.".format(self)) settings = self.settings() settings.setAttribute(QWebSettings.JavascriptEnabled, False) self.stop() self.page().shutdown() def openurl(self, url): """Open a URL in the browser. Args: url: The URL to load as QUrl """ qtutils.ensure_valid(url) urlstr = url.toDisplayString() log.webview.debug("New title: {}".format(urlstr)) self.titleChanged.emit(urlstr) self.cur_url = url self.url_text_changed.emit(url.toDisplayString()) self.load(url) if url.scheme() == 'qute': frame = self.page().mainFrame() frame.javaScriptWindowObjectCleared.connect(self.add_js_bridge) def add_js_bridge(self): """Add the javascript bridge for qute:... pages.""" frame = self.sender() if frame.url().scheme() == 'qute': bridge = objreg.get('js-bridge') frame.addToJavaScriptWindowObject('qute', bridge) def zoom_perc(self, perc, fuzzyval=True): """Zoom to a given zoom percentage. Args: perc: The zoom percentage as int. fuzzyval: Whether to set the NeighborLists fuzzyval. """ if fuzzyval: self._zoom.fuzzyval = int(perc) if perc < 0: raise ValueError("Can't zoom {}%!".format(perc)) self.setZoomFactor(float(perc) / 100) self._default_zoom_changed = True def zoom(self, offset): """Increase/Decrease the zoom level. Args: offset: The offset in the zoom level list. Return: The new zoom percentage. """ level = self._zoom.getitem(offset) self.zoom_perc(level, fuzzyval=False) return level @pyqtSlot('QUrl') def on_url_changed(self, url): """Update cur_url when URL has changed. If the URL is invalid, we just ignore it here. """ if url.isValid(): self.cur_url = url self.url_text_changed.emit(url.toDisplayString()) if not self.title(): self.titleChanged.emit(self.url().toDisplayString()) @pyqtSlot('QMouseEvent') def on_mouse_event(self, evt): """Post a new mouse event from a hintmanager.""" log.modes.debug("Hint triggered, focusing {!r}".format(self)) self.setFocus() QApplication.postEvent(self, evt) @pyqtSlot() def on_load_started(self): """Leave insert/hint mode and set vars when a new page is loading.""" self.progress = 0 self.viewing_source = False self._has_ssl_errors = False self._set_load_status(LoadStatus.loading) @pyqtSlot() def on_load_finished(self): """Handle a finished page load. We don't take loadFinished's ok argument here as it always seems to be true when the QWebPage has an ErrorPageExtension implemented. See https://github.com/The-Compiler/qutebrowser/issues/84 """ ok = not self.page().error_occurred if ok and not self._has_ssl_errors: self._set_load_status(LoadStatus.success) elif ok: self._set_load_status(LoadStatus.warn) else: self._set_load_status(LoadStatus.error) if not self.title(): self.titleChanged.emit(self.url().toDisplayString()) self._handle_auto_insert_mode(ok) def _handle_auto_insert_mode(self, ok): """Handle auto-insert-mode after loading finished.""" ai = False for mode in (usertypes.KeyMode.hint, usertypes.KeyMode.insert, usertypes.KeyMode.caret, usertypes.KeyMode.passthrough): modeman.maybe_leave(self.win_id, mode, 'load finished') for r in pt_masks: if re.match(r, self.cur_url.host()) is not None: print(re.match(r, self.cur_url.host()), re.match(r, self.cur_url.host()).groups()) ai = True break if ai: modeman.enter(self.win_id, usertypes.KeyMode.passthrough, 'load finished', only_if_normal=True) return if not config.get('input', 'auto-insert-mode'): return mode_manager = objreg.get('mode-manager', scope='window', window=self.win_id) cur_mode = mode_manager.mode if cur_mode == usertypes.KeyMode.insert or not ok: return frame = self.page().currentFrame() try: elem = webelem.WebElementWrapper(frame.findFirstElement(':focus')) except webelem.IsNullError: log.webview.debug("Focused element is null!") log.modes.debug("focus element: {}".format(repr(elem))) print("bazqux" in self.cur_url.host(), self.cur_url.host()) if elem.is_editable() or rss: modeman.enter(self.win_id, usertypes.KeyMode.insert, 'load finished', only_if_normal=True) @pyqtSlot(usertypes.KeyMode) def on_mode_entered(self, mode): """Ignore attempts to focus the widget if in any status-input mode.""" if mode in (usertypes.KeyMode.command, usertypes.KeyMode.prompt, usertypes.KeyMode.yesno): log.webview.debug("Ignoring focus because mode {} was " "entered.".format(mode)) self.setFocusPolicy(Qt.NoFocus) elif mode == usertypes.KeyMode.caret: settings = self.settings() settings.setAttribute(QWebSettings.CaretBrowsingEnabled, True) self.selection_enabled = bool(self.page().selectedText()) if self.isVisible(): # Sometimes the caret isn't immediately visible, but unfocusing # and refocusing it fixes that. self.clearFocus() self.setFocus(Qt.OtherFocusReason) # Move the caret to the first element in the viewport if there # isn't any text which is already selected. # # Note: We can't use hasSelection() here, as that's always # true in caret mode. if not self.page().selectedText(): self.page().currentFrame().evaluateJavaScript( utils.read_file('javascript/position_caret.js')) @pyqtSlot(usertypes.KeyMode) def on_mode_left(self, mode): """Restore focus policy if status-input modes were left.""" if mode in (usertypes.KeyMode.command, usertypes.KeyMode.prompt, usertypes.KeyMode.yesno): log.webview.debug("Restoring focus policy because mode {} was " "left.".format(mode)) elif mode == usertypes.KeyMode.caret: settings = self.settings() if settings.testAttribute(QWebSettings.CaretBrowsingEnabled): if self.selection_enabled and self.hasSelection(): # Remove selection if it exists self.triggerPageAction(QWebPage.MoveToNextChar) settings.setAttribute(QWebSettings.CaretBrowsingEnabled, False) self.selection_enabled = False self.setFocusPolicy(Qt.WheelFocus) def search(self, text, flags): """Search for text in the current page. Args: text: The text to search for. flags: The QWebPage::FindFlags. """ log.webview.debug("Searching with text '{}' and flags " "0x{:04x}.".format(text, int(flags))) old_scroll_pos = self.scroll_pos flags = QWebPage.FindFlags(flags) found = self.findText(text, flags) backward = flags & QWebPage.FindBackward if not found and not flags & QWebPage.HighlightAllOccurrences and text: # User disabled wrapping; but findText() just returns False. If we # have a selection, we know there's a match *somewhere* on the page if (not flags & QWebPage.FindWrapsAroundDocument and self.hasSelection()): if not backward: message.warning(self.win_id, "Search hit BOTTOM without " "match for: {}".format(text), immediately=True) else: message.warning(self.win_id, "Search hit TOP without " "match for: {}".format(text), immediately=True) else: message.error(self.win_id, "Text '{}' not found on " "page!".format(text), immediately=True) else: def check_scroll_pos(): """Check if the scroll position got smaller and show info.""" if not backward and self.scroll_pos < old_scroll_pos: message.info(self.win_id, "Search hit BOTTOM, continuing " "at TOP", immediately=True) elif backward and self.scroll_pos > old_scroll_pos: message.info(self.win_id, "Search hit TOP, continuing at " "BOTTOM", immediately=True) # We first want QWebPage to refresh. QTimer.singleShot(0, check_scroll_pos) def createWindow(self, wintype): """Called by Qt when a page wants to create a new window. This function is called from the createWindow() method of the associated QWebPage, each time the page wants to create a new window of the given type. This might be the result, for example, of a JavaScript request to open a document in a new window. Args: wintype: This enum describes the types of window that can be created by the createWindow() function. QWebPage::WebBrowserWindow: The window is a regular web browser window. QWebPage::WebModalDialog: The window acts as modal dialog. Return: The new QWebView object. """ if wintype == QWebPage.WebModalDialog: log.webview.warning("WebModalDialog requested, but we don't " "support that!") tabbed_browser = objreg.get('tabbed-browser', scope='window', window=self.win_id) return tabbed_browser.tabopen(background=False) def paintEvent(self, e): """Extend paintEvent to emit a signal if the scroll position changed. This is a bit of a hack: We listen to repaint requests here, in the hope a repaint will always be requested when scrolling, and if the scroll position actually changed, we emit a signal. Args: e: The QPaintEvent. Return: The superclass event return value. """ frame = self.page().mainFrame() new_pos = (frame.scrollBarValue(Qt.Horizontal), frame.scrollBarValue(Qt.Vertical)) if self._old_scroll_pos != new_pos: self._old_scroll_pos = new_pos m = (frame.scrollBarMaximum(Qt.Horizontal), frame.scrollBarMaximum(Qt.Vertical)) perc = (round(100 * new_pos[0] / m[0]) if m[0] != 0 else 0, round(100 * new_pos[1] / m[1]) if m[1] != 0 else 0) self.scroll_pos = perc self.scroll_pos_changed.emit(*perc) # Let superclass handle the event super().paintEvent(e) def mousePressEvent(self, e): """Extend QWidget::mousePressEvent(). This does the following things: - Check if a link was clicked with the middle button or Ctrl and set the page's open_target attribute accordingly. - Emit the editable_elem_selected signal if an editable element was clicked. Args: e: The arrived event. Return: The superclass return value. """ is_rocker_gesture = (config.get('input', 'rocker-gestures') and e.buttons() == Qt.LeftButton | Qt.RightButton) if e.button() in (Qt.XButton1, Qt.XButton2) or is_rocker_gesture: self._mousepress_backforward(e) super().mousePressEvent(e) return self._mousepress_insertmode(e) self._mousepress_opentarget(e) self._ignore_wheel_event = True super().mousePressEvent(e) def mouseReleaseEvent(self, e): """Extend mouseReleaseEvent to enter insert mode if needed.""" super().mouseReleaseEvent(e) # We want to make sure we check the focus element after the WebView is # updated completely. QTimer.singleShot(0, self.mouserelease_insertmode) def contextMenuEvent(self, e): """Save a reference to the context menu so we can close it.""" menu = self.page().createStandardContextMenu() self.shutting_down.connect(menu.close) modeman.instance(self.win_id).entered.connect(menu.close) menu.exec_(e.globalPos()) def wheelEvent(self, e): """Zoom on Ctrl-Mousewheel. Args: e: The QWheelEvent. """ if self._ignore_wheel_event: self._ignore_wheel_event = False # See https://github.com/The-Compiler/qutebrowser/issues/395 return if e.modifiers() & Qt.ControlModifier: e.accept() divider = config.get('input', 'mouse-zoom-divider') factor = self.zoomFactor() + e.angleDelta().y() / divider if factor < 0: return perc = int(100 * factor) message.info(self.win_id, "Zoom level: {}%".format(perc)) self._zoom.fuzzyval = perc self.setZoomFactor(factor) self._default_zoom_changed = True else: super().wheelEvent(e)