diff options
author | Lazlo Westerhof <github@lazlo.me> | 2014-08-14 16:37:33 +0200 |
---|---|---|
committer | Lazlo Westerhof <github@lazlo.me> | 2014-08-14 16:37:33 +0200 |
commit | 141707785fc6e756b5e5cb47211f6a8ab97024f0 (patch) | |
tree | 2e64299e81145bdfb731c8e26decf5b6c86de074 | |
parent | e8cc7c3a4a4d89e8c481a70d0c046e7e393fc980 (diff) | |
download | onionshare-141707785fc6e756b5e5cb47211f6a8ab97024f0.tar.gz onionshare-141707785fc6e756b5e5cb47211f6a8ab97024f0.zip |
First working version of native qt app
-rw-r--r-- | onionshare_gui/onionshare_gui.py | 319 |
1 files changed, 266 insertions, 53 deletions
diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index f6c78702..61822d67 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -1,7 +1,6 @@ -import os, sys, subprocess, inspect, platform, argparse -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from PyQt4.QtWebKit import * +from __future__ import division +import os, sys, subprocess, inspect, platform, argparse, threading, time, math +from PyQt4 import QtCore, QtGui if platform.system() == 'Darwin': onionshare_gui_dir = os.path.dirname(__file__) @@ -14,40 +13,274 @@ except ImportError: sys.path.append(os.path.abspath(onionshare_gui_dir+"/..")) import onionshare from onionshare import translated -import webapp +app = None window_icon = None +onion_host = None +onionshare_port = None +progress = None -class Application(QApplication): +# request types +REQUEST_LOAD = 0 +REQUEST_DOWNLOAD = 1 +REQUEST_PROGRESS = 2 +REQUEST_OTHER = 3 + +class Application(QtGui.QApplication): def __init__(self): platform = onionshare.get_platform() if platform == 'Tails' or platform == 'Linux': - self.setAttribute(Qt.AA_X11InitThreads, True) - - QApplication.__init__(self, sys.argv) - -class WebAppThread(QThread): - def __init__(self, webapp_port): - QThread.__init__(self) - self.webapp_port = webapp_port + self.setAttribute(QtCore.Qt.AA_X11InitThreads, True) + QtGui.QApplication.__init__(self, sys.argv) - def run(self): - webapp.app.run(port=self.webapp_port) +class OnionShareGui(QtGui.QWidget): + def __init__(self, filename, basename): + super(OnionShareGui, self).__init__() + # initialize ui + self.init_ui(filename, basename) + # check for requests every 1000ms + self.timer = QtCore.QTimer() + QtCore.QObject.connect(self.timer, QtCore.SIGNAL("timeout()"), self.check_for_requests) + self.timer.start(1000) + # copy url to clipboard + self.copy_to_clipboard() -class Window(QWebView): - def __init__(self, basename, webapp_port): - global window_icon - QWebView.__init__(self) + def init_ui(self, filename, basename): + # window self.setWindowTitle("{0} | OnionShare".format(basename)) self.resize(580, 400) self.setMinimumSize(580, 400) self.setMaximumSize(580, 400) + palette = QtGui.QPalette() + palette.setColor(QtGui.QPalette.Background, QtCore.Qt.white) + self.setPalette(palette) + + # icon self.setWindowIcon(window_icon) - self.load(QUrl("http://127.0.0.1:{0}".format(webapp_port))) -def alert(msg, icon=QMessageBox.NoIcon): + # widget + self.widget = QtGui.QWidget(self) + self.widget.setGeometry(QtCore.QRect(5, 5, 570, 390)) + + # wrapper + self.wrapper = QtGui.QVBoxLayout(self.widget) + self.wrapper.setMargin(0) + self.wrapper.setObjectName("wrapper") + + # header + self.header = QtGui.QHBoxLayout() + + # logo + self.logoLabel = QtGui.QLabel(self.widget) + self.logo = QtGui.QPixmap("{0}/static/logo.png".format(onionshare_gui_dir)) + self.logoLabel.setPixmap(self.logo) + self.header.addWidget(self.logoLabel) + + # fileinfo + self.fileinfo = QtGui.QVBoxLayout() + + # filename + self.filenameLabel = QtGui.QLabel(self.widget) + self.filenameLabel.setStyleSheet("font-family: sans-serif; font-size: 22px; font-weight: bold; color: #000000; white-space: nowrap") + self.filenameLabel.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse) + self.fileinfo.addWidget(self.filenameLabel) + + # checksum + self.checksumLabel = QtGui.QLabel(self.widget) + self.checksumLabel.setStyleSheet("font-family: arial; text-align: left; color: #666666") + self.checksumLabel.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse) + self.fileinfo.addWidget(self.checksumLabel) + + # filesize + self.filesizeLabel = QtGui.QLabel(self.widget) + self.filesizeLabel.setStyleSheet("font-family: arial; text-align: left; color: #666666") + self.filesizeLabel.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse) + self.fileinfo.addWidget(self.filesizeLabel) + self.header.addLayout(self.fileinfo) + + fileinfoSpacer = QtGui.QSpacerItem(20, 50, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Maximum) + self.header.addItem(fileinfoSpacer) + self.wrapper.addLayout(self.header) + + # header seperator + self.headerSeperator = QtGui.QFrame(self.widget) + self.headerSeperator.setFrameShape(QtGui.QFrame.HLine) + self.headerSeperator.setFrameShadow(QtGui.QFrame.Plain) + self.wrapper.addWidget(self.headerSeperator) + + # log + self.log = QtGui.QVBoxLayout() + self.log.setAlignment(QtCore.Qt.AlignTop) + self.wrapper.addLayout(self.log) + spacerItem2 = QtGui.QSpacerItem(1, 400, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Maximum) + self.wrapper.addItem(spacerItem2) + + # footer seperator + self.footerSeperator = QtGui.QFrame(self.widget) + self.footerSeperator.setFrameShape(QtGui.QFrame.HLine) + self.footerSeperator.setFrameShadow(QtGui.QFrame.Plain) + self.wrapper.addWidget(self.footerSeperator) + + # footer + self.footer = QtGui.QHBoxLayout() + + # close automatically checkbox + self.closeAutomatically = QtGui.QCheckBox(self.widget) + self.closeAutomatically.setCheckState(QtCore.Qt.Checked) + if onionshare.stay_open: + self.closeAutomatically.setCheckState(QtCore.Qt.Unchecked) + + self.closeAutomatically.setStyleSheet("font-size: 12px") + self.connect(self.closeAutomatically, QtCore.SIGNAL('stateChanged(int)'), self.stay_open_changed) + self.footer.addWidget(self.closeAutomatically) + + # footer spacer + spacerItem1 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.footer.addItem(spacerItem1) + + # copy url button + self.copyURL = QtGui.QPushButton(self.widget) + self.connect(self.copyURL, QtCore.SIGNAL("clicked()"), self.copy_to_clipboard) + + self.footer.addWidget(self.copyURL) + self.wrapper.addLayout(self.footer) + + url = 'http://{0}/{1}'.format(onion_host, onionshare.slug) + + filehash, filesize = onionshare.file_crunching(filename) + onionshare.set_file_info(filename, filehash, filesize) + onionshare.filesize = filesize + + # start onionshare service in new thread + t = threading.Thread(target=onionshare.app.run, kwargs={'port': onionshare_port}) + t.daemon = True + t.start() + + # show url to share + loaded = QtGui.QLabel(translated("give_this_url") + "<br /><strong>" + url + "</strong>") + loaded.setStyleSheet("color: #000000; font-size: 14px; padding: 5px 10px; border-bottom: 1px solid #cccccc;") + loaded.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse) + self.log.addWidget(loaded) + + # translate + self.filenameLabel.setText(basename) + self.checksumLabel.setText(translated("sha1_checksum") + ": <strong>" + filehash + "</strong>") + self.filesizeLabel.setText(translated("filesize") + ": <strong>" + onionshare.human_readable_filesize(filesize) + "</strong>") + self.closeAutomatically.setText(translated("close_on_finish")) + self.copyURL.setText(translated("copy_url")) + + # show dialog + self.show() + + def update_log(self, event, msg): + global progress + if event["type"] == REQUEST_LOAD: + label = QtGui.QLabel(msg) + label.setStyleSheet("color: #009900; font-weight: bold; font-size: 14px; padding: 5px 10px; border-bottom: 1px solid #cccccc;") + label.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse) + self.log.addWidget(label) + elif event["type"] == REQUEST_DOWNLOAD: + download = QtGui.QLabel(msg) + download.setStyleSheet("color: #009900; font-weight: bold; font-size: 14px; padding: 5px 10px; border-bottom: 1px solid #cccccc;") + download.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse) + self.log.addWidget(download) + progress = QtGui.QLabel() + progress.setStyleSheet("color: #0000cc; font-weight: bold; font-size: 14px; padding: 5px 10px; border-bottom: 1px solid #cccccc;") + progress.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse) + self.log.addWidget(progress) + elif event["type"] == REQUEST_PROGRESS: + progress.setText(msg) + elif event["path"] != '/favicon.ico': + other = QtGui.QLabel(msg) + other.setStyleSheet("color: #009900; font-weight: bold; font-size: 14px; padding: 5px 10px; border-bottom: 1px solid #cccccc;") + other.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse) + self.log.addWidget(other) + return + + def check_for_requests(self): + events = [] + + done = False + while not done: + try: + r = onionshare.q.get(False) + events.append(r) + except onionshare.Queue.Empty: + done = True + + for event in events: + if event["type"] == REQUEST_LOAD: + self.update_log(event, translated("download_page_loaded")) + elif event["type"] == REQUEST_DOWNLOAD: + self.update_log(event, translated("download_started")) + elif event["type"] == REQUEST_PROGRESS: + # is the download complete? + if event["data"]["bytes"] == onionshare.filesize: + self.update_log(event, translated("download_finished")) + # close on finish? + if not onionshare.stay_open: + time.sleep(1) + def close_countdown(i): + if i > 0: + QtGui.QApplication.quit() + else: + time.sleep(1) + i -= 1 + closing.setText(translated("close_countdown").format(str(i))) + print translated("close_countdown").format(str(i)) + close_countdown(i) + + closing = QtGui.QLabel(self.widget) + closing.setStyleSheet("font-weight: bold; font-style: italic; font-size: 14px; padding: 5px 10px; border-bottom: 1px solid #cccccc;") + closing.setText(translated("close_countdown").format("3")) + closing.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse) + self.log.addWidget(closing) + close_countdown(3) + + # still in progress + else: + percent = math.floor((event["data"]["bytes"] / onionshare.filesize) * 100) + self.update_log(event, " " + onionshare.human_readable_filesize(event["data"]["bytes"]) + ', ' + str(percent) +'%') + + elif event["path"] != '/favicon.ico': + self.update_log(event, translated("other_page_loaded")) + + def copy_to_clipboard(self): + global onion_host + url = 'http://{0}/{1}'.format(onion_host, onionshare.slug) + + if platform.system() == 'Windows': + # Qt's QClipboard isn't working in Windows + # https://github.com/micahflee/onionshare/issues/46 + import ctypes + GMEM_DDESHARE = 0x2000 + ctypes.windll.user32.OpenClipboard(None) + ctypes.windll.user32.EmptyClipboard() + hcd = ctypes.windll.kernel32.GlobalAlloc(GMEM_DDESHARE, len(bytes(url))+1) + pch_data = ctypes.windll.kernel32.GlobalLock(hcd) + ctypes.cdll.msvcrt.strcpy(ctypes.c_char_p(pch_data), bytes(url)) + ctypes.windll.kernel32.GlobalUnlock(hcd) + ctypes.windll.user32.SetClipboardData(1, hcd) + ctypes.windll.user32.CloseClipboard() + else: + clipboard = app.clipboard() + clipboard.setText(url) + + copied = QtGui.QLabel(translated("copied_url")) + copied.setStyleSheet("font-size: 14px; padding: 5px 10px; border-bottom: 1px solid #cccccc;") + copied.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse) + self.log.addWidget(copied) + return + + def stay_open_changed(self, state): + if state > 0: + onionshare.set_stay_open(False) + onionshare.set_stay_open(True) + return + +def alert(msg, icon=QtGui.QMessageBox.NoIcon): global window_icon - dialog = QMessageBox() + dialog = QtGui.QMessageBox() dialog.setWindowTitle("OnionShare") dialog.setWindowIcon(window_icon) dialog.setText(msg) @@ -61,7 +294,7 @@ def select_file(strings, filename=None): if onionshare.get_platform() == 'Tails': args['directory'] = '/home/amnesia' - filename = QFileDialog.getOpenFileName(caption=translated('choose_file'), options=QFileDialog.ReadOnly, **args) + filename = QtGui.QFileDialog.getOpenFileName(caption=translated('choose_file'), options=QtGui.QFileDialog.ReadOnly, **args) if not filename: return False, False @@ -69,7 +302,7 @@ def select_file(strings, filename=None): # validate filename if not os.path.isfile(filename): - alert(translated("not_a_file").format(filename), QMessageBox.Warning) + alert(translated("not_a_file").format(filename), QtGui.QMessageBox.Warning) return False, False filename = os.path.abspath(filename) @@ -77,9 +310,11 @@ def select_file(strings, filename=None): return filename, basename def main(): + global onionshare_port onionshare.strings = onionshare.load_strings() # start the Qt app + global app app = Application() # check for root in Tails @@ -104,16 +339,17 @@ def main(): # create the onionshare icon global window_icon, onionshare_gui_dir - window_icon = QIcon("{0}/onionshare-icon.png".format(onionshare_gui_dir)) + window_icon = QtGui.QIcon("{0}/static/logo.png".format(onionshare_gui_dir)) # try starting hidden service + global onion_host onionshare_port = onionshare.choose_port() local_host = "127.0.0.1:{0}".format(onionshare_port) if not local_only: try: onion_host = onionshare.start_hidden_service(onionshare_port) except onionshare.NoTor as e: - alert(e.args[0], QMessageBox.Warning) + alert(e.args[0], QtGui.QMessageBox.Warning) return onionshare.tails_open_port(onionshare_port) @@ -122,36 +358,13 @@ def main(): if not filename: return - # initialize the web app - webapp.onionshare = onionshare - webapp.onionshare_port = onionshare_port - webapp.filename = filename - webapp.qtapp = app - webapp.clipboard = app.clipboard() - webapp.stay_open = stay_open - if not local_only: - webapp.onion_host = onion_host - else: - webapp.onion_host = local_host - if debug: - onionshare.debug_mode() - webapp.debug_mode() - - # run the web app in a new thread - webapp_port = onionshare.choose_port() - onionshare.tails_open_port(webapp_port) - webapp_thread = WebAppThread(webapp_port) - webapp_thread.start() - # clean up when app quits def shutdown(): onionshare.tails_close_port(onionshare_port) - onionshare.tails_close_port(webapp_port) - app.connect(app, SIGNAL("aboutToQuit()"), shutdown) + app.connect(app, QtCore.SIGNAL("aboutToQuit()"), shutdown) - # launch the window - web = Window(basename, webapp_port) - web.show() + # launch the gui + gui = OnionShareGui(filename, basename) # all done sys.exit(app.exec_()) |