52 Commits
video ... gc

Author SHA1 Message Date
80b0ea4f0e history for gc fixes 2017-07-20 23:51:40 +03:00
6efb1790bb notifications fix 2017-07-19 19:39:56 +03:00
d5d1e616ba translations update and bug fix 2017-07-19 00:14:41 +03:00
1ea919bdc2 tab && bug fix 2017-07-18 23:36:40 +03:00
65167de1fe group notifications and bug fixes 2017-07-18 21:36:14 +03:00
db519e2608 bug fix and version++ 2017-07-17 22:27:52 +03:00
19893c5c28 chat menu 2017-07-17 22:15:29 +03:00
8e6d37e23c minimal working functionality 2017-07-17 21:53:35 +03:00
aae71d081f base backend for gc 2017-07-17 01:11:09 +03:00
9c129e925b base gc class, callbacks part1 2017-07-16 22:51:20 +03:00
87392ea95a wrapper for old gc (gen) 2017-07-16 20:02:33 +03:00
1bbd9a629c video calls fix 2017-07-15 23:11:49 +03:00
f4d806f5fc readme update 2017-07-15 12:28:19 +03:00
4854b6151d desktop sharing - area selection fix 2017-07-14 21:37:50 +03:00
c755b4a52a light theme fix 2017-07-14 21:21:53 +03:00
7505b06ddf translations update 2017-07-13 21:19:13 +03:00
ace663804e screen sharing - area selection 2017-07-13 21:02:42 +03:00
2ff41313f8 default profile bug fix. install.md fix 2017-07-12 21:36:19 +03:00
1e1772e306 screen sharing initial commit 2017-07-12 21:18:21 +03:00
300b28bdfa set alias fix 2017-07-10 18:23:20 +03:00
1f4e81af35 export fix. version++ 2017-07-09 17:37:05 +03:00
335d646c42 avatars fix 2017-07-09 17:22:37 +03:00
b6f5123495 setup.py fix for packages 2017-07-09 13:17:51 +03:00
fbe0b1f819 installation updates 2017-07-07 20:30:04 +03:00
000a4c7920 travis.yml update 2017-07-06 22:16:12 +03:00
262714d3ee fix and cleanup 2017-07-06 21:39:15 +03:00
d06982b38a updates for pip3 2017-07-06 21:32:35 +03:00
c21e39b158 bug fixes for updates 2017-07-03 21:36:11 +03:00
8d0426f775 .qm for french translation 2017-07-03 13:01:07 +03:00
2c031fce3f Merge pull request #48 from limalayla/develop
French translation up to v0.3.0
2017-07-03 02:49:55 -07:00
6e1b8a9f17 French translation up to v0.3.0 2017-07-03 00:51:43 +02:00
4b85401adf os x removed, minor updates 2017-06-29 22:14:52 +03:00
5932d8cb84 installation docs update 2017-06-24 15:44:55 +03:00
adf6cefd1f pyqt5 fixes - menu and smileys 2017-06-20 22:55:48 +03:00
142255ccc8 translations update. docs partial update 2017-06-20 22:34:24 +03:00
1b6b8e043a back to menu 2017-06-20 21:31:23 +03:00
1b4c211c1d version info updated 2017-06-20 20:10:17 +03:00
43c71ec1a5 Merge branch 'video' into develop
Conflicts:
	.travis.yml
	toxygen/calls.py
	toxygen/loginscreen.py
	toxygen/util.py
2017-06-20 19:58:59 +03:00
a20a00130d email notifications disabled 2017-06-11 22:58:11 +03:00
8ea1a77186 version++ 2017-05-06 19:35:24 +03:00
bf1bea1e93 Merge pull request #44 from SHooZ/light_theme
Default system theme
2017-05-03 19:51:38 +03:00
124decc34a Add padding for search field and contacts nicknames in default theme 2017-05-03 19:44:25 +03:00
89caef6905 Edit default avatar image for light themes compatibility 2017-05-03 18:43:22 +03:00
9118e01775 Set dark style as default in load screen 2017-05-02 20:59:27 +03:00
138135b9e9 Add ability to change theme 2017-05-02 02:59:24 +03:00
2863eb790d Merge pull request #42 from SHooZ/ukrainian_translation
Add ukrainian translation
2017-05-01 18:21:35 +03:00
06e8c79b3f Update toxygen.pro 2017-05-01 18:09:42 +03:00
81695737cd Add ukrainian translation 2017-05-01 00:44:55 +03:00
ac07cb529f reconnection bug fixed 2017-04-22 22:35:32 +03:00
4f77e2c105 @cached 2017-04-17 22:04:22 +03:00
47ce9252b7 Merge pull request #41 from nurupo/fix-md-formatting
Fix markdown formatting
2017-04-12 17:16:06 +03:00
9153836ead Fix markdown formatting 2017-04-12 10:10:48 -04:00
42 changed files with 4569 additions and 2564 deletions

3
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

View File

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

View File

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

View File

