#!/usr/bin/env python3 import sys oldexcepthook = sys.excepthook def newexcepthook(type,value,traceback): oldexcepthook(type,value,traceback) #input("Press ENTER to quit.") # - this causes issues with Qt's input hook sys.excepthook = newexcepthook import os p = os.path.join pUp = os.path.dirname s = False if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'): s = os.path.realpath(sys.executable) else: s = os.path.realpath(__file__) sp = pUp(s) # script start import qtpy import qtpy.QtGui as QtGui from qtpy.QtGui import * from qtpy.QtWidgets import * from qtpy.QtCore import * from qtpy.QtMultimedia import QSound import configparser import json import time import html import threading os.chdir(sp) distro = os.path.splitext(s.rsplit(os.path.sep)[-1])[0] version = { "major": 0, "minor": 10, "patch": 0, "stage": "dev" } versionString = ".".join(map(str,[version["major"],version["minor"]])) versionStringExt = ".".join(map(str,[version["major"],version["minor"],version["patch"]])) + " " + version["stage"] browserWindows = [] browserWindowsLock = threading.Lock() def runCode(str, lcs = False, description = "loose-code"): if lcs == False: lcs = {} code = compile(str,description,"exec") exec(code,globals(),lcs) return lcs def runScript(sf, lcs = False): if lcs == False: lcs = {} with open(sf) as script: runCode(script.read(),lcs,sf) return lcs def getDataDir(appName): basePath = False if sys.platform.startswith("win"): if "appdata" in os.environ: basePath = os.environ["appdata"] elif sys.platform.startswith("linux"): if "XDG_DATA_HOME" in os.environ: basePath = os.environ["XDG_DATA_HOME"] else: if "HOME" in os.environ: basePath = p(os.environ["HOME"],".config") elif sys.platform.startswith("darwin"): if "HOME" in os.environ: basePath = p(os.environ["HOME"],"Library","Application Support") if not basePath: raise return p(basePath,appName) def prettyJson(ls): return json.dumps(ls,indent=2) class browserWindow(QMainWindow): def __init__(self,*args,**kwargs): super().__init__(*args,**kwargs) self.cTitle = "Hello World - " +distro+ " " +versionStringExt self.cWidth = 640 self.cHeight = 480 self.setWindowTitle(self.cTitle) self.resize(self.cWidth,self.cHeight) self.cDocumentInfo = { "url": "", "body": "", "headers": {}, # additional info: "baseUrl": "" } self.cUserAgent = config["default"]["useragent"].replace("$OS",sys.platform).replace("$VER",versionString).replace("$DIST",distro) self.cDownloadId = 0 self.cTasks = [] self.cTaskLock = threading.Lock() self.cTaskTimer = QTimer() self.cTaskTimer.setInterval(100) self.cTaskTimer.timeout.connect(self.cRunTasks) self.cTaskTimer.start() self.cCreateElements() def cCreateElements(self): self.cMenuBar = self.menuBar() self.cFileMenu = self.cMenuBar.addMenu("&File") self.cNewWindowButton = self.cFileMenu.addAction("New &window") self.cNewWindowButton.triggered.connect(self.cNewWindow) self.cViewMenu = self.cMenuBar.addMenu("&View") self.cUrl,self.cAnchor = urlAnchorPairTo(config["default"]["home"]) self.cUrlBar = QLineEdit(urlAnchorPairFrom(self.cUrl,self.cAnchor),self) self.cUrlBar.returnPressed.connect(self.cNavigate) self.cButtonGo = QPushButton("Go",self) self.cButtonGo.clicked.connect(self.cNavigate) self.cDoc = browserDoc(self) self.cStatusBar = QStatusBar(self) self.cStatusBar.showMessage("Please wait...") self.cResizeElements() self.show() self.cNavigate(forceRefresh = True) self.cUrlBar.setFocus() def cResizeElements(self): barSize = 22 mb = self.cMenuBar.height() self.cUrlBar.move(0,mb) self.cUrlBar.resize(self.cWidth - barSize,barSize) self.cButtonGo.move(self.cWidth - barSize,mb) self.cButtonGo.resize(barSize,barSize) self.cDoc.move(0,mb + barSize) self.cDoc.resize(self.cWidth,self.cHeight - barSize - mb - mb) self.cStatusBar.move(0,self.cHeight - mb) self.cStatusBar.resize(self.cWidth,mb) def resizeEvent(self,event): self.cWidth = self.width() self.cHeight = self.height() self.cResizeElements() def closeEvent(self,event): browserWindowsLock.acquire() browserWindows.remove(self) self.destroy() browserWindowsLock.release() def cRunTasks(self): self.cTaskLock.acquire() for task in self.cTasks: task[0](*task[1],**task[2]) self.cTasks = [] self.cTaskLock.release() def cSetUrl(self,url): print("url set to: " +url) self.cUrl = url self.cUrlBar.setText(urlAnchorPairFrom(url,self.cAnchor)) def cSetAnchor(self,anchor): print("anchor set to: " +str(anchor)) self.cAnchor = anchor self.cUrlBar.setText(urlAnchorPairFrom(self.cUrl,anchor)) def cNavigate(self,event = None,forceRefresh = False): try: #print(prettyJson(parseUrl(self.cUrlBar.text()))) url,anchor = urlAnchorPairTo(self.cUrlBar.text()) self.cSetAnchor(anchor) url = url.replace(" ","%20") # dirty if url == self.cUrl and forceRefresh == False: if anchor != False: self.cDoc.cScrollToAnchor(anchor) return self.cStatusBar.showMessage("Downloading...") self.cStatusBar.repaint() self.cTaskLock.acquire() self.cDownloadId += 1 urlParsed = parseUrl(url) if urlParsed["protocol"] == "": while len(url) > 0 and url[0] == "/": url = url[1:] url = config["default"]["defaultProtocol"]+ "://" +url self.cSetUrl(url) threading.Thread(target=downloadPage, args=(self,self.cDownloadId,url,[("User-Agent",self.cUserAgent)])).start() self.cTaskLock.release() self.cDoc.setFocus() #print(prettyJson(response["headers"])) except Exception as e: self.cDoc.cRenderHtml(str(e),"text") raise def cHandleResponse(self,response): infoFetcher(response) if response["redirected"] == True: print("redirected.") url,anchor = urlAnchorPairTo(response["url"]) self.cSetUrl(url) self.cSetAnchor(anchor) self.cDocumentInfo = response self.cStatusBar.showMessage("Rendering...") self.cStatusBar.repaint() start = time.time() htm = response["body"] contentType, contentTypeArguments = getContentType(response["headers"],"text") if not "charset" in contentTypeArguments: contentTypeArguments["charset"] = config["default"]["defaultCharset"] print("content-type: " +contentType+ "\n" +prettyJson(contentTypeArguments)) try: htm = htm.decode(contentTypeArguments["charset"],errors="ignore") except Exception as e: print("decoding html as '" +contentTypeArguments["charset"]+ "' failed, trying " +config["default"]["defaultCharset"]+ "...") htm = htm.decode(config["default"]["defaultCharset"],errors="ignore") self.cDoc.cRenderHtml(htm,contentType) if self.cAnchor != False: self.cDoc.cScrollToAnchor(self.cAnchor) end = time.time() print("Rendering page: " +str(end - start)) self.cStatusBar.showMessage("Ready") def cNewWindow(self): w = browserWindow() browserWindowsLock.acquire() browserWindows.append(w) w.show() browserWindowsLock.release() def main(): # load program default addons addonDir = p(sp,"addons") for root,dirs,files in os.walk(addonDir): for file in files: ffile = p(root,file) lfile = ffile.replace(addonDir + os.path.sep,"",1) if lfile[0] == "-": continue print("Running addon: " +lfile) runScript(ffile) break global dataDir dataDir = getDataDir(distro) global config config = configparser.ConfigParser() # read program default config config.read(distro + ".ini") # read user config if os.path.isfile(p(dataDir,"config.ini")): config.read(p(dataDir,"config.ini")) app = QApplication(sys.argv) try: app.setWindowIcon(QtGui.QIcon("assets/BirdyNet-XP.ico")) except Exception as e: print("Could not set window icon: " +print(e)) browserWindowsLock.acquire() browserWindows.append(browserWindow()) browserWindowsLock.release() sys.exit(app.exec_()) if __name__ == '__main__': main()