5 Commits

Author SHA1 Message Date
3546dbd75b some broken dialogs fixed 2017-05-08 23:58:23 +03:00
b081bff2ce some mainscreen widgets fixes 2017-04-16 22:56:31 +03:00
e257d8f96f signals && imports fixes 2017-04-13 19:22:46 +03:00
972a073cb9 missed translations fix, travis fix, updates 2017-04-11 21:38:00 +03:00
e4998cd5b5 pyqt5 initial commit 2017-04-11 21:10:03 +03:00
52 changed files with 2821 additions and 5225 deletions

3
.gitignore vendored
View File

@ -6,7 +6,6 @@ tests/tests
tests/libs tests/libs
tests/.cache tests/.cache
tests/__pycache__ tests/__pycache__
tests/avatars
toxygen/libs toxygen/libs
.idea .idea
*~ *~
@ -24,4 +23,4 @@ toxygen/__pycache__
html html
Toxygen.egg-info Toxygen.egg-info
*.tox *.tox
.cache

View File

@ -2,24 +2,16 @@ language: python
python: python:
- "3.5" - "3.5"
- "3.6" - "3.6"
os:
- linux
dist: trusty
notifications:
email: false
before_install: before_install:
- sudo apt-get update - sudo apt-get update
- sudo apt-get install -y checkinstall build-essential - sudo apt-get install -y checkinstall build-essential
- sudo apt-get install portaudio19-dev - sudo apt-get install portaudio19-dev
- sudo apt-get install libsecret-1-dev
- sudo apt-get install libconfig-dev libvpx-dev check -qq - sudo apt-get install libconfig-dev libvpx-dev check -qq
- sudo apt-get install -y python3-pyqt5
install: install:
- pip install sip
- pip install pyqt5
- pip install pyaudio - pip install pyaudio
- pip install opencv-python
before_script: before_script:
# Opus # OPUS
- wget http://downloads.xiph.org/releases/opus/opus-1.0.3.tar.gz - wget http://downloads.xiph.org/releases/opus/opus-1.0.3.tar.gz
- tar xzf opus-1.0.3.tar.gz - tar xzf opus-1.0.3.tar.gz
- cd opus-1.0.3 - cd opus-1.0.3

View File

