onionshare/onionshare_gui/onionshare_gui.py
Micah Lee ab9e69b573 Merge pull request #111 from lazlolazlolazlo/localhost
Fix displaying of local host
2014-08-26 15:19:44 -07:00

387 lines
15 KiB
Python

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__)
else:
onionshare_gui_dir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
try:
import onionshare
except ImportError:
sys.path.append(os.path.abspath(onionshare_gui_dir+"/.."))
import onionshare
from onionshare import translated
app = None
window_icon = None
onion_host = None
port = None
progress = None
# 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(QtCore.Qt.AA_X11InitThreads, True)
QtGui.QApplication.__init__(self, sys.argv)
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()
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)
# 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.get_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': 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.get_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)
else:
onionshare.set_stay_open(True)
return
def alert(msg, icon=QtGui.QMessageBox.NoIcon):
global window_icon
dialog = QtGui.QMessageBox()
dialog.setWindowTitle("OnionShare")
dialog.setWindowIcon(window_icon)
dialog.setText(msg)
dialog.setIcon(icon)
dialog.exec_()
def select_file(strings, filename=None):
# get filename, either from argument or file chooser dialog
if not filename:
filename = QtGui.QFileDialog.getOpenFileName(caption=translated('choose_file'), options=QtGui.QFileDialog.ReadOnly)
if not filename:
return False, False
filename = str(filename)
# validate filename
if not os.path.isfile(filename):
alert(translated("not_a_file").format(filename), QtGui.QMessageBox.Warning)
return False, False
filename = os.path.abspath(filename)
basename = os.path.basename(filename)
return filename, basename
def main():
global port
onionshare.strings = onionshare.load_strings()
# start the Qt app
global app
app = Application()
# parse arguments
parser = argparse.ArgumentParser()
parser.add_argument('--local-only', action='store_true', dest='local_only', help=translated("help_local_only"))
parser.add_argument('--stay-open', action='store_true', dest='stay_open', help=translated("help_stay_open"))
parser.add_argument('--debug', action='store_true', dest='debug', help=translated("help_debug"))
parser.add_argument('filename', nargs='?', help=translated("help_filename"))
args = parser.parse_args()
filename = args.filename
local_only = bool(args.local_only)
stay_open = bool(args.stay_open)
debug = bool(args.debug)
onionshare.set_stay_open(stay_open)
if debug:
onionshare.debug_mode()
# create the onionshare icon
global window_icon, onionshare_gui_dir
window_icon = QtGui.QIcon("{0}/static/logo.png".format(onionshare_gui_dir))
# try starting hidden service
global onion_host
port = onionshare.choose_port()
local_host = "127.0.0.1:{0}".format(port)
if onionshare.get_platform() == 'Tails':
# if this is tails, start the root process
root_p = subprocess.Popen(['/usr/bin/gksudo', '-D', 'OnionShare', '--', '/usr/bin/onionshare', str(port)], stderr=subprocess.PIPE, stdout=subprocess.PIPE)
stdout = root_p.stdout.read(22) # .onion URLs are 22 chars long
if stdout:
onion_host = stdout
else:
if root_p.poll() == -1:
alert(root_p.stderr.read())
return
else:
alert(translated("error_tails_unknown_root"))
return
else:
# if not tails, start hidden service normally
if not local_only:
try:
onion_host = onionshare.start_hidden_service(port)
except onionshare.NoTor as e:
alert(e.args[0], QtGui.QMessageBox.Warning)
return
else:
onion_host = local_host
# select file to share
filename, basename = select_file(onionshare.strings, filename)
if not filename:
return
# clean up when app quits
def shutdown():
pass
app.connect(app, QtCore.SIGNAL("aboutToQuit()"), shutdown)
# launch the gui
gui = OnionShareGui(filename, basename)
# all done
sys.exit(app.exec_())
if __name__ == '__main__':
main()