mirror of
https://github.com/onionshare/onionshare.git
synced 2024-10-01 01:35:40 -04:00
Merge branch 'develop' of github.com:micahflee/onionshare into chat
This commit is contained in:
commit
41681f82ec
2
BUILD.md
2
BUILD.md
@ -103,7 +103,7 @@ Create a .rpm on Fedora-like distros: `./install/build_rpm.sh`
|
||||
|
||||
For openSUSE: There are instructions for building [in the wiki](https://github.com/micahflee/onionshare/wiki/Linux-Distribution-Support#opensuse-leap-150).
|
||||
|
||||
For ArchLinux: There is a PKBUILD available [here](https://aur.archlinux.org/packages/onionshare/) that can be used to install OnionShare.
|
||||
For ArchLinux: There is a PKBUILD available [here](https://www.archlinux.org/packages/community/any/onionshare/) that can be used to install OnionShare.
|
||||
|
||||
If you find that these instructions don't work for your Linux distribution or version, consult the [Linux Distribution Support wiki guide](https://github.com/micahflee/onionshare/wiki/Linux-Distribution-Support), which might contain extra instructions.
|
||||
|
||||
|
@ -209,6 +209,11 @@ class GuiCommon:
|
||||
color: #cc0000;
|
||||
}""",
|
||||
# Share mode and child widget styles
|
||||
"share_delete_all_files_button": """
|
||||
QPushButton {
|
||||
color: #3f7fcf;
|
||||
}
|
||||
""",
|
||||
"share_zip_progess_bar": """
|
||||
QProgressBar {
|
||||
border: 1px solid #4e064f;
|
||||
|
@ -138,7 +138,7 @@ class Mode(QtWidgets.QWidget):
|
||||
"""
|
||||
# If this is a scheduled share, display the countdown til the share starts
|
||||
if self.server_status.status == ServerStatus.STATUS_WORKING:
|
||||
if self.server_status.autostart_timer_datetime:
|
||||
if self.settings.get("general", "autostart_timer"):
|
||||
now = QtCore.QDateTime.currentDateTime()
|
||||
if self.server_status.local_only:
|
||||
seconds_remaining = now.secsTo(
|
||||
@ -227,13 +227,8 @@ class Mode(QtWidgets.QWidget):
|
||||
# Start the onion thread. If this share was scheduled for a future date,
|
||||
# the OnionThread will start and exit 'early' to obtain the port, password
|
||||
# and onion address, but it will not start the WebThread yet.
|
||||
if self.server_status.autostart_timer_datetime:
|
||||
if self.settings.get("general", "autostart_timer"):
|
||||
self.start_onion_thread(obtain_onion_early=True)
|
||||
else:
|
||||
self.start_onion_thread()
|
||||
|
||||
# If scheduling a share, delay starting the real share
|
||||
if self.server_status.autostart_timer_datetime:
|
||||
self.common.log("Mode", "start_server", "Starting auto-start timer")
|
||||
self.startup_thread = AutoStartTimer(self)
|
||||
# Once the timer has finished, start the real share, with a WebThread
|
||||
@ -241,6 +236,8 @@ class Mode(QtWidgets.QWidget):
|
||||
self.startup_thread.error.connect(self.start_server_error)
|
||||
self.startup_thread.canceled = False
|
||||
self.startup_thread.start()
|
||||
else:
|
||||
self.start_onion_thread()
|
||||
|
||||
def start_onion_thread(self, obtain_onion_early=False):
|
||||
self.common.log("Mode", "start_server", "Starting an onion thread")
|
||||
|
@ -338,8 +338,8 @@ class FileSelection(QtWidgets.QVBoxLayout):
|
||||
else:
|
||||
self.add_button = QtWidgets.QPushButton(strings._("gui_add"))
|
||||
self.add_button.clicked.connect(self.add)
|
||||
self.delete_button = QtWidgets.QPushButton(strings._("gui_delete"))
|
||||
self.delete_button.clicked.connect(self.delete)
|
||||
self.remove_button = QtWidgets.QPushButton(strings._("gui_remove"))
|
||||
self.remove_button.clicked.connect(self.delete)
|
||||
button_layout = QtWidgets.QHBoxLayout()
|
||||
button_layout.addStretch()
|
||||
if self.common.platform == "Darwin":
|
||||
@ -347,7 +347,7 @@ class FileSelection(QtWidgets.QVBoxLayout):
|
||||
button_layout.addWidget(self.add_folder_button)
|
||||
else:
|
||||
button_layout.addWidget(self.add_button)
|
||||
button_layout.addWidget(self.delete_button)
|
||||
button_layout.addWidget(self.remove_button)
|
||||
|
||||
# Add the widgets
|
||||
self.addWidget(self.file_list)
|
||||
@ -366,7 +366,7 @@ class FileSelection(QtWidgets.QVBoxLayout):
|
||||
self.add_folder_button.hide()
|
||||
else:
|
||||
self.add_button.hide()
|
||||
self.delete_button.hide()
|
||||
self.remove_button.hide()
|
||||
else:
|
||||
if self.common.platform == "Darwin":
|
||||
self.add_files_button.show()
|
||||
@ -376,9 +376,9 @@ class FileSelection(QtWidgets.QVBoxLayout):
|
||||
|
||||
# Delete button should be hidden if item isn't selected
|
||||
if len(self.file_list.selectedItems()) == 0:
|
||||
self.delete_button.hide()
|
||||
self.remove_button.hide()
|
||||
else:
|
||||
self.delete_button.show()
|
||||
self.remove_button.show()
|
||||
|
||||
# Update the file list
|
||||
self.file_list.update()
|
||||
|
@ -76,7 +76,10 @@ class ModeSettingsWidget(QtWidgets.QWidget):
|
||||
self.autostart_timer_widget.setCurrentSection(
|
||||
QtWidgets.QDateTimeEdit.MinuteSection
|
||||
)
|
||||
self.autostart_timer_widget.hide()
|
||||
if self.settings.get("general", "autostart_timer"):
|
||||
self.autostart_timer_widget.show()
|
||||
else:
|
||||
self.autostart_timer_widget.hide()
|
||||
|
||||
# Autostart timer layout
|
||||
autostart_timer_layout = QtWidgets.QHBoxLayout()
|
||||
@ -104,7 +107,10 @@ class ModeSettingsWidget(QtWidgets.QWidget):
|
||||
self.autostop_timer_widget.setCurrentSection(
|
||||
QtWidgets.QDateTimeEdit.MinuteSection
|
||||
)
|
||||
self.autostop_timer_widget.hide()
|
||||
if self.settings.get("general", "autostop_timer"):
|
||||
self.autostop_timer_widget.show()
|
||||
else:
|
||||
self.autostop_timer_widget.hide()
|
||||
|
||||
# Autostop timer layout
|
||||
autostop_timer_layout = QtWidgets.QHBoxLayout()
|
||||
|
@ -141,7 +141,7 @@ class ReceiveMode(Mode):
|
||||
f"selected dir: {selected_dir}",
|
||||
)
|
||||
self.data_dir_lineedit.setText(selected_dir)
|
||||
self.settings.set("receive", "data_dir", data_dir)
|
||||
self.settings.set("receive", "data_dir", selected_dir)
|
||||
|
||||
def get_stop_server_autostop_timer_text(self):
|
||||
"""
|
||||
|
@ -111,6 +111,17 @@ class ShareMode(Mode):
|
||||
self.info_label = QtWidgets.QLabel()
|
||||
self.info_label.hide()
|
||||
|
||||
# Delete all files button
|
||||
self.remove_all_button = QtWidgets.QPushButton(
|
||||
strings._("gui_file_selection_remove_all")
|
||||
)
|
||||
self.remove_all_button.setFlat(True)
|
||||
self.remove_all_button.setStyleSheet(
|
||||
self.common.gui.css["share_delete_all_files_button"]
|
||||
)
|
||||
self.remove_all_button.clicked.connect(self.delete_all)
|
||||
self.remove_all_button.hide()
|
||||
|
||||
# Toggle history
|
||||
self.toggle_history = ToggleHistory(
|
||||
self.common,
|
||||
@ -126,6 +137,7 @@ class ShareMode(Mode):
|
||||
top_bar_layout = QtWidgets.QHBoxLayout()
|
||||
top_bar_layout.addWidget(self.info_label)
|
||||
top_bar_layout.addStretch()
|
||||
top_bar_layout.addWidget(self.remove_all_button)
|
||||
top_bar_layout.addWidget(self.toggle_history)
|
||||
|
||||
# Primary action layout
|
||||
@ -198,6 +210,8 @@ class ShareMode(Mode):
|
||||
# Hide and reset the downloads if we have previously shared
|
||||
self.reset_info_counters()
|
||||
|
||||
self.remove_all_button.hide()
|
||||
|
||||
def start_server_step2_custom(self):
|
||||
"""
|
||||
Step 2 in starting the server. Zipping up files.
|
||||
@ -257,6 +271,8 @@ class ShareMode(Mode):
|
||||
self.history.update_in_progress()
|
||||
self.file_selection.file_list.adjustSize()
|
||||
|
||||
self.remove_all_button.show()
|
||||
|
||||
def cancel_server_custom(self):
|
||||
"""
|
||||
Stop the compression thread on cancel
|
||||
@ -343,6 +359,7 @@ class ShareMode(Mode):
|
||||
if self.server_status.file_selection.get_num_files() > 0:
|
||||
self.primary_action.show()
|
||||
self.info_label.show()
|
||||
self.remove_all_button.show()
|
||||
|
||||
def update_primary_action(self):
|
||||
self.common.log("ShareMode", "update_primary_action")
|
||||
@ -352,6 +369,7 @@ class ShareMode(Mode):
|
||||
if file_count > 0:
|
||||
self.primary_action.show()
|
||||
self.info_label.show()
|
||||
self.remove_all_button.show()
|
||||
|
||||
# Update the file count in the info label
|
||||
total_size_bytes = 0
|
||||
@ -374,6 +392,7 @@ class ShareMode(Mode):
|
||||
else:
|
||||
self.primary_action.hide()
|
||||
self.info_label.hide()
|
||||
self.remove_all_button.hide()
|
||||
|
||||
def reset_info_counters(self):
|
||||
"""
|
||||
@ -383,6 +402,15 @@ class ShareMode(Mode):
|
||||
self.toggle_history.indicator_count = 0
|
||||
self.toggle_history.update_indicator()
|
||||
|
||||
def delete_all(self):
|
||||
"""
|
||||
Delete All button clicked
|
||||
"""
|
||||
self.file_selection.file_list.clear()
|
||||
self.file_selection.file_list.files_updated.emit()
|
||||
|
||||
self.file_selection.file_list.setCurrentItem(None)
|
||||
|
||||
@staticmethod
|
||||
def _compute_total_size(filenames):
|
||||
total_size = 0
|
||||
|
@ -114,6 +114,17 @@ class WebsiteMode(Mode):
|
||||
self.info_label = QtWidgets.QLabel()
|
||||
self.info_label.hide()
|
||||
|
||||
# Delete all files button
|
||||
self.remove_all_button = QtWidgets.QPushButton(
|
||||
strings._("gui_file_selection_remove_all")
|
||||
)
|
||||
self.remove_all_button.setFlat(True)
|
||||
self.remove_all_button.setStyleSheet(
|
||||
self.common.gui.css["share_delete_all_files_button"]
|
||||
)
|
||||
self.remove_all_button.clicked.connect(self.delete_all)
|
||||
self.remove_all_button.hide()
|
||||
|
||||
# Toggle history
|
||||
self.toggle_history = ToggleHistory(
|
||||
self.common,
|
||||
@ -129,6 +140,7 @@ class WebsiteMode(Mode):
|
||||
top_bar_layout = QtWidgets.QHBoxLayout()
|
||||
top_bar_layout.addWidget(self.info_label)
|
||||
top_bar_layout.addStretch()
|
||||
top_bar_layout.addWidget(self.remove_all_button)
|
||||
top_bar_layout.addWidget(self.toggle_history)
|
||||
|
||||
# Primary action layout
|
||||
@ -191,6 +203,8 @@ class WebsiteMode(Mode):
|
||||
# Hide and reset the downloads if we have previously shared
|
||||
self.reset_info_counters()
|
||||
|
||||
self.remove_all_button.hide()
|
||||
|
||||
def start_server_step2_custom(self):
|
||||
"""
|
||||
Step 2 in starting the server. Zipping up files.
|
||||
@ -228,6 +242,8 @@ class WebsiteMode(Mode):
|
||||
self.history.completed_count = 0
|
||||
self.file_selection.file_list.adjustSize()
|
||||
|
||||
self.remove_all_button.show()
|
||||
|
||||
def cancel_server_custom(self):
|
||||
"""
|
||||
Log that the server has been cancelled
|
||||
@ -248,6 +264,7 @@ class WebsiteMode(Mode):
|
||||
if self.server_status.file_selection.get_num_files() > 0:
|
||||
self.primary_action.show()
|
||||
self.info_label.show()
|
||||
self.remove_all_button.show()
|
||||
|
||||
def update_primary_action(self):
|
||||
self.common.log("WebsiteMode", "update_primary_action")
|
||||
@ -257,6 +274,7 @@ class WebsiteMode(Mode):
|
||||
if file_count > 0:
|
||||
self.primary_action.show()
|
||||
self.info_label.show()
|
||||
self.remove_all_button.show()
|
||||
|
||||
# Update the file count in the info label
|
||||
total_size_bytes = 0
|
||||
@ -279,6 +297,7 @@ class WebsiteMode(Mode):
|
||||
else:
|
||||
self.primary_action.hide()
|
||||
self.info_label.hide()
|
||||
self.remove_all_button.hide()
|
||||
|
||||
def reset_info_counters(self):
|
||||
"""
|
||||
@ -288,6 +307,15 @@ class WebsiteMode(Mode):
|
||||
self.toggle_history.indicator_count = 0
|
||||
self.toggle_history.update_indicator()
|
||||
|
||||
def delete_all(self):
|
||||
"""
|
||||
Delete All button clicked
|
||||
"""
|
||||
self.file_selection.file_list.clear()
|
||||
self.file_selection.file_list.files_updated.emit()
|
||||
|
||||
self.file_selection.file_list.setCurrentItem(None)
|
||||
|
||||
@staticmethod
|
||||
def _compute_total_size(filenames):
|
||||
total_size = 0
|
||||
|
@ -20,10 +20,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import platform
|
||||
import textwrap
|
||||
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||
from PyQt5.QtCore import Qt
|
||||
|
||||
from onionshare import strings
|
||||
|
||||
from ..widgets import Alert
|
||||
from ..widgets import QRCodeDialog
|
||||
|
||||
|
||||
class ServerStatus(QtWidgets.QWidget):
|
||||
@ -85,17 +87,31 @@ class ServerStatus(QtWidgets.QWidget):
|
||||
self.url.setWordWrap(True)
|
||||
self.url.setMinimumSize(self.url.sizeHint())
|
||||
self.url.setStyleSheet(self.common.gui.css["server_status_url"])
|
||||
self.url.setTextInteractionFlags(
|
||||
Qt.TextSelectableByMouse | Qt.TextSelectableByKeyboard
|
||||
)
|
||||
|
||||
self.copy_url_button = QtWidgets.QPushButton(strings._("gui_copy_url"))
|
||||
self.copy_url_button.setFlat(True)
|
||||
self.copy_url_button.setStyleSheet(
|
||||
self.common.gui.css["server_status_url_buttons"]
|
||||
)
|
||||
self.copy_url_button.setMinimumHeight(65)
|
||||
self.copy_url_button.clicked.connect(self.copy_url)
|
||||
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 +119,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 +207,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:
|
||||
@ -273,7 +292,7 @@ class ServerStatus(QtWidgets.QWidget):
|
||||
self.common.gui.css["server_status_button_working"]
|
||||
)
|
||||
self.server_button.setEnabled(True)
|
||||
if self.autostart_timer_datetime:
|
||||
if self.settings.get("general", "autostart_timer"):
|
||||
self.server_button.setToolTip(
|
||||
strings._("gui_start_server_autostart_timer_tooltip").format(
|
||||
self.mode_settings_widget.autostart_timer_widget.dateTime().toString(
|
||||
@ -283,15 +302,6 @@ class ServerStatus(QtWidgets.QWidget):
|
||||
)
|
||||
else:
|
||||
self.server_button.setText(strings._("gui_please_wait"))
|
||||
|
||||
if self.settings.get("general", "autostart_timer"):
|
||||
self.server_button.setToolTip(
|
||||
strings._("gui_start_server_autostart_timer_tooltip").format(
|
||||
self.mode_settings_widget.autostart_timer_widget.dateTime().toString(
|
||||
"h:mm AP, MMMM dd, yyyy"
|
||||
)
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.server_button.setStyleSheet(
|
||||
self.common.gui.css["server_status_button_working"]
|
||||
@ -368,6 +378,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.
|
||||
@ -381,7 +397,7 @@ class ServerStatus(QtWidgets.QWidget):
|
||||
The server has finished starting.
|
||||
"""
|
||||
self.status = self.STATUS_STARTED
|
||||
self.copy_url()
|
||||
# self.copy_url()
|
||||
self.update()
|
||||
self.server_started_finished.emit()
|
||||
|
||||
|
@ -338,7 +338,7 @@ class Tab(QtWidgets.QWidget):
|
||||
strings._("gui_status_indicator_share_stopped")
|
||||
)
|
||||
elif self.share_mode.server_status.status == ServerStatus.STATUS_WORKING:
|
||||
if self.share_mode.server_status.autostart_timer_datetime:
|
||||
if self.settings.get("general", "autostart_timer"):
|
||||
self.set_server_status_indicator_working(
|
||||
strings._("gui_status_indicator_share_scheduled")
|
||||
)
|
||||
@ -357,9 +357,14 @@ class Tab(QtWidgets.QWidget):
|
||||
strings._("gui_status_indicator_share_stopped")
|
||||
)
|
||||
elif self.website_mode.server_status.status == ServerStatus.STATUS_WORKING:
|
||||
self.set_server_status_indicator_working(
|
||||
strings._("gui_status_indicator_share_working")
|
||||
)
|
||||
if self.website_mode.server_status.autostart_timer_datetime:
|
||||
self.set_server_status_indicator_working(
|
||||
strings._("gui_status_indicator_share_scheduled")
|
||||
)
|
||||
else:
|
||||
self.set_server_status_indicator_working(
|
||||
strings._("gui_status_indicator_share_working")
|
||||
)
|
||||
elif self.website_mode.server_status.status == ServerStatus.STATUS_STARTED:
|
||||
self.set_server_status_indicator_started(
|
||||
strings._("gui_status_indicator_share_started")
|
||||
@ -371,7 +376,7 @@ class Tab(QtWidgets.QWidget):
|
||||
strings._("gui_status_indicator_receive_stopped")
|
||||
)
|
||||
elif self.receive_mode.server_status.status == ServerStatus.STATUS_WORKING:
|
||||
if self.receive_mode.server_status.autostart_timer_datetime:
|
||||
if self.settings.get("general", "autostart_timer"):
|
||||
self.set_server_status_indicator_working(
|
||||
strings._("gui_status_indicator_receive_scheduled")
|
||||
)
|
||||
|
@ -60,6 +60,7 @@ class TabWidget(QtWidgets.QTabWidget):
|
||||
# Use a custom tab bar
|
||||
tab_bar = TabBar()
|
||||
tab_bar.move_new_tab_button.connect(self.move_new_tab_button)
|
||||
tab_bar.currentChanged.connect(self.tab_changed)
|
||||
self.setTabBar(tab_bar)
|
||||
|
||||
# Set up the tab widget
|
||||
@ -108,6 +109,24 @@ class TabWidget(QtWidgets.QTabWidget):
|
||||
self.new_tab_button.move(pos)
|
||||
self.new_tab_button.raise_()
|
||||
|
||||
def tab_changed(self):
|
||||
# Active tab was changed
|
||||
tab_id = self.currentIndex()
|
||||
self.common.log("TabWidget", "tab_changed", f"Tab was changed to {tab_id}")
|
||||
try:
|
||||
mode = self.tabs[tab_id].get_mode()
|
||||
if mode:
|
||||
# Update the server status indicator to reflect that of the current tab
|
||||
self.tabs[tab_id].update_server_status_indicator()
|
||||
else:
|
||||
# If this tab doesn't have a mode set yet, blank the server status indicator
|
||||
self.status_bar.server_status_image_label.clear()
|
||||
self.status_bar.server_status_label.clear()
|
||||
except KeyError:
|
||||
# When all current tabs are closed, index briefly drops to -1 before resetting to 0
|
||||
# which will otherwise trigger a KeyError on tab.get_mode() above.
|
||||
pass
|
||||
|
||||
def new_tab_clicked(self):
|
||||
# Create a new tab
|
||||
self.add_tab()
|
||||
|
@ -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,64 @@ class MinimumWidthWidget(QtWidgets.QWidget):
|
||||
super(MinimumWidthWidget, self).__init__()
|
||||
self.setMinimumWidth(width)
|
||||
|
||||
|
||||
class Image(qrcode.image.base.BaseImage):
|
||||
"""
|
||||
A custom Image class, for use with the QR Code pixmap.
|
||||
"""
|
||||
|
||||
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_()
|
||||
|
||||
|
32
poetry.lock
generated
32
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"
|
||||
|
||||
[[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.*"
|
||||
@ -412,6 +412,7 @@ doc = ["sphinx", "sphinx-rtd-theme"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
<<<<<<< HEAD
|
||||
description = "Engine.IO server"
|
||||
name = "python-engineio"
|
||||
optional = false
|
||||
@ -440,6 +441,23 @@ six = ">=1.9.0"
|
||||
[package.extras]
|
||||
asyncio_client = ["aiohttp (>=3.4)", "websockets (>=7.0)"]
|
||||
client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"]
|
||||
=======
|
||||
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"]
|
||||
>>>>>>> 172a30d5c9facbf50fe3b11f663b338a8d9a2c73
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
@ -536,7 +554,11 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
|
||||
testing = ["jaraco.itertools", "func-timeout"]
|
||||
|
||||
[metadata]
|
||||
<<<<<<< HEAD
|
||||
content-hash = "181ea24287292c0ab933307bbff258d4f2791642116c7c39d3bde83d573c089b"
|
||||
=======
|
||||
content-hash = "3f46cfec01bcb5166c9f354aaf4439064b477955f3ea2373fcfdb65d5b89276e"
|
||||
>>>>>>> 172a30d5c9facbf50fe3b11f663b338a8d9a2c73
|
||||
python-versions = "^3.7"
|
||||
|
||||
[metadata.files]
|
||||
@ -796,6 +818,7 @@ 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"},
|
||||
]
|
||||
<<<<<<< HEAD
|
||||
python-engineio = [
|
||||
{file = "python-engineio-3.12.1.tar.gz", hash = "sha256:2481732d93646998f7372ef0ecf003af7817b82720b881db173c3d50b4887916"},
|
||||
{file = "python_engineio-3.12.1-py2.py3-none-any.whl", hash = "sha256:222926adb4bc6e03a8fc8e0ef2a3309f030c1c3f8e0fcc94c9ba214574565f02"},
|
||||
@ -803,6 +826,11 @@ python-engineio = [
|
||||
python-socketio = [
|
||||
{file = "python-socketio-4.5.1.tar.gz", hash = "sha256:149b98c33f8c3d09273fb4ebeb83781e4dc9411b56b27d9f058bec1bd1ed74b7"},
|
||||
{file = "python_socketio-4.5.1-py2.py3-none-any.whl", hash = "sha256:81280cbbb7018d8ecdd006bf6025979733d347c0f2612282c1e21f6ed7d3b55b"},
|
||||
=======
|
||||
qrcode = [
|
||||
{file = "qrcode-6.1-py2.py3-none-any.whl", hash = "sha256:3996ee560fc39532910603704c82980ff6d4d5d629f9c3f25f34174ce8606cf5"},
|
||||
{file = "qrcode-6.1.tar.gz", hash = "sha256:505253854f607f2abf4d16092c61d4e9d511a3b4392e60bff957a68592b04369"},
|
||||
>>>>>>> 172a30d5c9facbf50fe3b11f663b338a8d9a2c73
|
||||
]
|
||||
requests = [
|
||||
{file = "requests-2.23.0-py2.py3-none-any.whl", hash = "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee"},
|
||||
|
@ -32,6 +32,7 @@ watchdog = "*"
|
||||
psutil = "*"
|
||||
flask-socketio = "^4.3.0"
|
||||
eventlet = "^0.25.2"
|
||||
qrcode = "^6.1"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
atomicwrites = "*"
|
||||
|
@ -11,7 +11,8 @@
|
||||
"gui_add": "Add",
|
||||
"gui_add_files": "Add Files",
|
||||
"gui_add_folder": "Add Folder",
|
||||
"gui_delete": "Delete",
|
||||
"gui_remove": "Remove",
|
||||
"gui_file_selection_remove_all": "Remove All",
|
||||
"gui_choose_items": "Choose",
|
||||
"gui_share_start_server": "Start sharing",
|
||||
"gui_share_stop_server": "Stop sharing",
|
||||
@ -31,6 +32,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 more easily share the OnionShare address with someone.",
|
||||
"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.",
|
||||
@ -210,4 +214,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)"
|
||||
}
|
||||
}
|
||||
|
2
share/static/js/jquery-3.4.0.min.js
vendored
2
share/static/js/jquery-3.4.0.min.js
vendored
File diff suppressed because one or more lines are too long
2
share/static/js/jquery-3.5.1.min.js
vendored
Normal file
2
share/static/js/jquery-3.5.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -4,6 +4,9 @@ $(function(){
|
||||
$('#flashes').append($('<li>').addClass(category).text(message));
|
||||
};
|
||||
|
||||
var scriptSrc = document.getElementById('receive-script').src;
|
||||
var staticImgPath = scriptSrc.substr(0, scriptSrc.lastIndexOf( '/' )+1).replace('js', 'img');
|
||||
|
||||
// Intercept submitting the form
|
||||
$('#send').submit(function(event){
|
||||
event.preventDefault();
|
||||
@ -38,7 +41,7 @@ $(function(){
|
||||
// and update the status
|
||||
if(event.loaded == event.total) {
|
||||
$('.cancel', ajax.$upload_div).remove();
|
||||
$('.upload-status', ajax.$upload_div).html('<img src="/static/img/ajax.gif" alt="" /> Waiting for data to finish traversing Tor network ...');
|
||||
$('.upload-status', ajax.$upload_div).html('<img src="' + staticImgPath + '/ajax.gif" alt="" /> Waiting for data to finish traversing Tor network ...');
|
||||
}
|
||||
}, false);
|
||||
|
||||
|
@ -38,7 +38,7 @@
|
||||
</form>
|
||||
|
||||
</div>
|
||||
<script src="{{ static_url_path }}/js/jquery-3.4.0.min.js"></script>
|
||||
<script async src="{{ static_url_path }}/js/receive.js"></script>
|
||||
<script src="{{ static_url_path }}/js/jquery-3.5.1.min.js"></script>
|
||||
<script async src="{{ static_url_path }}/js/receive.js" id="receive-script"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -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:
|
||||
@ -388,13 +399,13 @@ class GuiBaseTest(unittest.TestCase):
|
||||
tab.get_mode().server_status.file_selection.get_num_files(), num
|
||||
)
|
||||
|
||||
def add_delete_buttons_hidden(self, tab):
|
||||
"""Test that the add and delete buttons are hidden when the server starts"""
|
||||
def add_remove_buttons_hidden(self, tab):
|
||||
"""Test that the add and remove buttons are hidden when the server starts"""
|
||||
self.assertFalse(
|
||||
tab.get_mode().server_status.file_selection.add_button.isVisible()
|
||||
)
|
||||
self.assertFalse(
|
||||
tab.get_mode().server_status.file_selection.delete_button.isVisible()
|
||||
tab.get_mode().server_status.file_selection.remove_button.isVisible()
|
||||
)
|
||||
|
||||
# Auto-stop timer tests
|
||||
|
@ -20,7 +20,7 @@ do
|
||||
shift
|
||||
done
|
||||
|
||||
pytest $PARAMS -vvv ./tests/test_cli*.py
|
||||
pytest $PARAMS -vvv ./tests/test_cli*.py || exit 1
|
||||
for filename in ./tests/test_gui_*.py; do
|
||||
pytest $PARAMS -vvv --no-qt-log $filename
|
||||
pytest $PARAMS -vvv --no-qt-log $filename || exit 1
|
||||
done
|
||||
|
@ -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")
|
||||
|
||||
|
@ -12,8 +12,8 @@ from .gui_base_test import GuiBaseTest
|
||||
class TestShare(GuiBaseTest):
|
||||
# Shared test methods
|
||||
|
||||
def deleting_all_files_hides_delete_button(self, tab):
|
||||
"""Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button"""
|
||||
def removing_all_files_hides_remove_button(self, tab):
|
||||
"""Test that clicking on the file item shows the remove button. Test that removing the only item in the list hides the remove button"""
|
||||
rect = tab.get_mode().server_status.file_selection.file_list.visualItemRect(
|
||||
tab.get_mode().server_status.file_selection.file_list.item(0)
|
||||
)
|
||||
@ -22,12 +22,12 @@ class TestShare(GuiBaseTest):
|
||||
QtCore.Qt.LeftButton,
|
||||
pos=rect.center(),
|
||||
)
|
||||
# Delete button should be visible
|
||||
# Remove button should be visible
|
||||
self.assertTrue(
|
||||
tab.get_mode().server_status.file_selection.delete_button.isVisible()
|
||||
tab.get_mode().server_status.file_selection.remove_button.isVisible()
|
||||
)
|
||||
# Click delete, delete button should still be visible since we have one more file
|
||||
tab.get_mode().server_status.file_selection.delete_button.click()
|
||||
# Click remove, remove button should still be visible since we have one more file
|
||||
tab.get_mode().server_status.file_selection.remove_button.click()
|
||||
rect = tab.get_mode().server_status.file_selection.file_list.visualItemRect(
|
||||
tab.get_mode().server_status.file_selection.file_list.item(0)
|
||||
)
|
||||
@ -37,17 +37,17 @@ class TestShare(GuiBaseTest):
|
||||
pos=rect.center(),
|
||||
)
|
||||
self.assertTrue(
|
||||
tab.get_mode().server_status.file_selection.delete_button.isVisible()
|
||||
tab.get_mode().server_status.file_selection.remove_button.isVisible()
|
||||
)
|
||||
tab.get_mode().server_status.file_selection.delete_button.click()
|
||||
tab.get_mode().server_status.file_selection.remove_button.click()
|
||||
|
||||
# No more files, the delete button should be hidden
|
||||
# No more files, the remove button should be hidden
|
||||
self.assertFalse(
|
||||
tab.get_mode().server_status.file_selection.delete_button.isVisible()
|
||||
tab.get_mode().server_status.file_selection.remove_button.isVisible()
|
||||
)
|
||||
|
||||
def add_a_file_and_delete_using_its_delete_widget(self, tab):
|
||||
"""Test that we can also delete a file by clicking on its [X] widget"""
|
||||
def add_a_file_and_remove_using_its_remove_widget(self, tab):
|
||||
"""Test that we can also remove a file by clicking on its [X] widget"""
|
||||
num_files = tab.get_mode().server_status.file_selection.get_num_files()
|
||||
tab.get_mode().server_status.file_selection.file_list.add_file(self.tmpfiles[0])
|
||||
tab.get_mode().server_status.file_selection.file_list.item(
|
||||
@ -55,6 +55,14 @@ class TestShare(GuiBaseTest):
|
||||
).item_button.click()
|
||||
self.file_selection_widget_has_files(tab, num_files)
|
||||
|
||||
def add_a_file_and_remove_using_remove_all_widget(self, tab):
|
||||
"""Test that we can also remove all files by clicking on the Remove All widget"""
|
||||
tab.get_mode().server_status.file_selection.file_list.add_file(self.tmpfiles[0])
|
||||
tab.get_mode().server_status.file_selection.file_list.add_file(self.tmpfiles[1])
|
||||
tab.get_mode().remove_all_button.click()
|
||||
# Should be no files after clearing all
|
||||
self.file_selection_widget_has_files(tab, 0)
|
||||
|
||||
def file_selection_widget_read_files(self, tab):
|
||||
"""Re-add some files to the list so we can share"""
|
||||
num_files = tab.get_mode().server_status.file_selection.get_num_files()
|
||||
@ -189,7 +197,7 @@ class TestShare(GuiBaseTest):
|
||||
"""Test that we can cancel a share before it's started up """
|
||||
self.server_working_on_start_button_pressed(tab)
|
||||
self.server_status_indicator_says_scheduled(tab)
|
||||
self.add_delete_buttons_hidden(tab)
|
||||
self.add_remove_buttons_hidden(tab)
|
||||
self.mode_settings_widget_is_hidden(tab)
|
||||
self.set_autostart_timer(tab, 10)
|
||||
QtTest.QTest.mousePress(
|
||||
@ -216,21 +224,22 @@ class TestShare(GuiBaseTest):
|
||||
self.history_is_not_visible(tab)
|
||||
self.click_toggle_history(tab)
|
||||
self.history_is_visible(tab)
|
||||
self.deleting_all_files_hides_delete_button(tab)
|
||||
self.add_a_file_and_delete_using_its_delete_widget(tab)
|
||||
self.removing_all_files_hides_remove_button(tab)
|
||||
self.add_a_file_and_remove_using_its_remove_widget(tab)
|
||||
self.file_selection_widget_read_files(tab)
|
||||
|
||||
def run_all_share_mode_started_tests(self, tab, startup_time=2000):
|
||||
"""Tests in share mode after starting a share"""
|
||||
self.server_working_on_start_button_pressed(tab)
|
||||
self.server_status_indicator_says_starting(tab)
|
||||
self.add_delete_buttons_hidden(tab)
|
||||
self.add_remove_buttons_hidden(tab)
|
||||
self.mode_settings_widget_is_hidden(tab)
|
||||
self.server_is_started(tab, startup_time)
|
||||
self.web_server_is_running(tab)
|
||||
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):
|
||||
@ -269,7 +278,7 @@ class TestShare(GuiBaseTest):
|
||||
self.run_all_share_mode_started_tests(tab)
|
||||
self.run_all_share_mode_download_tests(tab)
|
||||
|
||||
def run_all_clear_all_button_tests(self, tab):
|
||||
def run_all_clear_all_history_button_tests(self, tab):
|
||||
"""Test the Clear All history button"""
|
||||
self.run_all_share_mode_setup_tests(tab)
|
||||
self.run_all_share_mode_started_tests(tab)
|
||||
@ -279,6 +288,11 @@ class TestShare(GuiBaseTest):
|
||||
self.individual_file_is_viewable_or_not(tab)
|
||||
self.clear_all_history_items(tab, 2)
|
||||
|
||||
def run_all_remove_all_file_selection_button_tests(self, tab):
|
||||
"""Test the Remove All File Selection button"""
|
||||
self.run_all_share_mode_setup_tests(tab)
|
||||
self.add_a_file_and_remove_using_remove_all_widget(tab)
|
||||
|
||||
def run_all_share_mode_individual_file_tests(self, tab):
|
||||
"""Tests in share mode when viewing an individual file"""
|
||||
self.run_all_share_mode_setup_tests(tab)
|
||||
@ -375,15 +389,27 @@ class TestShare(GuiBaseTest):
|
||||
self.close_all_tabs()
|
||||
|
||||
@pytest.mark.gui
|
||||
def test_clear_all_button(self):
|
||||
def test_clear_all_history_button(self):
|
||||
"""
|
||||
Test canceling a scheduled share
|
||||
Test clearing all history items
|
||||
"""
|
||||
tab = self.new_share_tab()
|
||||
tab.get_mode().autostop_sharing_checkbox.click()
|
||||
|
||||
self.run_all_common_setup_tests()
|
||||
self.run_all_clear_all_button_tests(tab)
|
||||
self.run_all_clear_all_history_button_tests(tab)
|
||||
|
||||
self.close_all_tabs()
|
||||
|
||||
@pytest.mark.gui
|
||||
def test_remove_all_file_selection_button(self):
|
||||
"""
|
||||
Test remove all file items at once
|
||||
"""
|
||||
tab = self.new_share_tab()
|
||||
|
||||
self.run_all_common_setup_tests()
|
||||
self.run_all_remove_all_file_selection_button_tests(tab)
|
||||
|
||||
self.close_all_tabs()
|
||||
|
||||
@ -561,6 +587,7 @@ class TestShare(GuiBaseTest):
|
||||
Rate limit should be triggered
|
||||
"""
|
||||
tab = self.new_share_tab()
|
||||
|
||||
def accept_dialog():
|
||||
window = tab.common.gui.qtapp.activeWindow()
|
||||
if window:
|
||||
@ -580,6 +607,7 @@ class TestShare(GuiBaseTest):
|
||||
Public mode should skip the rate limit
|
||||
"""
|
||||
tab = self.new_share_tab()
|
||||
|
||||
def accept_dialog():
|
||||
window = tab.common.gui.qtapp.activeWindow()
|
||||
if window:
|
||||
|
@ -138,18 +138,21 @@ class TestTabs(GuiBaseTest):
|
||||
self.gui.tabs.widget(1).share_button.click()
|
||||
self.assertFalse(self.gui.tabs.widget(1).new_tab.isVisible())
|
||||
self.assertTrue(self.gui.tabs.widget(1).share_mode.isVisible())
|
||||
self.assertEqual(self.gui.status_bar.server_status_label.text(), 'Ready to share')
|
||||
|
||||
# New tab, receive files
|
||||
self.gui.tabs.new_tab_button.click()
|
||||
self.gui.tabs.widget(2).receive_button.click()
|
||||
self.assertFalse(self.gui.tabs.widget(2).new_tab.isVisible())
|
||||
self.assertTrue(self.gui.tabs.widget(2).receive_mode.isVisible())
|
||||
self.assertEqual(self.gui.status_bar.server_status_label.text(), 'Ready to receive')
|
||||
|
||||
# New tab, publish website
|
||||
self.gui.tabs.new_tab_button.click()
|
||||
self.gui.tabs.widget(3).website_button.click()
|
||||
self.assertFalse(self.gui.tabs.widget(3).new_tab.isVisible())
|
||||
self.assertTrue(self.gui.tabs.widget(3).website_mode.isVisible())
|
||||
self.assertEqual(self.gui.status_bar.server_status_label.text(), 'Ready to share')
|
||||
|
||||
# Close tabs
|
||||
self.gui.tabs.tabBar().tabButton(0, QtWidgets.QTabBar.RightSide).click()
|
||||
|
@ -64,12 +64,13 @@ class TestWebsite(GuiBaseTest):
|
||||
"""Tests in website mode after starting a share"""
|
||||
self.server_working_on_start_button_pressed(tab)
|
||||
self.server_status_indicator_says_starting(tab)
|
||||
self.add_delete_buttons_hidden(tab)
|
||||
self.add_remove_buttons_hidden(tab)
|
||||
self.server_is_started(tab, startup_time)
|
||||
self.web_server_is_running(tab)
|
||||
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):
|
||||
|
Loading…
Reference in New Issue
Block a user