#!/usr/bin/env python import sys from PySide6 import QtCore, QtWidgets, QtGui, QtWebEngineWidgets, QtWebEngineCore import gemini class GeminiPage(QtWebEngineCore.QWebEnginePage): def acceptNavigationRequest(self, url, navtype, mainframe): """ Block non-gemini page navigation and send the url to the os instead """ if url.scheme() == 'gemini': return True else: QtGui.QDesktopServices().openUrl(url) return False class GeminiSchemeHandler(QtWebEngineCore.QWebEngineUrlSchemeHandler): def requestStarted(self, request): print(request.requestUrl().toString()) gem = gemini.get(request.requestUrl().toString()) print(gem['status'], gem['meta']) buf = QtCore.QBuffer(parent=request) buf.open(QtCore.QIODevice.WriteOnly) buf.write(gemini.gem2html(gem).encode('utf8')) buf.seek(0) buf.close() request.reply(b'text/html', buf) class GUrlBar(QtWidgets.QLineEdit): def __init__(self): QtWidgets.QLineEdit.__init__(self) def setUrl(self, url): return self.setText(url.toDisplayString()) class GBrowser(QtWidgets.QMainWindow): def __init__(self, initial_url=None, profile=None): if initial_url is None: initial_url = 'gemini://gemini.circumlunar.space/' if profile is None: profile = QtWebEngineCore.QWebEngineProfile.defaultProfile() QtWidgets.QMainWindow.__init__(self) # Main Viewport self._browser = QtWebEngineWidgets.QWebEngineView() page = GeminiPage(profile, self._browser) self._browser.setPage(page) self.setCentralWidget(self._browser) # Navigation Toolbar back = QtWidgets.QPushButton('←', self) forward = QtWidgets.QPushButton('→', self) self._address = GUrlBar() toolbar = QtWidgets.QToolBar() toolbar.setObjectName('navigationToolbar') toolbar.addWidget(back) toolbar.addWidget(forward) toolbar.addWidget(self._address) self.addToolBar(toolbar) # Status Bar request_status = QtWidgets.QLabel() self.statusBar().addWidget(request_status) # Connect signals back.clicked.connect(self._browser.back) forward.clicked.connect(self._browser.forward) self._browser.urlChanged.connect(self._address.setUrl) self._address.returnPressed.connect(self.loadAddress) self._browser.page().linkHovered.connect(self.set_status_url) self._browser.load(QtCore.QUrl(initial_url)) settings = QtCore.QSettings("xcolour.net", "GeminiBrowser") self.restoreGeometry(settings.value("geometry")) self.restoreState(settings.value("windowState")) self.setWindowTitle('Gemini Browser') self.show() # Shortcuts down_shortcut = QtGui.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_J), self) down_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, QtCore.Qt.Key_Down, QtCore.Qt.NoModifier) down_shortcut.activated.connect(lambda: QtWidgets.QApplication.sendEvent(self._browser.focusProxy(), down_event)) up_shortcut = QtGui.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_K), self) up_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, QtCore.Qt.Key_Up, QtCore.Qt.NoModifier) up_shortcut.activated.connect(lambda: QtWidgets.QApplication.sendEvent(self._browser.focusProxy(), up_event)) QtGui.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_H), self, self._browser.back) QtGui.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_L), self, self._browser.forward) QtGui.QShortcut(QtGui.QKeySequence("Shift+R"), self, self._browser.reload) QtGui.QShortcut(QtGui.QKeySequence("Esc"), self._address, lambda: self.setFocus()) QtGui.QShortcut(QtGui.QKeySequence("Shift+L"), self, lambda: self._address.setFocus()) def set_status_url(self, url): if url: self.statusBar().showMessage(url) else: self.statusBar().clearMessage() def closeEvent(self, event): settings = QtCore.QSettings("xcolour.net", "GeminiBrowser") settings.setValue("geometry", self.saveGeometry()) settings.setValue("windowState", self.saveState()) self._browser.page().deleteLater() super().closeEvent(event) def loadAddress(self): self._browser.load(self._address.text()) self._browser.setFocus() scheme = QtWebEngineCore.QWebEngineUrlScheme(b'gemini') scheme.setDefaultPort(1965) scheme.setFlags(QtWebEngineCore.QWebEngineUrlScheme.SecureScheme) QtWebEngineCore.QWebEngineUrlScheme.registerScheme(scheme) app = QtWidgets.QApplication(sys.argv) gem_handler = GeminiSchemeHandler() profile = QtWebEngineCore.QWebEngineProfile() profile.removeAllUrlSchemeHandlers() profile.installUrlSchemeHandler(b'gemini', gem_handler) ex = GBrowser(sys.argv[1] if len(sys.argv) > 1 else None, profile) sys.exit(app.exec())