BirdyNet/BirdyNet.py

276 lines
7.7 KiB
Python

#!/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()