@ -8,20 +8,23 @@ import sys
version = program_version + '.0'
MODULES = ['numpy', 'PyQt5']
if system() in ('Windows', 'Darwin'):
MODULES.append('PyAudio')
if system() == 'Windows':
MODULES = ['PyQt5', 'PyAudio', 'numpy', 'opencv-python']
else:
MODULES = []
try:
import pyaudio
except ImportError:
MODULES.append('PyAudio')
DEP_LINKS = []
if system() == 'Windows':
DEP_LINKS = [] # TODO: add opencv.whl
try:
import PyQt5
except ImportError:
MODULES.append('PyQt5')
try:
import numpy
except ImportError:
MODULES.append('numpy')
class InstallScript(install):
@ -30,9 +33,7 @@ class InstallScript(install):
def run(self):
install.run(self)
try:
if system() == 'Windows':
call(["toxygen", "--configure"])
else:
if system() != 'Windows':
call(["toxygen", "--clean"])
except:
try:
@ -42,9 +43,7 @@ class InstallScript(install):
if path[-1] not in ('/', '\\'):
path += '/'
path += 'bin/toxygen'
if system() == 'Windows':
call([path, "--configure"])
else:
if system() != 'Windows':
call([path, "--clean"])
except:
pass
@ -60,7 +59,6 @@ setup(name='Toxygen',
license='GPL3',
packages=['toxygen', 'toxygen.plugins', 'toxygen.styles'],
install_requires=MODULES,
dependency_links=DEP_LINKS,
include_package_data=True,
classifiers=[
'Programming Language :: Python :: 3 :: Only',
@ -72,5 +70,4 @@ setup(name='Toxygen',
},
cmdclass={
'install': InstallScript,
},
)
})

View File