@ -12,8 +12,9 @@ include toxygen/smileys/starwars/*.png
include toxygen/smileys/starwars/config.json include toxygen/smileys/starwars/config.json
include toxygen/smileys/ksk/*.png include toxygen/smileys/ksk/*.png
include toxygen/smileys/ksk/config.json include toxygen/smileys/ksk/config.json
include toxygen/styles/*.qss include toxygen/styles/style.qss
include toxygen/translations/*.qm include toxygen/translations/*.qm
include toxygen/libs/libtox.dll include toxygen/libs/libtox.dll
include toxygen/libs/libsodium.a include toxygen/libs/libsodium.a
include toxygen/nodes.json include toxygen/libs/libtox64.dll
include toxygen/libs/libsodium64.a

View File

@ -10,39 +10,41 @@ Toxygen is powerful cross-platform [Tox](https://tox.chat/) client written in pu
### [Install](/docs/install.md) - [Contribute](/docs/contributing.md) - [Plugins](/docs/plugins.md) - [Compile](/docs/compile.md) - [Contact](/docs/contact.md) - [Updater](https://github.com/toxygen-project/toxygen_updater) ### [Install](/docs/install.md) - [Contribute](/docs/contributing.md) - [Plugins](/docs/plugins.md) - [Compile](/docs/compile.md) - [Contact](/docs/contact.md) - [Updater](https://github.com/toxygen-project/toxygen_updater)
### Supported OS: Linux and Windows ### Supported OS:
![Linux, Windows and OS X](/docs/os.png)
### Features: ### Features:
- 1v1 messages - [x] 1v1 messages
- File transfers - [x] File transfers
- Audio calls - [x] Audio calls
- Video calls - [x] Plugins support
- Group chats - [x] Chat history
- Plugins support - [x] Emoticons
- Desktop sharing - [x] Stickers
- Chat history - [x] Screenshots
- Emoticons - [x] Name lookups (toxme.io support)
- Stickers - [x] Save file encryption
- Screenshots - [x] Profile import and export
- Name lookups (toxme.io support) - [x] Faux offline messaging
- Save file encryption - [x] Faux offline file transfers
- Profile import and export - [x] Inline images
- Faux offline messaging - [x] Message splitting
- Faux offline file transfers - [x] Proxy support
- Inline images - [x] Avatars
- Message splitting - [x] Multiprofile
- Proxy support - [x] Multilingual
- Avatars - [x] Sound notifications
- Multiprofile - [x] Contact aliases
- Multilingual - [x] Contact blocking
- Sound notifications - [x] Typing notifications
- Contact aliases - [x] Changing nospam
- Contact blocking - [x] File resuming
- Typing notifications - [x] Read receipts
- Changing nospam - [ ] Video calls
- File resuming - [ ] Desktop sharing
- Read receipts - [ ] Group chats
### Downloads ### Downloads
[Releases](https://github.com/toxygen-project/toxygen/releases) [Releases](https://github.com/toxygen-project/toxygen/releases)

View File

@ -1,22 +1,20 @@
# Issues #Issues
Help us find all bugs in Toxygen! Please provide following info: Help us find all bugs in Toxygen! Please provide following info:
- OS - OS
- Toxygen version - Toxygen version
- Toxygen executable info - python executable (.py), precompiled binary, from package etc. - Toxygen executable info - .py or precompiled binary, how was it installed in system
- Steps to reproduce the bug - Steps to reproduce the bug
Want to see new feature in Toxygen? [Ask for it!](https://github.com/toxygen-project/toxygen/issues) Want to see new feature in Toxygen? [Ask for it!](https://github.com/toxygen-project/toxygen/issues)
# Pull requests #Pull requests
Developer? Feel free to open pull request. Our dev team is small so we glad to get help. Developer? Feel free to open pull request. Our dev team is small so we glad to get help.
Don't know what to do? Improve UI, fix [issues](https://github.com/toxygen-project/toxygen/issues) or implement features from our TODO list. Don't know what to do? Improve UI, fix [issues](https://github.com/toxygen-project/toxygen/issues) or implement features from our TODO list.
You can find our TODO's in code, issues list and [here](/README.md). Also you can implement [plugins](/docs/plugins.md) for Toxygen. You can find our TODO's in code, issues list and [here](/README.md). Also you can implement [plugins](/docs/plugins.md) for Toxygen.
Note that we have a lot of branches for different purposes. Master branch is for stable versions (releases) only, so I recommend to open PR's to develop branch. Development of next Toxygen version usually goes there. Other branches used for implementing different tasks such as file transfers improvements or audio calls implementation etc. #Translations
# Translations Help us translate Toxygen! Translation can be created using pyside-lupdate (``pyside-lupdate toxygen.pro``) and QT Linguist.
Help us translate Toxygen! Translation can be created using pylupdate (``pylupdate5 toxygen.pro``) and QT Linguist.

View File

@ -1,13 +1,13 @@
# How to install Toxygen # How to install Toxygen
## Use precompiled binary (recommended for users): ## Use precompiled binary:
[Check our releases page](https://github.com/toxygen-project/toxygen/releases) [Check our releases page](https://github.com/toxygen-project/toxygen/releases)
## Using pip3 ## Using pip3
### Windows ### Windows
``pip install toxygen`` ``pip3.4 install toxygen``
Run app using ``toxygen`` command. Run app using ``toxygen`` command.
@ -16,11 +16,19 @@ Run app using ``toxygen`` command.
1. Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) with toxav support in your system (install in /usr/lib/) 1. Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) with toxav support in your system (install in /usr/lib/)
2. Install PortAudio: 2. Install PortAudio:
``sudo apt-get install portaudio19-dev`` ``sudo apt-get install portaudio19-dev``
3. For 32-bit Linux install PyQt5: ``sudo apt-get install python3-pyqt5`` 3. Install PySide: ``sudo apt-get install python3-pyside``
4. Install [OpenCV](http://docs.opencv.org/trunk/d7/d9f/tutorial_linux_install.html) or via ``sudo pip3 install opencv-python`` 4. Install toxygen:
5. Install toxygen: ``sudo pip3.4 install toxygen``
``sudo pip3 install toxygen`` 5. Run toxygen using ``toxygen`` command.
6. Run toxygen using ``toxygen`` command.
### OS X
1. Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) with toxav support in your system
2. Install PortAudio:
``brew install portaudio``
3. Install toxygen:
``pip3.4 install toxygen``
4. Run toxygen using ``toxygen`` command.
## Packages ## Packages
@ -32,19 +40,15 @@ Debian/Ubuntu: [tox.chat](https://tox.chat/download.html#gnulinux)
### Windows ### Windows
Note: 32-bit Python isn't supported due to bug with videocalls. It is strictly recommended to use 64-bit Python. 1. [Download and install latest Python 3.4](https://www.python.org/downloads/windows/)
2. [Install PySide](https://pypi.python.org/pypi/PySide/1.2.4#installing-pyside-on-a-windows-system) (recommended) or [PyQt4](https://riverbankcomputing.com/software/pyqt/download)
3. Install PyAudio: ``pip3.4 install pyaudio``
4. [Download toxygen](https://github.com/xveduk/toxygen/archive/master.zip)
5. Unpack archive
6. Download latest libtox.dll build, download latest libsodium.a build, put it into \src\libs\
7. Run \toxygen\main.py.
1. [Download and install latest Python 3 64-bit](https://www.python.org/downloads/windows/) Optional: install toxygen using setup.py: ``python3.4 setup.py install``
2. Install PyQt5: ``pip install pyqt5``
3. Install PyAudio: ``pip install pyaudio``
4. Install numpy: ``pip install numpy``
5. Install OpenCV: ``pip install opencv-python``
6. [Download toxygen](https://github.com/toxygen-project/toxygen/archive/master.zip)
7. Unpack archive
8. Download latest libtox.dll build, download latest libsodium.a build, put it into \toxygen\libs\
9. Run \toxygen\main.py.
Optional: install toxygen using setup.py: ``python setup.py install``
[libtox.dll for 32-bit Python](https://build.tox.chat/view/libtoxcore/job/libtoxcore_build_windows_x86_shared_release/lastSuccessfulBuild/artifact/libtoxcore_build_windows_x86_shared_release.zip) [libtox.dll for 32-bit Python](https://build.tox.chat/view/libtoxcore/job/libtoxcore_build_windows_x86_shared_release/lastSuccessfulBuild/artifact/libtoxcore_build_windows_x86_shared_release.zip)
@ -58,15 +62,27 @@ Optional: install toxygen using setup.py: ``python setup.py install``
1. Install latest Python3: 1. Install latest Python3:
``sudo apt-get install python3`` ``sudo apt-get install python3``
2. Install PyQt5: ``sudo apt-get install python3-pyqt5`` or ``sudo pip3 install pyqt5`` 2. Install PySide: ``sudo apt-get install python3-pyside`` or install [PyQt4](https://riverbankcomputing.com/software/pyqt/download) (``sudo apt-get install python3-pyqt4``).
3. Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) with toxav support in your system (install in /usr/lib/) 3. Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) with toxav support in your system (install in /usr/lib/)
4. Install PyAudio: 4. Install PyAudio:
``sudo apt-get install portaudio19-dev`` and ``sudo apt-get install python3-pyaudio`` (or ``sudo pip3 install pyaudio``) ``sudo apt-get install portaudio19-dev`` and ``sudo apt-get install python3-pyaudio`` (or ``pip3 install pyaudio``)
5. Install NumPy: ``sudo pip3 install numpy`` 5. [Download toxygen](https://github.com/xveduk/toxygen/archive/master.zip)
6. Install [OpenCV](http://docs.opencv.org/trunk/d7/d9f/tutorial_linux_install.html) or via ``sudo pip3 install opencv-python`` 6. Unpack archive
7. [Download toxygen](https://github.com/toxygen-project/toxygen/archive/master.zip) 7. Run app:
8. Unpack archive ``python3.4 main.py``
9. Run app:
``python3 main.py`` Optional: install toxygen using setup.py: ``python3.4 setup.py install``
### OS X
1. [Download and install latest Python 3.4](https://www.python.org/downloads/mac-osx/)
2. [Install PySide](https://pypi.python.org/pypi/PySide/1.2.4#installing-pyside-on-a-mac-os-x-system) (recommended) or [PyQt4](https://riverbankcomputing.com/software/pyqt/download)
3. Install PortAudio:
``brew install portaudio``
4. Install PyAudio: ``pip3 install pyaudio``
5. Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) with toxav support in your system
6. [Download toxygen](https://github.com/xveduk/toxygen/archive/master.zip)
7. Unpack archive
8. Run \toxygen\main.py.
Optional: install toxygen using setup.py: ``python3 setup.py install`` Optional: install toxygen using setup.py: ``python3 setup.py install``

BIN
docs/os.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -1,6 +1,6 @@
# Plugins API # Plugins API
In Toxygen plugin is single python (supported Python 3.4 - 3.6) module (.py file) and directory with data associated with it. In Toxygen plugin is single python (supported Python 3.0 - 3.4) module (.py file) and directory with data associated with it.
Every module must contain one class derived from PluginSuperClass defined in [plugin_super_class.py](/src/plugins/plugin_super_class.py). Instance of this class will be created by PluginLoader class (defined in [plugin_support.py](/src/plugin_support.py) ). This class can enable/disable plugins and send data to it. Every module must contain one class derived from PluginSuperClass defined in [plugin_super_class.py](/src/plugins/plugin_super_class.py). Instance of this class will be created by PluginLoader class (defined in [plugin_support.py](/src/plugin_support.py) ). This class can enable/disable plugins and send data to it.
Every plugin has its own full name and unique short name (1-5 symbols). Main app can get it using special methods. Every plugin has its own full name and unique short name (1-5 symbols). Main app can get it using special methods.
@ -45,7 +45,7 @@ Import statement will not work in case you import module that wasn't previously
About GUI: About GUI:
GUI is available via PyQt5. Plugin can have no GUI at all. It's strictly recommended to support both PySide and PyQt4 in GUI. Plugin can have no GUI at all.
Exceptions: Exceptions:

View File

@ -1,6 +1,6 @@
# Plugins # Plugins
Toxygen is the first [Tox](https://tox.chat/) client with plugins support. Plugin is Python 3.5 - 3.6 module (.py file) and directory with plugin's data which provide some additional functionality. Toxygen is the first [Tox](https://tox.chat/) client with plugins support. Plugin is Python 3.4 module (.py file) and directory with plugin's data which provide some additional functionality.
# How to write plugin # How to write plugin

View File

@ -1,4 +1,4 @@
# Smileys #Smileys
Toxygen support smileys. Smiley is small picture which replaces some symbol or combination of symbols. If you want to create your own smiley pack, create directory in src/smileys/. This directory must contain images with smileys and config.json. Example of config.json: Toxygen support smileys. Smiley is small picture which replaces some symbol or combination of symbols. If you want to create your own smiley pack, create directory in src/smileys/. This directory must contain images with smileys and config.json. Example of config.json:
@ -6,8 +6,8 @@ Toxygen support smileys. Smiley is small picture which replaces some symbol or c
Animated smileys (.gif) are supported too. Animated smileys (.gif) are supported too.
# Stickers #Stickers
Sticker is inline image. If you want to create your own sticker pack, create directory in src/stickers/ and place your stickers there. Sticker is inline image. If you want to create your own smiley pack, create directory in src/stickers/ and place your stickers there.
Users can import smileys and stickers using menu: Settings -> Interface Users can import smileys and stickers using menu: Settings -> Interface

View File

@ -8,27 +8,15 @@ import sys
version = program_version + '.0' version = program_version + '.0'
MODULES = []
if system() == 'Windows': if system() in ('Windows', 'Darwin'):
MODULES = ['PyQt5', 'PyAudio', 'numpy', 'opencv-python'] MODULES = ['PyAudio', 'PyQt5']
else: else:
MODULES = []
try: try:
import pyaudio import pyaudio
except ImportError: except ImportError:
MODULES.append('PyAudio') MODULES = ['PyAudio']
try:
import PyQt5
except ImportError:
MODULES.append('PyQt5')
try:
import numpy
except ImportError:
MODULES.append('numpy')
try:
import cv2
except ImportError:
MODULES.append('opencv-python')
class InstallScript(install): class InstallScript(install):
@ -37,7 +25,9 @@ class InstallScript(install):
def run(self): def run(self):
install.run(self) install.run(self)
try: try:
if system() != 'Windows': if system() == 'Windows':
call(["toxygen", "--configure"])
else:
call(["toxygen", "--clean"]) call(["toxygen", "--clean"])
except: except:
try: try:
@ -47,12 +37,13 @@ class InstallScript(install):
if path[-1] not in ('/', '\\'): if path[-1] not in ('/', '\\'):
path += '/' path += '/'
path += 'bin/toxygen' path += 'bin/toxygen'
if system() != 'Windows': if system() == 'Windows':
call([path, "--configure"])
else:
call([path, "--clean"]) call([path, "--clean"])
except: except:
pass pass
setup(name='Toxygen', setup(name='Toxygen',
version=version, version=version,
description='Toxygen - Tox client', description='Toxygen - Tox client',
@ -75,4 +66,5 @@ setup(name='Toxygen',
}, },
cmdclass={ cmdclass={
'install': InstallScript, 'install': InstallScript,
}) },
)

View File

@ -19,7 +19,6 @@ class IncomingCallWidget(widgets.CenteredWidget):
self.avatar_label.setScaledContents(False) self.avatar_label.setScaledContents(False)
self.name = widgets.DataLabel(self) self.name = widgets.DataLabel(self)
self.name.setGeometry(QtCore.QRect(90, 20, 300, 25)) self.name.setGeometry(QtCore.QRect(90, 20, 300, 25))
self._friend_number = friend_number
font = QtGui.QFont() font = QtGui.QFont()
font.setFamily(settings.Settings.get_instance()['font']) font.setFamily(settings.Settings.get_instance()['font'])
font.setPointSize(16) font.setPointSize(16)
@ -52,10 +51,10 @@ class IncomingCallWidget(widgets.CenteredWidget):
self.setWindowTitle(text) self.setWindowTitle(text)
self.name.setText(name) self.name.setText(name)
self.call_type.setText(text) self.call_type.setText(text)
self._processing = False pr = profile.Profile.get_instance()
self.accept_audio.clicked.connect(self.accept_call_with_audio) self.accept_audio.clicked.connect(lambda: pr.accept_call(friend_number, True, False) or self.stop())
self.accept_video.clicked.connect(self.accept_call_with_video) # self.accept_video.clicked.connect(lambda: pr.start_call(friend_number, True, True))
self.decline.clicked.connect(self.decline_call) self.decline.clicked.connect(lambda: pr.stop_call(friend_number, False) or self.stop())
class SoundPlay(QtCore.QThread): class SoundPlay(QtCore.QThread):
@ -106,29 +105,31 @@ class IncomingCallWidget(widgets.CenteredWidget):
self.thread.wait() self.thread.wait()
self.close() self.close()
def accept_call_with_audio(self):
if self._processing:
return
self._processing = True
pr = profile.Profile.get_instance()
pr.accept_call(self._friend_number, True, False)
self.stop()
def accept_call_with_video(self):
if self._processing:
return
self._processing = True
pr = profile.Profile.get_instance()
pr.accept_call(self._friend_number, True, True)
self.stop()
def decline_call(self):
if self._processing:
return
self._processing = True
pr = profile.Profile.get_instance()
pr.stop_call(self._friend_number, False)
self.stop()
def set_pixmap(self, pixmap): def set_pixmap(self, pixmap):
self.avatar_label.setPixmap(pixmap) self.avatar_label.setPixmap(pixmap)
class AudioMessageRecorder(widgets.CenteredWidget):
def __init__(self, friend_number, name):
super(AudioMessageRecorder, self).__init__()
self.label = QtWidgets.QLabel(self)
self.label.setGeometry(QtCore.QRect(10, 20, 250, 20))
text = QtWidgets.QApplication.translate("MenuWindow", "Send audio message to friend {}")
self.label.setText(text.format(name))
self.record = QtWidgets.QPushButton(self)
self.record.setGeometry(QtCore.QRect(20, 100, 150, 150))
self.record.setText(QtWidgets.QApplication.translate("MenuWindow", "Start recording"))
self.record.clicked.connect(self.start_or_stop_recording)
self.recording = False
self.friend_num = friend_number
def start_or_stop_recording(self):
if not self.recording:
self.recording = True
self.record.setText(QtWidgets.QApplication.translate("MenuWindow", "Stop recording"))
else:
self.close()

View File

@ -1,75 +1,84 @@
import random import random
import urllib.request
from util import log, curr_directory
import settings
from PyQt5 import QtNetwork, QtCore
import json
class Node: class Node:
def __init__(self, node): def __init__(self, ip, port, tox_key, rand):
self._ip, self._port, self._tox_key = node['ipv4'], node['port'], node['public_key'] self._ip, self._port, self._tox_key, self.rand = ip, port, tox_key, rand
self._priority = random.randint(1, 1000000) if node['status_tcp'] and node['status_udp'] else 0
def get_priority(self):
return self._priority
priority = property(get_priority)
def get_data(self): def get_data(self):
return bytes(self._ip, 'utf-8'), self._port, self._tox_key return bytes(self._ip, 'utf-8'), self._port, self._tox_key
def generate_nodes(): def node_generator():
with open(curr_directory() + '/nodes.json', 'rt') as fl: nodes = []
json_nodes = json.loads(fl.read())['nodes'] ips = [
nodes = map(lambda json_node: Node(json_node), json_nodes) "144.76.60.215", "23.226.230.47", "195.154.119.113", "biribiri.org",
sorted_nodes = sorted(nodes, key=lambda x: x.priority)[-4:] "46.38.239.179", "178.62.250.138", "130.133.110.14", "104.167.101.29",
for node in sorted_nodes: "205.185.116.116", "198.98.51.198", "80.232.246.79", "108.61.165.198",
yield node.get_data() "212.71.252.109", "194.249.212.109", "185.25.116.107", "192.99.168.140",
"46.101.197.175", "95.215.46.114", "5.189.176.217", "148.251.23.146",
"104.223.122.15", "78.47.114.252", "d4rk4.ru", "81.4.110.149",
"95.31.20.151", "104.233.104.126", "51.254.84.212", "home.vikingmakt.com.br",
"5.135.59.163", "185.58.206.164", "188.244.38.183", "mrflibble.c4.ee",
"82.211.31.116", "128.199.199.197", "103.230.156.174", "91.121.66.124",
"92.54.84.70", "tox1.privacydragon.me"
]
ports = [
33445, 33445, 33445, 33445,
33445, 33445, 33445, 33445,
33445, 33445, 33445, 33445,
33445, 33445, 33445, 33445,
443, 33445, 5190, 2306,
33445, 33445, 1813, 33445,
33445, 33445, 33445, 33445,
33445, 33445, 33445, 33445,
33445, 33445, 33445, 33445,
33445, 33445
]
ids = [
"04119E835DF3E78BACF0F84235B300546AF8B936F035185E2A8E9E0A67C8924F",
"A09162D68618E742FFBCA1C2C70385E6679604B2D80EA6E84AD0996A1AC8A074",
"E398A69646B8CEACA9F0B84F553726C1C49270558C57DF5F3C368F05A7D71354",
"F404ABAA1C99A9D37D61AB54898F56793E1DEF8BD46B1038B9D822E8460FAB67",
"F5A1A38EFB6BD3C2C8AF8B10D85F0F89E931704D349F1D0720C3C4059AF2440A",
"788236D34978D1D5BD822F0A5BEBD2C53C64CC31CD3149350EE27D4D9A2F9B6B",
"461FA3776EF0FA655F1A05477DF1B3B614F7D6B124F7DB1DD4FE3C08B03B640F",
"5918AC3C06955962A75AD7DF4F80A5D7C34F7DB9E1498D2E0495DE35B3FE8A57",
"A179B09749AC826FF01F37A9613F6B57118AE014D4196A0E1105A98F93A54702",
"1D5A5F2F5D6233058BF0259B09622FB40B482E4FA0931EB8FD3AB8E7BF7DAF6F",
"CF6CECA0A14A31717CC8501DA51BE27742B70746956E6676FF423A529F91ED5D",
"8E7D0B859922EF569298B4D261A8CCB5FEA14FB91ED412A7603A585A25698832",
"C4CEB8C7AC607C6B374E2E782B3C00EA3A63B80D4910B8649CCACDD19F260819",
"3CEE1F054081E7A011234883BC4FC39F661A55B73637A5AC293DDF1251D9432B",
"DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43",
"6A4D0607A296838434A6A7DDF99F50EF9D60A2C510BBF31FE538A25CB6B4652F",
"CD133B521159541FB1D326DE9850F5E56A6C724B5B8E5EB5CD8D950408E95707",
"5823FB947FF24CF83DDFAC3F3BAA18F96EA2018B16CC08429CB97FA502F40C23",
"2B2137E094F743AC8BD44652C55F41DFACC502F125E99E4FE24D40537489E32F",
"7AED21F94D82B05774F697B209628CD5A9AD17E0C073D9329076A4C28ED28147",
"0FB96EEBFB1650DDB52E70CF773DDFCABE25A95CC3BB50FC251082E4B63EF82A",
"1C5293AEF2114717547B39DA8EA6F1E331E5E358B35F9B6B5F19317911C5F976",
"53737F6D47FA6BD2808F378E339AF45BF86F39B64E79D6D491C53A1D522E7039",
"9E7BD4793FFECA7F32238FA2361040C09025ED3333744483CA6F3039BFF0211E",
"9CA69BB74DE7C056D1CC6B16AB8A0A38725C0349D187D8996766958584D39340",
"EDEE8F2E839A57820DE3DA4156D88350E53D4161447068A3457EE8F59F362414",
"AEC204B9A4501412D5F0BB67D9C81B5DB3EE6ADA64122D32A3E9B093D544327D",
"188E072676404ED833A4E947DC1D223DF8EFEBE5F5258573A236573688FB9761",
"2D320F971EF2CA18004416C2AAE7BA52BF7949DB34EA8E2E21AF67BD367BE211",
"24156472041E5F220D1FA11D9DF32F7AD697D59845701CDD7BE7D1785EB9DB39",
"15A0F9684E2423F9F46CFA5A50B562AE42525580D840CC50E518192BF333EE38",
"FAAB17014F42F7F20949F61E55F66A73C230876812A9737F5F6D2DCE4D9E4207",
"AF97B76392A6474AF2FD269220FDCF4127D86A42EF3A242DF53A40A268A2CD7C",
"B05C8869DBB4EDDD308F43C1A974A20A725A36EACCA123862FDE9945BF9D3E09",
"5C4C7A60183D668E5BD8B3780D1288203E2F1BAE4EEF03278019E21F86174C1D",
"4E3F7D37295664BBD0741B6DBCB6431D6CD77FC4105338C2FC31567BF5C8224A",
"5625A62618CB4FCA70E147A71B29695F38CC65FF0CBD68AD46254585BE564802",
"31910C0497D347FF160D6F3A6C0E317BAFA71E8E03BC4CBB2A185C9D4FB8B31E"
]
for i in range(len(ips)):
nodes.append(Node(ips[i], ports[i], ids[i], random.randint(0, 1000000)))
arr = sorted(nodes, key=lambda x: x.rand)[:4]
for elem in arr:
yield elem.get_data()
def save_nodes(nodes):
if not nodes:
return
print('Saving nodes...')
with open(curr_directory() + '/nodes.json', 'wb') as fl:
fl.write(nodes)
def download_nodes_list():
url = 'https://nodes.tox.chat/json'
s = settings.Settings.get_instance()
if not s['download_nodes_list']:
return
if not s['proxy_type']: # no proxy
try:
req = urllib.request.Request(url)
req.add_header('Content-Type', 'application/json')
response = urllib.request.urlopen(req)
result = response.read()
save_nodes(result)
except Exception as ex:
log('TOX nodes loading error: ' + str(ex))
else: # proxy
netman = QtNetwork.QNetworkAccessManager()
proxy = QtNetwork.QNetworkProxy()
proxy.setType(
QtNetwork.QNetworkProxy.Socks5Proxy if s['proxy_type'] == 2 else QtNetwork.QNetworkProxy.HttpProxy)
proxy.setHostName(s['proxy_host'])
proxy.setPort(s['proxy_port'])
netman.setProxy(proxy)
try:
request = QtNetwork.QNetworkRequest()
request.setUrl(QtCore.QUrl(url))
reply = netman.get(request)
while not reply.isFinished():
QtCore.QThread.msleep(1)
QtCore.QCoreApplication.processEvents()
data = bytes(reply.readAll().data())
save_nodes(data)
except Exception as ex:
log('TOX nodes loading error: ' + str(ex))

View File

@ -9,8 +9,7 @@ from plugin_support import PluginLoader
import queue import queue
import threading import threading
import util import util
import cv2
import numpy as np
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
# Threads # Threads
@ -33,7 +32,6 @@ class Invoker(QtCore.QObject):
event.fn(*event.args, **event.kwargs) event.fn(*event.args, **event.kwargs)
return True return True
_invoker = Invoker() _invoker = Invoker()
@ -67,7 +65,6 @@ class FileTransfersThread(threading.Thread):
except Exception as ex: except Exception as ex:
util.log('Exception in _thread: ' + str(ex)) util.log('Exception in _thread: ' + str(ex))
_thread = FileTransfersThread() _thread = FileTransfersThread()
@ -315,115 +312,11 @@ def callback_audio(toxav, friend_number, samples, audio_samples_per_channel, aud
""" """
New audio chunk New audio chunk
""" """
Profile.get_instance().call.audio_chunk( Profile.get_instance().call.chunk(
bytes(samples[:audio_samples_per_channel * 2 * audio_channels_count]), bytes(samples[:audio_samples_per_channel * 2 * audio_channels_count]),
audio_channels_count, audio_channels_count,
rate) rate)
# -----------------------------------------------------------------------------------------------------------------
# Callbacks - video
# -----------------------------------------------------------------------------------------------------------------
def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, ustride, vstride, user_data):
"""
Creates yuv frame from y, u, v and shows it using OpenCV
For yuv => bgr we need this YUV420 frame:
width
-------------------------
| |
| Y | height
| |
-------------------------
| | |
| U even | U odd | height // 4
| | |
-------------------------
| | |
| V even | V odd | height // 4
| | |
-------------------------
width // 2 width // 2
It can be created from initial y, u, v using slices
"""
try:
y_size = abs(max(width, abs(ystride)))
u_size = abs(max(width // 2, abs(ustride)))
v_size = abs(max(width // 2, abs(vstride)))
y = np.asarray(y[:y_size * height], dtype=np.uint8).reshape(height, y_size)
u = np.asarray(u[:u_size * height // 2], dtype=np.uint8).reshape(height // 2, u_size)
v = np.asarray(v[:v_size * height // 2], dtype=np.uint8).reshape(height // 2, v_size)
width -= width % 4
height -= height % 4
frame = np.zeros((int(height * 1.5), width), dtype=np.uint8)
frame[:height, :] = y[:height, :width]
frame[height:height * 5 // 4, :width // 2] = u[:height // 2:2, :width // 2]
frame[height:height * 5 // 4, width // 2:] = u[1:height // 2:2, :width // 2]
frame[height * 5 // 4:, :width // 2] = v[:height // 2:2, :width // 2]
frame[height * 5 // 4:, width // 2:] = v[1:height // 2:2, :width // 2]
frame = cv2.cvtColor(frame, cv2.COLOR_YUV2BGR_I420)
invoke_in_main_thread(cv2.imshow, str(friend_number), frame)
except Exception as ex:
print(ex)
# -----------------------------------------------------------------------------------------------------------------
# Callbacks - groups
# -----------------------------------------------------------------------------------------------------------------
def group_invite(tox, friend_number, gc_type, data, length, user_data):
invoke_in_main_thread(Profile.get_instance().group_invite, friend_number, gc_type,
bytes(data[:length]))
def show_gc_notification(window, tray, message, group_number, peer_number):
profile = Profile.get_instance()
settings = Settings.get_instance()
chat = profile.get_group_by_number(group_number)
peer_name = chat.get_peer_name(peer_number)
if not window.isActiveWindow() and (profile.name in message or settings['group_notifications']):
if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and not settings.locked:
invoke_in_main_thread(tray_notification, chat.name + ' ' + peer_name, message, tray, window)
if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
sound_notification(SOUND_NOTIFICATION['MESSAGE'])
invoke_in_main_thread(tray.setIcon, QtGui.QIcon(curr_directory() + '/images/icon_new_messages.png'))
def group_message(window, tray):
def wrapped(tox, group_number, peer_number, message, length, user_data):
message = str(message[:length], 'utf-8')
invoke_in_main_thread(Profile.get_instance().new_gc_message, group_number,
peer_number, TOX_MESSAGE_TYPE['NORMAL'], message)
show_gc_notification(window, tray, message, group_number, peer_number)
return wrapped
def group_action(window, tray):
def wrapped(tox, group_number, peer_number, message, length, user_data):
message = str(message[:length], 'utf-8')
invoke_in_main_thread(Profile.get_instance().new_gc_message, group_number,
peer_number, TOX_MESSAGE_TYPE['ACTION'], message)
show_gc_notification(window, tray, message, group_number, peer_number)
return wrapped
def group_title(tox, group_number, peer_number, title, length, user_data):
invoke_in_main_thread(Profile.get_instance().new_gc_title, group_number,
title[:length])
def group_namelist_change(tox, group_number, peer_number, change, user_data):
invoke_in_main_thread(Profile.get_instance().update_gc, group_number)
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
# Callbacks - initialization # Callbacks - initialization
@ -457,13 +350,7 @@ def init_callbacks(tox, window, tray):
toxav.callback_call_state(call_state, 0) toxav.callback_call_state(call_state, 0)
toxav.callback_call(call, 0) toxav.callback_call(call, 0)
toxav.callback_audio_receive_frame(callback_audio, 0) toxav.callback_audio_receive_frame(callback_audio, 0)
toxav.callback_video_receive_frame(video_receive_frame, 0)
tox.callback_friend_lossless_packet(lossless_packet, 0) tox.callback_friend_lossless_packet(lossless_packet, 0)
tox.callback_friend_lossy_packet(lossy_packet, 0) tox.callback_friend_lossy_packet(lossy_packet, 0)
tox.callback_group_invite(group_invite)
tox.callback_group_message(group_message(window, tray))
tox.callback_group_action(group_action(window, tray))
tox.callback_group_title(group_title)
tox.callback_group_namelist_change(group_namelist_change)

View File

@ -3,69 +3,14 @@ import time
import threading import threading
import settings import settings
from toxav_enums import * from toxav_enums import *
import cv2 # TODO: play sound until outgoing call will be started or cancelled and add timeout
import itertools # TODO: add widget for call
import numpy as np
import screen_sharing
# TODO: play sound until outgoing call will be started or cancelled
CALL_TYPE = {
class Call: 'NONE': 0,
'AUDIO': 1,
def __init__(self, out_audio, out_video, in_audio=False, in_video=False): 'VIDEO': 2
self._in_audio = in_audio }
self._in_video = in_video
self._out_audio = out_audio
self._out_video = out_video
self._is_active = False
def get_is_active(self):
return self._is_active
def set_is_active(self, value):
self._is_active = value
is_active = property(get_is_active, set_is_active)
# -----------------------------------------------------------------------------------------------------------------
# Audio
# -----------------------------------------------------------------------------------------------------------------
def get_in_audio(self):
return self._in_audio
def set_in_audio(self, value):
self._in_audio = value
in_audio = property(get_in_audio, set_in_audio)
def get_out_audio(self):
return self._out_audio
def set_out_audio(self, value):
self._out_audio = value
out_audio = property(get_out_audio, set_out_audio)
# -----------------------------------------------------------------------------------------------------------------
# Video
# -----------------------------------------------------------------------------------------------------------------
def get_in_video(self):
return self._in_video
def set_in_video(self, value):
self._in_video = value
in_video = property(get_in_video, set_in_video)
def get_out_video(self):
return self._out_video
def set_out_video(self, value):
self._out_video = value
out_video = property(get_out_video, set_out_video)
class AV: class AV:
@ -74,7 +19,7 @@ class AV:
self._toxav = toxav self._toxav = toxav
self._running = True self._running = True
self._calls = {} # dict: key - friend number, value - Call instance self._calls = {} # dict: key - friend number, value - call type
self._audio = None self._audio = None
self._audio_stream = None self._audio_stream = None
@ -87,78 +32,27 @@ class AV:
self._audio_duration = 60 self._audio_duration = 60
self._audio_sample_count = self._audio_rate * self._audio_channels * self._audio_duration // 1000 self._audio_sample_count = self._audio_rate * self._audio_channels * self._audio_duration // 1000
self._video = None
self._video_thread = None
self._video_running = False
self._video_width = 640
self._video_height = 480
def stop(self):
self._running = False
self.stop_audio_thread()
self.stop_video_thread()
def __contains__(self, friend_number): def __contains__(self, friend_number):
return friend_number in self._calls return friend_number in self._calls
# -----------------------------------------------------------------------------------------------------------------
# Calls
# -----------------------------------------------------------------------------------------------------------------
def __call__(self, friend_number, audio, video): def __call__(self, friend_number, audio, video):
"""Call friend with specified number""" """Call friend with specified number"""
self._toxav.call(friend_number, 32 if audio else 0, 5000 if video else 0) self._toxav.call(friend_number, 32 if audio else 0, 5000 if video else 0)
self._calls[friend_number] = Call(audio, video) self._calls[friend_number] = CALL_TYPE['AUDIO']
threading.Timer(30.0, lambda: self.finish_not_started_call(friend_number)).start() self.start_audio_thread()
def accept_call(self, friend_number, audio_enabled, video_enabled):
if self._running:
self._calls[friend_number] = Call(audio_enabled, video_enabled)
self._toxav.answer(friend_number, 32 if audio_enabled else 0, 5000 if video_enabled else 0)
if audio_enabled:
self.start_audio_thread()
if video_enabled:
self.start_video_thread()
def finish_call(self, friend_number, by_friend=False): def finish_call(self, friend_number, by_friend=False):
if not by_friend: if not by_friend:
self._toxav.call_control(friend_number, TOXAV_CALL_CONTROL['CANCEL']) self._toxav.call_control(friend_number, TOXAV_CALL_CONTROL['CANCEL'])
if friend_number in self._calls: if friend_number in self._calls:
del self._calls[friend_number] del self._calls[friend_number]
if not len(list(filter(lambda c: c.out_audio, self._calls))): if not len(self._calls):
self.stop_audio_thread() self.stop_audio_thread()
if not len(list(filter(lambda c: c.out_video, self._calls))):
self.stop_video_thread()
def finish_not_started_call(self, friend_number): def stop(self):
if friend_number in self: self._running = False
call = self._calls[friend_number] self.stop_audio_thread()
if not call.is_active:
self.finish_call(friend_number)
def toxav_call_state_cb(self, friend_number, state):
"""
New call state
"""
call = self._calls[friend_number]
call.is_active = True
call.in_audio = state | TOXAV_FRIEND_CALL_STATE['SENDING_A'] > 0
call.in_video = state | TOXAV_FRIEND_CALL_STATE['SENDING_V'] > 0
if state | TOXAV_FRIEND_CALL_STATE['ACCEPTING_A'] and call.out_audio:
self.start_audio_thread()
if state | TOXAV_FRIEND_CALL_STATE['ACCEPTING_V'] and call.out_video:
self.start_video_thread()
def is_video_call(self, number):
return number in self and self._calls[number].in_video
# -----------------------------------------------------------------------------------------------------------------
# Threads
# -----------------------------------------------------------------------------------------------------------------
def start_audio_thread(self): def start_audio_thread(self):
""" """
@ -198,41 +92,7 @@ class AV:
self._out_stream.close() self._out_stream.close()
self._out_stream = None self._out_stream = None
def start_video_thread(self): def chunk(self, samples, channels_count, rate):
if self._video_thread is not None:
return
self._video_running = True
s = settings.Settings.get_instance()
self._video_width = s.video['width']
self._video_height = s.video['height']
if s.video['device'] == -1:
self._video = screen_sharing.DesktopGrabber(s.video['x'], s.video['y'],
s.video['width'], s.video['height'])
else:
self._video = cv2.VideoCapture(s.video['device'])
self._video.set(cv2.CAP_PROP_FPS, 25)
self._video.set(cv2.CAP_PROP_FRAME_WIDTH, self._video_width)
self._video.set(cv2.CAP_PROP_FRAME_HEIGHT, self._video_height)
self._video_thread = threading.Thread(target=self.send_video)
self._video_thread.start()
def stop_video_thread(self):
if self._video_thread is None:
return
self._video_running = False
self._video_thread.join()
self._video_thread = None
self._video = None
# -----------------------------------------------------------------------------------------------------------------
# Incoming chunks
# -----------------------------------------------------------------------------------------------------------------
def audio_chunk(self, samples, channels_count, rate):
""" """
Incoming chunk Incoming chunk
""" """
@ -245,10 +105,6 @@ class AV:
output=True) output=True)
self._out_stream.write(samples) self._out_stream.write(samples)
# -----------------------------------------------------------------------------------------------------------------
# AV sending
# -----------------------------------------------------------------------------------------------------------------
def send_audio(self): def send_audio(self):
""" """
This method sends audio to friends This method sends audio to friends
@ -258,10 +114,10 @@ class AV:
try: try:
pcm = self._audio_stream.read(self._audio_sample_count) pcm = self._audio_stream.read(self._audio_sample_count)
if pcm: if pcm:
for friend_num in self._calls: for friend in self._calls:
if self._calls[friend_num].out_audio: if self._calls[friend] & 1:
try: try:
self._toxav.audio_send_frame(friend_num, pcm, self._audio_sample_count, self._toxav.audio_send_frame(friend, pcm, self._audio_sample_count,
self._audio_channels, self._audio_rate) self._audio_channels, self._audio_rate)
except: except:
pass pass
@ -270,70 +126,19 @@ class AV:
time.sleep(0.01) time.sleep(0.01)
def send_video(self): def accept_call(self, friend_number, audio_enabled, video_enabled):
if self._running:
self._calls[friend_number] = int(video_enabled) * 2 + int(audio_enabled)
self._toxav.answer(friend_number, 32 if audio_enabled else 0, 5000 if video_enabled else 0)
self.start_audio_thread()
def toxav_call_state_cb(self, friend_number, state):
""" """
This method sends video to friends New call state
""" """
while self._video_running: if self._running:
try:
result, frame = self._video.read()
if result:
height, width, channels = frame.shape
for friend_num in self._calls:
if self._calls[friend_num].out_video:
try:
y, u, v = self.convert_bgr_to_yuv(frame)
self._toxav.video_send_frame(friend_num, width, height, y, u, v)
except:
pass
except:
pass
time.sleep(0.01) if state & TOXAV_FRIEND_CALL_STATE['ACCEPTING_A']:
self._calls[friend_number] |= 1
def convert_bgr_to_yuv(self, frame):
"""
:param frame: input bgr frame
:return y, u, v: y, u, v values of frame
How this function works:
OpenCV creates YUV420 frame from BGR
This frame has following structure and size:
width, height - dim of input frame
width, height * 1.5 - dim of output frame
width
-------------------------
| |
| Y | height
| |
-------------------------
| | |
| U even | U odd | height // 4
| | |
-------------------------
| | |
| V even | V odd | height // 4
| | |
-------------------------
width // 2 width // 2
Y, U, V can be extracted using slices and joined in one list using itertools.chain.from_iterable()
Function returns bytes(y), bytes(u), bytes(v), because it is required for ctypes
"""
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2YUV_I420)
y = frame[:self._video_height, :]
y = list(itertools.chain.from_iterable(y))
u = np.zeros((self._video_height // 2, self._video_width // 2), dtype=np.int)
u[::2, :] = frame[self._video_height:self._video_height * 5 // 4, :self._video_width // 2]
u[1::2, :] = frame[self._video_height:self._video_height * 5 // 4, self._video_width // 2:]
u = list(itertools.chain.from_iterable(u))
v = np.zeros((self._video_height // 2, self._video_width // 2), dtype=np.int)
v[::2, :] = frame[self._video_height * 5 // 4:, :self._video_width // 2]
v[1::2, :] = frame[self._video_height * 5 // 4:, self._video_width // 2:]
v = list(itertools.chain.from_iterable(v))
return bytes(y), bytes(u), bytes(v)

View File

@ -61,8 +61,6 @@ class Contact(basecontact.BaseContact):
""" """
Get all chat history from db for current friend Get all chat history from db for current friend
""" """
if self._message_getter is None:
return
data = list(self._message_getter.get_all()) data = list(self._message_getter.get_all())
if data is not None and len(data): if data is not None and len(data):
data.reverse() data.reverse()
@ -126,7 +124,7 @@ class Contact(basecontact.BaseContact):
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
def delete_message(self, time): def delete_message(self, time):
elem = list(filter(lambda x: type(x) in (TextMessage, GroupChatMessage) and x.get_data()[2] == time, self._corr))[0] elem = list(filter(lambda x: type(x) is TextMessage and x.get_data()[2] == time, self._corr))[0]
tmp = list(filter(lambda x: x.get_type() <= 1, self._corr)) tmp = list(filter(lambda x: x.get_type() <= 1, self._corr))
if elem in tmp[-self._unsaved_messages:] and self._unsaved_messages: if elem in tmp[-self._unsaved_messages:] and self._unsaved_messages:
self._unsaved_messages -= 1 self._unsaved_messages -= 1

View File

@ -29,7 +29,7 @@ ALLOWED_FILES = ('toxygen_inline.png', 'utox-inline.png', 'sticker.png')
def is_inline(file_name): def is_inline(file_name):
return file_name in ALLOWED_FILES or file_name.startswith('qTox_Screenshot_') or file_name.startswith('qTox_Image_') return file_name in ALLOWED_FILES or file_name.startswith('qTox_Screenshot_')
class StateSignal(QtCore.QObject): class StateSignal(QtCore.QObject):

View File

@ -66,10 +66,3 @@ class Friend(contact.Contact):
if self._receipts: if self._receipts:
self._receipts -= 1 self._receipts -= 1
self.mark_as_sent() self.mark_as_sent()
# -----------------------------------------------------------------------------------------------------------------
# Full status
# -----------------------------------------------------------------------------------------------------------------
def get_full_status(self):
return self._status_message

View File

@ -1,49 +0,0 @@
import contact
import util
from PyQt5 import QtGui, QtCore
import toxcore_enums_and_consts as constants
class GroupChat(contact.Contact):
def __init__(self, name, status_message, widget, tox, group_number):
super().__init__(None, group_number, name, status_message, widget, None)
self._tox = tox
self.set_status(constants.TOX_USER_STATUS['NONE'])
def set_name(self, name):
self._tox.group_set_title(self._number, name)
super().set_name(name)
def send_message(self, message):
self._tox.group_message_send(self._number, message.encode('utf-8'))
def new_title(self, title):
super().set_name(title)
def load_avatar(self):
path = util.curr_directory() + '/images/group.png'
width = self._widget.avatar_label.width()
pixmap = QtGui.QPixmap(path)
self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio,
QtCore.Qt.SmoothTransformation))
self._widget.avatar_label.repaint()
def remove_invalid_unsent_files(self):
pass
def get_names(self):
peers_count = self._tox.group_number_peers(self._number)
names = []
for i in range(peers_count):
name = self._tox.group_peername(self._number, i)
names.append(name)
names = sorted(names, key=lambda n: n.lower())
return names
def get_full_status(self):
names = self.get_names()
return '\n'.join(names)
def get_peer_name(self, peer_number):
return self._tox.group_peername(self._number, peer_number)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -533,13 +533,19 @@ class InlineImageItem(QtWidgets.QScrollArea):
self._elem.setSizeHint(QtCore.QSize(self.width(), self.height())) self._elem.setSizeHint(QtCore.QSize(self.width(), self.height()))
elif event.button() == QtCore.Qt.RightButton: # save inline elif event.button() == QtCore.Qt.RightButton: # save inline
directory = QtWidgets.QFileDialog.getExistingDirectory(self, directory = QtWidgets.QFileDialog.getExistingDirectory(self,
QtWidgets.QApplication.translate("MainWindow", QtWidgets.QApplication.translate("MainWindow",
'Choose folder'), 'Choose folder'),
curr_directory(), curr_directory(),
QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog) QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog)
if directory: if directory:
fl = QtCore.QFile(directory + '/toxygen_inline_' + curr_time().replace(':', '_') + '.png') fl = QtCore.QFile(directory + '/toxygen_inline_' + curr_time().replace(':', '_') + '.png')
self._pixmap.save(fl, 'PNG') self._pixmap.save(fl, 'PNG')
return False
def mark_as_sent(self): def mark_as_sent(self):
return False return False

View File

@ -3,11 +3,11 @@ from loginscreen import LoginScreen
import profile import profile
from settings import * from settings import *
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
from bootstrap import generate_nodes, download_nodes_list from bootstrap import node_generator
from mainscreen import MainWindow from mainscreen import MainWindow
from callbacks import init_callbacks, stop, start from callbacks import init_callbacks, stop, start
from util import curr_directory, program_version, remove from util import curr_directory, program_version, remove, is_64_bit
import styles.style # reqired for styles loading import styles.style
import platform import platform
import toxes import toxes
from passwordscreen import PasswordScreen, UnlockAppScreen, SetProfilePasswordScreen from passwordscreen import PasswordScreen, UnlockAppScreen, SetProfilePasswordScreen
@ -54,9 +54,10 @@ class Toxygen:
if platform.system() == 'Linux': if platform.system() == 'Linux':
QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads) QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
with open(curr_directory() + '/styles/dark_style.qss') as fl: # application color scheme
style = fl.read() with open(curr_directory() + '/styles/style.qss') as fl:
app.setStyleSheet(style) dark_style = fl.read()
app.setStyleSheet(dark_style)
encrypt_save = toxes.ToxES() encrypt_save = toxes.ToxES()
@ -100,7 +101,7 @@ class Toxygen:
msgBox.setWindowTitle( msgBox.setWindowTitle(
QtWidgets.QApplication.translate("MainWindow", "Error")) QtWidgets.QApplication.translate("MainWindow", "Error"))
text = (QtWidgets.QApplication.translate("MainWindow", text = (QtWidgets.QApplication.translate("MainWindow",
'Profile with this name already exists')) 'Profile with this name already exists'))
msgBox.setText(text) msgBox.setText(text)
msgBox.exec_() msgBox.exec_()
return return
@ -108,9 +109,9 @@ class Toxygen:
self.tox.self_set_name(bytes(_login.name, 'utf-8') if _login.name else b'Toxygen User') self.tox.self_set_name(bytes(_login.name, 'utf-8') if _login.name else b'Toxygen User')
self.tox.self_set_status_message(b'Toxing on Toxygen') self.tox.self_set_status_message(b'Toxing on Toxygen')
reply = QtWidgets.QMessageBox.question(None, reply = QtWidgets.QMessageBox.question(None,
'Profile {}'.format(name), 'Profile {}'.format(name),
QtWidgets.QApplication.translate("login", QtWidgets.QApplication.translate("login",
'Do you want to set profile password?'), 'Do you want to set profile password?'),
QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No) QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.Yes: if reply == QtWidgets.QMessageBox.Yes:
@ -119,10 +120,10 @@ class Toxygen:
self.app.lastWindowClosed.connect(self.app.quit) self.app.lastWindowClosed.connect(self.app.quit)
self.app.exec_() self.app.exec_()
reply = QtWidgets.QMessageBox.question(None, reply = QtWidgets.QMessageBox.question(None,
'Profile {}'.format(name), 'Profile {}'.format(name),
QtWidgets.QApplication.translate("login", QtWidgets.QApplication.translate("login",
'Do you want to save profile in default folder? If no, profile will be saved in program folder'), 'Do you want to save profile in default folder? If no, profile will be saved in program folder'),
QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No) QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.Yes: if reply == QtWidgets.QMessageBox.Yes:
path = Settings.get_default_path() path = Settings.get_default_path()
@ -135,7 +136,7 @@ class Toxygen:
log('Profile creation exception: ' + str(ex)) log('Profile creation exception: ' + str(ex))
msgBox = QtWidgets.QMessageBox() msgBox = QtWidgets.QMessageBox()
msgBox.setText(QtWidgets.QApplication.translate("login", msgBox.setText(QtWidgets.QApplication.translate("login",
'Profile saving error! Does Toxygen have permission to write to this directory?')) 'Profile saving error! Does Toxygen have permission to write to this directory?'))
msgBox.exec_() msgBox.exec_()
return return
path = Settings.get_default_path() path = Settings.get_default_path()
@ -162,8 +163,8 @@ class Toxygen:
if Settings.is_active_profile(path, name): # profile is in use if Settings.is_active_profile(path, name): # profile is in use
reply = QtWidgets.QMessageBox.question(None, reply = QtWidgets.QMessageBox.question(None,
'Profile {}'.format(name), 'Profile {}'.format(name),
QtWidgets.QApplication.translate("login", 'Other instance of Toxygen uses this profile or profile was not properly closed. Continue?'), QtWidgets.QApplication.translate("login", 'Other instance of Toxygen uses this profile or profile was not properly closed. Continue?'),
QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No) QtWidgets.QMessageBox.No)
if reply != QtWidgets.QMessageBox.Yes: if reply != QtWidgets.QMessageBox.Yes:
@ -171,13 +172,6 @@ class Toxygen:
else: else:
settings.set_active_profile() settings.set_active_profile()
# application color scheme
for theme in settings.built_in_themes().keys():
if settings['theme'] == theme:
with open(curr_directory() + settings.built_in_themes()[theme]) as fl:
style = fl.read()
app.setStyleSheet(style)
lang = Settings.supported_languages()[settings['language']] lang = Settings.supported_languages()[settings['language']]
translator = QtCore.QTranslator() translator = QtCore.QTranslator()
translator.load(curr_directory() + '/translations/' + lang) translator.load(curr_directory() + '/translations/' + lang)
@ -283,9 +277,9 @@ class Toxygen:
updating = True updating = True
else: else:
reply = QtWidgets.QMessageBox.question(None, reply = QtWidgets.QMessageBox.question(None,
'Toxygen', 'Toxygen',
QtWidgets.QApplication.translate("login", QtWidgets.QApplication.translate("login",
'Update for Toxygen was found. Download and install it?'), 'Update for Toxygen was found. Download and install it?'),
QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No) QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.Yes: if reply == QtWidgets.QMessageBox.Yes:
@ -327,7 +321,6 @@ class Toxygen:
self.mainloop.wait() self.mainloop.wait()
self.init.wait() self.init.wait()
self.avloop.wait() self.avloop.wait()
self.tray.hide()
data = self.tox.get_savedata() data = self.tox.get_savedata()
ProfileHelper.get_instance().save_profile(data) ProfileHelper.get_instance().save_profile(data)
settings.close() settings.close()
@ -379,11 +372,9 @@ class Toxygen:
def run(self): def run(self):
# initializing callbacks # initializing callbacks
init_callbacks(self.tox, self.ms, self.tray) init_callbacks(self.tox, self.ms, self.tray)
# download list of nodes if needed
download_nodes_list()
# bootstrap # bootstrap
try: try:
for data in generate_nodes(): for data in node_generator():
if self.stop: if self.stop:
return return
self.tox.bootstrap(*data) self.tox.bootstrap(*data)
@ -396,7 +387,7 @@ class Toxygen:
self.msleep(1000) self.msleep(1000)
while not self.tox.self_get_connection_status(): while not self.tox.self_get_connection_status():
try: try:
for data in generate_nodes(): for data in node_generator():
if self.stop: if self.stop:
return return
self.tox.bootstrap(*data) self.tox.bootstrap(*data)
@ -457,6 +448,27 @@ def clean():
remove(d) remove(d)
def configure():
"""Removes unused libs"""
d = curr_directory() + '/libs/'
is_64bits = is_64_bit()
if not is_64bits:
if os.path.exists(d + 'libtox64.dll'):
os.remove(d + 'libtox64.dll')
if os.path.exists(d + 'libsodium64.a'):
os.remove(d + 'libsodium64.a')
else:
if os.path.exists(d + 'libtox.dll'):
os.remove(d + 'libtox.dll')
if os.path.exists(d + 'libsodium.a'):
os.remove(d + 'libsodium.a')
try:
os.rename(d + 'libtox64.dll', d + 'libtox.dll')
os.rename(d + 'libsodium64.a', d + 'libsodium.a')
except:
pass
def reset(): def reset():
Settings.reset_auto_profile() Settings.reset_auto_profile()
@ -472,6 +484,9 @@ def main():
elif arg == '--help': elif arg == '--help':
print('Usage:\ntoxygen path_to_profile\ntoxygen tox_id\ntoxygen --version\ntoxygen --reset') print('Usage:\ntoxygen path_to_profile\ntoxygen tox_id\ntoxygen --version\ntoxygen --reset')
return return
elif arg == '--configure':
configure()
return
elif arg == '--clean': elif arg == '--clean':
clean() clean()
return return

View File

@ -5,6 +5,7 @@ from widgets import MultilineEdit, ComboBox
import plugin_support import plugin_support
from mainscreen_widgets import * from mainscreen_widgets import *
import settings import settings
import platform
import toxes import toxes
@ -21,50 +22,52 @@ class MainWindow(QtWidgets.QMainWindow, Singleton):
if settings.Settings.get_instance()['show_welcome_screen']: if settings.Settings.get_instance()['show_welcome_screen']:
self.ws = WelcomeScreen() self.ws = WelcomeScreen()
def setup_menu(self, window): def setup_menu(self, Form):
self.menubar = QtWidgets.QMenuBar(window) box = QtWidgets.QHBoxLayout()
self.menubar.setObjectName("menubar") box.setContentsMargins(0, 0, 0, 0)
self.menubar.setNativeMenuBar(False) box.setAlignment(QtCore.Qt.AlignLeft)
self.menubar.setMinimumSize(self.width(), 25) self.profile_button = MainMenuButton(Form)
self.menubar.setMaximumSize(self.width(), 25) box.addWidget(self.profile_button)
self.menubar.setBaseSize(self.width(), 25) self.settings_button = MainMenuButton(Form)
self.menuProfile = QtWidgets.QMenu(self.menubar) box.addWidget(self.settings_button)
self.plugins_button = MainMenuButton(Form)
box.addWidget(self.plugins_button)
self.about_button = MainMenuButton(Form)
box.addWidget(self.about_button)
box.setSpacing(0)
self.menuProfile = QtWidgets.QMenu(self.menubar) self.menuProfile = QtWidgets.QMenu()
self.menuProfile.setObjectName("menuProfile") self.menuProfile.setObjectName("menuProfile")
self.menuSettings = QtWidgets.QMenu(self.menubar) self.menuSettings = QtWidgets.QMenu()
self.menuSettings.setObjectName("menuSettings") self.menuSettings.setObjectName("menuSettings")
self.menuPlugins = QtWidgets.QMenu(self.menubar) self.menuPlugins = QtWidgets.QMenu()
self.menuPlugins.setObjectName("menuPlugins") self.menuPlugins.setObjectName("menuPlugins")
self.menuAbout = QtWidgets.QMenu(self.menubar) self.menuAbout = QtWidgets.QMenu()
self.menuAbout.setObjectName("menuAbout") self.menuAbout.setObjectName("menuAbout")
self.actionAdd_friend = QtWidgets.QAction(window) self.actionAdd_friend = QtWidgets.QAction(Form)
self.actionAdd_gc = QtWidgets.QAction(window)
self.actionAdd_friend.setObjectName("actionAdd_friend") self.actionAdd_friend.setObjectName("actionAdd_friend")
self.actionprofilesettings = QtWidgets.QAction(window) self.actionprofilesettings = QtWidgets.QAction(Form)
self.actionprofilesettings.setObjectName("actionprofilesettings") self.actionprofilesettings.setObjectName("actionprofilesettings")
self.actionPrivacy_settings = QtWidgets.QAction(window) self.actionPrivacy_settings = QtWidgets.QAction(Form)
self.actionPrivacy_settings.setObjectName("actionPrivacy_settings") self.actionPrivacy_settings.setObjectName("actionPrivacy_settings")
self.actionInterface_settings = QtWidgets.QAction(window) self.actionInterface_settings = QtWidgets.QAction(Form)
self.actionInterface_settings.setObjectName("actionInterface_settings") self.actionInterface_settings.setObjectName("actionInterface_settings")
self.actionNotifications = QtWidgets.QAction(window) self.actionNotifications = QtWidgets.QAction(Form)
self.actionNotifications.setObjectName("actionNotifications") self.actionNotifications.setObjectName("actionNotifications")
self.actionNetwork = QtWidgets.QAction(window) self.actionNetwork = QtWidgets.QAction(Form)
self.actionNetwork.setObjectName("actionNetwork") self.actionNetwork.setObjectName("actionNetwork")
self.actionAbout_program = QtWidgets.QAction(window) self.actionAbout_program = QtWidgets.QAction(Form)
self.actionAbout_program.setObjectName("actionAbout_program") self.actionAbout_program.setObjectName("actionAbout_program")
self.updateSettings = QtWidgets.QAction(window) self.updateSettings = QtWidgets.QAction(Form)
self.actionSettings = QtWidgets.QAction(window) self.actionSettings = QtWidgets.QAction(Form)
self.actionSettings.setObjectName("actionSettings") self.actionSettings.setObjectName("actionSettings")
self.audioSettings = QtWidgets.QAction(window) self.audioSettings = QtWidgets.QAction(Form)
self.videoSettings = QtWidgets.QAction(window) self.pluginData = QtWidgets.QAction(Form)
self.pluginData = QtWidgets.QAction(window) self.importPlugin = QtWidgets.QAction(Form)
self.importPlugin = QtWidgets.QAction(window) self.reloadPlugins = QtWidgets.QAction(Form)
self.reloadPlugins = QtWidgets.QAction(window) self.lockApp = QtWidgets.QAction(Form)
self.lockApp = QtWidgets.QAction(window)
self.menuProfile.addAction(self.actionAdd_friend) self.menuProfile.addAction(self.actionAdd_friend)
self.menuProfile.addAction(self.actionAdd_gc)
self.menuProfile.addAction(self.actionSettings) self.menuProfile.addAction(self.actionSettings)
self.menuProfile.addAction(self.lockApp) self.menuProfile.addAction(self.lockApp)
self.menuSettings.addAction(self.actionPrivacy_settings) self.menuSettings.addAction(self.actionPrivacy_settings)
@ -72,34 +75,34 @@ class MainWindow(QtWidgets.QMainWindow, Singleton):
self.menuSettings.addAction(self.actionNotifications) self.menuSettings.addAction(self.actionNotifications)
self.menuSettings.addAction(self.actionNetwork) self.menuSettings.addAction(self.actionNetwork)
self.menuSettings.addAction(self.audioSettings) self.menuSettings.addAction(self.audioSettings)
self.menuSettings.addAction(self.videoSettings)
self.menuSettings.addAction(self.updateSettings) self.menuSettings.addAction(self.updateSettings)
self.menuPlugins.addAction(self.pluginData) self.menuPlugins.addAction(self.pluginData)
self.menuPlugins.addAction(self.importPlugin) self.menuPlugins.addAction(self.importPlugin)
self.menuPlugins.addAction(self.reloadPlugins) self.menuPlugins.addAction(self.reloadPlugins)
self.menuAbout.addAction(self.actionAbout_program) self.menuAbout.addAction(self.actionAbout_program)
self.menubar.addAction(self.menuProfile.menuAction()) self.profile_button.setMenu(self.menuProfile)
self.menubar.addAction(self.menuSettings.menuAction()) self.settings_button.setMenu(self.menuSettings)
self.menubar.addAction(self.menuPlugins.menuAction()) self.plugins_button.setMenu(self.menuPlugins)
self.menubar.addAction(self.menuAbout.menuAction()) self.about_button.setMenu(self.menuAbout)
self.actionAbout_program.triggered.connect(self.about_program) self.actionAbout_program.triggered.connect(self.about_program)
self.actionNetwork.triggered.connect(self.network_settings) self.actionNetwork.triggered.connect(self.network_settings)
self.actionAdd_friend.triggered.connect(self.add_contact) self.actionAdd_friend.triggered.connect(self.add_contact)
self.actionAdd_gc.triggered.connect(self.create_gc)
self.actionSettings.triggered.connect(self.profile_settings) self.actionSettings.triggered.connect(self.profile_settings)
self.actionPrivacy_settings.triggered.connect(self.privacy_settings) self.actionPrivacy_settings.triggered.connect(self.privacy_settings)
self.actionInterface_settings.triggered.connect(self.interface_settings) self.actionInterface_settings.triggered.connect(self.interface_settings)
self.actionNotifications.triggered.connect(self.notification_settings) self.actionNotifications.triggered.connect(self.notification_settings)
self.audioSettings.triggered.connect(self.audio_settings) self.audioSettings.triggered.connect(self.audio_settings)
self.videoSettings.triggered.connect(self.video_settings)
self.updateSettings.triggered.connect(self.update_settings) self.updateSettings.triggered.connect(self.update_settings)
self.pluginData.triggered.connect(self.plugins_menu) self.pluginData.triggered.connect(self.plugins_menu)
self.lockApp.triggered.connect(self.lock_app) self.lockApp.triggered.connect(self.lock_app)
self.importPlugin.triggered.connect(self.import_plugin) self.importPlugin.triggered.connect(self.import_plugin)
self.reloadPlugins.triggered.connect(self.reload_plugins) self.reloadPlugins.triggered.connect(self.reload_plugins)
Form.setLayout(box)
QtCore.QMetaObject.connectSlotsByName(Form)
def languageChange(self, *args, **kwargs): def languageChange(self, *args, **kwargs):
self.retranslateUi() self.retranslateUi()
@ -111,13 +114,12 @@ class MainWindow(QtWidgets.QMainWindow, Singleton):
def retranslateUi(self): def retranslateUi(self):
self.lockApp.setText(QtWidgets.QApplication.translate("MainWindow", "Lock")) self.lockApp.setText(QtWidgets.QApplication.translate("MainWindow", "Lock"))
self.menuPlugins.setTitle(QtWidgets.QApplication.translate("MainWindow", "Plugins")) self.plugins_button.setText(QtWidgets.QApplication.translate("MainWindow", "Plugins"))
self.pluginData.setText(QtWidgets.QApplication.translate("MainWindow", "List of plugins")) self.pluginData.setText(QtWidgets.QApplication.translate("MainWindow", "List of plugins"))
self.menuProfile.setTitle(QtWidgets.QApplication.translate("MainWindow", "Profile")) self.profile_button.setText(QtWidgets.QApplication.translate("MainWindow", "Profile"))
self.menuSettings.setTitle(QtWidgets.QApplication.translate("MainWindow", "Settings")) self.settings_button.setText(QtWidgets.QApplication.translate("MainWindow", "Settings"))
self.menuAbout.setTitle(QtWidgets.QApplication.translate("MainWindow", "About")) self.about_button.setText(QtWidgets.QApplication.translate("MainWindow", "About"))
self.actionAdd_friend.setText(QtWidgets.QApplication.translate("MainWindow", "Add contact")) self.actionAdd_friend.setText(QtWidgets.QApplication.translate("MainWindow", "Add contact"))
self.actionAdd_gc.setText(QtWidgets.QApplication.translate("MainWindow", "Create group chat"))
self.actionprofilesettings.setText(QtWidgets.QApplication.translate("MainWindow", "Profile")) self.actionprofilesettings.setText(QtWidgets.QApplication.translate("MainWindow", "Profile"))
self.actionPrivacy_settings.setText(QtWidgets.QApplication.translate("MainWindow", "Privacy")) self.actionPrivacy_settings.setText(QtWidgets.QApplication.translate("MainWindow", "Privacy"))
self.actionInterface_settings.setText(QtWidgets.QApplication.translate("MainWindow", "Interface")) self.actionInterface_settings.setText(QtWidgets.QApplication.translate("MainWindow", "Interface"))
@ -126,7 +128,6 @@ class MainWindow(QtWidgets.QMainWindow, Singleton):
self.actionAbout_program.setText(QtWidgets.QApplication.translate("MainWindow", "About program")) self.actionAbout_program.setText(QtWidgets.QApplication.translate("MainWindow", "About program"))
self.actionSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Settings")) self.actionSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Settings"))
self.audioSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Audio")) self.audioSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Audio"))
self.videoSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Video"))
self.updateSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Updates")) self.updateSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Updates"))
self.contact_name.setPlaceholderText(QtWidgets.QApplication.translate("MainWindow", "Search")) self.contact_name.setPlaceholderText(QtWidgets.QApplication.translate("MainWindow", "Search"))
self.sendMessageButton.setToolTip(QtWidgets.QApplication.translate("MainWindow", "Send message")) self.sendMessageButton.setToolTip(QtWidgets.QApplication.translate("MainWindow", "Send message"))
@ -378,8 +379,12 @@ class MainWindow(QtWidgets.QMainWindow, Singleton):
self.close() self.close()
def resizeEvent(self, *args, **kwargs): def resizeEvent(self, *args, **kwargs):
self.messages.setGeometry(0, 0, self.width() - 270, self.height() - 155) if platform.system() == 'Windows':
self.friends_list.setGeometry(0, 0, 270, self.height() - 125) self.messages.setGeometry(0, 0, self.width() - 270, self.height() - 155)
self.friends_list.setGeometry(0, 0, 270, self.height() - 125)
else:
self.messages.setGeometry(0, 0, self.width() - 270, self.height() - 159)
self.friends_list.setGeometry(0, 0, 270, self.height() - 129)
self.videocallButton.setGeometry(QtCore.QRect(self.width() - 330, 10, 50, 50)) self.videocallButton.setGeometry(QtCore.QRect(self.width() - 330, 10, 50, 50))
self.callButton.setGeometry(QtCore.QRect(self.width() - 390, 10, 50, 50)) self.callButton.setGeometry(QtCore.QRect(self.width() - 390, 10, 50, 50))
@ -418,10 +423,8 @@ class MainWindow(QtWidgets.QMainWindow, Singleton):
import util import util
msgBox = QtWidgets.QMessageBox() msgBox = QtWidgets.QMessageBox()
msgBox.setWindowTitle(QtWidgets.QApplication.translate("MainWindow", "About")) msgBox.setWindowTitle(QtWidgets.QApplication.translate("MainWindow", "About"))
text = (QtWidgets.QApplication.translate("MainWindow", 'Toxygen is Tox client written on Python.<br>Version: ')) text = (QtWidgets.QApplication.translate("MainWindow", 'Toxygen is Tox client written on Python.\nVersion: '))
github = '<br><a href="https://github.com/toxygen-project/toxygen/">Github</a>' msgBox.setText(text + util.program_version + '\nGitHub: https://github.com/toxygen-project/toxygen/')
submit_a_bug = '<br><a href="https://github.com/toxygen-project/toxygen/issues">Submit a bug</a>'
msgBox.setText(text + util.program_version + github + submit_a_bug)
msgBox.exec_() msgBox.exec_()
def network_settings(self): def network_settings(self):
@ -436,9 +439,6 @@ class MainWindow(QtWidgets.QMainWindow, Singleton):
self.a_c = AddContact(link or '') self.a_c = AddContact(link or '')
self.a_c.show() self.a_c.show()
def create_gc(self):
self.profile.create_group_chat()
def profile_settings(self, *args): def profile_settings(self, *args):
self.p_s = ProfileSettings() self.p_s = ProfileSettings()
self.p_s.show() self.p_s.show()
@ -459,10 +459,6 @@ class MainWindow(QtWidgets.QMainWindow, Singleton):
self.audio_s = AudioSettings() self.audio_s = AudioSettings()
self.audio_s.show() self.audio_s.show()
def video_settings(self):
self.video_s = VideoSettings()
self.video_s.show()
def update_settings(self): def update_settings(self):
self.update_s = UpdateSettings() self.update_s = UpdateSettings()
self.update_s.show() self.update_s.show()
@ -520,7 +516,7 @@ class MainWindow(QtWidgets.QMainWindow, Singleton):
def send_file(self): def send_file(self):
self.menu.hide() self.menu.hide()
if self.profile.active_friend + 1and self.profile.is_active_a_friend(): if self.profile.active_friend + 1:
choose = QtWidgets.QApplication.translate("MainWindow", 'Choose file') choose = QtWidgets.QApplication.translate("MainWindow", 'Choose file')
name = QtWidgets.QFileDialog.getOpenFileName(self, choose, options=QtWidgets.QFileDialog.DontUseNativeDialog) name = QtWidgets.QFileDialog.getOpenFileName(self, choose, options=QtWidgets.QFileDialog.DontUseNativeDialog)
if name[0]: if name[0]:
@ -528,7 +524,7 @@ class MainWindow(QtWidgets.QMainWindow, Singleton):
def send_screenshot(self, hide=False): def send_screenshot(self, hide=False):
self.menu.hide() self.menu.hide()
if self.profile.active_friend + 1 and self.profile.is_active_a_friend(): if self.profile.active_friend + 1:
self.sw = ScreenShotWindow(self) self.sw = ScreenShotWindow(self)
self.sw.show() self.sw.show()
if hide: if hide:
@ -546,7 +542,7 @@ class MainWindow(QtWidgets.QMainWindow, Singleton):
def send_sticker(self): def send_sticker(self):
self.menu.hide() self.menu.hide()
if self.profile.active_friend + 1 and self.profile.is_active_a_friend(): if self.profile.active_friend + 1:
self.sticker = StickerWindow(self) self.sticker = StickerWindow(self)
self.sticker.setGeometry(QtCore.QRect(self.x() if Settings.get_instance()['mirror_mode'] else 270 + self.x(), self.sticker.setGeometry(QtCore.QRect(self.x() if Settings.get_instance()['mirror_mode'] else 270 + self.x(),
self.y() + self.height() - 200, self.y() + self.height() - 200,
@ -563,15 +559,14 @@ class MainWindow(QtWidgets.QMainWindow, Singleton):
def call_finished(self): def call_finished(self):
self.update_call_state('call') self.update_call_state('call')
def update_call_state(self, state): def update_call_state(self, fl):
# TODO: do smth with video call button
os.chdir(curr_directory() + '/images/') os.chdir(curr_directory() + '/images/')
pixmap = QtGui.QPixmap(curr_directory() + '/images/{}.png'.format(fl))
pixmap = QtGui.QPixmap(curr_directory() + '/images/{}.png'.format(state))
icon = QtGui.QIcon(pixmap) icon = QtGui.QIcon(pixmap)
self.callButton.setIcon(icon) self.callButton.setIcon(icon)
self.callButton.setIconSize(QtCore.QSize(50, 50)) self.callButton.setIconSize(QtCore.QSize(50, 50))
pixmap = QtGui.QPixmap(curr_directory() + '/images/videocall.png')
pixmap = QtGui.QPixmap(curr_directory() + '/images/{}_video.png'.format(state))
icon = QtGui.QIcon(pixmap) icon = QtGui.QIcon(pixmap)
self.videocallButton.setIcon(icon) self.videocallButton.setIcon(icon)
self.videocallButton.setIconSize(QtCore.QSize(35, 35)) self.videocallButton.setIconSize(QtCore.QSize(35, 35))
@ -591,10 +586,7 @@ class MainWindow(QtWidgets.QMainWindow, Singleton):
auto = QtWidgets.QApplication.translate("MainWindow", 'Disallow auto accept') if allowed else QtWidgets.QApplication.translate("MainWindow", 'Allow auto accept') auto = QtWidgets.QApplication.translate("MainWindow", 'Disallow auto accept') if allowed else QtWidgets.QApplication.translate("MainWindow", 'Allow auto accept')
if item is not None: if item is not None:
self.listMenu = QtWidgets.QMenu() self.listMenu = QtWidgets.QMenu()
is_friend = type(friend) is Friend set_alias_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Set alias'))
if is_friend:
set_alias_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Set alias'))
set_alias_item.triggered.connect(lambda: self.set_alias(num))
history_menu = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Chat history')) history_menu = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Chat history'))
clear_history_item = history_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Clear history')) clear_history_item = history_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Clear history'))
@ -604,39 +596,26 @@ class MainWindow(QtWidgets.QMainWindow, Singleton):
copy_menu = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Copy')) copy_menu = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Copy'))
copy_name_item = copy_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Name')) copy_name_item = copy_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Name'))
copy_status_item = copy_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Status message')) copy_status_item = copy_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Status message'))
if is_friend: copy_key_item = copy_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Public key'))
copy_key_item = copy_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Public key'))
auto_accept_item = self.listMenu.addAction(auto) auto_accept_item = self.listMenu.addAction(auto)
remove_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Remove friend')) remove_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Remove friend'))
block_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Block friend')) block_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Block friend'))
notes_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Notes')) notes_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Notes'))
chats = self.profile.get_group_chats() plugins_loader = plugin_support.PluginLoader.get_instance()
if len(chats) and self.profile.is_active_online(): if plugins_loader is not None:
invite_menu = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Invite to group chat')) submenu = plugins_loader.get_menu(self.listMenu, num)
for i in range(len(chats)): if len(submenu):
name, number = chats[i] plug = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Plugins'))
item = invite_menu.addAction(name) plug.addActions(submenu)
item.triggered.connect(lambda number=number: self.invite_friend_to_gc(num, number)) set_alias_item.triggered.connect(lambda: self.set_alias(num))
remove_item.triggered.connect(lambda: self.remove_friend(num))
plugins_loader = plugin_support.PluginLoader.get_instance() block_item.triggered.connect(lambda: self.block_friend(num))
if plugins_loader is not None: copy_key_item.triggered.connect(lambda: self.copy_friend_key(num))
submenu = plugins_loader.get_menu(self.listMenu, num)
if len(submenu):
plug = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Plugins'))
plug.addActions(submenu)
copy_key_item.triggered.connect(lambda: self.copy_friend_key(num))
remove_item.triggered.connect(lambda: self.remove_friend(num))
block_item.triggered.connect(lambda: self.block_friend(num))
auto_accept_item.triggered.connect(lambda: self.auto_accept(num, not allowed))
notes_item.triggered.connect(lambda: self.show_note(friend))
else:
leave_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Leave chat'))
set_title_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Set title'))
leave_item.triggered.connect(lambda: self.leave_gc(num))
set_title_item.triggered.connect(lambda: self.set_title(num))
clear_history_item.triggered.connect(lambda: self.clear_history(num)) clear_history_item.triggered.connect(lambda: self.clear_history(num))
auto_accept_item.triggered.connect(lambda: self.auto_accept(num, not allowed))
notes_item.triggered.connect(lambda: self.show_note(friend))
copy_name_item.triggered.connect(lambda: self.copy_name(friend)) copy_name_item.triggered.connect(lambda: self.copy_name(friend))
copy_status_item.triggered.connect(lambda: self.copy_status(friend)) copy_status_item.triggered.connect(lambda: self.copy_status(friend))
export_to_text_item.triggered.connect(lambda: self.export_history(num)) export_to_text_item.triggered.connect(lambda: self.export_history(num))
@ -662,18 +641,15 @@ class MainWindow(QtWidgets.QMainWindow, Singleton):
def export_history(self, num, as_text=True): def export_history(self, num, as_text=True):
s = self.profile.export_history(num, as_text) s = self.profile.export_history(num, as_text)
extension = 'txt' if as_text else 'html' directory = QtWidgets.QFileDialog.getExistingDirectory(None,
file_name, _ = QtWidgets.QFileDialog.getSaveFileName(None, QtWidgets.QApplication.translate("MainWindow",
QtWidgets.QApplication.translate("MainWindow", 'Choose folder'),
'Choose file name'), curr_directory(),
curr_directory(), QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog)
filter=extension,
options=QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog)
if file_name: if directory:
if not file_name.endswith('.' + extension): name = 'exported_history_{}.{}'.format(convert_time(time.time()), 'txt' if as_text else 'html')
file_name += '.' + extension with open(directory + '/' + name, 'wt') as fl:
with open(file_name, 'wt') as fl:
fl.write(s) fl.write(s)
def set_alias(self, num): def set_alias(self, num):
@ -702,12 +678,6 @@ class MainWindow(QtWidgets.QMainWindow, Singleton):
def clear_history(self, num): def clear_history(self, num):
self.profile.clear_history(num) self.profile.clear_history(num)
def leave_gc(self, num):
self.profile.leave_gc(num)
def set_title(self, num):
self.profile.set_title(num)
def auto_accept(self, num, value): def auto_accept(self, num, value):
settings = Settings.get_instance() settings = Settings.get_instance()
tox_id = self.profile.friend_public_key(num) tox_id = self.profile.friend_public_key(num)
@ -717,9 +687,6 @@ class MainWindow(QtWidgets.QMainWindow, Singleton):
settings['auto_accept_from_friends'].remove(tox_id) settings['auto_accept_from_friends'].remove(tox_id)
settings.save() settings.save()
def invite_friend_to_gc(self, friend_number, group_number):
self.profile.invite_friend(friend_number, group_number)
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
# Functions which called when user click somewhere else # Functions which called when user click somewhere else
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------

View File

@ -1,9 +1,8 @@
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
from widgets import RubberBandWindow, create_menu, QRightClickButton, CenteredWidget, LineEdit from widgets import RubberBand, create_menu, QRightClickButton, CenteredWidget, LineEdit
from profile import Profile from profile import Profile
import smileys import smileys
import util import util
import platform
class MessageArea(QtWidgets.QPlainTextEdit): class MessageArea(QtWidgets.QPlainTextEdit):
@ -35,10 +34,6 @@ class MessageArea(QtWidgets.QPlainTextEdit):
self.parent.send_message() self.parent.send_message()
elif event.key() == QtCore.Qt.Key_Up and not self.toPlainText(): elif event.key() == QtCore.Qt.Key_Up and not self.toPlainText():
self.appendPlainText(Profile.get_instance().get_last_message()) self.appendPlainText(Profile.get_instance().get_last_message())
elif event.key() == QtCore.Qt.Key_Tab and not self.parent.profile.is_active_a_friend():
text = self.toPlainText()
pos = self.textCursor().position()
self.insertPlainText(Profile.get_instance().get_gc_peer_name(text[:pos]))
else: else:
self.parent.profile.send_typing(True) self.parent.profile.send_typing(True)
if self.timer.isActive(): if self.timer.isActive():
@ -71,25 +66,43 @@ class MessageArea(QtWidgets.QPlainTextEdit):
def pasteEvent(self, text=None): def pasteEvent(self, text=None):
text = text or QtWidgets.QApplication.clipboard().text() text = text or QtWidgets.QApplication.clipboard().text()
if text.startswith('file://'): if text.startswith('file://'):
file_name = self.parse_file_name(text) self.parent.profile.send_file(text[7:])
self.parent.profile.send_file(file_name)
else: else:
self.insertPlainText(text) self.insertPlainText(text)
def parse_file_name(self, file_name):
import urllib
if file_name.endswith('\r\n'):
file_name = file_name[:-2]
file_name = urllib.parse.unquote(file_name)
return file_name[8 if platform.system() == 'Windows' else 7:]
class ScreenShotWindow(QtWidgets.QWidget):
class ScreenShotWindow(RubberBandWindow): def __init__(self, parent):
super(ScreenShotWindow, self).__init__()
self.parent = parent
self.setMouseTracking(True)
self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint)
self.showFullScreen()
self.setWindowOpacity(0.5)
self.rubberband = RubberBand()
self.rubberband.setWindowFlags(self.rubberband.windowFlags() | QtCore.Qt.FramelessWindowHint)
self.rubberband.setAttribute(QtCore.Qt.WA_TranslucentBackground)
def closeEvent(self, *args): def closeEvent(self, *args):
if self.parent.isHidden(): if self.parent.isHidden():
self.parent.show() self.parent.show()
def mousePressEvent(self, event):
self.origin = event.pos()
self.rubberband.setGeometry(QtCore.QRect(self.origin, QtCore.QSize()))
self.rubberband.show()
QtWidgets.QWidget.mousePressEvent(self, event)
def mouseMoveEvent(self, event):
if self.rubberband.isVisible():
self.rubberband.setGeometry(QtCore.QRect(self.origin, event.pos()).normalized())
left = QtGui.QRegion(QtCore.QRect(0, 0, self.rubberband.x(), self.height()))
right = QtGui.QRegion(QtCore.QRect(self.rubberband.x() + self.rubberband.width(), 0, self.width(), self.height()))
top = QtGui.QRegion(0, 0, self.width(), self.rubberband.y())
bottom = QtGui.QRegion(0, self.rubberband.y() + self.rubberband.height(), self.width(), self.height())
self.setMask(left + right + top + bottom)
def mouseReleaseEvent(self, event): def mouseReleaseEvent(self, event):
if self.rubberband.isVisible(): if self.rubberband.isVisible():
self.rubberband.hide() self.rubberband.hide()
@ -108,6 +121,13 @@ class ScreenShotWindow(RubberBandWindow):
Profile.get_instance().send_screenshot(bytes(byte_array.data())) Profile.get_instance().send_screenshot(bytes(byte_array.data()))
self.close() self.close()
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_Escape:
self.rubberband.setHidden(True)
self.close()
else:
super(ScreenShotWindow, self).keyPressEvent(event)
class SmileyWindow(QtWidgets.QWidget): class SmileyWindow(QtWidgets.QWidget):
""" """
@ -133,7 +153,7 @@ class SmileyWindow(QtWidgets.QWidget):
for i in range(self.page_count): # buttons with smileys for i in range(self.page_count): # buttons with smileys
elem = QtWidgets.QRadioButton(self) elem = QtWidgets.QRadioButton(self)
elem.setGeometry(QtCore.QRect(i * 20 + 5, 180, 20, 20)) elem.setGeometry(QtCore.QRect(i * 20 + 5, 180, 20, 20))
elem.clicked.connect(lambda c, t=i: self.checked(t)) elem.clicked.connect(lambda i=i: self.checked(i))
self.radio.append(elem) self.radio.append(elem)
width = max(self.page_count * 20 + 30, (self.page_size + 5) * 8 // 10) width = max(self.page_count * 20 + 30, (self.page_size + 5) * 8 // 10)
self.setMaximumSize(width, 200) self.setMaximumSize(width, 200)
@ -142,7 +162,7 @@ class SmileyWindow(QtWidgets.QWidget):
for i in range(self.page_size): # pages - radio buttons for i in range(self.page_size): # pages - radio buttons
b = QtWidgets.QPushButton(self) b = QtWidgets.QPushButton(self)
b.setGeometry(QtCore.QRect((i // 8) * 20 + 5, (i % 8) * 20, 20, 20)) b.setGeometry(QtCore.QRect((i // 8) * 20 + 5, (i % 8) * 20, 20, 20))
b.clicked.connect(lambda c, t=i: self.clicked(t)) b.clicked.connect(lambda i=i: self.clicked(i))
self.buttons.append(b) self.buttons.append(b)
self.checked(0) self.checked(0)
@ -187,8 +207,8 @@ class DropdownMenu(QtWidgets.QWidget):
super(DropdownMenu, self).__init__(parent) super(DropdownMenu, self).__init__(parent)
self.installEventFilter(self) self.installEventFilter(self)
self.setWindowFlags(QtCore.Qt.FramelessWindowHint) self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
self.setMaximumSize(120, 120) self.setMaximumSize(180, 120)
self.setMinimumSize(120, 120) self.setMinimumSize(180, 120)
self.screenshotButton = QRightClickButton(self) self.screenshotButton = QRightClickButton(self)
self.screenshotButton.setGeometry(QtCore.QRect(0, 60, 60, 60)) self.screenshotButton.setGeometry(QtCore.QRect(0, 60, 60, 60))
self.screenshotButton.setObjectName("screenshotButton") self.screenshotButton.setObjectName("screenshotButton")
@ -197,9 +217,15 @@ class DropdownMenu(QtWidgets.QWidget):
self.fileTransferButton.setGeometry(QtCore.QRect(60, 60, 60, 60)) self.fileTransferButton.setGeometry(QtCore.QRect(60, 60, 60, 60))
self.fileTransferButton.setObjectName("fileTransferButton") self.fileTransferButton.setObjectName("fileTransferButton")
self.audioMessageButton = QtWidgets.QPushButton(self)
self.audioMessageButton.setGeometry(QtCore.QRect(120, 60, 60, 60))
self.smileyButton = QtWidgets.QPushButton(self) self.smileyButton = QtWidgets.QPushButton(self)
self.smileyButton.setGeometry(QtCore.QRect(0, 0, 60, 60)) self.smileyButton.setGeometry(QtCore.QRect(0, 0, 60, 60))
self.videoMessageButton = QtWidgets.QPushButton(self)
self.videoMessageButton.setGeometry(QtCore.QRect(120, 0, 60, 60))
self.stickerButton = QtWidgets.QPushButton(self) self.stickerButton = QtWidgets.QPushButton(self)
self.stickerButton.setGeometry(QtCore.QRect(60, 0, 60, 60)) self.stickerButton.setGeometry(QtCore.QRect(60, 0, 60, 60))
@ -207,17 +233,22 @@ class DropdownMenu(QtWidgets.QWidget):
icon = QtGui.QIcon(pixmap) icon = QtGui.QIcon(pixmap)
self.fileTransferButton.setIcon(icon) self.fileTransferButton.setIcon(icon)
self.fileTransferButton.setIconSize(QtCore.QSize(50, 50)) self.fileTransferButton.setIconSize(QtCore.QSize(50, 50))
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/screenshot.png') pixmap = QtGui.QPixmap(util.curr_directory() + '/images/screenshot.png')
icon = QtGui.QIcon(pixmap) icon = QtGui.QIcon(pixmap)
self.screenshotButton.setIcon(icon) self.screenshotButton.setIcon(icon)
self.screenshotButton.setIconSize(QtCore.QSize(50, 60)) self.screenshotButton.setIconSize(QtCore.QSize(50, 60))
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/audio_message.png')
icon = QtGui.QIcon(pixmap)
self.audioMessageButton.setIcon(icon)
self.audioMessageButton.setIconSize(QtCore.QSize(50, 50))
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/smiley.png') pixmap = QtGui.QPixmap(util.curr_directory() + '/images/smiley.png')
icon = QtGui.QIcon(pixmap) icon = QtGui.QIcon(pixmap)
self.smileyButton.setIcon(icon) self.smileyButton.setIcon(icon)
self.smileyButton.setIconSize(QtCore.QSize(50, 50)) self.smileyButton.setIconSize(QtCore.QSize(50, 50))
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/video_message.png')
icon = QtGui.QIcon(pixmap)
self.videoMessageButton.setIcon(icon)
self.videoMessageButton.setIconSize(QtCore.QSize(55, 55))
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/sticker.png') pixmap = QtGui.QPixmap(util.curr_directory() + '/images/sticker.png')
icon = QtGui.QIcon(pixmap) icon = QtGui.QIcon(pixmap)
self.stickerButton.setIcon(icon) self.stickerButton.setIcon(icon)
@ -225,6 +256,8 @@ class DropdownMenu(QtWidgets.QWidget):
self.screenshotButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send screenshot")) self.screenshotButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send screenshot"))
self.fileTransferButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send file")) self.fileTransferButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send file"))
self.audioMessageButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send audio message"))
self.videoMessageButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send video message"))
self.smileyButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Add smiley")) self.smileyButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Add smiley"))
self.stickerButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send sticker")) self.stickerButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send sticker"))
@ -237,7 +270,7 @@ class DropdownMenu(QtWidgets.QWidget):
def leaveEvent(self, event): def leaveEvent(self, event):
self.close() self.close()
def eventFilter(self, obj, event): def eventFilter(self, object, event):
if event.type() == QtCore.QEvent.WindowDeactivate: if event.type() == QtCore.QEvent.WindowDeactivate:
self.close() self.close()
return False return False
@ -309,34 +342,31 @@ class WelcomeScreen(CenteredWidget):
text = QtWidgets.QApplication.translate('WelcomeScreen', 'Press Esc if you want hide app to tray.') text = QtWidgets.QApplication.translate('WelcomeScreen', 'Press Esc if you want hide app to tray.')
elif num == 1: elif num == 1:
text = QtWidgets.QApplication.translate('WelcomeScreen', text = QtWidgets.QApplication.translate('WelcomeScreen',
'Right click on screenshot button hides app to tray during screenshot.') 'Right click on screenshot button hides app to tray during screenshot.')
elif num == 2: elif num == 2:
text = QtWidgets.QApplication.translate('WelcomeScreen', text = QtWidgets.QApplication.translate('WelcomeScreen',
'You can use Tox over Tor. For more info read <a href="https://wiki.tox.chat/users/tox_over_tor_tot">this post</a>') 'You can use Tox over Tor. For more info read <a href="https://wiki.tox.chat/users/tox_over_tor_tot">this post</a>')
elif num == 3: elif num == 3:
text = QtWidgets.QApplication.translate('WelcomeScreen', text = QtWidgets.QApplication.translate('WelcomeScreen',
'Use Settings -> Interface to customize interface.') 'Use Settings -> Interface to customize interface.')
elif num == 4: elif num == 4:
text = QtWidgets.QApplication.translate('WelcomeScreen', text = QtWidgets.QApplication.translate('WelcomeScreen',
'Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings.') 'Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings.')
elif num == 5: elif num == 5:
text = QtWidgets.QApplication.translate('WelcomeScreen', text = QtWidgets.QApplication.translate('WelcomeScreen',
'Since v0.1.3 Toxygen supports plugins. <a href="https://github.com/toxygen-project/toxygen/blob/master/docs/plugins.md">Read more</a>') 'Since v0.1.3 Toxygen supports plugins. <a href="https://github.com/xveduk/toxygen/blob/master/docs/plugins.md">Read more</a>')
elif num == 6: elif num in (6, 7):
text = QtWidgets.QApplication.translate('WelcomeScreen', text = QtWidgets.QApplication.translate('WelcomeScreen',
'Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later.') 'Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later.')
elif num == 7:
text = QtWidgets.QApplication.translate('WelcomeScreen',
'New in Toxygen 0.4.1:<br>Downloading nodes from tox.chat<br>Bug fixes')
elif num == 8: elif num == 8:
text = QtWidgets.QApplication.translate('WelcomeScreen', text = QtWidgets.QApplication.translate('WelcomeScreen',
'Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu') 'Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu')
elif num == 9: elif num == 9:
text = QtWidgets.QApplication.translate('WelcomeScreen', text = QtWidgets.QApplication.translate('WelcomeScreen',
'Use right click on inline image to save it') 'Use right click on inline image to save it')
else: else:
text = QtWidgets.QApplication.translate('WelcomeScreen', text = QtWidgets.QApplication.translate('WelcomeScreen',
'Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam.') 'Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam.')
self.text.setHtml(text) self.text.setHtml(text)
self.checkbox.stateChanged.connect(self.not_show) self.checkbox.stateChanged.connect(self.not_show)
QtCore.QTimer.singleShot(1000, self.show) QtCore.QTimer.singleShot(1000, self.show)

View File

@ -2,7 +2,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
from settings import * from settings import *
from profile import Profile from profile import Profile
from util import curr_directory, copy from util import curr_directory, copy
from widgets import CenteredWidget, DataLabel, LineEdit, RubberBandWindow from widgets import CenteredWidget, DataLabel, LineEdit
import pyaudio import pyaudio
import toxes import toxes
import plugin_support import plugin_support
@ -247,12 +247,12 @@ class ProfileSettings(CenteredWidget):
def set_avatar(self): def set_avatar(self):
choose = QtWidgets.QApplication.translate("ProfileSettingsForm", "Choose avatar") choose = QtWidgets.QApplication.translate("ProfileSettingsForm", "Choose avatar")
name = QtWidgets.QFileDialog.getOpenFileName(self, choose, None, 'Images (*.png)', name = QtWidgets.QFileDialog.getOpenFileName(self, choose, None, None, 'Images (*.png)',
options=QtWidgets.QFileDialog.DontUseNativeDialog) QtWidgets.QFileDialog.DontUseNativeDialog)
if name[0]: if name[0]:
bitmap = QtGui.QPixmap(name[0]) bitmap = QtGui.QPixmap(name[0])
bitmap.scaled(QtCore.QSize(128, 128), aspectRatioMode=QtCore.Qt.KeepAspectRatio, bitmap.scaled(128, 128, QtCore.Qt.KeepAspectRatio,
transformMode=QtCore.Qt.SmoothTransformation) QtCore.Qt.SmoothTransformation)
byte_array = QtCore.QByteArray() byte_array = QtCore.QByteArray()
buffer = QtCore.QBuffer(byte_array) buffer = QtCore.QBuffer(byte_array)
@ -261,8 +261,9 @@ class ProfileSettings(CenteredWidget):
Profile.get_instance().set_avatar(bytes(byte_array.data())) Profile.get_instance().set_avatar(bytes(byte_array.data()))
def export_profile(self): def export_profile(self):
directory = QtWidgets.QFileDialog.getExistingDirectory(self, '', curr_directory(), directory = QtWidgets.QFileDialog.getExistingDirectory(self, '', curr_directory() + '/',
QtWidgets.QFileDialog.DontUseNativeDialog) + '/' QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog)
if directory != '/': if directory != '/':
reply = QtWidgets.QMessageBox.question(None, reply = QtWidgets.QMessageBox.question(None,
QtWidgets.QApplication.translate("ProfileSettingsForm", QtWidgets.QApplication.translate("ProfileSettingsForm",
@ -294,10 +295,10 @@ class NetworkSettings(CenteredWidget):
def initUI(self): def initUI(self):
self.setObjectName("NetworkSettings") self.setObjectName("NetworkSettings")
self.resize(300, 400) self.resize(300, 330)
self.setMinimumSize(QtCore.QSize(300, 400)) self.setMinimumSize(QtCore.QSize(300, 330))
self.setMaximumSize(QtCore.QSize(300, 400)) self.setMaximumSize(QtCore.QSize(300, 330))
self.setBaseSize(QtCore.QSize(300, 400)) self.setBaseSize(QtCore.QSize(300, 330))
self.ipv = QtWidgets.QCheckBox(self) self.ipv = QtWidgets.QCheckBox(self)
self.ipv.setGeometry(QtCore.QRect(20, 10, 97, 22)) self.ipv.setGeometry(QtCore.QRect(20, 10, 97, 22))
self.ipv.setObjectName("ipv") self.ipv.setObjectName("ipv")
@ -332,9 +333,6 @@ class NetworkSettings(CenteredWidget):
self.warning = QtWidgets.QLabel(self) self.warning = QtWidgets.QLabel(self)
self.warning.setGeometry(QtCore.QRect(5, 270, 290, 60)) self.warning.setGeometry(QtCore.QRect(5, 270, 290, 60))
self.warning.setStyleSheet('QLabel { color: #BC1C1C; }') self.warning.setStyleSheet('QLabel { color: #BC1C1C; }')
self.nodes = QtWidgets.QCheckBox(self)
self.nodes.setGeometry(QtCore.QRect(20, 350, 270, 22))
self.nodes.setChecked(settings['download_nodes_list'])
self.retranslateUi() self.retranslateUi()
self.proxy.stateChanged.connect(lambda x: self.activate()) self.proxy.stateChanged.connect(lambda x: self.activate())
self.activate() self.activate()
@ -349,7 +347,6 @@ class NetworkSettings(CenteredWidget):
self.label_2.setText(QtWidgets.QApplication.translate("Form", "Port:")) self.label_2.setText(QtWidgets.QApplication.translate("Form", "Port:"))
self.reconnect.setText(QtWidgets.QApplication.translate("NetworkSettings", "Restart TOX core")) self.reconnect.setText(QtWidgets.QApplication.translate("NetworkSettings", "Restart TOX core"))
self.http.setText(QtWidgets.QApplication.translate("Form", "HTTP")) self.http.setText(QtWidgets.QApplication.translate("Form", "HTTP"))
self.nodes.setText(QtWidgets.QApplication.translate("Form", "Download nodes list from tox.chat"))
self.warning.setText(QtWidgets.QApplication.translate("Form", "WARNING:\nusing proxy with enabled UDP\ncan produce IP leak")) self.warning.setText(QtWidgets.QApplication.translate("Form", "WARNING:\nusing proxy with enabled UDP\ncan produce IP leak"))
def activate(self): def activate(self):
@ -366,7 +363,6 @@ class NetworkSettings(CenteredWidget):
settings['proxy_type'] = 2 - int(self.http.isChecked()) if self.proxy.isChecked() else 0 settings['proxy_type'] = 2 - int(self.http.isChecked()) if self.proxy.isChecked() else 0
settings['proxy_host'] = str(self.proxyip.text()) settings['proxy_host'] = str(self.proxyip.text())
settings['proxy_port'] = int(self.proxyport.text()) settings['proxy_port'] = int(self.proxyport.text())
settings['download_nodes_list'] = self.nodes.isChecked()
settings.save() settings.save()
# recreate tox instance # recreate tox instance
Profile.get_instance().reset(self.reset) Profile.get_instance().reset(self.reset)
@ -498,7 +494,8 @@ class PrivacySettings(CenteredWidget):
settings.save() settings.save()
def new_path(self): def new_path(self):
directory = QtWidgets.QFileDialog.getExistingDirectory(options=QtWidgets.QFileDialog.DontUseNativeDialog) + '/' directory = QtWidgets.QFileDialog.getExistingDirectory(self, '', curr_directory() + '/',
QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog) + '/'
if directory != '/': if directory != '/':
self.path.setPlainText(directory) self.path.setPlainText(directory)
@ -513,17 +510,15 @@ class NotificationsSettings(CenteredWidget):
def initUI(self): def initUI(self):
self.setObjectName("notificationsForm") self.setObjectName("notificationsForm")
self.resize(350, 210) self.resize(350, 180)
self.setMinimumSize(QtCore.QSize(350, 210)) self.setMinimumSize(QtCore.QSize(350, 180))
self.setMaximumSize(QtCore.QSize(350, 210)) self.setMaximumSize(QtCore.QSize(350, 180))
self.enableNotifications = QtWidgets.QCheckBox(self) self.enableNotifications = QtWidgets.QCheckBox(self)
self.enableNotifications.setGeometry(QtCore.QRect(10, 20, 340, 18)) self.enableNotifications.setGeometry(QtCore.QRect(10, 20, 340, 18))
self.callsSound = QtWidgets.QCheckBox(self) self.callsSound = QtWidgets.QCheckBox(self)
self.callsSound.setGeometry(QtCore.QRect(10, 170, 340, 18)) self.callsSound.setGeometry(QtCore.QRect(10, 120, 340, 18))
self.soundNotifications = QtWidgets.QCheckBox(self) self.soundNotifications = QtWidgets.QCheckBox(self)
self.soundNotifications.setGeometry(QtCore.QRect(10, 70, 340, 18)) self.soundNotifications.setGeometry(QtCore.QRect(10, 70, 340, 18))
self.groupNotifications = QtWidgets.QCheckBox(self)
self.groupNotifications.setGeometry(QtCore.QRect(10, 120, 340, 18))
font = QtGui.QFont() font = QtGui.QFont()
s = Settings.get_instance() s = Settings.get_instance()
font.setFamily(s['font']) font.setFamily(s['font'])
@ -531,10 +526,8 @@ class NotificationsSettings(CenteredWidget):
self.callsSound.setFont(font) self.callsSound.setFont(font)
self.soundNotifications.setFont(font) self.soundNotifications.setFont(font)
self.enableNotifications.setFont(font) self.enableNotifications.setFont(font)
self.groupNotifications.setFont(font)
self.enableNotifications.setChecked(s['notifications']) self.enableNotifications.setChecked(s['notifications'])
self.soundNotifications.setChecked(s['sound_notifications']) self.soundNotifications.setChecked(s['sound_notifications'])
self.groupNotifications.setChecked(s['group_notifications'])
self.callsSound.setChecked(s['calls_sound']) self.callsSound.setChecked(s['calls_sound'])
self.retranslateUi() self.retranslateUi()
QtCore.QMetaObject.connectSlotsByName(self) QtCore.QMetaObject.connectSlotsByName(self)
@ -542,7 +535,6 @@ class NotificationsSettings(CenteredWidget):
def retranslateUi(self): def retranslateUi(self):
self.setWindowTitle(QtWidgets.QApplication.translate("notificationsForm", "Notification settings")) self.setWindowTitle(QtWidgets.QApplication.translate("notificationsForm", "Notification settings"))
self.enableNotifications.setText(QtWidgets.QApplication.translate("notificationsForm", "Enable notifications")) self.enableNotifications.setText(QtWidgets.QApplication.translate("notificationsForm", "Enable notifications"))
self.groupNotifications.setText(QtWidgets.QApplication.translate("notificationsForm", "Notify about all messages in groups"))
self.callsSound.setText(QtWidgets.QApplication.translate("notificationsForm", "Enable call\'s sound")) self.callsSound.setText(QtWidgets.QApplication.translate("notificationsForm", "Enable call\'s sound"))
self.soundNotifications.setText(QtWidgets.QApplication.translate("notificationsForm", "Enable sound notifications")) self.soundNotifications.setText(QtWidgets.QApplication.translate("notificationsForm", "Enable sound notifications"))
@ -550,7 +542,6 @@ class NotificationsSettings(CenteredWidget):
settings = Settings.get_instance() settings = Settings.get_instance()
settings['notifications'] = self.enableNotifications.isChecked() settings['notifications'] = self.enableNotifications.isChecked()
settings['sound_notifications'] = self.soundNotifications.isChecked() settings['sound_notifications'] = self.soundNotifications.isChecked()
settings['group_notifications'] = self.groupNotifications.isChecked()
settings['calls_sound'] = self.callsSound.isChecked() settings['calls_sound'] = self.callsSound.isChecked()
settings.save() settings.save()
@ -576,10 +567,11 @@ class InterfaceSettings(CenteredWidget):
self.label.setFont(font) self.label.setFont(font)
self.themeSelect = QtWidgets.QComboBox(self) self.themeSelect = QtWidgets.QComboBox(self)
self.themeSelect.setGeometry(QtCore.QRect(30, 40, 120, 30)) self.themeSelect.setGeometry(QtCore.QRect(30, 40, 120, 30))
self.themeSelect.addItems(list(settings.built_in_themes().keys())) list_of_themes = ['dark']
self.themeSelect.addItems(list_of_themes)
theme = settings['theme'] theme = settings['theme']
if theme in settings.built_in_themes().keys(): if theme in list_of_themes:
index = list(settings.built_in_themes().keys()).index(theme) index = list_of_themes.index(theme)
else: else:
index = 0 index = 0
self.themeSelect.setCurrentIndex(index) self.themeSelect.setCurrentIndex(index)
@ -616,7 +608,7 @@ class InterfaceSettings(CenteredWidget):
self.messages_font_size_label.setFont(font) self.messages_font_size_label.setFont(font)
self.messages_font_size = QtWidgets.QComboBox(self) self.messages_font_size = QtWidgets.QComboBox(self)
self.messages_font_size.setGeometry(QtCore.QRect(30, 330, 160, 30)) self.messages_font_size.setGeometry(QtCore.QRect(30, 330, 160, 30))
self.messages_font_size.addItems([str(x) for x in range(10, 25)]) self.messages_font_size.addItems([str(x) for x in range(10, 19)])
self.messages_font_size.setCurrentIndex(settings['message_font_size'] - 10) self.messages_font_size.setCurrentIndex(settings['message_font_size'] - 10)
self.unread = QtWidgets.QPushButton(self) self.unread = QtWidgets.QPushButton(self)
@ -668,10 +660,10 @@ class InterfaceSettings(CenteredWidget):
def import_st(self): def import_st(self):
directory = QtWidgets.QFileDialog.getExistingDirectory(self, directory = QtWidgets.QFileDialog.getExistingDirectory(self,
QtWidgets.QApplication.translate("MainWindow", QtWidgets.QApplication.translate("MainWindow",
'Choose folder with sticker pack'), 'Choose folder with sticker pack'),
curr_directory(), curr_directory(),
QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog) QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog)
if directory: if directory:
src = directory + '/' src = directory + '/'
@ -714,14 +706,6 @@ class InterfaceSettings(CenteredWidget):
def closeEvent(self, event): def closeEvent(self, event):
settings = Settings.get_instance() settings = Settings.get_instance()
settings['theme'] = str(self.themeSelect.currentText()) settings['theme'] = str(self.themeSelect.currentText())
try:
theme = settings['theme']
app = QtWidgets.QApplication.instance()
with open(curr_directory() + settings.built_in_themes()[theme]) as fl:
style = fl.read()
app.setStyleSheet(style)
except IsADirectoryError:
app.setStyleSheet('') # for default style
settings['smileys'] = self.smileys.isChecked() settings['smileys'] = self.smileys.isChecked()
restart = False restart = False
if settings['mirror_mode'] != self.mirror_mode.isChecked(): if settings['mirror_mode'] != self.mirror_mode.isChecked():
@ -813,130 +797,6 @@ class AudioSettings(CenteredWidget):
settings.save() settings.save()
class DesktopAreaSelectionWindow(RubberBandWindow):
def mouseReleaseEvent(self, event):
if self.rubberband.isVisible():
self.rubberband.hide()
rect = self.rubberband.geometry()
width, height = rect.width(), rect.height()
if width >= 8 and height >= 8:
self.parent.save(rect.x(), rect.y(), width - (width % 4), height - (height % 4))
self.close()
class VideoSettings(CenteredWidget):
"""
Audio calls settings form
"""
def __init__(self):
super().__init__()
self.initUI()
self.retranslateUi()
self.center()
self.desktopAreaSelection = None
def initUI(self):
self.setObjectName("videoSettingsForm")
self.resize(400, 120)
self.setMinimumSize(QtCore.QSize(400, 120))
self.setMaximumSize(QtCore.QSize(400, 120))
self.in_label = QtWidgets.QLabel(self)
self.in_label.setGeometry(QtCore.QRect(25, 5, 350, 20))
settings = Settings.get_instance()
font = QtGui.QFont()
font.setPointSize(16)
font.setBold(True)
font.setFamily(settings['font'])
self.in_label.setFont(font)
self.video_size = QtWidgets.QComboBox(self)
self.video_size.setGeometry(QtCore.QRect(25, 70, 350, 30))
self.input = QtWidgets.QComboBox(self)
self.input.setGeometry(QtCore.QRect(25, 30, 350, 30))
self.input.currentIndexChanged.connect(self.selectionChanged)
self.button = QtWidgets.QPushButton(self)
self.button.clicked.connect(self.button_clicked)
self.button.setGeometry(QtCore.QRect(25, 70, 350, 30))
import cv2
self.devices = [-1]
screen = QtWidgets.QApplication.primaryScreen()
size = screen.size()
self.frame_max_sizes = [(size.width(), size.height())]
desktop = QtWidgets.QApplication.translate("videoSettingsForm", "Desktop")
self.input.addItem(desktop)
for i in range(10):
v = cv2.VideoCapture(i)
if v.isOpened():
v.set(cv2.CAP_PROP_FRAME_WIDTH, 10000)
v.set(cv2.CAP_PROP_FRAME_HEIGHT, 10000)
width = int(v.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(v.get(cv2.CAP_PROP_FRAME_HEIGHT))
del v
self.devices.append(i)
self.frame_max_sizes.append((width, height))
self.input.addItem('Device #' + str(i))
try:
index = self.devices.index(settings.video['device'])
self.input.setCurrentIndex(index)
except:
print('Video devices error!')
def retranslateUi(self):
self.setWindowTitle(QtWidgets.QApplication.translate("videoSettingsForm", "Video settings"))
self.in_label.setText(QtWidgets.QApplication.translate("videoSettingsForm", "Device:"))
self.button.setText(QtWidgets.QApplication.translate("videoSettingsForm", "Select region"))
def button_clicked(self):
self.desktopAreaSelection = DesktopAreaSelectionWindow(self)
def closeEvent(self, event):
if self.input.currentIndex() == 0:
return
try:
settings = Settings.get_instance()
settings.video['device'] = self.devices[self.input.currentIndex()]
text = self.video_size.currentText()
settings.video['width'] = int(text.split(' ')[0])
settings.video['height'] = int(text.split(' ')[-1])
settings.save()
except Exception as ex:
print('Saving video settings error: ' + str(ex))
def save(self, x, y, width, height):
self.desktopAreaSelection = None
settings = Settings.get_instance()
settings.video['device'] = -1
settings.video['width'] = width
settings.video['height'] = height
settings.video['x'] = x
settings.video['y'] = y
settings.save()
def selectionChanged(self):
if self.input.currentIndex() == 0:
self.button.setVisible(True)
self.video_size.setVisible(False)
else:
self.button.setVisible(False)
self.video_size.setVisible(True)
width, height = self.frame_max_sizes[self.input.currentIndex()]
self.video_size.clear()
dims = [
(320, 240),
(640, 360),
(640, 480),
(720, 480),
(1280, 720),
(1920, 1080),
(2560, 1440)
]
for w, h in dims:
if w <= width and h <= height:
self.video_size.addItem(str(w) + ' * ' + str(h))
class PluginsSettings(CenteredWidget): class PluginsSettings(CenteredWidget):
""" """
Plugins settings form Plugins settings form

View File

@ -5,9 +5,7 @@ MESSAGE_TYPE = {
'ACTION': 1, 'ACTION': 1,
'FILE_TRANSFER': 2, 'FILE_TRANSFER': 2,
'INLINE': 3, 'INLINE': 3,
'INFO_MESSAGE': 4, 'INFO_MESSAGE': 4
'GC_TEXT': 5,
'GC_ACTION': 6
} }
@ -41,16 +39,6 @@ class TextMessage(Message):
return self._message, self._owner, self._time, self._type return self._message, self._owner, self._time, self._type
class GroupChatMessage(TextMessage):
def __init__(self, message, owner, time, message_type, name):
super().__init__(message, owner, time, message_type)
self._user_name = name
def get_data(self):
return self._message, self._owner, self._time, self._type, self._user_name
class TransferMessage(Message): class TransferMessage(Message):
""" """
Message with info about file transfer Message with info about file transfer

File diff suppressed because one or more lines are too long

View File

@ -97,13 +97,10 @@ class PluginLoader(util.Singleton):
""" """
result = [] result = []
for data in self._plugins.values(): for data in self._plugins.values():
try: result.append([data[0].get_name(), # plugin full name
result.append([data[0].get_name(), # plugin full name data[1], # is enabled
data[1], # is enabled data[0].get_description(), # plugin description
data[0].get_description(), # plugin description data[0].get_short_name()]) # key - short unique name
data[0].get_short_name()]) # key - short unique name
except:
continue
return result return result
def plugin_window(self, key): def plugin_window(self, key):

View File

@ -14,10 +14,6 @@ import avwidgets
import plugin_support import plugin_support
import basecontact import basecontact
import items_factory import items_factory
import cv2
import threading
from group_chat import *
import re
class Profile(basecontact.BaseContact, Singleton): class Profile(basecontact.BaseContact, Singleton):
@ -40,7 +36,6 @@ class Profile(basecontact.BaseContact, Singleton):
self._tox = tox self._tox = tox
self._file_transfers = {} # dict of file transfers. key - tuple (friend_number, file_number) self._file_transfers = {} # dict of file transfers. key - tuple (friend_number, file_number)
self._call = calls.AV(tox.AV) # object with data about calls self._call = calls.AV(tox.AV) # object with data about calls
self._call_widgets = {} # dict of incoming call widgets
self._incoming_calls = set() self._incoming_calls = set()
self._load_history = True self._load_history = True
self._waiting_for_reconnection = False self._waiting_for_reconnection = False
@ -72,8 +67,6 @@ class Profile(basecontact.BaseContact, Singleton):
friend = Friend(message_getter, i, name, status_message, item, tox_id) friend = Friend(message_getter, i, name, status_message, item, tox_id)
friend.set_alias(alias) friend.set_alias(alias)
self._contacts.append(friend) self._contacts.append(friend)
if len(self._contacts):
self.set_active(0)
self.filtration_and_sorting(self._sorting) self.filtration_and_sorting(self._sorting)
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
@ -92,7 +85,6 @@ class Profile(basecontact.BaseContact, Singleton):
if status is not None: if status is not None:
self._tox.self_set_status(status) self._tox.self_set_status(status)
elif not self._waiting_for_reconnection: elif not self._waiting_for_reconnection:
self._waiting_for_reconnection = True
QtCore.QTimer.singleShot(50000, self.reconnect) QtCore.QTimer.singleShot(50000, self.reconnect)
def set_name(self, value): def set_name(self, value):
@ -133,7 +125,6 @@ class Profile(basecontact.BaseContact, Singleton):
filter_str = filter_str.lower() filter_str = filter_str.lower()
settings = Settings.get_instance() settings = Settings.get_instance()
number = self.get_active_number() number = self.get_active_number()
is_friend = self.is_active_a_friend()
if sorting > 1: if sorting > 1:
if sorting & 2: if sorting & 2:
self._contacts = sorted(self._contacts, key=lambda x: int(x.status is not None), reverse=True) self._contacts = sorted(self._contacts, key=lambda x: int(x.status is not None), reverse=True)
@ -169,7 +160,7 @@ class Profile(basecontact.BaseContact, Singleton):
self._sorting, self._filter_string = sorting, filter_str self._sorting, self._filter_string = sorting, filter_str
settings['sorting'] = self._sorting settings['sorting'] = self._sorting
settings.save() settings.save()
self.set_active_by_number_and_type(number, is_friend) self.set_active_by_number(number)
def update_filtration(self): def update_filtration(self):
""" """
@ -182,7 +173,7 @@ class Profile(basecontact.BaseContact, Singleton):
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
def get_friend_by_number(self, num): def get_friend_by_number(self, num):
return list(filter(lambda x: x.number == num and type(x) is Friend, self._contacts))[0] return list(filter(lambda x: x.number == num, self._contacts))[0]
def get_friend(self, num): def get_friend(self, num):
if num < 0 or num >= len(self._contacts): if num < 0 or num >= len(self._contacts):
@ -209,7 +200,6 @@ class Profile(basecontact.BaseContact, Singleton):
if value == -1: # all friends were deleted if value == -1: # all friends were deleted
self._screen.account_name.setText('') self._screen.account_name.setText('')
self._screen.account_status.setText('') self._screen.account_status.setText('')
self._screen.account_status.setToolTip('')
self._active_friend = -1 self._active_friend = -1
self._screen.account_avatar.setHidden(True) self._screen.account_avatar.setHidden(True)
self._messages.clear() self._messages.clear()
@ -257,15 +247,12 @@ class Profile(basecontact.BaseContact, Singleton):
print('Incoming not started transfer - no info found') print('Incoming not started transfer - no info found')
elif message.get_type() == MESSAGE_TYPE['INLINE']: # inline elif message.get_type() == MESSAGE_TYPE['INLINE']: # inline
self.create_inline_item(message.get_data()) self.create_inline_item(message.get_data())
elif message.get_type() < 5: # info message else: # info message
data = message.get_data() data = message.get_data()
self.create_message_item(data[0], self.create_message_item(data[0],
data[2], data[2],
'', '',
data[3]) data[3])
else:
data = message.get_data()
self.create_gc_message_item(data[0], data[2], data[1], data[4], data[3])
self._messages.scrollToBottom() self._messages.scrollToBottom()
self._load_history = True self._load_history = True
if value in self._call: if value in self._call:
@ -279,11 +266,7 @@ class Profile(basecontact.BaseContact, Singleton):
self._screen.account_name.setText(friend.name) self._screen.account_name.setText(friend.name)
self._screen.account_status.setText(friend.status_message) self._screen.account_status.setText(friend.status_message)
self._screen.account_status.setToolTip(friend.get_full_status()) avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(friend.tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
if friend.tox_id is None:
avatar_path = curr_directory() + '/images/group.png'
else:
avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(friend.tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
if not os.path.isfile(avatar_path): # load default image if not os.path.isfile(avatar_path): # load default image
avatar_path = curr_directory() + '/images/avatar.png' avatar_path = curr_directory() + '/images/avatar.png'
os.chdir(os.path.dirname(avatar_path)) os.chdir(os.path.dirname(avatar_path))
@ -295,10 +278,9 @@ class Profile(basecontact.BaseContact, Singleton):
log('Error in set active: ' + str(ex)) log('Error in set active: ' + str(ex))
raise raise
def set_active_by_number_and_type(self, number, is_friend): def set_active_by_number(self, number):
for i in range(len(self._contacts)): for i in range(len(self._contacts)):
c = self._contacts[i] if self._contacts[i].number == number:
if c.number == number and (type(c) is Friend == is_friend):
self._active_friend = i self._active_friend = i
break break
@ -361,7 +343,7 @@ class Profile(basecontact.BaseContact, Singleton):
elif data[1] == friend_number and not data[2]: elif data[1] == friend_number and not data[2]:
self.send_file(data[0], friend_number, True, key) self.send_file(data[0], friend_number, True, key)
del self._paused_file_transfers[key] del self._paused_file_transfers[key]
if friend_number == self.get_active_number() and self.is_active_a_friend(): if friend_number == self.get_active_number():
self.update() self.update()
except Exception as ex: except Exception as ex:
print('Exception in file sending: ' + str(ex)) print('Exception in file sending: ' + str(ex))
@ -403,7 +385,7 @@ class Profile(basecontact.BaseContact, Singleton):
""" """
Display incoming typing notification Display incoming typing notification
""" """
if friend_number == self.get_active_number() and self.is_active_a_friend(): if friend_number == self.get_active_number():
self._screen.typing.setVisible(typing) self._screen.typing.setVisible(typing)
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
@ -459,7 +441,7 @@ class Profile(basecontact.BaseContact, Singleton):
:param message_type: message type - plain text or action message (/me) :param message_type: message type - plain text or action message (/me)
:param message: text of message :param message: text of message
""" """
if friend_num == self.get_active_number()and self.is_active_a_friend(): # add message to list if friend_num == self.get_active_number(): # add message to list
t = time.time() t = time.time()
self.create_message_item(message, t, MESSAGE_OWNER['FRIEND'], message_type) self.create_message_item(message, t, MESSAGE_OWNER['FRIEND'], message_type)
self._messages.scrollToBottom() self._messages.scrollToBottom()
@ -479,9 +461,6 @@ class Profile(basecontact.BaseContact, Singleton):
:param text: message text :param text: message text
:param friend_num: num of friend :param friend_num: num of friend
""" """
if not self.is_active_a_friend():
self.send_gc_message(text)
return
if friend_num is None: if friend_num is None:
friend_num = self.get_active_number() friend_num = self.get_active_number()
if text.startswith('/plugin '): if text.startswith('/plugin '):
@ -497,8 +476,8 @@ class Profile(basecontact.BaseContact, Singleton):
friend.inc_receipts() friend.inc_receipts()
if friend.status is not None: if friend.status is not None:
self.split_and_send(friend.number, message_type, text.encode('utf-8')) self.split_and_send(friend.number, message_type, text.encode('utf-8'))
t = time.time() if friend.number == self.get_active_number():
if friend.number == self.get_active_number() and self.is_active_a_friend(): t = time.time()
self.create_message_item(text, t, MESSAGE_OWNER['NOT_SENT'], message_type) self.create_message_item(text, t, MESSAGE_OWNER['NOT_SENT'], message_type)
self._screen.messageEdit.clear() self._screen.messageEdit.clear()
self._messages.scrollToBottom() self._messages.scrollToBottom()
@ -521,7 +500,7 @@ class Profile(basecontact.BaseContact, Singleton):
s = Settings.get_instance() s = Settings.get_instance()
if hasattr(self, '_history'): if hasattr(self, '_history'):
if s['save_history']: if s['save_history']:
for friend in filter(lambda x: type(x) is Friend, self._contacts): for friend in self._contacts:
if not self._history.friend_exists_in_db(friend.tox_id): if not self._history.friend_exists_in_db(friend.tox_id):
self._history.add_friend_to_db(friend.tox_id) self._history.add_friend_to_db(friend.tox_id)
if not s['save_unsent_only']: if not s['save_unsent_only']:
@ -588,16 +567,13 @@ class Profile(basecontact.BaseContact, Singleton):
print('Incoming not started transfer - no info found') print('Incoming not started transfer - no info found')
elif message.get_type() == MESSAGE_TYPE['INLINE']: # inline image elif message.get_type() == MESSAGE_TYPE['INLINE']: # inline image
self.create_inline_item(message.get_data(), False) self.create_inline_item(message.get_data(), False)
elif message.get_type() < 5: # info message else: # info message
data = message.get_data() data = message.get_data()
self.create_message_item(data[0], self.create_message_item(data[0],
data[2], data[2],
'', '',
data[3], data[3],
False) False)
else:
data = message.get_data()
self.create_gc_message_item(data[0], data[2], data[1], data[4], data[3])
self._load_history = True self._load_history = True
def export_db(self, directory): def export_db(self, directory):
@ -656,16 +632,6 @@ class Profile(basecontact.BaseContact, Singleton):
return self._factory.message_item(text, time, name, owner != MESSAGE_OWNER['NOT_SENT'], return self._factory.message_item(text, time, name, owner != MESSAGE_OWNER['NOT_SENT'],
message_type, append, pixmap) message_type, append, pixmap)
def create_gc_message_item(self, text, time, owner, name, message_type, append=True):
pixmap = None
if self._show_avatars:
if owner == MESSAGE_OWNER['FRIEND']:
pixmap = self.get_curr_friend().get_pixmap()
else:
pixmap = self.get_pixmap()
return self._factory.message_item(text, time, name, True,
message_type - 5, append, pixmap)
def create_file_transfer_item(self, tm, append=True): def create_file_transfer_item(self, tm, append=True):
data = list(tm.get_data()) data = list(tm.get_data())
data[3] = self.get_friend_by_number(data[4]).name if data[3] else self._name data[3] = self.get_friend_by_number(data[4]).name if data[3] else self._name
@ -693,15 +659,15 @@ class Profile(basecontact.BaseContact, Singleton):
friend = self._contacts[num] friend = self._contacts[num]
name = friend.name name = friend.name
dialog = QtWidgets.QApplication.translate('MainWindow', dialog = QtWidgets.QApplication.translate('MainWindow',
"Enter new alias for friend {} or leave empty to use friend's name:") "Enter new alias for friend {} or leave empty to use friend's name:")
dialog = dialog.format(name) dialog = dialog.format(name)
title = QtWidgets.QApplication.translate('MainWindow', title = QtWidgets.QApplication.translate('MainWindow',
'Set alias') 'Set alias')
text, ok = QtWidgets.QInputDialog.getText(None, text, ok = QtGui.QInputDialog.getText(None,
title, title,
dialog, dialog,
QtWidgets.QLineEdit.Normal, QtWidgets.QLineEdit.Normal,
name) name)
if ok: if ok:
settings = Settings.get_instance() settings = Settings.get_instance()
aliases = settings['friends_aliases'] aliases = settings['friends_aliases']
@ -722,7 +688,7 @@ class Profile(basecontact.BaseContact, Singleton):
except: except:
pass pass
settings.save() settings.save()
if num == self.get_active_number() and self.is_active_a_friend(): if num == self.get_active_number():
self.update() self.update()
def friend_public_key(self, num): def friend_public_key(self, num):
@ -873,11 +839,8 @@ class Profile(basecontact.BaseContact, Singleton):
Recreate tox instance Recreate tox instance
:param restart: method which calls restart and returns new tox instance :param restart: method which calls restart and returns new tox instance
""" """
for contact in self._contacts: for friend in self._contacts:
if type(contact) is Friend: self.friend_exit(friend.number)
self.friend_exit(contact.number)
else:
self.leave_gc(contact.number)
self._call.stop() self._call.stop()
del self._call del self._call
del self._tox del self._tox
@ -896,7 +859,7 @@ class Profile(basecontact.BaseContact, Singleton):
QtCore.QTimer.singleShot(50000, self.reconnect) QtCore.QTimer.singleShot(50000, self.reconnect)
def close(self): def close(self):
for friend in filter(lambda x: type(x) is Friend, self._contacts): for friend in self._contacts:
self.friend_exit(friend.number) self.friend_exit(friend.number)
for i in range(len(self._contacts)): for i in range(len(self._contacts)):
del self._contacts[0] del self._contacts[0]
@ -969,7 +932,7 @@ class Profile(basecontact.BaseContact, Singleton):
friend_number, friend_number,
file_number) file_number)
accepted = False accepted = False
if friend_number == self.get_active_number() and self.is_active_a_friend(): if friend_number == self.get_active_number():
item = self.create_file_transfer_item(tm) item = self.create_file_transfer_item(tm)
if accepted: if accepted:
self._file_transfers[(friend_number, file_number)].set_state_changed_handler(item.update_transfer_state) self._file_transfers[(friend_number, file_number)].set_state_changed_handler(item.update_transfer_state)
@ -1000,15 +963,14 @@ class Profile(basecontact.BaseContact, Singleton):
else: else:
if not already_cancelled: if not already_cancelled:
self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL']) self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL'])
if friend_number == self.get_active_number() and self.is_active_a_friend(): if friend_number == self.get_active_number():
tmp = self._messages.count() + i tmp = self._messages.count() + i
if tmp >= 0: if tmp >= 0:
self._messages.itemWidget( self._messages.itemWidget(self._messages.item(tmp)).update(TOX_FILE_TRANSFER_STATE['CANCELLED'],
self._messages.item(tmp)).update_transfer_state(TOX_FILE_TRANSFER_STATE['CANCELLED'], 0, -1)
0, -1)
def cancel_not_started_transfer(self, cancel_time): def cancel_not_started_transfer(self, time):
self.get_curr_friend().delete_one_unsent_file(cancel_time) self.get_curr_friend().delete_one_unsent_file(time)
self.update() self.update()
def pause_transfer(self, friend_number, file_number, by_friend=False): def pause_transfer(self, friend_number, file_number, by_friend=False):
@ -1155,7 +1117,7 @@ class Profile(basecontact.BaseContact, Singleton):
t = type(transfer) t = type(transfer)
if t is ReceiveAvatar: if t is ReceiveAvatar:
self.get_friend_by_number(friend_number).load_avatar() self.get_friend_by_number(friend_number).load_avatar()
if friend_number == self.get_active_number() and self.is_active_a_friend(): if friend_number == self.get_active_number():
self.set_active(None) self.set_active(None)
elif t is ReceiveToBuffer or (t is SendFromBuffer and Settings.get_instance()['allow_inline']): # inline image elif t is ReceiveToBuffer or (t is SendFromBuffer and Settings.get_instance()['allow_inline']): # inline image
print('inline') print('inline')
@ -1163,7 +1125,7 @@ class Profile(basecontact.BaseContact, Singleton):
i = self.get_friend_by_number(friend_number).update_transfer_data(file_number, i = self.get_friend_by_number(friend_number).update_transfer_data(file_number,
TOX_FILE_TRANSFER_STATE['FINISHED'], TOX_FILE_TRANSFER_STATE['FINISHED'],
inline) inline)
if friend_number == self.get_active_number() and self.is_active_a_friend(): if friend_number == self.get_active_number():
count = self._messages.count() count = self._messages.count()
if count + i + 1 >= 0: if count + i + 1 >= 0:
elem = QtWidgets.QListWidgetItem() elem = QtWidgets.QListWidgetItem()
@ -1205,7 +1167,7 @@ class Profile(basecontact.BaseContact, Singleton):
ra.set_transfer_finished_handler(self.transfer_finished) ra.set_transfer_finished_handler(self.transfer_finished)
else: else:
self.get_friend_by_number(friend_number).load_avatar() self.get_friend_by_number(friend_number).load_avatar()
if self.get_active_number() == friend_number and self.is_active_a_friend(): if self.get_active_number() == friend_number:
self.set_active(None) self.set_active(None)
def reset_avatar(self): def reset_avatar(self):
@ -1230,8 +1192,6 @@ class Profile(basecontact.BaseContact, Singleton):
def call_click(self, audio=True, video=False): def call_click(self, audio=True, video=False):
"""User clicked audio button in main window""" """User clicked audio button in main window"""
num = self.get_active_number() num = self.get_active_number()
if not self.is_active_a_friend():
return
if num not in self._call and self.is_active_online(): # start call if num not in self._call and self.is_active_online(): # start call
if not Settings.get_instance().audio['enabled']: if not Settings.get_instance().audio['enabled']:
return return
@ -1249,7 +1209,7 @@ class Profile(basecontact.BaseContact, Singleton):
def incoming_call(self, audio, video, friend_number): def incoming_call(self, audio, video, friend_number):
""" """
Incoming call from friend. Incoming call from friend. Only audio is supported now
""" """
if not Settings.get_instance().audio['enabled']: if not Settings.get_instance().audio['enabled']:
return return
@ -1266,9 +1226,10 @@ class Profile(basecontact.BaseContact, Singleton):
self._messages.scrollToBottom() self._messages.scrollToBottom()
else: else:
friend.actions = True friend.actions = True
self._call_widgets[friend_number] = avwidgets.IncomingCallWidget(friend_number, text, friend.name) # TODO: dict of widgets
self._call_widgets[friend_number].set_pixmap(friend.get_pixmap()) self._call_widget = avwidgets.IncomingCallWidget(friend_number, text, friend.name)
self._call_widgets[friend_number].show() self._call_widget.set_pixmap(friend.get_pixmap())
self._call_widget.show()
def accept_call(self, friend_number, audio, video): def accept_call(self, friend_number, audio, video):
""" """
@ -1278,7 +1239,8 @@ class Profile(basecontact.BaseContact, Singleton):
self._screen.active_call() self._screen.active_call()
if friend_number in self._incoming_calls: if friend_number in self._incoming_calls:
self._incoming_calls.remove(friend_number) self._incoming_calls.remove(friend_number)
del self._call_widgets[friend_number] if hasattr(self, '_call_widget'):
del self._call_widget
def stop_call(self, friend_number, by_friend): def stop_call(self, friend_number, by_friend):
""" """
@ -1290,149 +1252,16 @@ class Profile(basecontact.BaseContact, Singleton):
else: else:
text = QtWidgets.QApplication.translate("incoming_call", "Call finished") text = QtWidgets.QApplication.translate("incoming_call", "Call finished")
self._screen.call_finished() self._screen.call_finished()
is_video = self._call.is_video_call(friend_number)
self._call.finish_call(friend_number, by_friend) # finish or decline call self._call.finish_call(friend_number, by_friend) # finish or decline call
if hasattr(self, '_call_widget'): if hasattr(self, '_call_widget'):
self._call_widget[friend_number].close() self._call_widget.close()
del self._call_widget[friend_number] del self._call_widget
def destroy_window():
if is_video:
cv2.destroyWindow(str(friend_number))
threading.Timer(2.0, destroy_window).start()
friend = self.get_friend_by_number(friend_number) friend = self.get_friend_by_number(friend_number)
friend.append_message(InfoMessage(text, time.time())) friend.append_message(InfoMessage(text, time.time()))
if friend_number == self.get_active_number(): if friend_number == self.get_active_number():
self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE']) self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
self._messages.scrollToBottom() self._messages.scrollToBottom()
# -----------------------------------------------------------------------------------------------------------------
# GC support
# -----------------------------------------------------------------------------------------------------------------
def is_active_a_friend(self):
return type(self.get_curr_friend()) is Friend
def get_group_by_number(self, number):
groups = filter(lambda x: type(x) is GroupChat and x.number == number, self._contacts)
return list(groups)[0]
def add_gc(self, number):
if number == -1:
return
widget = self.create_friend_item()
gc = GroupChat('Group chat #' + str(number), '', widget, self._tox, number)
self._contacts.append(gc)
def create_group_chat(self):
number = self._tox.add_av_groupchat()
self.add_gc(number)
def leave_gc(self, num):
gc = self._contacts[num]
self._tox.del_groupchat(gc.number)
del self._contacts[num]
self._screen.friends_list.takeItem(num)
if num == self._active_friend: # active friend was deleted
if not len(self._contacts): # last friend was deleted
self.set_active(-1)
else:
self.set_active(0)
def group_invite(self, friend_number, gc_type, data):
text = QtWidgets.QApplication.translate('MainWindow', 'User {} invites you to group chat. Accept?')
title = QtWidgets.QApplication.translate('MainWindow', 'Group chat invite')
friend = self.get_friend_by_number(friend_number)
reply = QtWidgets.QMessageBox.question(None, title, text.format(friend.name), QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.Yes: # accepted
if gc_type == TOX_GROUPCHAT_TYPE['TEXT']:
number = self._tox.join_groupchat(friend_number, data)
else:
number = self._tox.join_av_groupchat(friend_number, data)
self.add_gc(number)
def new_gc_message(self, group_number, peer_number, message_type, message):
name = self._tox.group_peername(group_number, peer_number)
message_type += 5
if group_number == self.get_active_number() and not self.is_active_a_friend(): # add message to list
t = time.time()
self.create_gc_message_item(message, t, MESSAGE_OWNER['FRIEND'], name, message_type)
self._messages.scrollToBottom()
self.get_curr_friend().append_message(
GroupChatMessage(message, MESSAGE_OWNER['FRIEND'], t, message_type, name))
else:
gc = self.get_group_by_number(group_number)
gc.inc_messages()
gc.append_message(
GroupChatMessage(message, MESSAGE_OWNER['FRIEND'], time.time(), message_type, name))
if not gc.visibility:
self.update_filtration()
def new_gc_title(self, group_number, title):
gc = self.get_group_by_number(group_number)
gc.new_title(title)
if not self.is_active_a_friend() and self.get_active_number() == group_number:
self.update()
def update_gc(self, group_number):
count = self._tox.group_number_peers(group_number)
gc = self.get_group_by_number(group_number)
text = QtWidgets.QApplication.translate('MainWindow', '{} users in chat')
gc.status_message = text.format(str(count)).encode('utf-8')
if not self.is_active_a_friend() and self.get_active_number() == group_number:
self.update()
def send_gc_message(self, text):
group_number = self.get_active_number()
if text.startswith('/me '):
text = text[4:]
self._tox.group_action_send(group_number, text.encode('utf-8'))
else:
self._tox.group_message_send(group_number, text.encode('utf-8'))
self._screen.messageEdit.clear()
def set_title(self, num):
"""
Set new title for gc
"""
gc = self._contacts[num]
name = gc.name
dialog = QtWidgets.QApplication.translate('MainWindow',
"Enter new title for group {}:")
dialog = dialog.format(name)
title = QtWidgets.QApplication.translate('MainWindow',
'Set title')
text, ok = QtWidgets.QInputDialog.getText(None,
title,
dialog,
QtWidgets.QLineEdit.Normal,
name)
if ok:
text = text.encode('utf-8')
self._tox.group_set_title(gc.number, text)
self.new_gc_title(gc.number, text)
def get_group_chats(self):
chats = filter(lambda x: type(x) is GroupChat, self._contacts)
chats = map(lambda c: (c.name, c.number), chats)
return list(chats)
def invite_friend(self, friend_num, group_number):
friend = self._contacts[friend_num]
self._tox.invite_friend(friend.number, group_number)
def get_gc_peer_name(self, text):
gc = self.get_curr_friend()
if type(gc) is not GroupChat:
return '\t'
names = gc.get_names()
name = re.split("\s+", text)[-1]
suggested_names = list(filter(lambda x: x.startswith(name), names))
if not len(suggested_names):
return '\t'
return suggested_names[0][len(name):] + ': '
def tox_factory(data=None, settings=None): def tox_factory(data=None, settings=None):
""" """
@ -1443,8 +1272,6 @@ def tox_factory(data=None, settings=None):
if settings is None: if settings is None:
settings = Settings.get_default_settings() settings = Settings.get_default_settings()
tox_options = Tox.options_new() tox_options = Tox.options_new()
# see <https://github.com/irungentoo/toxcore/blob/master/toxcore/tox.h> lines 393-401
tox_options.contents.ipv6_enabled = settings['ipv6_enabled']
tox_options.contents.udp_enabled = settings['udp_enabled'] tox_options.contents.udp_enabled = settings['udp_enabled']
tox_options.contents.proxy_type = settings['proxy_type'] tox_options.contents.proxy_type = settings['proxy_type']
tox_options.contents.proxy_host = bytes(settings['proxy_host'], 'UTF-8') tox_options.contents.proxy_host = bytes(settings['proxy_host'], 'UTF-8')

View File

@ -1,22 +0,0 @@
import numpy as np
from PyQt5 import QtWidgets
class DesktopGrabber:
def __init__(self, x, y, width, height):
self._x = x
self._y = y
self._width = width
self._height = height
self._width -= width % 4
self._height -= height % 4
self._screen = QtWidgets.QApplication.primaryScreen()
def read(self):
pixmap = self._screen.grabWindow(0, self._x, self._y, self._width, self._height)
image = pixmap.toImage()
s = image.bits().asstring(self._width * self._height * 4)
arr = np.fromstring(s, dtype=np.uint8).reshape((self._height, self._width, 4))
return True, arr

View File

@ -47,7 +47,6 @@ class Settings(dict, Singleton):
self.audio = {'input': p.get_default_input_device_info()['index'] if input_devices else -1, self.audio = {'input': p.get_default_input_device_info()['index'] if input_devices else -1,
'output': p.get_default_output_device_info()['index'] if output_devices else -1, 'output': p.get_default_output_device_info()['index'] if output_devices else -1,
'enabled': input_devices and output_devices} 'enabled': input_devices and output_devices}
self.video = {'device': -1, 'width': 640, 'height': 480, 'x': 0, 'y': 0}
@staticmethod @staticmethod
def get_auto_profile(): def get_auto_profile():
@ -57,10 +56,7 @@ class Settings(dict, Singleton):
data = fl.read() data = fl.read()
auto = json.loads(data) auto = json.loads(data)
if 'path' in auto and 'name' in auto: if 'path' in auto and 'name' in auto:
path = str(auto['path']) return str(auto['path']), str(auto['name'])
name = str(auto['name'])
if os.path.isfile(append_slash(path) + name + '.tox'):
return path, name
return '', '' return '', ''
@staticmethod @staticmethod
@ -103,8 +99,8 @@ class Settings(dict, Singleton):
Default profile settings Default profile settings
""" """
return { return {
'theme': 'dark', 'theme': 'default',
'ipv6_enabled': False, 'ipv6_enabled': True,
'udp_enabled': True, 'udp_enabled': True,
'proxy_type': 0, 'proxy_type': 0,
'proxy_host': '127.0.0.1', 'proxy_host': '127.0.0.1',
@ -144,25 +140,15 @@ class Settings(dict, Singleton):
'show_welcome_screen': True, 'show_welcome_screen': True,
'close_to_tray': False, 'close_to_tray': False,
'font': 'Times New Roman', 'font': 'Times New Roman',
'update': 1, 'update': 1
'group_notifications': True,
'download_nodes_list': False
} }
@staticmethod @staticmethod
def supported_languages(): def supported_languages():
return { return {
'English': 'en_EN', 'English': 'en_EN',
'French': 'fr_FR',
'Russian': 'ru_RU', 'Russian': 'ru_RU',
'Ukrainian': 'uk_UA' 'French': 'fr_FR'
}
@staticmethod
def built_in_themes():
return {
'dark': '/styles/dark_style.qss',
'default': '/styles/style.qss'
} }
def upgrade(self): def upgrade(self):

File diff suppressed because it is too large Load Diff

View File

@ -41,9 +41,6 @@
<file>rc/radio_unchecked.png</file> <file>rc/radio_unchecked.png</file>
</qresource> </qresource>
<qresource prefix="qdarkstyle"> <qresource prefix="qdarkstyle">
<file>dark_style.qss</file>
</qresource>
<qresource prefix="defaultstyle">
<file>style.qss</file> <file>style.qss</file>
</qresource> </qresource>
</RCC> </RCC>

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,6 @@
from ctypes import * # -*- coding: utf-8 -*-
from ctypes import c_char_p, Structure, c_bool, byref, c_int, c_size_t, POINTER, c_uint16, c_void_p, c_uint64
from ctypes import create_string_buffer, ArgumentError, CFUNCTYPE, c_uint32, sizeof, c_uint8
from toxcore_enums_and_consts import * from toxcore_enums_and_consts import *
from toxav import ToxAV from toxav import ToxAV
from libtox import LibToxCore from libtox import LibToxCore
@ -90,11 +92,6 @@ class Tox:
self.file_recv_chunk_cb = None self.file_recv_chunk_cb = None
self.friend_lossy_packet_cb = None self.friend_lossy_packet_cb = None
self.friend_lossless_packet_cb = None self.friend_lossless_packet_cb = None
self.group_namelist_change_cb = None
self.group_title_cb = None
self.group_action_cb = None
self.group_message_cb = None
self.group_invite_cb = None
self.AV = ToxAV(self._tox_pointer) self.AV = ToxAV(self._tox_pointer)
@ -1513,89 +1510,3 @@ class Tox:
return result return result
elif tox_err_get_port == TOX_ERR_GET_PORT['NOT_BOUND']: elif tox_err_get_port == TOX_ERR_GET_PORT['NOT_BOUND']:
raise RuntimeError('The instance was not bound to any port.') raise RuntimeError('The instance was not bound to any port.')
# -----------------------------------------------------------------------------------------------------------------
# Group chats
# -----------------------------------------------------------------------------------------------------------------
def del_groupchat(self, groupnumber):
result = Tox.libtoxcore.tox_del_groupchat(self._tox_pointer, c_int(groupnumber), None)
return result
def group_peername(self, groupnumber, peernumber):
buffer = create_string_buffer(TOX_MAX_NAME_LENGTH)
result = Tox.libtoxcore.tox_group_peername(self._tox_pointer, c_int(groupnumber), c_int(peernumber),
buffer, None)
return str(buffer[:result], 'utf-8')
def invite_friend(self, friendnumber, groupnumber):
result = Tox.libtoxcore.tox_invite_friend(self._tox_pointer, c_int(friendnumber),
c_int(groupnumber), None)
return result
def join_groupchat(self, friendnumber, data):
result = Tox.libtoxcore.tox_join_groupchat(self._tox_pointer,
c_int(friendnumber), c_char_p(data), c_uint16(len(data)), None)
return result
def group_message_send(self, groupnumber, message):
result = Tox.libtoxcore.tox_group_message_send(self._tox_pointer, c_int(groupnumber), c_char_p(message),
c_uint16(len(message)), None)
return result
def group_action_send(self, groupnumber, action):
result = Tox.libtoxcore.tox_group_action_send(self._tox_pointer,
c_int(groupnumber), c_char_p(action),
c_uint16(len(action)), None)
return result
def group_set_title(self, groupnumber, title):
result = Tox.libtoxcore.tox_group_set_title(self._tox_pointer, c_int(groupnumber),
c_char_p(title), c_uint8(len(title)), None)
return result
def group_get_title(self, groupnumber):
buffer = create_string_buffer(TOX_MAX_NAME_LENGTH)
result = Tox.libtoxcore.tox_group_get_title(self._tox_pointer,
c_int(groupnumber), buffer,
c_uint32(TOX_MAX_NAME_LENGTH), None)
return str(buffer[:result], 'utf-8')
def group_number_peers(self, groupnumber):
result = Tox.libtoxcore.tox_group_number_peers(self._tox_pointer, c_int(groupnumber), None)
return result
def add_av_groupchat(self):
result = self.AV.libtoxav.toxav_add_av_groupchat(self._tox_pointer, None, None)
return result
def join_av_groupchat(self, friendnumber, data):
result = self.AV.libtoxav.toxav_join_av_groupchat(self._tox_pointer, c_int32(friendnumber),
c_char_p(data), c_uint16(len(data)),
None, None)
return result
def callback_group_invite(self, callback, user_data=None):
c_callback = CFUNCTYPE(None, c_void_p, c_int32, c_uint8, POINTER(c_uint8), c_uint16, c_void_p)
self.group_invite_cb = c_callback(callback)
Tox.libtoxcore.tox_callback_group_invite(self._tox_pointer, self.group_invite_cb, user_data)
def callback_group_message(self, callback, user_data=None):
c_callback = CFUNCTYPE(None, c_void_p, c_int, c_int, c_char_p, c_uint16, c_void_p)
self.group_message_cb = c_callback(callback)
Tox.libtoxcore.tox_callback_group_message(self._tox_pointer, self.group_message_cb, user_data)
def callback_group_action(self, callback, user_data=None):
c_callback = CFUNCTYPE(None, c_void_p, c_int, c_int, c_char_p, c_uint16, c_void_p)
self.group_action_cb = c_callback(callback)
Tox.libtoxcore.tox_callback_group_action(self._tox_pointer, self.group_action_cb, user_data)
def callback_group_title(self, callback, user_data=None):
c_callback = CFUNCTYPE(None, c_void_p, c_int, c_int, c_char_p, c_uint8, c_void_p)
self.group_title_cb = c_callback(callback)
Tox.libtoxcore.tox_callback_group_title(self._tox_pointer, self.group_title_cb, user_data)
def callback_group_namelist_change(self, callback, user_data=None):
c_callback = CFUNCTYPE(None, c_void_p, c_int, c_int, c_uint8, c_void_p)
self.group_namelist_change_cb = c_callback(callback)
Tox.libtoxcore.tox_callback_group_namelist_change(self._tox_pointer, self.group_namelist_change_cb, user_data)

View File

@ -30,8 +30,7 @@ def tox_dns(email):
netman.setProxy(proxy) netman.setProxy(proxy)
for url in urls: for url in urls:
try: try:
request = QtNetwork.QNetworkRequest() request = QtNetwork.QNetworkRequest(url)
request.setUrl(QtCore.QUrl(url))
request.setHeader(QtNetwork.QNetworkRequest.ContentTypeHeader, "application/json") request.setHeader(QtNetwork.QNetworkRequest.ContentTypeHeader, "application/json")
reply = netman.post(request, bytes(json.dumps(data), 'utf-8')) reply = netman.post(request, bytes(json.dumps(data), 'utf-8'))

View File

@ -188,17 +188,6 @@ TOX_ERR_GET_PORT = {
'NOT_BOUND': 1, 'NOT_BOUND': 1,
} }
TOX_CHAT_CHANGE = {
'PEER_ADD': 0,
'PEER_DEL': 1,
'PEER_NAME': 2
}
TOX_GROUPCHAT_TYPE = {
'TEXT': 0,
'AV': 1
}
TOX_PUBLIC_KEY_SIZE = 32 TOX_PUBLIC_KEY_SIZE = 32
TOX_ADDRESS_SIZE = TOX_PUBLIC_KEY_SIZE + 6 TOX_ADDRESS_SIZE = TOX_PUBLIC_KEY_SIZE + 6

View File

@ -1,2 +1,2 @@
SOURCES = main.py profile.py menu.py list_items.py loginscreen.py mainscreen.py plugins/plugin_super_class.py callbacks.py widgets.py avwidgets.py mainscreen_widgets.py passwordscreen.py SOURCES = main.py profile.py menu.py list_items.py loginscreen.py mainscreen.py plugins/plugin_super_class.py callbacks.py widgets.py avwidgets.py mainscreen_widgets.py passwordscreen.py
TRANSLATIONS = translations/en_GB.ts translations/ru_RU.ts translations/fr_FR.ts translations/uk_UA.ts TRANSLATIONS = translations/en_GB.ts translations/ru_RU.ts translations/fr_FR.ts

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -57,10 +57,7 @@ def get_url(version):
def get_params(url, version): def get_params(url, version):
if is_from_sources(): if is_from_sources():
if platform.system() == 'Windows': return ['python3', 'toxygen_updater.py', url, version]
return ['python', 'toxygen_updater.py', url, version]
else:
return ['python3', 'toxygen_updater.py', url, version]
elif platform.system() == 'Windows': elif platform.system() == 'Windows':
return [util.curr_directory() + '/toxygen_updater.exe', url, version] return [util.curr_directory() + '/toxygen_updater.exe', url, version]
else: else:
@ -90,8 +87,7 @@ def send_request(version):
netman.setProxy(proxy) netman.setProxy(proxy)
url = test_url(version) url = test_url(version)
try: try:
request = QtNetwork.QNetworkRequest() request = QtNetwork.QNetworkRequest(url)
request.setUrl(QtCore.QUrl(url))
reply = netman.get(request) reply = netman.get(request)
while not reply.isFinished(): while not reply.isFinished():
QtCore.QThread.msleep(1) QtCore.QThread.msleep(1)

View File

@ -4,32 +4,14 @@ import shutil
import sys import sys
import re import re
program_version = '0.2.8'
program_version = '0.4.2'
def cached(func):
saved_result = None
def wrapped_func():
nonlocal saved_result
if saved_result is None:
saved_result = func()
return saved_result
return wrapped_func
def log(data): def log(data):
try: with open(curr_directory() + '/logs.log', 'a') as fl:
with open(curr_directory() + '/logs.log', 'a') as fl: fl.write(str(data) + '\n')
fl.write(str(data) + '\n')
except:
pass
@cached
def curr_directory(): def curr_directory():
return os.path.dirname(os.path.realpath(__file__)) return os.path.dirname(os.path.realpath(__file__))
@ -64,8 +46,9 @@ def convert_time(t):
return '%02d:%02d' % (h, m) return '%02d:%02d' % (h, m)
@cached
def time_offset(): def time_offset():
if hasattr(time_offset, 'offset'):
return time_offset.offset
hours = int(time.strftime('%H')) hours = int(time.strftime('%H'))
minutes = int(time.strftime('%M')) minutes = int(time.strftime('%M'))
sec = int(time.time()) - time.timezone sec = int(time.time()) - time.timezone
@ -73,6 +56,7 @@ def time_offset():
h, m = divmod(m, 60) h, m = divmod(m, 60)
d, h = divmod(h, 24) d, h = divmod(h, 24)
result = hours * 60 + minutes - h * 60 - m result = hours * 60 + minutes - h * 60 - m
time_offset.offset = result
return result return result
@ -82,7 +66,6 @@ def append_slash(s):
return s return s
@cached
def is_64_bit(): def is_64_bit():
return sys.maxsize > 2 ** 32 return sys.maxsize > 2 ** 32

View File

@ -77,42 +77,6 @@ class RubberBand(QtWidgets.QRubberBand):
self.painter.end() self.painter.end()
class RubberBandWindow(QtWidgets.QWidget):
def __init__(self, parent):
super().__init__()
self.parent = parent
self.setMouseTracking(True)
self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint)
self.showFullScreen()
self.setWindowOpacity(0.5)
self.rubberband = RubberBand()
self.rubberband.setWindowFlags(self.rubberband.windowFlags() | QtCore.Qt.FramelessWindowHint)
self.rubberband.setAttribute(QtCore.Qt.WA_TranslucentBackground)
def mousePressEvent(self, event):
self.origin = event.pos()
self.rubberband.setGeometry(QtCore.QRect(self.origin, QtCore.QSize()))
self.rubberband.show()
QtWidgets.QWidget.mousePressEvent(self, event)
def mouseMoveEvent(self, event):
if self.rubberband.isVisible():
self.rubberband.setGeometry(QtCore.QRect(self.origin, event.pos()).normalized())
left = QtGui.QRegion(QtCore.QRect(0, 0, self.rubberband.x(), self.height()))
right = QtGui.QRegion(QtCore.QRect(self.rubberband.x() + self.rubberband.width(), 0, self.width(), self.height()))
top = QtGui.QRegion(0, 0, self.width(), self.rubberband.y())
bottom = QtGui.QRegion(0, self.rubberband.y() + self.rubberband.height(), self.width(), self.height())
self.setMask(left + right + top + bottom)
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_Escape:
self.rubberband.setHidden(True)
self.close()
else:
super().keyPressEvent(event)
def create_menu(menu): def create_menu(menu):
""" """
:return translated menu :return translated menu
@ -164,3 +128,4 @@ class MultilineEdit(CenteredWidget):
def button_click(self): def button_click(self):
self.save(self.edit.toPlainText()) self.save(self.edit.toPlainText())
self.close() self.close()