class IPFSSearchView(QWidget): titleNeedUpdate = pyqtSignal(str) def __init__(self, searchQuery='', parent=None): super(IPFSSearchView, self).__init__(parent) self.setLayout(QVBoxLayout()) self.tab = parent self.app = QApplication.instance() self.setAttribute(Qt.WA_DeleteOnClose) # Templates self.resultsTemplate = self.app.getJinjaTemplate('ipfssearch.html') self.loadingTemplate = self.app.getJinjaTemplate( 'ipfssearch-loading.html') self.browser = QWebEngineView(parent=self) self.layout().addWidget(self.browser) self.resultsPage = self.app.mainWindow.ipfsSearchPageFactory.getPage() self.resultsPage.setParent(self) self.resultsPage.handler.searchStarted.connect( lambda query: self.titleNeedUpdate.emit(query)) self.browser.setPage(self.resultsPage) self.browser.setFocus(Qt.OtherFocusReason)
class MyBrowser(QMainWindow): def __init__(self, url): super(QMainWindow, self).__init__() self.progress = 0 QNetworkProxyFactory.setUseSystemConfiguration(True) self.generateView(url) self.editLocation() self.generateToolBar() self.generateViewMenu() self.generateEffectMenu() self.generateToolsMenu() self.setCentralWidget(self.view) def generateToolsMenu(self): """Method generates the Tools menu on the tool bar and adds actions to it.""" toolsMenu = self.menuBar().addMenu("&Tools") toolsMenu.addAction("Filler Action") def generateViewMenu(self): """Method generates the View menu on the tool bar and adds actions to it.""" viewMenu = self.menuBar().addMenu("&View") viewSourceAction = QAction("Page Source", self) # viewSourceAction.triggered.connect(self.viewSource) viewMenu.addAction(viewSourceAction) def generateEffectMenu(self): """Method generates the Effect menu on the tool bar and adds actions to it.""" effectMenu = self.menuBar().addMenu("&Effect") effectMenu.addAction("Filler Action") def editLocation(self): """Method edits the URL location based on what is typed into the url bar.""" self.locationEdit = QLineEdit(self) self.locationEdit.setSizePolicy(QSizePolicy.Expanding, self.locationEdit.sizePolicy().verticalPolicy()) self.locationEdit.returnPressed.connect(self.changeLocation) def generateToolBar(self): """Method generates the navigation tool bar. It then adds the Back, Forward, Reload, Stop, and URL bar functionality to it.""" toolBar = self.addToolBar("Navigation") toolBar.addAction(self.view.pageAction(QWebEnginePage.Back)) toolBar.addAction(self.view.pageAction(QWebEnginePage.Forward)) toolBar.addAction(self.view.pageAction(QWebEnginePage.Reload)) toolBar.addAction(self.view.pageAction(QWebEnginePage.Stop)) toolBar.addWidget(self.locationEdit) def generateView(self, url): """Method generates the QWebEngineView and handles all page loading.""" self.view = QWebEngineView(self) self.view.load(url) self.view.loadFinished.connect(self.adjustLocation) self.view.titleChanged.connect(self.adjustTitle) self.view.loadProgress.connect(self.setProgress) self.view.loadFinished.connect(self.finishLoading) # networkAcccessManager does not work with QWebEnginePage. Need to research # how to do with QtWebEngine. def viewSource(self): """WIP Method: It should allow user to view the page source.""" accessManager = self.view.page().networkAccessManager() request = QNetworkRequest(self.view.url()) reply = accessManager.get(request) reply.finished.connect(self.slotSourceDownloaded) def slotSourceDownloaded(self): """Downloads source code.""" reply = self.sender() self.textEdit = QTextEdit() self.textEdit.setAttribute(Qt.WA_DeleteOnClose) self.textEdit.show() self.textEdit.setPlainText(QTextStream(reply).readAll()) self.textEdit.resize(600, 400) reply.deleteLater() def adjustLocation(self): """Adjusts url location when called.""" self.locationEdit.setText(self.view.url().toString()) def changeLocation(self): """Similar to adjustLocation, however only changes based on user input into the URL bar.""" url = QUrl.fromUserInput(self.locationEdit.text()) self.view.load(url) self.view.setFocus() def adjustTitle(self): """Adjusts the window's title based on the website visited. Also shows page loading as a percentage.""" if 0 < self.progress < 100: self.setWindowTitle("%s (%s%%)" % (self.view.title(), self.progress)) else: self.setWindowTitle(self.view.title()) def setProgress(self, p): """Sets the page progress and calls adjustTitle.""" self.progress = p self.adjustTitle() def finishLoading(self): """When the progress is 100%, changes the window's title to its text value without the progress displayed.""" self.progress = 100 self.adjustTitle()
class Browser(Application): # pylint: disable=too-many-instance-attributes """The main browser""" url_scheme: QWebEngineUrlScheme bridge_initialized: bool dev_view: QWebEngineView dev_page: WebPage qdock: QDockWidget def __init__(self): super().__init__() self.init() # self.load() def init(self): """Initialize browser""" logger.debug("Initializing Browser Window") if web_greeter_config["config"]["greeter"]["debug_mode"]: os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = '12345' url_scheme = "web-greeter" self.url_scheme = QWebEngineUrlScheme(url_scheme.encode()) self.url_scheme.setDefaultPort(QWebEngineUrlScheme.PortUnspecified) self.url_scheme.setFlags(QWebEngineUrlScheme.SecureScheme or QWebEngineUrlScheme.LocalScheme or QWebEngineUrlScheme.LocalAccessAllowed) QWebEngineUrlScheme.registerScheme(self.url_scheme) self.profile = QWebEngineProfile.defaultProfile() self.interceptor = QtUrlRequestInterceptor(url_scheme) self.url_scheme_handler = QtUrlSchemeHandler() self.view = QWebEngineView(parent=self.window) self.page = WebPage() self.view.setPage(self.page) self.page.setObjectName("WebG Page") self.view.setObjectName("WebG View") self.channel = QWebChannel(self.page) self.bridge_initialized = False self.profile.installUrlSchemeHandler(url_scheme.encode(), self.url_scheme_handler) self._initialize_page() if web_greeter_config["config"]["greeter"]["debug_mode"]: self._initialize_devtools() else: self.view.setContextMenuPolicy(Qt.PreventContextMenu) self._init_actions() if web_greeter_config["app"]["frame"]: self._init_menu_bar() else: self.window.setWindowFlags(self.window.windowFlags() | Qt.FramelessWindowHint) if web_greeter_config["config"]["greeter"]["secure_mode"]: if hasattr(QWebEngineProfile, "setUrlRequestInterceptor"): self.profile.setUrlRequestInterceptor(self.interceptor) else: # Older Qt5 versions self.profile.setRequestInterceptor(self.interceptor) self.page.setBackgroundColor(QColor(0, 0, 0)) self.window.setStyleSheet("""QMainWindow, QWebEngineView { background: #000000; }""") self.window.setCentralWidget(self.view) logger.debug("Browser Window created") def load(self): """Load theme and initialize bridge""" self.load_theme() self.bridge_objects = (self.greeter, self.greeter_config, self.theme_utils) self.initialize_bridge_objects() self.load_script(':/_greeter/js/bundle.js', 'Web Greeter Bundle') def _initialize_devtools(self): self.dev_view = QWebEngineView(parent=self.window) self.dev_page = WebPage() self.dev_view.setPage(self.dev_page) self.page.setDevToolsPage(self.dev_page) self.dev_view.setObjectName("Devtools view") self.dev_page.setObjectName("Devtools page") self.dev_page.windowCloseRequested.connect( lambda: self.toggle_devtools_value(False)) inspect_element_action = self.page.action(self.page.InspectElement) inspect_element_action.triggered.connect( lambda: self.toggle_devtools_value(True)) self.qdock = QDockWidget() self.qdock.setWidget(self.dev_view) self.qdock.setFeatures(QDockWidget.DockWidgetMovable or QDockWidget.DockWidgetClosable) self.window.addDockWidget(Qt.RightDockWidgetArea, self.qdock) self.qdock.hide() logger.debug("DevTools initialized") def toggle_devtools(self): """Toggle devtools""" if not web_greeter_config["config"]["greeter"]["debug_mode"]: return self.toggle_devtools_value(not self.qdock.isVisible()) def toggle_devtools_value(self, value: bool): """Toggle devtools by value""" if not web_greeter_config["config"]["greeter"]["debug_mode"]: return if value: self.qdock.show() self.dev_view.setFocus() else: self.qdock.hide() self.view.setFocus() def _init_actions(self): """Init browser actions""" self.exit_action = QAction(QIcon("exit.png"), "&Quit", self.window) self.exit_action.setShortcut("Ctrl+Q") self.exit_action.setStatusTip("Exit application") self.exit_action.triggered.connect(qApp.quit) self.toggle_dev_action = QAction("Toggle Developer Tools", self.window) self.toggle_dev_action.setShortcut("Ctrl+Shift+I") self.toggle_dev_action.triggered.connect(self.toggle_devtools) self.fullscreen_action = QAction("Toggle Fullscreen", self.window) self.fullscreen_action.setShortcut("F11") self.fullscreen_action.triggered.connect( lambda: self.toggle_fullscreen(not self.window.isFullScreen())) self.inc_zoom_action = QAction("Zoom In", self.window) self.inc_zoom_action.setShortcut("Ctrl++") self.inc_zoom_action.triggered.connect(self._inc_zoom) self.dec_zoom_action = QAction("Zoom Out", self.window) self.dec_zoom_action.setShortcut("Ctrl+-") self.dec_zoom_action.triggered.connect(self._dec_zoom) self.reset_zoom_action = QAction("Actual Size", self.window) self.reset_zoom_action.setShortcut("Ctrl+0") self.reset_zoom_action.triggered.connect(self._reset_zoom) self.window.addAction(self.exit_action) self.window.addAction(self.toggle_dev_action) self.window.addAction(self.fullscreen_action) self.window.addAction(self.inc_zoom_action) self.window.addAction(self.dec_zoom_action) self.window.addAction(self.reset_zoom_action) def _inc_zoom(self): if self.view.hasFocus(): self.page.increaseZoom() else: self.dev_page.increaseZoom() def _dec_zoom(self): if self.view.hasFocus(): self.page.decreaseZoom() else: self.dev_page.decreaseZoom() def _reset_zoom(self): if self.view.hasFocus(): self.page.setZoomFactor(1) else: self.dev_page.setZoomFactor(1) def _init_menu_bar(self): minimize_action = QAction("Minimize", self.window) minimize_action.setShortcut("Ctrl+M") minimize_action.triggered.connect(self.window.showMinimized) close_action = QAction("Close", self.window) close_action.setShortcut("Ctrl+W") close_action.triggered.connect(self.window.close) self.page.action( self.page.ReloadAndBypassCache).setText("Force Reload") self.page.fullScreenRequested.connect(self.accept_fullscreen) self.menu_bar = QMenuBar() file_menu = self.menu_bar.addMenu("&File") file_menu.addAction(self.exit_action) edit_menu = self.menu_bar.addMenu("&Edit") edit_menu.addAction(self.page.action(self.page.Undo)) edit_menu.addAction(self.page.action(self.page.Redo)) edit_menu.addSeparator() edit_menu.addAction(self.page.action(self.page.Cut)) edit_menu.addAction(self.page.action(self.page.Copy)) edit_menu.addAction(self.page.action(self.page.Paste)) edit_menu.addSeparator() edit_menu.addAction(self.page.action(self.page.SelectAll)) view_menu = self.menu_bar.addMenu("&View") view_menu.addAction(self.page.action(self.page.Reload)) view_menu.addAction(self.page.action(self.page.ReloadAndBypassCache)) view_menu.addAction(self.toggle_dev_action) view_menu.addSeparator() view_menu.addAction(self.reset_zoom_action) view_menu.addAction(self.inc_zoom_action) view_menu.addAction(self.dec_zoom_action) view_menu.addSeparator() view_menu.addAction(self.fullscreen_action) window_menu = self.menu_bar.addMenu("&Window") window_menu.addAction(minimize_action) window_menu.addAction(close_action) # help_menu = menu_bar.addMenu("&Help") self.window.setMenuBar(self.menu_bar) def accept_fullscreen(self, request): """Accepts fullscreen requests""" if web_greeter_config["config"]["greeter"]["debug_mode"]: request.reject() return if request.toggleOn(): self.toggle_fullscreen(True) else: self.toggle_fullscreen(False) request.accept() def toggle_fullscreen(self, value: bool): """Toggle fullscreen""" if not web_greeter_config["config"]["greeter"]["debug_mode"]: return if value: state = self.states["FULLSCREEN"] self.window.setWindowFlags(self.window.windowFlags() or Qt.FramelessWindowHint) self.menu_bar.setParent(None) self.window.setMenuBar(None) else: state = self.states["NORMAL"] self.window.setWindowFlags(self.window.windowFlags() or not Qt.FramelessWindowHint) self.window.setMenuBar(self.menu_bar) try: self.window.windowHandle().setWindowState(state) except (AttributeError, TypeError): self.window.setWindowState(state) def _initialize_page(self): page_settings = self.page.settings().globalSettings() if not web_greeter_config["config"]["greeter"]["secure_mode"]: ENABLED_SETTINGS.append('LocalContentCanAccessRemoteUrls') else: DISABLED_SETTINGS.append('LocalContentCanAccessRemoteUrls') for setting in DISABLED_SETTINGS: try: page_settings.setAttribute( getattr(QWebEngineSettings, setting), False) except AttributeError: pass for setting in ENABLED_SETTINGS: try: page_settings.setAttribute( getattr(QWebEngineSettings, setting), True) except AttributeError: pass self.page.setView(self.view) def load_theme(self): """Load theme""" theme = web_greeter_config["config"]["greeter"]["theme"] dir_t = "/usr/share/web-greeter/themes/" path_to_theme = os.path.join(dir_t, theme, "index.html") def_theme = "gruvbox" if theme.startswith("/"): path_to_theme = theme elif theme.__contains__(".") or theme.__contains__("/"): path_to_theme = os.path.join(os.getcwd(), theme) path_to_theme = os.path.realpath(path_to_theme) if not path_to_theme.endswith(".html"): path_to_theme = os.path.join(path_to_theme, "index.html") if not os.path.exists(path_to_theme): print("Path does not exists", path_to_theme) path_to_theme = os.path.join(dir_t, def_theme, "index.html") web_greeter_config["config"]["greeter"]["theme"] = path_to_theme url = QUrl(f"web-greeter://app/{path_to_theme}") self.page.load(url) logger.debug("Theme loaded") @staticmethod def _create_webengine_script(path: Url, name: str) -> QWebEngineScript: script = QWebEngineScript() script_file = QFile(path) # print(script_file, path) if script_file.open(QFile.ReadOnly): script_string = str(script_file.readAll(), 'utf-8') script.setInjectionPoint(QWebEngineScript.DocumentCreation) script.setName(name) script.setWorldId(QWebEngineScript.MainWorld) script.setSourceCode(script_string) # print(script_string) return script def _get_channel_api_script(self) -> QWebEngineScript: return self._create_webengine_script(':/qtwebchannel/qwebchannel.js', 'QWebChannel API') def _init_bridge_channel(self) -> None: self.page.setWebChannel(self.channel) self.bridge_initialized = True def initialize_bridge_objects(self) -> None: """Initialize bridge objects :D""" if not self.bridge_initialized: self._init_bridge_channel() registered_objects = self.channel.registeredObjects() for obj in self.bridge_objects: if obj not in registered_objects: # pylint: disable=protected-access self.channel.registerObject(obj._name, obj) # print("Registered", obj._name) def load_script(self, path: Url, name: str): """Loads a script in page""" qt_api = self._get_channel_api_script() qt_api_source = qt_api.sourceCode() script = self._create_webengine_script(path, name) script.setSourceCode(qt_api_source + "\n" + script.sourceCode()) self.page.scripts().insert(script)
class MainWindow(QMainWindow): def __init__(self, url): super().__init__() self.setAttribute(Qt.WA_DeleteOnClose, True) self.progress = 0 f = QFile() f.setFileName(":/jquery.min.js") f.open(QIODevice.ReadOnly) self.jQuery = f.readAll().data().decode() self.jQuery += "\nvar qt = { 'jQuery': jQuery.noConflict(true) };" f.close() self.view = QWebEngineView(self) self.view.load(url) self.view.loadFinished.connect(self.adjustLocation) self.view.titleChanged.connect(self.adjustTitle) self.view.loadProgress.connect(self.setProgress) self.view.loadFinished.connect(self.finishLoading) self.locationEdit = QLineEdit(self) self.locationEdit.setSizePolicy( QSizePolicy.Expanding, self.locationEdit.sizePolicy().verticalPolicy()) self.locationEdit.returnPressed.connect(self.changeLocation) toolBar = self.addToolBar(self.tr("Navigation")) toolBar.addAction(self.view.pageAction(QWebEnginePage.Back)) toolBar.addAction(self.view.pageAction(QWebEnginePage.Forward)) toolBar.addAction(self.view.pageAction(QWebEnginePage.Reload)) toolBar.addAction(self.view.pageAction(QWebEnginePage.Stop)) toolBar.addWidget(self.locationEdit) viewMenu = self.menuBar().addMenu(self.tr("&View")) viewSourceAction = QAction(self.tr("Page Source"), self) viewSourceAction.triggered.connect(self.viewSource) viewMenu.addAction(viewSourceAction) effectMenu = self.menuBar().addMenu(self.tr("&Effect")) effectMenu.addAction(self.tr("Highlight all links"), self.highlightAllLinks) self.rotateAction = QAction(self) self.rotateAction.setIcon(self.style().standardIcon( QStyle.SP_FileDialogDetailedView)) self.rotateAction.setCheckable(True) self.rotateAction.setText(self.tr("Turn images upside down")) self.rotateAction.toggled.connect(self.rotateImages) effectMenu.addAction(self.rotateAction) toolsMenu = self.menuBar().addMenu(self.tr("&Tools")) toolsMenu.addAction(self.tr("Remove GIF images"), self.removeGifImages) toolsMenu.addAction(self.tr("Remove all inline frames"), self.removeInlineFrames) toolsMenu.addAction(self.tr("Remove all object elements"), self.removeObjectElements) toolsMenu.addAction(self.tr("Remove all embedded elements"), self.removeEmbeddedElements) self.setCentralWidget(self.view) @pyqtSlot() def adjustLocation(self): self.locationEdit.setText(self.view.url().toString()) @pyqtSlot() def changeLocation(self): url = QUrl.fromUserInput(self.locationEdit.text()) self.view.load(url) self.view.setFocus() @pyqtSlot() def adjustTitle(self): if self.progress <= 0 or self.progress >= 100: self.setWindowTitle(self.view.title()) else: self.setWindowTitle("%s (%2d)" % (self.view.title(), self.progress)) @pyqtSlot(int) def setProgress(self, p): self.progress = p self.adjustTitle() @pyqtSlot() def finishLoading(self): self.progress = 100 self.adjustTitle() self.view.page().runJavaScript(self.jQuery) self.rotateImages(self.rotateAction.isChecked()) @pyqtSlot() def viewSource(self): self.textEdit = QTextEdit() self.textEdit.setAttribute(Qt.WA_DeleteOnClose) self.textEdit.adjustSize() self.textEdit.move(self.geometry().center() - self.textEdit.rect().center()) self.textEdit.show() self.view.page().toHtml(self.textEdit.setPlainText) @pyqtSlot() def highlightAllLinks(self): code = "qt.jQuery('a').each( function () { qt.jQuery(this).css('background-color', 'yellow') } )" self.view.page().runJavaScript(code) @pyqtSlot(bool) def rotateImages(self, invert): code = "" if invert: code = "qt.jQuery('img').each( function () { qt.jQuery(this).css('transition', 'transform 2s'); qt.jQuery(this).css('transform', 'rotate(180deg)') } )" else: code = "qt.jQuery('img').each( function () { qt.jQuery(this).css('transition', 'transform 2s'); qt.jQuery(this).css('transform', 'rotate(0deg)') } )" self.view.page().runJavaScript(code) @pyqtSlot() def removeGifImages(self): code = "qt.jQuery('[src*=gif]').remove()" self.view.page().runJavaScript(code) @pyqtSlot() def removeInlineFrames(self): code = "qt.jQuery('iframe').remove()" self.view.page().runJavaScript(code) @pyqtSlot() def removeObjectElements(self): code = "qt.jQuery('object').remove()" self.view.page().runJavaScript(code) @pyqtSlot() def removeEmbeddedElements(self): code = "qt.jQuery('embed').remove()" self.view.page().runJavaScript(code)