@ -346,7 +346,6 @@ def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, u
width // 2 width // 2
It can be created from initial y, u, v using slices
For more info see callback_video_receive_frame docs
"""
try:
y_size = abs(max(width, abs(ystride)))
@ -375,6 +374,55 @@ def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, u
except Exception as ex:
print(ex)
# -----------------------------------------------------------------------------------------------------------------
# Callbacks - groups
# -----------------------------------------------------------------------------------------------------------------
def group_invite(tox, friend_number, gc_type, data, length, user_data):
invoke_in_main_thread(Profile.get_instance().group_invite, friend_number, gc_type,
bytes(data[:length]))
def show_gc_notification(window, tray, message, group_number, peer_number):
profile = Profile.get_instance()
settings = Settings.get_instance()
chat = profile.get_group_by_number(group_number)
peer_name = chat.get_peer_name(peer_number)
if not window.isActiveWindow() and (profile.name in message or settings['group_notifications']):
if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and not settings.locked:
invoke_in_main_thread(tray_notification, chat.name + ' ' + peer_name, message, tray, window)
if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
sound_notification(SOUND_NOTIFICATION['MESSAGE'])
invoke_in_main_thread(tray.setIcon, QtGui.QIcon(curr_directory() + '/images/icon_new_messages.png'))
def group_message(window, tray):
def wrapped(tox, group_number, peer_number, message, length, user_data):
message = str(message[:length], 'utf-8')
invoke_in_main_thread(Profile.get_instance().new_gc_message, group_number,
peer_number, TOX_MESSAGE_TYPE['NORMAL'], message)
show_gc_notification(window, tray, message, group_number, peer_number)
return wrapped
def group_action(window, tray):
def wrapped(tox, group_number, peer_number, message, length, user_data):
message = str(message[:length], 'utf-8')
invoke_in_main_thread(Profile.get_instance().new_gc_message, group_number,
peer_number, TOX_MESSAGE_TYPE['ACTION'], message)
show_gc_notification(window, tray, message, group_number, peer_number)
return wrapped
def group_title(tox, group_number, peer_number, title, length, user_data):
invoke_in_main_thread(Profile.get_instance().new_gc_title, group_number,
title[:length])
def group_namelist_change(tox, group_number, peer_number, change, user_data):
invoke_in_main_thread(Profile.get_instance().update_gc, group_number)
# -----------------------------------------------------------------------------------------------------------------
# Callbacks - initialization
# -----------------------------------------------------------------------------------------------------------------
@ -411,3 +459,9 @@ def init_callbacks(tox, window, tray):
tox.callback_friend_lossless_packet(lossless_packet, 0)
tox.callback_friend_lossy_packet(lossy_packet, 0)
tox.callback_group_invite(group_invite)
tox.callback_group_message(group_message(window, tray))
tox.callback_group_action(group_action(window, tray))
tox.callback_group_title(group_title)
tox.callback_group_namelist_change(group_namelist_change)

View File

@ -6,6 +6,7 @@ from toxav_enums import *
import cv2
import itertools
import numpy as np
import screen_sharing
# TODO: play sound until outgoing call will be started or cancelled
@ -203,6 +204,10 @@ class AV:
self._video_width = s.video['width']
self._video_height = s.video['height']
if s.video['device'] == -1:
self._video = screen_sharing.DesktopGrabber(s.video['x'], s.video['y'],
s.video['width'], s.video['height'])
else:
self._video = cv2.VideoCapture(s.video['device'])
self._video.set(cv2.CAP_PROP_FPS, 25)
self._video.set(cv2.CAP_PROP_FRAME_WIDTH, self._video_width)
@ -323,7 +328,6 @@ class AV:
u[::2, :] = frame[self._video_height:self._video_height * 5 // 4, :self._video_width // 2]
u[1::2, :] = frame[self._video_height:self._video_height * 5 // 4, self._video_width // 2:]
u = list(itertools.chain.from_iterable(u))
v = np.zeros((self._video_height // 2, self._video_width // 2), dtype=np.int)
v[::2, :] = frame[self._video_height * 5 // 4:, :self._video_width // 2]
v[1::2, :] = frame[self._video_height * 5 // 4:, self._video_width // 2:]

View File

@ -61,6 +61,8 @@ class Contact(basecontact.BaseContact):
"""
Get all chat history from db for current friend
"""
if self._message_getter is None:
return
data = list(self._message_getter.get_all())
if data is not None and len(data):
data.reverse()
@ -124,7 +126,7 @@ class Contact(basecontact.BaseContact):
# -----------------------------------------------------------------------------------------------------------------
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))
if elem in tmp[-self._unsaved_messages:] and self._unsaved_messages:
self._unsaved_messages -= 1

View File

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

49
toxygen/group_chat.py Normal file
View 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)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

BIN
toxygen/images/group.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -54,10 +54,9 @@ class Toxygen:
if platform.system() == 'Linux':
QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
# application color scheme
with open(curr_directory() + '/styles/style.qss') as fl:
dark_style = fl.read()
app.setStyleSheet(dark_style)
with open(curr_directory() + '/styles/dark_style.qss') as fl:
style = fl.read()
app.setStyleSheet(style)
encrypt_save = toxes.ToxES()
@ -107,7 +106,7 @@ class Toxygen:
return
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_status_message(b'Toxing on Toxygen')
self.tox.self_set_status_message(b'Toxing on T03')
reply = QtWidgets.QMessageBox.question(None,
'Profile {}'.format(name),
QtWidgets.QApplication.translate("login",
@ -172,6 +171,13 @@ class Toxygen:
else:
settings.set_active_profile()
# application color scheme
for theme in settings.built_in_themes().keys():
if settings['theme'] == theme:
with open(curr_directory() + settings.built_in_themes()[theme]) as fl:
style = fl.read()
app.setStyleSheet(style)
lang = Settings.supported_languages()[settings['language']]
translator = QtCore.QTranslator()
translator.load(curr_directory() + '/translations/' + lang)
@ -448,27 +454,6 @@ def clean():
remove(d)
def configure():
"""Removes unused libs"""
d = curr_directory() + '/libs/'
is_64bits = is_64_bit()
if not is_64bits:
if os.path.exists(d + 'libtox64.dll'):
os.remove(d + 'libtox64.dll')
if os.path.exists(d + 'libsodium64.a'):
os.remove(d + 'libsodium64.a')
else:
if os.path.exists(d + 'libtox.dll'):
os.remove(d + 'libtox.dll')
if os.path.exists(d + 'libsodium.a'):
os.remove(d + 'libsodium.a')
try:
os.rename(d + 'libtox64.dll', d + 'libtox.dll')
os.rename(d + 'libsodium64.a', d + 'libsodium.a')
except:
pass
def reset():
Settings.reset_auto_profile()
@ -484,9 +469,6 @@ def main():
elif arg == '--help':
print('Usage:\ntoxygen path_to_profile\ntoxygen tox_id\ntoxygen --version\ntoxygen --reset')
return
elif arg == '--configure':
configure()
return
elif arg == '--clean':
clean()
return

View File

@ -5,7 +5,6 @@ from widgets import MultilineEdit, ComboBox
import plugin_support
from mainscreen_widgets import *
import settings
import platform
import toxes
@ -22,53 +21,50 @@ class MainWindow(QtWidgets.QMainWindow, Singleton):
if settings.Settings.get_instance()['show_welcome_screen']:
self.ws = WelcomeScreen()
def setup_menu(self, Form):
box = QtWidgets.QHBoxLayout()
box.setContentsMargins(0, 0, 0, 0)
box.setAlignment(QtCore.Qt.AlignLeft)
self.profile_button = MainMenuButton(Form)
box.addWidget(self.profile_button)
self.settings_button = MainMenuButton(Form)
box.addWidget(self.settings_button)
self.plugins_button = MainMenuButton(Form)
box.addWidget(self.plugins_button)
self.about_button = MainMenuButton(Form)
box.addWidget(self.about_button)
box.setSpacing(0)
def setup_menu(self, window):
self.menubar = QtWidgets.QMenuBar(window)
self.menubar.setObjectName("menubar")
self.menubar.setNativeMenuBar(False)
self.menubar.setMinimumSize(self.width(), 25)
self.menubar.setMaximumSize(self.width(), 25)
self.menubar.setBaseSize(self.width(), 25)
self.menuProfile = QtWidgets.QMenu(self.menubar)
self.menuProfile = QtWidgets.QMenu()
self.menuProfile = QtWidgets.QMenu(self.menubar)
self.menuProfile.setObjectName("menuProfile")
self.menuSettings = QtWidgets.QMenu()
self.menuSettings = QtWidgets.QMenu(self.menubar)
self.menuSettings.setObjectName("menuSettings")
self.menuPlugins = QtWidgets.QMenu()
self.menuPlugins = QtWidgets.QMenu(self.menubar)
self.menuPlugins.setObjectName("menuPlugins")
self.menuAbout = QtWidgets.QMenu()
self.menuAbout = QtWidgets.QMenu(self.menubar)
self.menuAbout.setObjectName("menuAbout")
self.actionAdd_friend = QtWidgets.QAction(Form)
self.actionAdd_friend = QtWidgets.QAction(window)
self.actionAdd_gc = QtWidgets.QAction(window)
self.actionAdd_friend.setObjectName("actionAdd_friend")
self.actionprofilesettings = QtWidgets.QAction(Form)
self.actionprofilesettings = QtWidgets.QAction(window)
self.actionprofilesettings.setObjectName("actionprofilesettings")
self.actionPrivacy_settings = QtWidgets.QAction(Form)
self.actionPrivacy_settings = QtWidgets.QAction(window)
self.actionPrivacy_settings.setObjectName("actionPrivacy_settings")
self.actionInterface_settings = QtWidgets.QAction(Form)
self.actionInterface_settings = QtWidgets.QAction(window)
self.actionInterface_settings.setObjectName("actionInterface_settings")
self.actionNotifications = QtWidgets.QAction(Form)
self.actionNotifications = QtWidgets.QAction(window)
self.actionNotifications.setObjectName("actionNotifications")
self.actionNetwork = QtWidgets.QAction(Form)
self.actionNetwork = QtWidgets.QAction(window)
self.actionNetwork.setObjectName("actionNetwork")
self.actionAbout_program = QtWidgets.QAction(Form)
self.actionAbout_program = QtWidgets.QAction(window)
self.actionAbout_program.setObjectName("actionAbout_program")
self.updateSettings = QtWidgets.QAction(Form)
self.actionSettings = QtWidgets.QAction(Form)
self.updateSettings = QtWidgets.QAction(window)
self.actionSettings = QtWidgets.QAction(window)
self.actionSettings.setObjectName("actionSettings")
self.audioSettings = QtWidgets.QAction(Form)
self.videoSettings = QtWidgets.QAction(Form)
self.pluginData = QtWidgets.QAction(Form)
self.importPlugin = QtWidgets.QAction(Form)
self.reloadPlugins = QtWidgets.QAction(Form)
self.lockApp = QtWidgets.QAction(Form)
self.audioSettings = QtWidgets.QAction(window)
self.videoSettings = QtWidgets.QAction(window)
self.pluginData = QtWidgets.QAction(window)
self.importPlugin = QtWidgets.QAction(window)
self.reloadPlugins = QtWidgets.QAction(window)
self.lockApp = QtWidgets.QAction(window)
self.menuProfile.addAction(self.actionAdd_friend)
self.menuProfile.addAction(self.actionAdd_gc)
self.menuProfile.addAction(self.actionSettings)
self.menuProfile.addAction(self.lockApp)
self.menuSettings.addAction(self.actionPrivacy_settings)
@ -83,14 +79,15 @@ class MainWindow(QtWidgets.QMainWindow, Singleton):
self.menuPlugins.addAction(self.reloadPlugins)
self.menuAbout.addAction(self.actionAbout_program)
self.profile_button.setMenu(self.menuProfile)
self.settings_button.setMenu(self.menuSettings)
self.plugins_button.setMenu(self.menuPlugins)
self.about_button.setMenu(self.menuAbout)
self.menubar.addAction(self.menuProfile.menuAction())
self.menubar.addAction(self.menuSettings.menuAction())
self.menubar.addAction(self.menuPlugins.menuAction())
self.menubar.addAction(self.menuAbout.menuAction())
self.actionAbout_program.triggered.connect(self.about_program)
self.actionNetwork.triggered.connect(self.network_settings)
self.actionAdd_friend.triggered.connect(self.add_contact)
self.actionAdd_gc.triggered.connect(self.create_gc)
self.actionSettings.triggered.connect(self.profile_settings)
self.actionPrivacy_settings.triggered.connect(self.privacy_settings)
self.actionInterface_settings.triggered.connect(self.interface_settings)
@ -103,9 +100,6 @@ class MainWindow(QtWidgets.QMainWindow, Singleton):
self.importPlugin.triggered.connect(self.import_plugin)
self.reloadPlugins.triggered.connect(self.reload_plugins)
Form.setLayout(box)
QtCore.QMetaObject.connectSlotsByName(Form)
def languageChange(self, *args, **kwargs):
self.retranslateUi()
@ -117,12 +111,13 @@ class MainWindow(QtWidgets.QMainWindow, Singleton):
def retranslateUi(self):
self.lockApp.setText(QtWidgets.QApplication.translate("MainWindow", "Lock"))
self.plugins_button.setText(QtWidgets.QApplication.translate("MainWindow", "Plugins"))
self.menuPlugins.setTitle(QtWidgets.QApplication.translate("MainWindow", "Plugins"))
self.pluginData.setText(QtWidgets.QApplication.translate("MainWindow", "List of plugins"))
self.profile_button.setText(QtWidgets.QApplication.translate("MainWindow", "Profile"))
self.settings_button.setText(QtWidgets.QApplication.translate("MainWindow", "Settings"))
self.about_button.setText(QtWidgets.QApplication.translate("MainWindow", "About"))
self.menuProfile.setTitle(QtWidgets.QApplication.translate("MainWindow", "Profile"))
self.menuSettings.setTitle(QtWidgets.QApplication.translate("MainWindow", "Settings"))
self.menuAbout.setTitle(QtWidgets.QApplication.translate("MainWindow", "About"))
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.actionPrivacy_settings.setText(QtWidgets.QApplication.translate("MainWindow", "Privacy"))
self.actionInterface_settings.setText(QtWidgets.QApplication.translate("MainWindow", "Interface"))
@ -383,12 +378,8 @@ class MainWindow(QtWidgets.QMainWindow, Singleton):
self.close()
def resizeEvent(self, *args, **kwargs):
if platform.system() == 'Windows':
self.messages.setGeometry(0, 0, self.width() - 270, self.height() - 155)
self.friends_list.setGeometry(0, 0, 270, self.height() - 125)
else:
self.messages.setGeometry(0, 0, self.width() - 270, self.height() - 159)
self.friends_list.setGeometry(0, 0, 270, self.height() - 129)
self.videocallButton.setGeometry(QtCore.QRect(self.width() - 330, 10, 50, 50))
self.callButton.setGeometry(QtCore.QRect(self.width() - 390, 10, 50, 50))
@ -443,6 +434,9 @@ class MainWindow(QtWidgets.QMainWindow, Singleton):
self.a_c = AddContact(link or '')
self.a_c.show()
def create_gc(self):
self.profile.create_group_chat()
def profile_settings(self, *args):
self.p_s = ProfileSettings()
self.p_s.show()
@ -524,7 +518,7 @@ class MainWindow(QtWidgets.QMainWindow, Singleton):
def send_file(self):
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')
name = QtWidgets.QFileDialog.getOpenFileName(self, choose, options=QtWidgets.QFileDialog.DontUseNativeDialog)
if name[0]:
@ -532,7 +526,7 @@ class MainWindow(QtWidgets.QMainWindow, Singleton):
def send_screenshot(self, hide=False):
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.show()
if hide:
@ -550,7 +544,7 @@ class MainWindow(QtWidgets.QMainWindow, Singleton):
def send_sticker(self):
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.setGeometry(QtCore.QRect(self.x() if Settings.get_instance()['mirror_mode'] else 270 + self.x(),
self.y() + self.height() - 200,
@ -595,7 +589,10 @@ class MainWindow(QtWidgets.QMainWindow, Singleton):
auto = QtWidgets.QApplication.translate("MainWindow", 'Disallow auto accept') if allowed else QtWidgets.QApplication.translate("MainWindow", 'Allow auto accept')
if item is not None:
self.listMenu = QtWidgets.QMenu()
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'))
clear_history_item = history_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Clear history'))
@ -605,6 +602,7 @@ class MainWindow(QtWidgets.QMainWindow, Singleton):
copy_menu = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Copy'))
copy_name_item = copy_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Name'))
copy_status_item = copy_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Status message'))
if is_friend:
copy_key_item = copy_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Public key'))
auto_accept_item = self.listMenu.addAction(auto)
@ -612,19 +610,31 @@ class MainWindow(QtWidgets.QMainWindow, Singleton):
block_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Block friend'))
notes_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Notes'))
chats = self.profile.get_group_chats()
if len(chats) and self.profile.is_active_online():
invite_menu = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Invite to group chat'))
for i in range(len(chats)):
name, number = chats[i]
item = invite_menu.addAction(name)
item.triggered.connect(lambda: self.invite_friend_to_gc(num, number))
plugins_loader = plugin_support.PluginLoader.get_instance()
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)
set_alias_item.triggered.connect(lambda: self.set_alias(num))
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))
copy_key_item.triggered.connect(lambda: self.copy_friend_key(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))
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))
copy_name_item.triggered.connect(lambda: self.copy_name(friend))
copy_status_item.triggered.connect(lambda: self.copy_status(friend))
export_to_text_item.triggered.connect(lambda: self.export_history(num))
@ -687,6 +697,12 @@ class MainWindow(QtWidgets.QMainWindow, Singleton):
def clear_history(self, 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):
settings = Settings.get_instance()
tox_id = self.profile.friend_public_key(num)
@ -696,6 +712,9 @@ class MainWindow(QtWidgets.QMainWindow, Singleton):
settings['auto_accept_from_friends'].remove(tox_id)
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
# -----------------------------------------------------------------------------------------------------------------

View File

@ -1,5 +1,5 @@
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
import smileys
import util
@ -34,6 +34,10 @@ class MessageArea(QtWidgets.QPlainTextEdit):
self.parent.send_message()
elif event.key() == QtCore.Qt.Key_Up and not self.toPlainText():
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:
self.parent.profile.send_typing(True)
if self.timer.isActive():
@ -71,38 +75,12 @@ class MessageArea(QtWidgets.QPlainTextEdit):
self.insertPlainText(text)
class ScreenShotWindow(QtWidgets.QWidget):
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)
class ScreenShotWindow(RubberBandWindow):
def closeEvent(self, *args):
if self.parent.isHidden():
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):
if self.rubberband.isVisible():
self.rubberband.hide()
@ -121,13 +99,6 @@ class ScreenShotWindow(QtWidgets.QWidget):
Profile.get_instance().send_screenshot(bytes(byte_array.data()))
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):
"""
@ -153,7 +124,7 @@ class SmileyWindow(QtWidgets.QWidget):
for i in range(self.page_count): # buttons with smileys
elem = QtWidgets.QRadioButton(self)
elem.setGeometry(QtCore.QRect(i * 20 + 5, 180, 20, 20))
elem.clicked.connect(lambda i=i: self.checked(i))
elem.clicked.connect(lambda c, t=i: self.checked(t))
self.radio.append(elem)
width = max(self.page_count * 20 + 30, (self.page_size + 5) * 8 // 10)
self.setMaximumSize(width, 200)
@ -162,7 +133,7 @@ class SmileyWindow(QtWidgets.QWidget):
for i in range(self.page_size): # pages - radio buttons
b = QtWidgets.QPushButton(self)
b.setGeometry(QtCore.QRect((i // 8) * 20 + 5, (i % 8) * 20, 20, 20))
b.clicked.connect(lambda i=i: self.clicked(i))
b.clicked.connect(lambda c, t=i: self.clicked(t))
self.buttons.append(b)
self.checked(0)
@ -341,10 +312,13 @@ class WelcomeScreen(CenteredWidget):
'Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings.')
elif num == 5:
text = QtWidgets.QApplication.translate('WelcomeScreen',
'Since v0.1.3 Toxygen supports plugins. <a href="https://github.com/xveduk/toxygen/blob/master/docs/plugins.md">Read more</a>')
elif num in (6, 7):
'Since v0.1.3 Toxygen supports plugins. <a href="https://github.com/toxygen-project/toxygen/blob/master/docs/plugins.md">Read more</a>')
elif num == 6:
text = QtWidgets.QApplication.translate('WelcomeScreen',
'Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later.')
elif num == 7:
text = QtWidgets.QApplication.translate('WelcomeScreen',
'New in Toxygen 0.3.0:<br>Video calls<br>Python3.6 support<br>Migration to PyQt5')
elif num == 8:
text = QtWidgets.QApplication.translate('WelcomeScreen',
'Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu')

View File

@ -2,7 +2,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
from settings import *
from profile import Profile
from util import curr_directory, copy
from widgets import CenteredWidget, DataLabel, LineEdit
from widgets import CenteredWidget, DataLabel, LineEdit, RubberBandWindow
import pyaudio
import toxes
import plugin_support
@ -248,11 +248,11 @@ class ProfileSettings(CenteredWidget):
def set_avatar(self):
choose = QtWidgets.QApplication.translate("ProfileSettingsForm", "Choose avatar")
name = QtWidgets.QFileDialog.getOpenFileName(self, choose, None, 'Images (*.png)',
QtGui.QComboBoxQtWidgets.QFileDialog.DontUseNativeDialog)
options=QtWidgets.QFileDialog.DontUseNativeDialog)
if name[0]:
bitmap = QtGui.QPixmap(name[0])
bitmap.scaled(QtCore.QSize(128, 128), aspectMode=QtCore.Qt.KeepAspectRatio,
mode=QtCore.Qt.SmoothTransformation)
bitmap.scaled(QtCore.QSize(128, 128), aspectRatioMode=QtCore.Qt.KeepAspectRatio,
transformMode=QtCore.Qt.SmoothTransformation)
byte_array = QtCore.QByteArray()
buffer = QtCore.QBuffer(byte_array)
@ -261,8 +261,8 @@ class ProfileSettings(CenteredWidget):
Profile.get_instance().set_avatar(bytes(byte_array.data()))
def export_profile(self):
directory = QtWidgets.QFileDialog.getExistingDirectory(options=QtWidgets.QFileDialog.DontUseNativeDialog,
dir=curr_directory()) + '/'
directory = QtWidgets.QFileDialog.getExistingDirectory(self, '', curr_directory(),
QtWidgets.QFileDialog.DontUseNativeDialog) + '/'
if directory != '/':
reply = QtWidgets.QMessageBox.question(None,
QtWidgets.QApplication.translate("ProfileSettingsForm",
@ -508,15 +508,17 @@ class NotificationsSettings(CenteredWidget):
def initUI(self):
self.setObjectName("notificationsForm")
self.resize(350, 180)
self.setMinimumSize(QtCore.QSize(350, 180))
self.setMaximumSize(QtCore.QSize(350, 180))
self.resize(350, 210)
self.setMinimumSize(QtCore.QSize(350, 210))
self.setMaximumSize(QtCore.QSize(350, 210))
self.enableNotifications = QtWidgets.QCheckBox(self)
self.enableNotifications.setGeometry(QtCore.QRect(10, 20, 340, 18))
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.setGeometry(QtCore.QRect(10, 70, 340, 18))
self.groupNotifications = QtWidgets.QCheckBox(self)
self.groupNotifications.setGeometry(QtCore.QRect(10, 120, 340, 18))
font = QtGui.QFont()
s = Settings.get_instance()
font.setFamily(s['font'])
@ -524,8 +526,10 @@ class NotificationsSettings(CenteredWidget):
self.callsSound.setFont(font)
self.soundNotifications.setFont(font)
self.enableNotifications.setFont(font)
self.groupNotifications.setFont(font)
self.enableNotifications.setChecked(s['notifications'])
self.soundNotifications.setChecked(s['sound_notifications'])
self.groupNotifications.setChecked(s['group_notifications'])
self.callsSound.setChecked(s['calls_sound'])
self.retranslateUi()
QtCore.QMetaObject.connectSlotsByName(self)
@ -533,6 +537,7 @@ class NotificationsSettings(CenteredWidget):
def retranslateUi(self):
self.setWindowTitle(QtWidgets.QApplication.translate("notificationsForm", "Notification settings"))
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.soundNotifications.setText(QtWidgets.QApplication.translate("notificationsForm", "Enable sound notifications"))
@ -540,6 +545,7 @@ class NotificationsSettings(CenteredWidget):
settings = Settings.get_instance()
settings['notifications'] = self.enableNotifications.isChecked()
settings['sound_notifications'] = self.soundNotifications.isChecked()
settings['group_notifications'] = self.groupNotifications.isChecked()
settings['calls_sound'] = self.callsSound.isChecked()
settings.save()
@ -565,11 +571,10 @@ class InterfaceSettings(CenteredWidget):
self.label.setFont(font)
self.themeSelect = QtWidgets.QComboBox(self)
self.themeSelect.setGeometry(QtCore.QRect(30, 40, 120, 30))
list_of_themes = ['dark']
self.themeSelect.addItems(list_of_themes)
self.themeSelect.addItems(list(settings.built_in_themes().keys()))
theme = settings['theme']
if theme in list_of_themes:
index = list_of_themes.index(theme)
if theme in settings.built_in_themes().keys():
index = list(settings.built_in_themes().keys()).index(theme)
else:
index = 0
self.themeSelect.setCurrentIndex(index)
@ -704,6 +709,14 @@ class InterfaceSettings(CenteredWidget):
def closeEvent(self, event):
settings = Settings.get_instance()
settings['theme'] = str(self.themeSelect.currentText())
try:
theme = settings['theme']
app = QtWidgets.QApplication.instance()
with open(curr_directory() + settings.built_in_themes()[theme]) as fl:
style = fl.read()
app.setStyleSheet(style)
except IsADirectoryError:
app.setStyleSheet('') # for default style
settings['smileys'] = self.smileys.isChecked()
restart = False
if settings['mirror_mode'] != self.mirror_mode.isChecked():
@ -795,6 +808,18 @@ class AudioSettings(CenteredWidget):
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, height)
self.close()
class VideoSettings(CenteredWidget):
"""
Audio calls settings form
@ -805,6 +830,7 @@ class VideoSettings(CenteredWidget):
self.initUI()
self.retranslateUi()
self.center()
self.desktopAreaSelection = None
def initUI(self):
self.setObjectName("videoSettingsForm")
@ -824,9 +850,16 @@ class VideoSettings(CenteredWidget):
self.input = QtWidgets.QComboBox(self)
self.input.setGeometry(QtCore.QRect(25, 30, 350, 30))
self.input.currentIndexChanged.connect(self.selectionChanged)
self.button = QtWidgets.QPushButton(self)
self.button.clicked.connect(self.button_clicked)
self.button.setGeometry(QtCore.QRect(25, 70, 350, 30))
import cv2
self.devices = []
self.frame_max_sizes = []
self.devices = [-1]
screen = QtWidgets.QApplication.primaryScreen()
size = screen.size()
self.frame_max_sizes = [(size.width(), size.height())]
desktop = QtWidgets.QApplication.translate("videoSettingsForm", "Desktop")
self.input.addItem(desktop)
for i in range(10):
v = cv2.VideoCapture(i)
if v.isOpened():
@ -839,23 +872,50 @@ class VideoSettings(CenteredWidget):
self.devices.append(i)
self.frame_max_sizes.append((width, height))
self.input.addItem('Device #' + str(i))
try:
index = self.devices.index(settings.video['device'])
if index + 1:
self.input.setCurrentIndex(index)
except:
print('Video devices error!')
def retranslateUi(self):
self.setWindowTitle(QtWidgets.QApplication.translate("videoSettingsForm", "Video settings"))
self.in_label.setText(QtWidgets.QApplication.translate("videoSettingsForm", "Device:"))
self.button.setText(QtWidgets.QApplication.translate("videoSettingsForm", "Select region"))
def button_clicked(self):
self.desktopAreaSelection = DesktopAreaSelectionWindow(self)
def closeEvent(self, event):
if self.input.currentIndex() == 0:
return
try:
settings = Settings.get_instance()
settings.video['device'] = self.devices[self.input.currentIndex()]
text = self.video_size.currentText()
settings.video['width'] = int(text.split(' ')[0])
settings.video['height'] = int(text.split(' ')[-1])
settings.save()
except Exception as ex:
print('Saving video settings error: ' + str(ex))
def save(self, x, y, width, height):
self.desktopAreaSelection = None
settings = Settings.get_instance()
settings.video['device'] = -1
settings.video['width'] = width
settings.video['height'] = height
settings.video['x'] = x
settings.video['y'] = y
settings.save()
def selectionChanged(self):
if self.input.currentIndex() == 0:
self.button.setVisible(True)
self.video_size.setVisible(False)
else:
self.button.setVisible(False)
self.video_size.setVisible(True)
width, height = self.frame_max_sizes[self.input.currentIndex()]
self.video_size.clear()
dims = [

View File

@ -5,7 +5,9 @@ MESSAGE_TYPE = {
'ACTION': 1,
'FILE_TRANSFER': 2,
'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
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):
"""
Message with info about file transfer

View File

@ -16,6 +16,8 @@ import basecontact
import items_factory
import cv2
import threading
from group_chat import *
import re
class Profile(basecontact.BaseContact, Singleton):
@ -88,6 +90,7 @@ class Profile(basecontact.BaseContact, Singleton):
if status is not None:
self._tox.self_set_status(status)
elif not self._waiting_for_reconnection:
self._waiting_for_reconnection = True
QtCore.QTimer.singleShot(50000, self.reconnect)
def set_name(self, value):
@ -128,6 +131,7 @@ class Profile(basecontact.BaseContact, Singleton):
filter_str = filter_str.lower()
settings = Settings.get_instance()
number = self.get_active_number()
is_friend = self.is_active_a_friend()
if sorting > 1:
if sorting & 2:
self._contacts = sorted(self._contacts, key=lambda x: int(x.status is not None), reverse=True)
@ -163,7 +167,7 @@ class Profile(basecontact.BaseContact, Singleton):
self._sorting, self._filter_string = sorting, filter_str
settings['sorting'] = self._sorting
settings.save()
self.set_active_by_number(number)
self.set_active_by_number_and_type(number, is_friend)
def update_filtration(self):
"""
@ -176,7 +180,7 @@ class Profile(basecontact.BaseContact, Singleton):
# -----------------------------------------------------------------------------------------------------------------
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):
if num < 0 or num >= len(self._contacts):
@ -203,6 +207,7 @@ class Profile(basecontact.BaseContact, Singleton):
if value == -1: # all friends were deleted
self._screen.account_name.setText('')
self._screen.account_status.setText('')
self._screen.account_status.setToolTip('')
self._active_friend = -1
self._screen.account_avatar.setHidden(True)
self._messages.clear()
@ -250,12 +255,15 @@ class Profile(basecontact.BaseContact, Singleton):
print('Incoming not started transfer - no info found')
elif message.get_type() == MESSAGE_TYPE['INLINE']: # inline
self.create_inline_item(message.get_data())
else: # info message
elif message.get_type() < 5: # info message
data = message.get_data()
self.create_message_item(data[0],
data[2],
'',
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._load_history = True
if value in self._call:
@ -269,6 +277,10 @@ class Profile(basecontact.BaseContact, Singleton):
self._screen.account_name.setText(friend.name)
self._screen.account_status.setText(friend.status_message)
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
avatar_path = curr_directory() + '/images/avatar.png'
@ -281,9 +293,10 @@ class Profile(basecontact.BaseContact, Singleton):
log('Error in set active: ' + str(ex))
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)):
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
break
@ -346,7 +359,7 @@ class Profile(basecontact.BaseContact, Singleton):
elif data[1] == friend_number and not data[2]:
self.send_file(data[0], friend_number, True, 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()
except Exception as ex:
print('Exception in file sending: ' + str(ex))
@ -388,7 +401,7 @@ class Profile(basecontact.BaseContact, Singleton):
"""
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)
# -----------------------------------------------------------------------------------------------------------------
@ -444,7 +457,7 @@ class Profile(basecontact.BaseContact, Singleton):
:param message_type: message type - plain text or action message (/me)
: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()
self.create_message_item(message, t, MESSAGE_OWNER['FRIEND'], message_type)
self._messages.scrollToBottom()
@ -464,6 +477,9 @@ class Profile(basecontact.BaseContact, Singleton):
:param text: message text
:param friend_num: num of friend
"""
if not self.is_active_a_friend():
self.send_gc_message(text)
return
if friend_num is None:
friend_num = self.get_active_number()
if text.startswith('/plugin '):
@ -479,8 +495,8 @@ class Profile(basecontact.BaseContact, Singleton):
friend.inc_receipts()
if friend.status is not None:
self.split_and_send(friend.number, message_type, text.encode('utf-8'))
if friend.number == self.get_active_number():
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._screen.messageEdit.clear()
self._messages.scrollToBottom()
@ -503,7 +519,7 @@ class Profile(basecontact.BaseContact, Singleton):
s = Settings.get_instance()
if hasattr(self, '_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):
self._history.add_friend_to_db(friend.tox_id)
if not s['save_unsent_only']:
@ -635,6 +651,16 @@ class Profile(basecontact.BaseContact, Singleton):
return self._factory.message_item(text, time, name, owner != MESSAGE_OWNER['NOT_SENT'],
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):
data = list(tm.get_data())
data[3] = self.get_friend_by_number(data[4]).name if data[3] else self._name
@ -666,7 +692,7 @@ class Profile(basecontact.BaseContact, Singleton):
dialog = dialog.format(name)
title = QtWidgets.QApplication.translate('MainWindow',
'Set alias')
text, ok = QtGui.QInputDialog.getText(None,
text, ok = QtWidgets.QInputDialog.getText(None,
title,
dialog,
QtWidgets.QLineEdit.Normal,
@ -691,7 +717,7 @@ class Profile(basecontact.BaseContact, Singleton):
except:
pass
settings.save()
if num == self.get_active_number():
if num == self.get_active_number() and self.is_active_a_friend():
self.update()
def friend_public_key(self, num):
@ -862,7 +888,7 @@ class Profile(basecontact.BaseContact, Singleton):
QtCore.QTimer.singleShot(50000, self.reconnect)
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)
for i in range(len(self._contacts)):
del self._contacts[0]
@ -935,7 +961,7 @@ class Profile(basecontact.BaseContact, Singleton):
friend_number,
file_number)
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)
if accepted:
self._file_transfers[(friend_number, file_number)].set_state_changed_handler(item.update_transfer_state)
@ -966,7 +992,7 @@ class Profile(basecontact.BaseContact, Singleton):
else:
if not already_cancelled:
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
if tmp >= 0:
self._messages.itemWidget(self._messages.item(tmp)).update(TOX_FILE_TRANSFER_STATE['CANCELLED'],
@ -1120,7 +1146,7 @@ class Profile(basecontact.BaseContact, Singleton):
t = type(transfer)
if t is ReceiveAvatar:
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)
elif t is ReceiveToBuffer or (t is SendFromBuffer and Settings.get_instance()['allow_inline']): # inline image
print('inline')
@ -1128,7 +1154,7 @@ class Profile(basecontact.BaseContact, Singleton):
i = self.get_friend_by_number(friend_number).update_transfer_data(file_number,
TOX_FILE_TRANSFER_STATE['FINISHED'],
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()
if count + i + 1 >= 0:
elem = QtWidgets.QListWidgetItem()
@ -1170,7 +1196,7 @@ class Profile(basecontact.BaseContact, Singleton):
ra.set_transfer_finished_handler(self.transfer_finished)
else:
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)
def reset_avatar(self):
@ -1195,6 +1221,8 @@ class Profile(basecontact.BaseContact, Singleton):
def call_click(self, audio=True, video=False):
"""User clicked audio button in main window"""
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 not Settings.get_instance().audio['enabled']:
return
@ -1264,6 +1292,130 @@ class Profile(basecontact.BaseContact, Singleton):
self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
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):
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):
"""

22
toxygen/screen_sharing.py Normal file
View 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

View File

@ -47,7 +47,7 @@ class Settings(dict, Singleton):
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,
'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
def get_auto_profile():
@ -57,7 +57,10 @@ class Settings(dict, Singleton):
data = fl.read()
auto = json.loads(data)
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 '', ''
@staticmethod
@ -100,7 +103,7 @@ class Settings(dict, Singleton):
Default profile settings
"""
return {
'theme': 'default',
'theme': 'dark',
'ipv6_enabled': True,
'udp_enabled': True,
'proxy_type': 0,
@ -141,15 +144,24 @@ class Settings(dict, Singleton):
'show_welcome_screen': True,
'close_to_tray': False,
'font': 'Times New Roman',
'update': 1
'update': 1,
'group_notifications': True
}
@staticmethod
def supported_languages():
return {
'English': 'en_EN',
'French': 'fr_FR',
'Russian': 'ru_RU',
'French': 'fr_FR'
'Ukrainian': 'uk_UA'
}
@staticmethod
def built_in_themes():
return {
'dark': '/styles/dark_style.qss',
'default': '/styles/style.qss'
}
def upgrade(self):

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -188,6 +188,17 @@ TOX_ERR_GET_PORT = {
'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_ADDRESS_SIZE = TOX_PUBLIC_KEY_SIZE + 6

View File

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

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -77,6 +77,42 @@ class RubberBand(QtWidgets.QRubberBand):
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):
"""
:return translated menu
@ -128,4 +164,3 @@ class MultilineEdit(CenteredWidget):
def button_click(self):
self.save(self.edit.toPlainText())
self.close()