mirror of
https://github.com/onionshare/onionshare.git
synced 2024-12-27 08:19:41 -05:00
#1115 Add QR Code for onion URL
This commit is contained in:
parent
1c424500f0
commit
4cf508ed8e
@ -24,7 +24,8 @@ from PyQt5 import QtCore, QtWidgets, QtGui
|
|||||||
from onionshare import strings
|
from onionshare import strings
|
||||||
|
|
||||||
from ..widgets import Alert
|
from ..widgets import Alert
|
||||||
|
from ..widgets import Image
|
||||||
|
from ..widgets import QRCodeDialog
|
||||||
|
|
||||||
class ServerStatus(QtWidgets.QWidget):
|
class ServerStatus(QtWidgets.QWidget):
|
||||||
"""
|
"""
|
||||||
@ -96,6 +97,14 @@ class ServerStatus(QtWidgets.QWidget):
|
|||||||
self.copy_hidservauth_button = QtWidgets.QPushButton(
|
self.copy_hidservauth_button = QtWidgets.QPushButton(
|
||||||
strings._("gui_copy_hidservauth")
|
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.setFlat(True)
|
||||||
self.copy_hidservauth_button.setStyleSheet(
|
self.copy_hidservauth_button.setStyleSheet(
|
||||||
self.common.gui.css["server_status_url_buttons"]
|
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)
|
self.copy_hidservauth_button.clicked.connect(self.copy_hidservauth)
|
||||||
url_buttons_layout = QtWidgets.QHBoxLayout()
|
url_buttons_layout = QtWidgets.QHBoxLayout()
|
||||||
url_buttons_layout.addWidget(self.copy_url_button)
|
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.addWidget(self.copy_hidservauth_button)
|
||||||
url_buttons_layout.addStretch()
|
url_buttons_layout.addStretch()
|
||||||
|
|
||||||
@ -190,6 +200,8 @@ class ServerStatus(QtWidgets.QWidget):
|
|||||||
self.url.show()
|
self.url.show()
|
||||||
self.copy_url_button.show()
|
self.copy_url_button.show()
|
||||||
|
|
||||||
|
self.show_url_qr_code_button.show()
|
||||||
|
|
||||||
if self.settings.get("general", "client_auth"):
|
if self.settings.get("general", "client_auth"):
|
||||||
self.copy_hidservauth_button.show()
|
self.copy_hidservauth_button.show()
|
||||||
else:
|
else:
|
||||||
@ -364,6 +376,12 @@ class ServerStatus(QtWidgets.QWidget):
|
|||||||
self.cancel_server()
|
self.cancel_server()
|
||||||
self.button_clicked.emit()
|
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):
|
def start_server(self):
|
||||||
"""
|
"""
|
||||||
Start the server.
|
Start the server.
|
||||||
|
@ -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/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
from PyQt5 import QtCore, QtWidgets, QtGui
|
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||||
|
from onionshare import strings
|
||||||
|
import qrcode
|
||||||
|
|
||||||
class Alert(QtWidgets.QMessageBox):
|
class Alert(QtWidgets.QMessageBox):
|
||||||
"""
|
"""
|
||||||
@ -90,3 +91,60 @@ class MinimumWidthWidget(QtWidgets.QWidget):
|
|||||||
super(MinimumWidthWidget, self).__init__()
|
super(MinimumWidthWidget, self).__init__()
|
||||||
self.setMinimumWidth(width)
|
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
30
poetry.lock
generated
@ -53,9 +53,9 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
|||||||
version = "7.1.1"
|
version = "7.1.1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "dev"
|
category = "main"
|
||||||
description = "Cross-platform colored terminal text."
|
description = "Cross-platform colored terminal text."
|
||||||
marker = "sys_platform == \"win32\""
|
marker = "platform_system == \"Windows\" or sys_platform == \"win32\""
|
||||||
name = "colorama"
|
name = "colorama"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
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"]
|
dev = ["pre-commit", "tox"]
|
||||||
doc = ["sphinx", "sphinx-rtd-theme"]
|
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]]
|
[[package]]
|
||||||
category = "main"
|
category = "main"
|
||||||
description = "Python HTTP for Humans."
|
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"]
|
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
category = "dev"
|
category = "main"
|
||||||
description = "Python 2 and 3 compatibility utilities"
|
description = "Python 2 and 3 compatibility utilities"
|
||||||
name = "six"
|
name = "six"
|
||||||
optional = false
|
optional = false
|
||||||
@ -451,7 +469,7 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
|
|||||||
testing = ["jaraco.itertools", "func-timeout"]
|
testing = ["jaraco.itertools", "func-timeout"]
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
content-hash = "41d68ea93701fdaa1aa56159195db7a65863e3b34cc7305ef4a3f5d02f2bdf13"
|
content-hash = "3f46cfec01bcb5166c9f354aaf4439064b477955f3ea2373fcfdb65d5b89276e"
|
||||||
python-versions = "^3.7"
|
python-versions = "^3.7"
|
||||||
|
|
||||||
[metadata.files]
|
[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.tar.gz", hash = "sha256:714b0bf86c5313413f2d300ac613515db3a1aef595051ab8ba2ffe619dbe8925"},
|
||||||
{file = "pytest_qt-3.3.0-py2.py3-none-any.whl", hash = "sha256:5f8928288f50489d83f5d38caf2d7d9fcd6e7cf769947902caa4661dc7c851e3"},
|
{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 = [
|
requests = [
|
||||||
{file = "requests-2.23.0-py2.py3-none-any.whl", hash = "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee"},
|
{file = "requests-2.23.0-py2.py3-none-any.whl", hash = "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee"},
|
||||||
{file = "requests-2.23.0.tar.gz", hash = "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"},
|
{file = "requests-2.23.0.tar.gz", hash = "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"},
|
||||||
|
@ -30,6 +30,7 @@ urllib3 = "*"
|
|||||||
Werkzeug = "*"
|
Werkzeug = "*"
|
||||||
watchdog = "*"
|
watchdog = "*"
|
||||||
psutil = "*"
|
psutil = "*"
|
||||||
|
qrcode = "^6.1"
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
atomicwrites = "*"
|
atomicwrites = "*"
|
||||||
|
@ -28,6 +28,9 @@
|
|||||||
"gui_copied_url": "OnionShare address copied to clipboard",
|
"gui_copied_url": "OnionShare address copied to clipboard",
|
||||||
"gui_copied_hidservauth_title": "Copied HidServAuth",
|
"gui_copied_hidservauth_title": "Copied HidServAuth",
|
||||||
"gui_copied_hidservauth": "HidServAuth line copied to clipboard",
|
"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_waiting_to_start": "Scheduled to start in {}. Click to cancel.",
|
||||||
"gui_please_wait": "Starting… 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.",
|
"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.",
|
||||||
|
@ -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}",
|
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):
|
def server_status_indicator_says_started(self, tab):
|
||||||
"""Test that the Server Status indicator shows we are started"""
|
"""Test that the Server Status indicator shows we are started"""
|
||||||
if type(tab.get_mode()) == ReceiveMode:
|
if type(tab.get_mode()) == ReceiveMode:
|
||||||
|
@ -112,6 +112,7 @@ class TestReceive(GuiBaseTest):
|
|||||||
self.have_a_password(tab)
|
self.have_a_password(tab)
|
||||||
self.url_description_shown(tab)
|
self.url_description_shown(tab)
|
||||||
self.have_copy_url_button(tab)
|
self.have_copy_url_button(tab)
|
||||||
|
self.have_show_qr_code_button(tab)
|
||||||
self.server_status_indicator_says_started(tab)
|
self.server_status_indicator_says_started(tab)
|
||||||
self.web_page(tab, "Select the files you want to send, then click")
|
self.web_page(tab, "Select the files you want to send, then click")
|
||||||
|
|
||||||
|
@ -231,6 +231,7 @@ class TestShare(GuiBaseTest):
|
|||||||
self.have_a_password(tab)
|
self.have_a_password(tab)
|
||||||
self.url_description_shown(tab)
|
self.url_description_shown(tab)
|
||||||
self.have_copy_url_button(tab)
|
self.have_copy_url_button(tab)
|
||||||
|
self.have_show_qr_code_button(tab)
|
||||||
self.server_status_indicator_says_started(tab)
|
self.server_status_indicator_says_started(tab)
|
||||||
|
|
||||||
def run_all_share_mode_download_tests(self, tab):
|
def run_all_share_mode_download_tests(self, tab):
|
||||||
|
@ -70,6 +70,7 @@ class TestWebsite(GuiBaseTest):
|
|||||||
self.have_a_password(tab)
|
self.have_a_password(tab)
|
||||||
self.url_description_shown(tab)
|
self.url_description_shown(tab)
|
||||||
self.have_copy_url_button(tab)
|
self.have_copy_url_button(tab)
|
||||||
|
self.have_show_qr_code_button(tab)
|
||||||
self.server_status_indicator_says_started(tab)
|
self.server_status_indicator_says_started(tab)
|
||||||
|
|
||||||
def run_all_website_mode_download_tests(self, tab):
|
def run_all_website_mode_download_tests(self, tab):
|
||||||
|
Loading…
Reference in New Issue
Block a user