From 819e406d46529d83cae8cf7bbadf46f0396c5a53 Mon Sep 17 00:00:00 2001 From: Saptak S Date: Mon, 9 Mar 2020 17:44:00 +0530 Subject: [PATCH] Create GUI for onionshare chat mode with the tab workflow --- onionshare/web/chat_mode.py | 1 - onionshare_gui/gui_common.py | 1 + onionshare_gui/tab/mode/chat_mode/__init__.py | 133 ++++++++++++++++++ onionshare_gui/tab/server_status.py | 4 + onionshare_gui/tab/tab.py | 51 +++++++ share/locale/en.json | 5 + share/static/css/style.css | 20 +++ share/static/js/chat.js | 1 - 8 files changed, 214 insertions(+), 2 deletions(-) create mode 100644 onionshare_gui/tab/mode/chat_mode/__init__.py diff --git a/onionshare/web/chat_mode.py b/onionshare/web/chat_mode.py index c81bd636..45f6a8cd 100644 --- a/onionshare/web/chat_mode.py +++ b/onionshare/web/chat_mode.py @@ -38,7 +38,6 @@ class ChatModeWeb: history_id = self.cur_history_id self.cur_history_id += 1 self.web.add_request( - self.web.REQUEST_INDIVIDUAL_FILE_STARTED, request.path, {"id": history_id, "status_code": 200}, ) diff --git a/onionshare_gui/gui_common.py b/onionshare_gui/gui_common.py index 4381545e..87d0b915 100644 --- a/onionshare_gui/gui_common.py +++ b/onionshare_gui/gui_common.py @@ -31,6 +31,7 @@ class GuiCommon: MODE_SHARE = "share" MODE_RECEIVE = "receive" MODE_WEBSITE = "website" + MODE_CHAT = "chat" def __init__(self, common, qtapp, local_only): self.common = common diff --git a/onionshare_gui/tab/mode/chat_mode/__init__.py b/onionshare_gui/tab/mode/chat_mode/__init__.py new file mode 100644 index 00000000..52b61592 --- /dev/null +++ b/onionshare_gui/tab/mode/chat_mode/__init__.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" +import os +import random +import string + +from PyQt5 import QtCore, QtWidgets, QtGui + +from onionshare import strings +from onionshare.onion import * +from onionshare.common import Common +from onionshare.web import Web + +from .. import Mode +from ....widgets import MinimumWidthWidget + + +class ChatMode(Mode): + """ + Parts of the main window UI for sharing files. + """ + + success = QtCore.pyqtSignal() + error = QtCore.pyqtSignal(str) + + def init(self): + """ + Custom initialization for ChatMode. + """ + # Create the Web object + self.web = Web(self.common, True, self.settings, "chat") + + # Header + self.header_label.setText(strings._("gui_new_tab_chat_button")) + + # Server status + self.server_status.set_mode("chat") + self.server_status.server_started_finished.connect(self.update_primary_action) + self.server_status.server_stopped.connect(self.update_primary_action) + self.server_status.server_canceled.connect(self.update_primary_action) + # Tell server_status about web, then update + self.server_status.web = self.web + self.server_status.update() + + # Top bar + top_bar_layout = QtWidgets.QHBoxLayout() + top_bar_layout.addStretch() + + # Main layout + self.main_layout = QtWidgets.QVBoxLayout() + self.main_layout.addLayout(top_bar_layout) + self.main_layout.addWidget(self.primary_action) + self.main_layout.addStretch() + self.main_layout.addWidget(MinimumWidthWidget(700)) + + # Column layout + self.column_layout = QtWidgets.QHBoxLayout() + self.column_layout.addLayout(self.main_layout) + + # Wrapper layout + self.wrapper_layout = QtWidgets.QVBoxLayout() + self.wrapper_layout.addWidget(self.header_label) + self.wrapper_layout.addLayout(self.column_layout) + self.setLayout(self.wrapper_layout) + + def get_stop_server_autostop_timer_text(self): + """ + Return the string to put on the stop server button, if there's an auto-stop timer + """ + return strings._("gui_share_stop_server_autostop_timer") + + def autostop_timer_finished_should_stop_server(self): + """ + The auto-stop timer expired, should we stop the server? Returns a bool + """ + + self.server_status.stop_server() + self.server_status_label.setText(strings._("close_on_autostop_timer")) + return True + + def start_server_custom(self): + """ + Starting the server. + """ + # Reset web counters + self.web.chat_mode.cur_history_id = 0 + self.web.reset_invalid_passwords() + + def start_server_step2_custom(self): + """ + Step 2 in starting the server. Zipping up files. + """ + # Continue + self.starting_server_step3.emit() + self.start_server_finished.emit() + + def cancel_server_custom(self): + """ + Log that the server has been cancelled + """ + self.common.log("ChatMode", "cancel_server") + + def handle_tor_broke_custom(self): + """ + Connection to Tor broke. + """ + self.primary_action.hide() + + def on_reload_settings(self): + """ + We should be ok to re-enable the 'Start Receive Mode' button now. + """ + self.primary_action.show() + + def update_primary_action(self): + self.common.log("ChatMode", "update_primary_action") diff --git a/onionshare_gui/tab/server_status.py b/onionshare_gui/tab/server_status.py index 0fbc11b6..6815c474 100644 --- a/onionshare_gui/tab/server_status.py +++ b/onionshare_gui/tab/server_status.py @@ -250,6 +250,8 @@ class ServerStatus(QtWidgets.QWidget): self.server_button.setText(strings._("gui_share_start_server")) elif self.mode == self.common.gui.MODE_WEBSITE: self.server_button.setText(strings._("gui_share_start_server")) + elif self.mode == self.common.gui.MODE_CHAT: + self.server_button.setText(strings._("gui_chat_start_server")) else: self.server_button.setText(strings._("gui_receive_start_server")) self.server_button.setToolTip("") @@ -262,6 +264,8 @@ class ServerStatus(QtWidgets.QWidget): self.server_button.setText(strings._("gui_share_stop_server")) elif self.mode == self.common.gui.MODE_WEBSITE: self.server_button.setText(strings._("gui_share_stop_server")) + elif self.mode == self.common.gui.MODE_CHAT: + self.server_button.setText(strings._("gui_chat_stop_server")) else: self.server_button.setText(strings._("gui_receive_stop_server")) elif self.status == self.STATUS_WORKING: diff --git a/onionshare_gui/tab/tab.py b/onionshare_gui/tab/tab.py index aa4518b5..fdad4898 100644 --- a/onionshare_gui/tab/tab.py +++ b/onionshare_gui/tab/tab.py @@ -28,6 +28,7 @@ from onionshare.mode_settings import ModeSettings from .mode.share_mode import ShareMode from .mode.receive_mode import ReceiveMode from .mode.website_mode import WebsiteMode +from .mode.chat_mode import ChatMode from .server_status import ServerStatus @@ -93,6 +94,16 @@ class Tab(QtWidgets.QWidget): ) website_description.setWordWrap(True) + self.chat_button = QtWidgets.QPushButton( + strings._("gui_new_tab_chat_button") + ) + self.chat_button.setStyleSheet(self.common.gui.css["mode_new_tab_button"]) + self.chat_button.clicked.connect(self.chat_mode_clicked) + chat_description = QtWidgets.QLabel( + strings._("gui_new_tab_chat_description") + ) + chat_description.setWordWrap(True) + new_tab_layout = QtWidgets.QVBoxLayout() new_tab_layout.addStretch(1) new_tab_layout.addWidget(self.share_button) @@ -103,6 +114,9 @@ class Tab(QtWidgets.QWidget): new_tab_layout.addSpacing(50) new_tab_layout.addWidget(self.website_button) new_tab_layout.addWidget(website_description) + new_tab_layout.addSpacing(50) + new_tab_layout.addWidget(self.chat_button) + new_tab_layout.addWidget(chat_description) new_tab_layout.addStretch(3) new_tab_inner = QtWidgets.QWidget() @@ -278,6 +292,43 @@ class Tab(QtWidgets.QWidget): self.update_server_status_indicator() self.timer.start(500) + def chat_mode_clicked(self): + self.common.log("Tab", "chat_mode_clicked") + self.mode = self.common.gui.MODE_CHAT + self.new_tab.hide() + + self.chat_mode = ChatMode(self) + self.chat_mode.change_persistent.connect(self.change_persistent) + + self.layout.addWidget(self.chat_mode) + self.chat_mode.show() + + self.chat_mode.init() + self.chat_mode.server_status.server_started.connect( + self.update_server_status_indicator + ) + self.chat_mode.server_status.server_stopped.connect( + self.update_server_status_indicator + ) + self.chat_mode.start_server_finished.connect( + self.update_server_status_indicator + ) + self.chat_mode.stop_server_finished.connect( + self.update_server_status_indicator + ) + self.chat_mode.stop_server_finished.connect(self.stop_server_finished) + self.chat_mode.start_server_finished.connect(self.clear_message) + self.chat_mode.server_status.button_clicked.connect(self.clear_message) + self.chat_mode.server_status.url_copied.connect(self.copy_url) + self.chat_mode.server_status.hidservauth_copied.connect( + self.copy_hidservauth + ) + + self.change_title.emit(self.tab_id, strings._("gui_new_tab_chat_button")) + + self.update_server_status_indicator() + self.timer.start(500) + def update_server_status_indicator(self): # Set the status image if self.mode == self.common.gui.MODE_SHARE: diff --git a/share/locale/en.json b/share/locale/en.json index cca7d92e..d45f894d 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -16,6 +16,9 @@ "gui_share_start_server": "Start sharing", "gui_share_stop_server": "Stop sharing", "gui_share_stop_server_autostop_timer": "Stop Sharing ({})", + "gui_chat_stop_server_autostop_timer": "Stop Chat Server ({})", + "gui_chat_start_server": "Start chat server", + "gui_chat_stop_server": "Stop chat server", "gui_stop_server_autostop_timer_tooltip": "Auto-stop timer ends at {}", "gui_start_server_autostart_timer_tooltip": "Auto-start timer ends at {}", "gui_receive_start_server": "Start Receive Mode", @@ -182,6 +185,8 @@ "gui_new_tab_receive_description": "Turn your computer into an online dropbox. People will be able to use Tor Browser to send files to your computer.", "gui_new_tab_website_button": "Publish Website", "gui_new_tab_website_description": "Host a static HTML onion website from your computer.", + "gui_new_tab_chat_button": "Start Chat Server", + "gui_new_tab_chat_description": "Start an onion chat server and use it to chat in Tor Browser.", "gui_close_tab_warning_title": "Are you sure?", "gui_close_tab_warning_persistent_description": "This tab is persistent. If you close it you'll lose the onion address that it's using. Are you sure you want to close it?", "gui_close_tab_warning_share_description": "You're in the process of sending files. Are you sure you want to close this tab?", diff --git a/share/static/css/style.css b/share/static/css/style.css index af41b155..206fe45a 100644 --- a/share/static/css/style.css +++ b/share/static/css/style.css @@ -130,6 +130,26 @@ table.file-list td:last-child { width: 100%; } +.chat-wrapper { + display: flex; + flex-direction: column; + margin: 0 1rem; + height: calc(100vh - (45px + 2em)); +} + +.chat-wrapper #chat { + border: 1px solid rgba(0, 0, 0, 0.1); + border-radius: 2px; + flex: 1; + overflow: auto; + background: #f2f2f2; + padding: 0 1rem; +} + +.chat-wrapper .chat-form { + display: flex; +} + .upload-wrapper { align-items: center; justify-content: center; diff --git a/share/static/js/chat.js b/share/static/js/chat.js index 474f84c1..daf2e7d1 100644 --- a/share/static/js/chat.js +++ b/share/static/js/chat.js @@ -6,7 +6,6 @@ $(function(){ socket.emit('joined', {}); }); socket.on('status', function(data) { - console.log("received") $('#chat').append('

' + data.msg + '

'); $('#chat').scrollTop($('#chat')[0].scrollHeight); });