#1115 Add QR Code for onion URL

This commit is contained in:
Miguel Jacq 2020-05-31 17:44:57 +10:00
parent 1c424500f0
commit 4cf508ed8e
No known key found for this signature in database
GPG Key ID: EEA4341C6D97A0B6
9 changed files with 123 additions and 7 deletions

View File

@ -24,7 +24,8 @@ from PyQt5 import QtCore, QtWidgets, QtGui
from onionshare import strings
from ..widgets import Alert
from ..widgets import Image
from ..widgets import QRCodeDialog
class ServerStatus(QtWidgets.QWidget):
"""
@ -96,6 +97,14 @@ class ServerStatus(QtWidgets.QWidget):
self.copy_hidservauth_button = QtWidgets.QPushButton(
strings._("gui_copy_hidservauth")
)
self.show_url_qr_code_button = QtWidgets.QPushButton(strings._("gui_show_url_qr_code"))
self.show_url_qr_code_button.hide()
self.show_url_qr_code_button.clicked.connect(self.show_url_qr_code_button_clicked)
self.show_url_qr_code_button.setFlat(True)
self.show_url_qr_code_button.setStyleSheet(
self.common.gui.css["server_status_url_buttons"]
)
self.copy_hidservauth_button.setFlat(True)
self.copy_hidservauth_button.setStyleSheet(
self.common.gui.css["server_status_url_buttons"]
@ -103,6 +112,7 @@ class ServerStatus(QtWidgets.QWidget):
self.copy_hidservauth_button.clicked.connect(self.copy_hidservauth)
url_buttons_layout = QtWidgets.QHBoxLayout()
url_buttons_layout.addWidget(self.copy_url_button)
url_buttons_layout.addWidget(self.show_url_qr_code_button)
url_buttons_layout.addWidget(self.copy_hidservauth_button)
url_buttons_layout.addStretch()
@ -190,6 +200,8 @@ class ServerStatus(QtWidgets.QWidget):
self.url.show()
self.copy_url_button.show()
self.show_url_qr_code_button.show()
if self.settings.get("general", "client_auth"):
self.copy_hidservauth_button.show()
else:
@ -364,6 +376,12 @@ class ServerStatus(QtWidgets.QWidget):
self.cancel_server()
self.button_clicked.emit()
def show_url_qr_code_button_clicked(self):
"""
Show a QR code of the onion URL.
"""
self.qr_code_dialog = QRCodeDialog(self.common, self.get_url())
def start_server(self):
"""
Start the server.

View File

