Compare commits
45 Commits
Author | SHA1 | Date | |
---|---|---|---|
5019535c0d | |||
1554d9e53a | |||
a984b624b5 | |||
2aea5df33c | |||
1fa13db4e4 | |||
3582722faa | |||
74396834cf | |||
ce84cc526b | |||
98cc288bcd | |||
9b5d768819 | |||
762eb89a46 | |||
b428bd54c4 | |||
f76a1c0fbe | |||
bb2a857ecf | |||
62c5df751d | |||
55a127a820 | |||
32055050ee | |||
a6633f1e77 | |||
23b55522ba | |||
5a5b0e9069 | |||
24c8b18f7e | |||
3ddb7470fc | |||
80b0ea4f0e | |||
6efb1790bb | |||
d5d1e616ba | |||
1ea919bdc2 | |||
65167de1fe | |||
db519e2608 | |||
19893c5c28 | |||
8e6d37e23c | |||
aae71d081f | |||
9c129e925b | |||
87392ea95a | |||
1bbd9a629c | |||
f4d806f5fc | |||
4854b6151d | |||
c755b4a52a | |||
7505b06ddf | |||
ace663804e | |||
2ff41313f8 | |||
1e1772e306 | |||
300b28bdfa | |||
1f4e81af35 | |||
335d646c42 | |||
b6f5123495 |
@ -16,3 +16,4 @@ include toxygen/styles/*.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
|
||||||
|
58
README.md
58
README.md
@ -14,35 +14,35 @@ Toxygen is powerful cross-platform [Tox](https://tox.chat/) client written in pu
|
|||||||
|
|
||||||
### Features:
|
### Features:
|
||||||
|
|
||||||
- [x] 1v1 messages
|
- 1v1 messages
|
||||||
- [x] File transfers
|
- File transfers
|
||||||
- [x] Audio calls
|
- Audio calls
|
||||||
- [x] Video 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
|
||||||
- [ ] Desktop sharing
|
- File resuming
|
||||||
- [ ] Group chats
|
- Read receipts
|
||||||
|
|
||||||
### Downloads
|
### Downloads
|
||||||
[Releases](https://github.com/toxygen-project/toxygen/releases)
|
[Releases](https://github.com/toxygen-project/toxygen/releases)
|
||||||
|
@ -17,7 +17,7 @@ Run app using ``toxygen`` command.
|
|||||||
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. For 32-bit Linux install PyQt5: ``sudo apt-get install python3-pyqt5``
|
||||||
4. Install [OpenCV](http://docs.opencv.org/trunk/d7/d9f/tutorial_linux_install.html)
|
4. Install [OpenCV](http://docs.opencv.org/trunk/d7/d9f/tutorial_linux_install.html) or via ``sudo pip3 install opencv-python``
|
||||||
5. Install toxygen:
|
5. Install toxygen:
|
||||||
``sudo pip3 install toxygen``
|
``sudo pip3 install toxygen``
|
||||||
6. Run toxygen using ``toxygen`` command.
|
6. Run toxygen using ``toxygen`` command.
|
||||||
@ -38,10 +38,10 @@ Note: 32-bit Python isn't supported due to bug with videocalls. It is strictly r
|
|||||||
2. Install PyQt5: ``pip install pyqt5``
|
2. Install PyQt5: ``pip install pyqt5``
|
||||||
3. Install PyAudio: ``pip install pyaudio``
|
3. Install PyAudio: ``pip install pyaudio``
|
||||||
4. Install numpy: ``pip install numpy``
|
4. Install numpy: ``pip install numpy``
|
||||||
5. Install OpenCV: ``pip install opencv-python`` or via ``sudo apt-get install python3-opencv``
|
5. Install OpenCV: ``pip install opencv-python``
|
||||||
6. [Download toxygen](https://github.com/toxygen-project/toxygen/archive/master.zip)
|
6. [Download toxygen](https://github.com/toxygen-project/toxygen/archive/master.zip)
|
||||||
7. Unpack archive
|
7. Unpack archive
|
||||||
8. Download latest libtox.dll build, download latest libsodium.a build, put it into \src\libs\
|
8. Download latest libtox.dll build, download latest libsodium.a build, put it into \toxygen\libs\
|
||||||
9. Run \toxygen\main.py.
|
9. Run \toxygen\main.py.
|
||||||
|
|
||||||
Optional: install toxygen using setup.py: ``python setup.py install``
|
Optional: install toxygen using setup.py: ``python setup.py install``
|
||||||
@ -63,7 +63,7 @@ Optional: install toxygen using setup.py: ``python setup.py install``
|
|||||||
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 ``sudo pip3 install pyaudio``)
|
||||||
5. Install NumPy: ``sudo pip3 install numpy``
|
5. Install NumPy: ``sudo pip3 install numpy``
|
||||||
6. Install [OpenCV](http://docs.opencv.org/trunk/d7/d9f/tutorial_linux_install.html) or via ``sudo apt-get install python3-opencv``
|
6. Install [OpenCV](http://docs.opencv.org/trunk/d7/d9f/tutorial_linux_install.html) or via ``sudo pip3 install opencv-python``
|
||||||
7. [Download toxygen](https://github.com/toxygen-project/toxygen/archive/master.zip)
|
7. [Download toxygen](https://github.com/toxygen-project/toxygen/archive/master.zip)
|
||||||
8. Unpack archive
|
8. Unpack archive
|
||||||
9. Run app:
|
9. Run app:
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# Plugins
|
# Plugins
|
||||||
|
|
||||||
Toxygen is the first [Tox](https://tox.chat/) client with plugins support. Plugin is Python 3.4 - 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.5 - 3.6 module (.py file) and directory with plugin's data which provide some additional functionality.
|
||||||
|
|
||||||
# How to write plugin
|
# How to write plugin
|
||||||
|
|
||||||
|
@ -8,6 +8,6 @@ Animated smileys (.gif) are supported too.
|
|||||||
|
|
||||||
# Stickers
|
# Stickers
|
||||||
|
|
||||||
Sticker is inline image. If you want to create your own smiley pack, create directory in src/stickers/ and place your stickers there.
|
Sticker is inline image. If you want to create your own sticker 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
|
||||||
|
22
setup.py
22
setup.py
@ -8,10 +8,27 @@ import sys
|
|||||||
|
|
||||||
version = program_version + '.0'
|
version = program_version + '.0'
|
||||||
|
|
||||||
MODULES = ['PyQt5', 'PyAudio', 'numpy']
|
|
||||||
|
|
||||||
if system() == 'Windows':
|
if system() == 'Windows':
|
||||||
MODULES.append('opencv-python')
|
MODULES = ['PyQt5', 'PyAudio', 'numpy', 'opencv-python']
|
||||||
|
else:
|
||||||
|
MODULES = []
|
||||||
|
try:
|
||||||
|
import pyaudio
|
||||||
|
except ImportError:
|
||||||
|
MODULES.append('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):
|
||||||
@ -35,6 +52,7 @@ class InstallScript(install):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
setup(name='Toxygen',
|
setup(name='Toxygen',
|
||||||
version=version,
|
version=version,
|
||||||
description='Toxygen - Tox client',
|
description='Toxygen - Tox client',
|
||||||
|
@ -1,84 +1,75 @@
|
|||||||
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, ip, port, tox_key, rand):
|
def __init__(self, node):
|
||||||
self._ip, self._port, self._tox_key, self.rand = ip, port, tox_key, rand
|
self._ip, self._port, self._tox_key = node['ipv4'], node['port'], node['public_key']
|
||||||
|
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 node_generator():
|
def generate_nodes():
|
||||||
nodes = []
|
with open(curr_directory() + '/nodes.json', 'rt') as fl:
|
||||||
ips = [
|
json_nodes = json.loads(fl.read())['nodes']
|
||||||
"144.76.60.215", "23.226.230.47", "195.154.119.113", "biribiri.org",
|
nodes = map(lambda json_node: Node(json_node), json_nodes)
|
||||||
"46.38.239.179", "178.62.250.138", "130.133.110.14", "104.167.101.29",
|
sorted_nodes = sorted(nodes, key=lambda x: x.priority)[-4:]
|
||||||
"205.185.116.116", "198.98.51.198", "80.232.246.79", "108.61.165.198",
|
for node in sorted_nodes:
|
||||||
"212.71.252.109", "194.249.212.109", "185.25.116.107", "192.99.168.140",
|
yield node.get_data()
|
||||||
"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))
|
||||||
|
@ -33,6 +33,7 @@ class Invoker(QtCore.QObject):
|
|||||||
event.fn(*event.args, **event.kwargs)
|
event.fn(*event.args, **event.kwargs)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
_invoker = Invoker()
|
_invoker = Invoker()
|
||||||
|
|
||||||
|
|
||||||
@ -66,6 +67,7 @@ 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()
|
||||||
|
|
||||||
|
|
||||||
@ -346,7 +348,6 @@ def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, u
|
|||||||
width // 2 width // 2
|
width // 2 width // 2
|
||||||
|
|
||||||
It can be created from initial y, u, v using slices
|
It can be created from initial y, u, v using slices
|
||||||
For more info see callback_video_receive_frame docs
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
y_size = abs(max(width, abs(ystride)))
|
y_size = abs(max(width, abs(ystride)))
|
||||||
@ -375,6 +376,55 @@ def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, u
|
|||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print(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
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
@ -411,3 +461,9 @@ def init_callbacks(tox, window, tray):
|
|||||||
|
|
||||||
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)
|
||||||
|
@ -6,6 +6,7 @@ from toxav_enums import *
|
|||||||
import cv2
|
import cv2
|
||||||
import itertools
|
import itertools
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import screen_sharing
|
||||||
# TODO: play sound until outgoing call will be started or cancelled
|
# TODO: play sound until outgoing call will be started or cancelled
|
||||||
|
|
||||||
|
|
||||||
@ -62,7 +63,7 @@ class Call:
|
|||||||
return self._out_video
|
return self._out_video
|
||||||
|
|
||||||
def set_out_video(self, value):
|
def set_out_video(self, value):
|
||||||
self._in_video = value
|
self._out_video = value
|
||||||
|
|
||||||
out_video = property(get_out_video, set_out_video)
|
out_video = property(get_out_video, set_out_video)
|
||||||
|
|
||||||
@ -143,8 +144,8 @@ class AV:
|
|||||||
call = self._calls[friend_number]
|
call = self._calls[friend_number]
|
||||||
call.is_active = True
|
call.is_active = True
|
||||||
|
|
||||||
call.in_audio = state | TOXAV_FRIEND_CALL_STATE['SENDING_A']
|
call.in_audio = state | TOXAV_FRIEND_CALL_STATE['SENDING_A'] > 0
|
||||||
call.in_video = state | TOXAV_FRIEND_CALL_STATE['SENDING_V']
|
call.in_video = state | TOXAV_FRIEND_CALL_STATE['SENDING_V'] > 0
|
||||||
|
|
||||||
if state | TOXAV_FRIEND_CALL_STATE['ACCEPTING_A'] and call.out_audio:
|
if state | TOXAV_FRIEND_CALL_STATE['ACCEPTING_A'] and call.out_audio:
|
||||||
self.start_audio_thread()
|
self.start_audio_thread()
|
||||||
@ -152,6 +153,9 @@ class AV:
|
|||||||
if state | TOXAV_FRIEND_CALL_STATE['ACCEPTING_V'] and call.out_video:
|
if state | TOXAV_FRIEND_CALL_STATE['ACCEPTING_V'] and call.out_video:
|
||||||
self.start_video_thread()
|
self.start_video_thread()
|
||||||
|
|
||||||
|
def is_video_call(self, number):
|
||||||
|
return number in self and self._calls[number].in_video
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
# Threads
|
# Threads
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
@ -203,10 +207,14 @@ class AV:
|
|||||||
self._video_width = s.video['width']
|
self._video_width = s.video['width']
|
||||||
self._video_height = s.video['height']
|
self._video_height = s.video['height']
|
||||||
|
|
||||||
self._video = cv2.VideoCapture(s.video['device'])
|
if s.video['device'] == -1:
|
||||||
self._video.set(cv2.CAP_PROP_FPS, 25)
|
self._video = screen_sharing.DesktopGrabber(s.video['x'], s.video['y'],
|
||||||
self._video.set(cv2.CAP_PROP_FRAME_WIDTH, self._video_width)
|
s.video['width'], s.video['height'])
|
||||||
self._video.set(cv2.CAP_PROP_FRAME_HEIGHT, self._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 = threading.Thread(target=self.send_video)
|
||||||
self._video_thread.start()
|
self._video_thread.start()
|
||||||
@ -276,12 +284,12 @@ class AV:
|
|||||||
try:
|
try:
|
||||||
y, u, v = self.convert_bgr_to_yuv(frame)
|
y, u, v = self.convert_bgr_to_yuv(frame)
|
||||||
self._toxav.video_send_frame(friend_num, width, height, y, u, v)
|
self._toxav.video_send_frame(friend_num, width, height, y, u, v)
|
||||||
except Exception as e:
|
except:
|
||||||
print(e)
|
pass
|
||||||
except Exception as e:
|
except:
|
||||||
print(e)
|
pass
|
||||||
|
|
||||||
time.sleep(0.01)
|
time.sleep(0.01)
|
||||||
|
|
||||||
def convert_bgr_to_yuv(self, frame):
|
def convert_bgr_to_yuv(self, frame):
|
||||||
"""
|
"""
|
||||||
|
@ -61,6 +61,8 @@ 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()
|
||||||
@ -124,7 +126,7 @@ class Contact(basecontact.BaseContact):
|
|||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
def delete_message(self, time):
|
def delete_message(self, time):
|
||||||
elem = list(filter(lambda x: type(x) is TextMessage and x.get_data()[2] == time, self._corr))[0]
|
elem = list(filter(lambda x: type(x) in (TextMessage, GroupChatMessage) 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
|
||||||
|
@ -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_')
|
return file_name in ALLOWED_FILES or file_name.startswith('qTox_Screenshot_') or file_name.startswith('qTox_Image_')
|
||||||
|
|
||||||
|
|
||||||
class StateSignal(QtCore.QObject):
|
class StateSignal(QtCore.QObject):
|
||||||
|
@ -66,3 +66,10 @@ 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
|
||||||
|
49
toxygen/group_chat.py
Normal file
49
toxygen/group_chat.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
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)
|
BIN
toxygen/images/group.png
Normal file
BIN
toxygen/images/group.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.0 KiB |
@ -543,7 +543,3 @@ class InlineImageItem(QtWidgets.QScrollArea):
|
|||||||
|
|
||||||
def mark_as_sent(self):
|
def mark_as_sent(self):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -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 node_generator
|
from bootstrap import generate_nodes, download_nodes_list
|
||||||
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, is_64_bit
|
from util import curr_directory, program_version, remove
|
||||||
import styles.style
|
import styles.style # reqired for styles loading
|
||||||
import platform
|
import platform
|
||||||
import toxes
|
import toxes
|
||||||
from passwordscreen import PasswordScreen, UnlockAppScreen, SetProfilePasswordScreen
|
from passwordscreen import PasswordScreen, UnlockAppScreen, SetProfilePasswordScreen
|
||||||
@ -106,7 +106,7 @@ class Toxygen:
|
|||||||
return
|
return
|
||||||
self.tox = profile.tox_factory()
|
self.tox = profile.tox_factory()
|
||||||
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 T03')
|
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",
|
||||||
@ -327,6 +327,7 @@ 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()
|
||||||
@ -378,9 +379,11 @@ 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 node_generator():
|
for data in generate_nodes():
|
||||||
if self.stop:
|
if self.stop:
|
||||||
return
|
return
|
||||||
self.tox.bootstrap(*data)
|
self.tox.bootstrap(*data)
|
||||||
@ -393,7 +396,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 node_generator():
|
for data in generate_nodes():
|
||||||
if self.stop:
|
if self.stop:
|
||||||
return
|
return
|
||||||
self.tox.bootstrap(*data)
|
self.tox.bootstrap(*data)
|
||||||
|
@ -5,7 +5,6 @@ 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
|
||||||
|
|
||||||
|
|
||||||
@ -41,6 +40,7 @@ class MainWindow(QtWidgets.QMainWindow, Singleton):
|
|||||||
self.menuAbout.setObjectName("menuAbout")
|
self.menuAbout.setObjectName("menuAbout")
|
||||||
|
|
||||||
self.actionAdd_friend = QtWidgets.QAction(window)
|
self.actionAdd_friend = QtWidgets.QAction(window)
|
||||||
|
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(window)
|
||||||
self.actionprofilesettings.setObjectName("actionprofilesettings")
|
self.actionprofilesettings.setObjectName("actionprofilesettings")
|
||||||
@ -64,6 +64,7 @@ class MainWindow(QtWidgets.QMainWindow, Singleton):
|
|||||||
self.reloadPlugins = QtWidgets.QAction(window)
|
self.reloadPlugins = QtWidgets.QAction(window)
|
||||||
self.lockApp = QtWidgets.QAction(window)
|
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)
|
||||||
@ -86,6 +87,7 @@ class MainWindow(QtWidgets.QMainWindow, Singleton):
|
|||||||
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)
|
||||||
@ -115,6 +117,7 @@ class MainWindow(QtWidgets.QMainWindow, Singleton):
|
|||||||
self.menuSettings.setTitle(QtWidgets.QApplication.translate("MainWindow", "Settings"))
|
self.menuSettings.setTitle(QtWidgets.QApplication.translate("MainWindow", "Settings"))
|
||||||
self.menuAbout.setTitle(QtWidgets.QApplication.translate("MainWindow", "About"))
|
self.menuAbout.setTitle(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"))
|
||||||
@ -415,8 +418,10 @@ 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.\nVersion: '))
|
text = (QtWidgets.QApplication.translate("MainWindow", 'Toxygen is Tox client written on Python.<br>Version: '))
|
||||||
msgBox.setText(text + util.program_version + '\nGitHub: https://github.com/toxygen-project/toxygen/')
|
github = '<br><a href="https://github.com/toxygen-project/toxygen/">Github</a>'
|
||||||
|
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):
|
||||||
@ -431,6 +436,9 @@ 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()
|
||||||
@ -512,7 +520,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 + 1:
|
if self.profile.active_friend + 1and self.profile.is_active_a_friend():
|
||||||
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]:
|
||||||
@ -520,7 +528,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:
|
if self.profile.active_friend + 1 and self.profile.is_active_a_friend():
|
||||||
self.sw = ScreenShotWindow(self)
|
self.sw = ScreenShotWindow(self)
|
||||||
self.sw.show()
|
self.sw.show()
|
||||||
if hide:
|
if hide:
|
||||||
@ -538,7 +546,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:
|
if self.profile.active_friend + 1 and self.profile.is_active_a_friend():
|
||||||
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,
|
||||||
@ -583,7 +591,10 @@ 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()
|
||||||
set_alias_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Set alias'))
|
is_friend = type(friend) is Friend
|
||||||
|
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'))
|
||||||
@ -593,26 +604,39 @@ 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'))
|
||||||
copy_key_item = copy_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Public key'))
|
if is_friend:
|
||||||
|
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'))
|
||||||
|
|
||||||
plugins_loader = plugin_support.PluginLoader.get_instance()
|
chats = self.profile.get_group_chats()
|
||||||
if plugins_loader is not None:
|
if len(chats) and self.profile.is_active_online():
|
||||||
submenu = plugins_loader.get_menu(self.listMenu, num)
|
invite_menu = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Invite to group chat'))
|
||||||
if len(submenu):
|
for i in range(len(chats)):
|
||||||
plug = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Plugins'))
|
name, number = chats[i]
|
||||||
plug.addActions(submenu)
|
item = invite_menu.addAction(name)
|
||||||
set_alias_item.triggered.connect(lambda: self.set_alias(num))
|
item.triggered.connect(lambda number=number: self.invite_friend_to_gc(num, number))
|
||||||
remove_item.triggered.connect(lambda: self.remove_friend(num))
|
|
||||||
block_item.triggered.connect(lambda: self.block_friend(num))
|
plugins_loader = plugin_support.PluginLoader.get_instance()
|
||||||
copy_key_item.triggered.connect(lambda: self.copy_friend_key(num))
|
if plugins_loader is not None:
|
||||||
|
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))
|
||||||
@ -638,15 +662,18 @@ 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)
|
||||||
directory = QtWidgets.QFileDialog.getExistingDirectory(None,
|
extension = 'txt' if as_text else 'html'
|
||||||
QtWidgets.QApplication.translate("MainWindow",
|
file_name, _ = QtWidgets.QFileDialog.getSaveFileName(None,
|
||||||
'Choose folder'),
|
QtWidgets.QApplication.translate("MainWindow",
|
||||||
curr_directory(),
|
'Choose file name'),
|
||||||
QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog)
|
curr_directory(),
|
||||||
|
filter=extension,
|
||||||
|
options=QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog)
|
||||||
|
|
||||||
if directory:
|
if file_name:
|
||||||
name = 'exported_history_{}.{}'.format(convert_time(time.time()), 'txt' if as_text else 'html')
|
if not file_name.endswith('.' + extension):
|
||||||
with open(directory + '/' + name, 'wt') as fl:
|
file_name += '.' + extension
|
||||||
|
with open(file_name, 'wt') as fl:
|
||||||
fl.write(s)
|
fl.write(s)
|
||||||
|
|
||||||
def set_alias(self, num):
|
def set_alias(self, num):
|
||||||
@ -675,6 +702,12 @@ 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)
|
||||||
@ -684,6 +717,9 @@ 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
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
from widgets import RubberBand, create_menu, QRightClickButton, CenteredWidget, LineEdit
|
from widgets import RubberBandWindow, 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):
|
||||||
@ -34,6 +35,10 @@ 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():
|
||||||
@ -66,43 +71,33 @@ 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://'):
|
||||||
self.parent.profile.send_file(text[7:])
|
file_name = self.parse_file_name(text)
|
||||||
else:
|
self.parent.profile.send_file(file_name)
|
||||||
|
elif text:
|
||||||
self.insertPlainText(text)
|
self.insertPlainText(text)
|
||||||
|
else:
|
||||||
|
image = QtWidgets.QApplication.clipboard().image()
|
||||||
|
if image is not None:
|
||||||
|
byte_array = QtCore.QByteArray()
|
||||||
|
buffer = QtCore.QBuffer(byte_array)
|
||||||
|
buffer.open(QtCore.QIODevice.WriteOnly)
|
||||||
|
image.save(buffer, 'PNG')
|
||||||
|
self.parent.profile.send_screenshot(bytes(byte_array.data()))
|
||||||
|
|
||||||
|
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()
|
||||||
@ -121,13 +116,6 @@ class ScreenShotWindow(QtWidgets.QWidget):
|
|||||||
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):
|
||||||
"""
|
"""
|
||||||
@ -347,7 +335,7 @@ class WelcomeScreen(CenteredWidget):
|
|||||||
'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:
|
elif num == 7:
|
||||||
text = QtWidgets.QApplication.translate('WelcomeScreen',
|
text = QtWidgets.QApplication.translate('WelcomeScreen',
|
||||||
'New in Toxygen 0.3.0:<br>Video calls<br>Python3.6 support<br>Migration to PyQt5')
|
'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')
|
||||||
|
@ -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
|
from widgets import CenteredWidget, DataLabel, LineEdit, RubberBandWindow
|
||||||
import pyaudio
|
import pyaudio
|
||||||
import toxes
|
import toxes
|
||||||
import plugin_support
|
import plugin_support
|
||||||
@ -248,11 +248,11 @@ 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, 'Images (*.png)',
|
||||||
QtGui.QComboBoxQtWidgets.QFileDialog.DontUseNativeDialog)
|
options=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), aspectMode=QtCore.Qt.KeepAspectRatio,
|
bitmap.scaled(QtCore.QSize(128, 128), aspectRatioMode=QtCore.Qt.KeepAspectRatio,
|
||||||
mode=QtCore.Qt.SmoothTransformation)
|
transformMode=QtCore.Qt.SmoothTransformation)
|
||||||
|
|
||||||
byte_array = QtCore.QByteArray()
|
byte_array = QtCore.QByteArray()
|
||||||
buffer = QtCore.QBuffer(byte_array)
|
buffer = QtCore.QBuffer(byte_array)
|
||||||
@ -261,14 +261,14 @@ 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(options=QtWidgets.QFileDialog.DontUseNativeDialog,
|
directory = QtWidgets.QFileDialog.getExistingDirectory(self, '', curr_directory(),
|
||||||
dir=curr_directory()) + '/'
|
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",
|
||||||
'Use new path'),
|
'Use new path'),
|
||||||
QtWidgets.QApplication.translate("ProfileSettingsForm",
|
QtWidgets.QApplication.translate("ProfileSettingsForm",
|
||||||
'Do you want to move your profile to this location?'),
|
'Do you want to move your profile to this location?'),
|
||||||
QtWidgets.QMessageBox.Yes,
|
QtWidgets.QMessageBox.Yes,
|
||||||
QtWidgets.QMessageBox.No)
|
QtWidgets.QMessageBox.No)
|
||||||
settings = Settings.get_instance()
|
settings = Settings.get_instance()
|
||||||
@ -294,10 +294,10 @@ class NetworkSettings(CenteredWidget):
|
|||||||
|
|
||||||
def initUI(self):
|
def initUI(self):
|
||||||
self.setObjectName("NetworkSettings")
|
self.setObjectName("NetworkSettings")
|
||||||
self.resize(300, 330)
|
self.resize(300, 400)
|
||||||
self.setMinimumSize(QtCore.QSize(300, 330))
|
self.setMinimumSize(QtCore.QSize(300, 400))
|
||||||
self.setMaximumSize(QtCore.QSize(300, 330))
|
self.setMaximumSize(QtCore.QSize(300, 400))
|
||||||
self.setBaseSize(QtCore.QSize(300, 330))
|
self.setBaseSize(QtCore.QSize(300, 400))
|
||||||
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,6 +332,9 @@ 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()
|
||||||
@ -346,6 +349,7 @@ 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):
|
||||||
@ -362,6 +366,7 @@ 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)
|
||||||
@ -508,15 +513,17 @@ class NotificationsSettings(CenteredWidget):
|
|||||||
|
|
||||||
def initUI(self):
|
def initUI(self):
|
||||||
self.setObjectName("notificationsForm")
|
self.setObjectName("notificationsForm")
|
||||||
self.resize(350, 180)
|
self.resize(350, 210)
|
||||||
self.setMinimumSize(QtCore.QSize(350, 180))
|
self.setMinimumSize(QtCore.QSize(350, 210))
|
||||||
self.setMaximumSize(QtCore.QSize(350, 180))
|
self.setMaximumSize(QtCore.QSize(350, 210))
|
||||||
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, 120, 340, 18))
|
self.callsSound.setGeometry(QtCore.QRect(10, 170, 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'])
|
||||||
@ -524,8 +531,10 @@ 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)
|
||||||
@ -533,6 +542,7 @@ 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"))
|
||||||
|
|
||||||
@ -540,6 +550,7 @@ 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()
|
||||||
|
|
||||||
@ -605,7 +616,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, 19)])
|
self.messages_font_size.addItems([str(x) for x in range(10, 25)])
|
||||||
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)
|
||||||
@ -802,6 +813,18 @@ 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):
|
class VideoSettings(CenteredWidget):
|
||||||
"""
|
"""
|
||||||
Audio calls settings form
|
Audio calls settings form
|
||||||
@ -812,6 +835,7 @@ class VideoSettings(CenteredWidget):
|
|||||||
self.initUI()
|
self.initUI()
|
||||||
self.retranslateUi()
|
self.retranslateUi()
|
||||||
self.center()
|
self.center()
|
||||||
|
self.desktopAreaSelection = None
|
||||||
|
|
||||||
def initUI(self):
|
def initUI(self):
|
||||||
self.setObjectName("videoSettingsForm")
|
self.setObjectName("videoSettingsForm")
|
||||||
@ -831,9 +855,16 @@ class VideoSettings(CenteredWidget):
|
|||||||
self.input = QtWidgets.QComboBox(self)
|
self.input = QtWidgets.QComboBox(self)
|
||||||
self.input.setGeometry(QtCore.QRect(25, 30, 350, 30))
|
self.input.setGeometry(QtCore.QRect(25, 30, 350, 30))
|
||||||
self.input.currentIndexChanged.connect(self.selectionChanged)
|
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
|
import cv2
|
||||||
self.devices = []
|
self.devices = [-1]
|
||||||
self.frame_max_sizes = []
|
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):
|
for i in range(10):
|
||||||
v = cv2.VideoCapture(i)
|
v = cv2.VideoCapture(i)
|
||||||
if v.isOpened():
|
if v.isOpened():
|
||||||
@ -855,8 +886,14 @@ class VideoSettings(CenteredWidget):
|
|||||||
def retranslateUi(self):
|
def retranslateUi(self):
|
||||||
self.setWindowTitle(QtWidgets.QApplication.translate("videoSettingsForm", "Video settings"))
|
self.setWindowTitle(QtWidgets.QApplication.translate("videoSettingsForm", "Video settings"))
|
||||||
self.in_label.setText(QtWidgets.QApplication.translate("videoSettingsForm", "Device:"))
|
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):
|
def closeEvent(self, event):
|
||||||
|
if self.input.currentIndex() == 0:
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
settings = Settings.get_instance()
|
settings = Settings.get_instance()
|
||||||
settings.video['device'] = self.devices[self.input.currentIndex()]
|
settings.video['device'] = self.devices[self.input.currentIndex()]
|
||||||
@ -867,7 +904,23 @@ class VideoSettings(CenteredWidget):
|
|||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print('Saving video settings error: ' + str(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):
|
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()]
|
width, height = self.frame_max_sizes[self.input.currentIndex()]
|
||||||
self.video_size.clear()
|
self.video_size.clear()
|
||||||
dims = [
|
dims = [
|
||||||
|
@ -5,7 +5,9 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -39,6 +41,16 @@ 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
|
||||||
|
1
toxygen/nodes.json
Normal file
1
toxygen/nodes.json
Normal file
File diff suppressed because one or more lines are too long
@ -97,10 +97,13 @@ class PluginLoader(util.Singleton):
|
|||||||
"""
|
"""
|
||||||
result = []
|
result = []
|
||||||
for data in self._plugins.values():
|
for data in self._plugins.values():
|
||||||
result.append([data[0].get_name(), # plugin full name
|
try:
|
||||||
data[1], # is enabled
|
result.append([data[0].get_name(), # plugin full name
|
||||||
data[0].get_description(), # plugin description
|
data[1], # is enabled
|
||||||
data[0].get_short_name()]) # key - short unique name
|
data[0].get_description(), # plugin description
|
||||||
|
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):
|
||||||
|
@ -16,6 +16,8 @@ import basecontact
|
|||||||
import items_factory
|
import items_factory
|
||||||
import cv2
|
import cv2
|
||||||
import threading
|
import threading
|
||||||
|
from group_chat import *
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
class Profile(basecontact.BaseContact, Singleton):
|
class Profile(basecontact.BaseContact, Singleton):
|
||||||
@ -70,6 +72,8 @@ 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)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
@ -129,6 +133,7 @@ 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)
|
||||||
@ -164,7 +169,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(number)
|
self.set_active_by_number_and_type(number, is_friend)
|
||||||
|
|
||||||
def update_filtration(self):
|
def update_filtration(self):
|
||||||
"""
|
"""
|
||||||
@ -177,7 +182,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, self._contacts))[0]
|
return list(filter(lambda x: x.number == num and type(x) is Friend, 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):
|
||||||
@ -204,6 +209,7 @@ 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()
|
||||||
@ -251,12 +257,15 @@ 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())
|
||||||
else: # info message
|
elif message.get_type() < 5: # 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:
|
||||||
@ -270,7 +279,11 @@ 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)
|
||||||
avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(friend.tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
self._screen.account_status.setToolTip(friend.get_full_status())
|
||||||
|
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))
|
||||||
@ -282,9 +295,10 @@ 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(self, number):
|
def set_active_by_number_and_type(self, number, is_friend):
|
||||||
for i in range(len(self._contacts)):
|
for i in range(len(self._contacts)):
|
||||||
if self._contacts[i].number == number:
|
c = self._contacts[i]
|
||||||
|
if c.number == number and (type(c) is Friend == is_friend):
|
||||||
self._active_friend = i
|
self._active_friend = i
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -347,7 +361,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():
|
if friend_number == self.get_active_number() and self.is_active_a_friend():
|
||||||
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))
|
||||||
@ -389,7 +403,7 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
"""
|
"""
|
||||||
Display incoming typing notification
|
Display incoming typing notification
|
||||||
"""
|
"""
|
||||||
if friend_number == self.get_active_number():
|
if friend_number == self.get_active_number() and self.is_active_a_friend():
|
||||||
self._screen.typing.setVisible(typing)
|
self._screen.typing.setVisible(typing)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
@ -445,7 +459,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(): # add message to list
|
if friend_num == self.get_active_number()and self.is_active_a_friend(): # 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()
|
||||||
@ -465,6 +479,9 @@ 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 '):
|
||||||
@ -480,8 +497,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'))
|
||||||
if friend.number == self.get_active_number():
|
t = time.time()
|
||||||
t = time.time()
|
if friend.number == self.get_active_number() and self.is_active_a_friend():
|
||||||
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()
|
||||||
@ -504,7 +521,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 self._contacts:
|
for friend in filter(lambda x: type(x) is Friend, 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']:
|
||||||
@ -571,13 +588,16 @@ 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)
|
||||||
else: # info message
|
elif message.get_type() < 5: # 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], False)
|
||||||
self._load_history = True
|
self._load_history = True
|
||||||
|
|
||||||
def export_db(self, directory):
|
def export_db(self, directory):
|
||||||
@ -636,6 +656,16 @@ 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
|
||||||
@ -663,15 +693,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 = QtGui.QInputDialog.getText(None,
|
text, ok = QtWidgets.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']
|
||||||
@ -692,7 +722,7 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
settings.save()
|
settings.save()
|
||||||
if num == self.get_active_number():
|
if num == self.get_active_number() and self.is_active_a_friend():
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def friend_public_key(self, num):
|
def friend_public_key(self, num):
|
||||||
@ -843,8 +873,11 @@ 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 friend in self._contacts:
|
for contact in self._contacts:
|
||||||
self.friend_exit(friend.number)
|
if type(contact) is Friend:
|
||||||
|
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
|
||||||
@ -863,7 +896,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 self._contacts:
|
for friend in filter(lambda x: type(x) is Friend, 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]
|
||||||
@ -936,7 +969,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():
|
if friend_number == self.get_active_number() and self.is_active_a_friend():
|
||||||
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)
|
||||||
@ -967,14 +1000,15 @@ 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():
|
if friend_number == self.get_active_number() and self.is_active_a_friend():
|
||||||
tmp = self._messages.count() + i
|
tmp = self._messages.count() + i
|
||||||
if tmp >= 0:
|
if tmp >= 0:
|
||||||
self._messages.itemWidget(self._messages.item(tmp)).update(TOX_FILE_TRANSFER_STATE['CANCELLED'],
|
self._messages.itemWidget(
|
||||||
0, -1)
|
self._messages.item(tmp)).update_transfer_state(TOX_FILE_TRANSFER_STATE['CANCELLED'],
|
||||||
|
0, -1)
|
||||||
|
|
||||||
def cancel_not_started_transfer(self, time):
|
def cancel_not_started_transfer(self, cancel_time):
|
||||||
self.get_curr_friend().delete_one_unsent_file(time)
|
self.get_curr_friend().delete_one_unsent_file(cancel_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):
|
||||||
@ -1065,10 +1099,11 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
file_name,
|
file_name,
|
||||||
friend.number,
|
friend.number,
|
||||||
st.get_file_number())
|
st.get_file_number())
|
||||||
item = self.create_file_transfer_item(tm)
|
|
||||||
friend.append_message(tm)
|
friend.append_message(tm)
|
||||||
st.set_state_changed_handler(item.update_transfer_state)
|
if friend_number == self.get_active_number():
|
||||||
self._messages.scrollToBottom()
|
item = self.create_file_transfer_item(tm)
|
||||||
|
st.set_state_changed_handler(item.update_transfer_state)
|
||||||
|
self._messages.scrollToBottom()
|
||||||
|
|
||||||
def send_file(self, path, number=None, is_resend=False, file_id=None):
|
def send_file(self, path, number=None, is_resend=False, file_id=None):
|
||||||
"""
|
"""
|
||||||
@ -1121,7 +1156,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():
|
if friend_number == self.get_active_number() and self.is_active_a_friend():
|
||||||
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')
|
||||||
@ -1129,7 +1164,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():
|
if friend_number == self.get_active_number() and self.is_active_a_friend():
|
||||||
count = self._messages.count()
|
count = self._messages.count()
|
||||||
if count + i + 1 >= 0:
|
if count + i + 1 >= 0:
|
||||||
elem = QtWidgets.QListWidgetItem()
|
elem = QtWidgets.QListWidgetItem()
|
||||||
@ -1171,7 +1206,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:
|
if self.get_active_number() == friend_number and self.is_active_a_friend():
|
||||||
self.set_active(None)
|
self.set_active(None)
|
||||||
|
|
||||||
def reset_avatar(self):
|
def reset_avatar(self):
|
||||||
@ -1196,6 +1231,8 @@ 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
|
||||||
@ -1213,7 +1250,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. Only audio is supported now
|
Incoming call from friend.
|
||||||
"""
|
"""
|
||||||
if not Settings.get_instance().audio['enabled']:
|
if not Settings.get_instance().audio['enabled']:
|
||||||
return
|
return
|
||||||
@ -1254,17 +1291,149 @@ 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[friend_number].close()
|
||||||
del self._call_widget[friend_number]
|
del self._call_widget[friend_number]
|
||||||
threading.Timer(2.0, lambda: cv2.destroyWindow(str(friend_number))).start()
|
|
||||||
|
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):
|
||||||
"""
|
"""
|
||||||
@ -1275,6 +1444,8 @@ 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')
|
||||||
|
22
toxygen/screen_sharing.py
Normal file
22
toxygen/screen_sharing.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
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
|
@ -47,7 +47,7 @@ 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': 0, 'width': 640, 'height': 480}
|
self.video = {'device': -1, 'width': 640, 'height': 480, 'x': 0, 'y': 0}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_auto_profile():
|
def get_auto_profile():
|
||||||
@ -57,7 +57,10 @@ 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:
|
||||||
return str(auto['path']), str(auto['name'])
|
path = str(auto['path'])
|
||||||
|
name = str(auto['name'])
|
||||||
|
if os.path.isfile(append_slash(path) + name + '.tox'):
|
||||||
|
return path, name
|
||||||
return '', ''
|
return '', ''
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -101,7 +104,7 @@ class Settings(dict, Singleton):
|
|||||||
"""
|
"""
|
||||||
return {
|
return {
|
||||||
'theme': 'dark',
|
'theme': 'dark',
|
||||||
'ipv6_enabled': True,
|
'ipv6_enabled': False,
|
||||||
'udp_enabled': True,
|
'udp_enabled': True,
|
||||||
'proxy_type': 0,
|
'proxy_type': 0,
|
||||||
'proxy_host': '127.0.0.1',
|
'proxy_host': '127.0.0.1',
|
||||||
@ -141,7 +144,9 @@ 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
|
||||||
|
@ -2,3 +2,28 @@
|
|||||||
{
|
{
|
||||||
padding-left: 22px;
|
padding-left: 22px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MessageEdit
|
||||||
|
{
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageEdit::focus
|
||||||
|
{
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageItem::focus
|
||||||
|
{
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageEdit:hover
|
||||||
|
{
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageEdit
|
||||||
|
{
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
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 *
|
||||||
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
|
||||||
@ -91,6 +90,11 @@ 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)
|
||||||
|
|
||||||
@ -1509,3 +1513,89 @@ 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)
|
||||||
|
@ -188,6 +188,17 @@ 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
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -5,7 +5,7 @@ import sys
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
program_version = '0.3.0'
|
program_version = '0.4.3'
|
||||||
|
|
||||||
|
|
||||||
def cached(func):
|
def cached(func):
|
||||||
|
@ -77,6 +77,42 @@ 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
|
||||||
|
Reference in New Issue
Block a user