11 Commits

Author SHA1 Message Date
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
17 changed files with 1150 additions and 1002 deletions

View File

@ -14,35 +14,34 @@ 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 - Plugins support
- [x] Chat history - Desktop sharing
- [x] Emoticons - Chat history
- [x] Stickers - Emoticons
- [x] Screenshots - Stickers
- [x] Name lookups (toxme.io support) - Screenshots
- [x] Save file encryption - Name lookups (toxme.io support)
- [x] Profile import and export - Save file encryption
- [x] Faux offline messaging - Profile import and export
- [x] Faux offline file transfers - Faux offline messaging
- [x] Inline images - Faux offline file transfers
- [x] Message splitting - Inline images
- [x] Proxy support - Message splitting
- [x] Avatars - Proxy support
- [x] Multiprofile - Avatars
- [x] Multilingual - Multiprofile
- [x] Sound notifications - Multilingual
- [x] Contact aliases - Sound notifications
- [x] Contact blocking - Contact aliases
- [x] Typing notifications - Contact blocking
- [x] Changing nospam - Typing notifications
- [x] File resuming - Changing nospam
- [x] Read receipts - File resuming
- [ ] Desktop sharing - Read receipts
- [ ] Group chats
### Downloads ### Downloads
[Releases](https://github.com/toxygen-project/toxygen/releases) [Releases](https://github.com/toxygen-project/toxygen/releases)

View File

@ -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 apt-get install python3-opencv``
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``

View File

@ -8,10 +8,23 @@ 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')
class InstallScript(install): class InstallScript(install):

View File

@ -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
@ -203,10 +204,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()

View File

@ -1,5 +1,5 @@
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
@ -71,38 +71,12 @@ class MessageArea(QtWidgets.QPlainTextEdit):
self.insertPlainText(text) self.insertPlainText(text)
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 +95,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):
""" """

View File

@ -2,7 +2,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
from settings import * from settings import *
from profile import Profile from profile import Profile
from util import curr_directory, copy from util import curr_directory, copy
from widgets import CenteredWidget, DataLabel, LineEdit 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()
@ -802,6 +802,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, height)
self.close()
class VideoSettings(CenteredWidget): class VideoSettings(CenteredWidget):
""" """
Audio calls settings form Audio calls settings form
@ -812,6 +824,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 +844,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,6 +875,10 @@ 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):
try: try:
@ -867,7 +891,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 = [

View File

@ -663,15 +663,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']

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, 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

View File

@ -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;
}

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

View File

@ -5,7 +5,7 @@ import sys
import re import re
program_version = '0.3.0' program_version = '0.3.1'
def cached(func): def cached(func):

View File

@ -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