276 lines
7.7 KiB
Python
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() |