@ -18,7 +18,8 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from PyQt5 import QtCore, QtWidgets, QtGui
from onionshare import strings
import qrcode
class Alert(QtWidgets.QMessageBox):
"""
@ -90,3 +91,60 @@ class MinimumWidthWidget(QtWidgets.QWidget):
super(MinimumWidthWidget, self).__init__()
self.setMinimumWidth(width)
class Image(qrcode.image.base.BaseImage):
def __init__(self, border, width, box_size):
self.border = border
self.width = width
self.box_size = box_size
size = (width + border * 2) * box_size
self._image = QtGui.QImage(
size, size, QtGui.QImage.Format_RGB16)
self._image.fill(QtCore.Qt.white)
def pixmap(self):
return QtGui.QPixmap.fromImage(self._image)
def drawrect(self, row, col):
painter = QtGui.QPainter(self._image)
painter.fillRect(
(col + self.border) * self.box_size,
(row + self.border) * self.box_size,
self.box_size, self.box_size,
QtCore.Qt.black)
def save(self, stream, kind=None):
pass
class QRCodeDialog(QtWidgets.QDialog):
"""
A dialog showing a QR code.
"""
def __init__(self, common, text):
super(QRCodeDialog, self).__init__()
self.common = common
self.text = text
self.common.log("QrCode", "__init__")
self.qr_label = QtWidgets.QLabel(self)
self.qr_label.setPixmap(
qrcode.make(self.text, image_factory=Image).pixmap())
self.qr_label_description = QtWidgets.QLabel(self)
self.qr_label_description.setText(strings._("gui_qr_code_description"))
self.qr_label_description.setWordWrap(True)
self.setWindowTitle(strings._("gui_qr_code_dialog_title"))
self.setWindowIcon(
QtGui.QIcon(self.common.get_resource_path("images/logo.png"))
)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.qr_label)
layout.addWidget(self.qr_label_description)
self.exec_()

30
poetry.lock generated
View File

@ -53,9 +53,9 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "7.1.1"
[[package]]
category = "dev"
category = "main"
description = "Cross-platform colored terminal text."
marker = "sys_platform == \"win32\""
marker = "platform_system == \"Windows\" or sys_platform == \"win32\""
name = "colorama"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
@ -356,6 +356,24 @@ pytest = ">=3.0.0"
dev = ["pre-commit", "tox"]
doc = ["sphinx", "sphinx-rtd-theme"]
[[package]]
category = "main"
description = "QR Code image generator"
name = "qrcode"
optional = false
python-versions = "*"
version = "6.1"
[package.dependencies]
colorama = "*"
six = "*"
[package.extras]
dev = ["tox", "pytest", "mock"]
maintainer = ["zest.releaser"]
pil = ["pillow"]
test = ["pytest", "pytest-cov", "mock"]
[[package]]
category = "main"
description = "Python HTTP for Humans."
@ -375,7 +393,7 @@ security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"]
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"]
[[package]]
category = "dev"
category = "main"
description = "Python 2 and 3 compatibility utilities"
name = "six"
optional = false
@ -451,7 +469,7 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
testing = ["jaraco.itertools", "func-timeout"]
[metadata]
content-hash = "41d68ea93701fdaa1aa56159195db7a65863e3b34cc7305ef4a3f5d02f2bdf13"
content-hash = "3f46cfec01bcb5166c9f354aaf4439064b477955f3ea2373fcfdb65d5b89276e"
python-versions = "^3.7"
[metadata.files]
@ -671,6 +689,10 @@ pytest-qt = [
{file = "pytest-qt-3.3.0.tar.gz", hash = "sha256:714b0bf86c5313413f2d300ac613515db3a1aef595051ab8ba2ffe619dbe8925"},
{file = "pytest_qt-3.3.0-py2.py3-none-any.whl", hash = "sha256:5f8928288f50489d83f5d38caf2d7d9fcd6e7cf769947902caa4661dc7c851e3"},
]
qrcode = [
{file = "qrcode-6.1-py2.py3-none-any.whl", hash = "sha256:3996ee560fc39532910603704c82980ff6d4d5d629f9c3f25f34174ce8606cf5"},
{file = "qrcode-6.1.tar.gz", hash = "sha256:505253854f607f2abf4d16092c61d4e9d511a3b4392e60bff957a68592b04369"},
]
requests = [
{file = "requests-2.23.0-py2.py3-none-any.whl", hash = "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee"},
{file = "requests-2.23.0.tar.gz", hash = "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"},

View File

@ -30,6 +30,7 @@ urllib3 = "*"
Werkzeug = "*"
watchdog = "*"
psutil = "*"
qrcode = "^6.1"
[tool.poetry.dev-dependencies]
atomicwrites = "*"

View File

@ -28,6 +28,9 @@
"gui_copied_url": "OnionShare address copied to clipboard",
"gui_copied_hidservauth_title": "Copied HidServAuth",
"gui_copied_hidservauth": "HidServAuth line copied to clipboard",
"gui_show_url_qr_code": "Show QR code",
"gui_qr_code_dialog_title": "OnionShare QR Code",
"gui_qr_code_description": "Scan this QR code with a QR reader, such as the camera on your phone, in order to share it over an application such as Signal.",
"gui_waiting_to_start": "Scheduled to start in {}. Click to cancel.",
"gui_please_wait": "Starting… Click to cancel.",
"error_rate_limit": "Someone has made too many wrong attempts to guess your password, so OnionShare has stopped the server. Start sharing again and send the recipient a new address to share.",
@ -205,4 +208,4 @@
"mode_settings_receive_data_dir_label": "Save files to",
"mode_settings_receive_data_dir_browse_button": "Browse",
"mode_settings_website_disable_csp_checkbox": "Disable Content Security Policy header (allows your website to use third-party resources)"
}
}

View File

@ -297,6 +297,17 @@ class GuiBaseTest(unittest.TestCase):
f"http://onionshare:{tab.get_mode().server_status.web.password}@127.0.0.1:{tab.app.port}",
)
def have_show_qr_code_button(self, tab):
"""Test that the Show QR Code URL button is shown and that it loads a QR Code Dialog"""
self.assertTrue(tab.get_mode().server_status.show_url_qr_code_button.isVisible())
def accept_dialog():
window = tab.common.gui.qtapp.activeWindow()
if window:
window.close()
QtCore.QTimer.singleShot(500, accept_dialog)
tab.get_mode().server_status.show_url_qr_code_button.click()
def server_status_indicator_says_started(self, tab):
"""Test that the Server Status indicator shows we are started"""
if type(tab.get_mode()) == ReceiveMode:

View File

@ -112,6 +112,7 @@ class TestReceive(GuiBaseTest):
self.have_a_password(tab)
self.url_description_shown(tab)
self.have_copy_url_button(tab)
self.have_show_qr_code_button(tab)
self.server_status_indicator_says_started(tab)
self.web_page(tab, "Select the files you want to send, then click")

View File

@ -231,6 +231,7 @@ class TestShare(GuiBaseTest):
self.have_a_password(tab)
self.url_description_shown(tab)
self.have_copy_url_button(tab)
self.have_show_qr_code_button(tab)
self.server_status_indicator_says_started(tab)
def run_all_share_mode_download_tests(self, tab):

View File

@ -70,6 +70,7 @@ class TestWebsite(GuiBaseTest):
self.have_a_password(tab)
self.url_description_shown(tab)
self.have_copy_url_button(tab)
self.have_show_qr_code_button(tab)
self.server_status_indicator_says_started(tab)
def run_all_website_mode_download_tests(self, tab):