diff --git a/toxygen/avwidgets.py b/toxygen/avwidgets.py
deleted file mode 100644
index 8c81387..0000000
--- a/toxygen/avwidgets.py
+++ /dev/null
@@ -1,134 +0,0 @@
-from PyQt5 import QtCore, QtGui, QtWidgets
-import widgets
-import profile
-import util
-import pyaudio
-import wave
-import settings
-from util import curr_directory
-
-
-class IncomingCallWidget(widgets.CenteredWidget):
-
- def __init__(self, friend_number, text, name):
- super(IncomingCallWidget, self).__init__()
- self.setWindowFlags(QtCore.Qt.CustomizeWindowHint | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowStaysOnTopHint)
- self.resize(QtCore.QSize(500, 270))
- self.avatar_label = QtWidgets.QLabel(self)
- self.avatar_label.setGeometry(QtCore.QRect(10, 20, 64, 64))
- self.avatar_label.setScaledContents(False)
- self.name = widgets.DataLabel(self)
- self.name.setGeometry(QtCore.QRect(90, 20, 300, 25))
- self._friend_number = friend_number
- font = QtGui.QFont()
- font.setFamily(settings.Settings.get_instance()['font'])
- font.setPointSize(16)
- font.setBold(True)
- self.name.setFont(font)
- self.call_type = widgets.DataLabel(self)
- self.call_type.setGeometry(QtCore.QRect(90, 55, 300, 25))
- self.call_type.setFont(font)
- self.accept_audio = QtWidgets.QPushButton(self)
- self.accept_audio.setGeometry(QtCore.QRect(20, 100, 150, 150))
- self.accept_video = QtWidgets.QPushButton(self)
- self.accept_video.setGeometry(QtCore.QRect(170, 100, 150, 150))
- self.decline = QtWidgets.QPushButton(self)
- self.decline.setGeometry(QtCore.QRect(320, 100, 150, 150))
- pixmap = QtGui.QPixmap(util.curr_directory() + '/images/accept_audio.png')
- icon = QtGui.QIcon(pixmap)
- self.accept_audio.setIcon(icon)
- pixmap = QtGui.QPixmap(util.curr_directory() + '/images/accept_video.png')
- icon = QtGui.QIcon(pixmap)
- self.accept_video.setIcon(icon)
- pixmap = QtGui.QPixmap(util.curr_directory() + '/images/decline_call.png')
- icon = QtGui.QIcon(pixmap)
- self.decline.setIcon(icon)
- self.accept_audio.setIconSize(QtCore.QSize(150, 150))
- self.accept_video.setIconSize(QtCore.QSize(140, 140))
- self.decline.setIconSize(QtCore.QSize(140, 140))
- self.accept_audio.setStyleSheet("QPushButton { border: none }")
- self.accept_video.setStyleSheet("QPushButton { border: none }")
- self.decline.setStyleSheet("QPushButton { border: none }")
- self.setWindowTitle(text)
- self.name.setText(name)
- self.call_type.setText(text)
- self._processing = False
- self.accept_audio.clicked.connect(self.accept_call_with_audio)
- self.accept_video.clicked.connect(self.accept_call_with_video)
- self.decline.clicked.connect(self.decline_call)
-
- class SoundPlay(QtCore.QThread):
-
- def __init__(self):
- QtCore.QThread.__init__(self)
- self.a = None
-
- def run(self):
- class AudioFile:
- chunk = 1024
-
- def __init__(self, fl):
- self.stop = False
- self.fl = fl
- self.wf = wave.open(self.fl, 'rb')
- self.p = pyaudio.PyAudio()
- self.stream = self.p.open(
- format=self.p.get_format_from_width(self.wf.getsampwidth()),
- channels=self.wf.getnchannels(),
- rate=self.wf.getframerate(),
- output=True)
-
- def play(self):
- while not self.stop:
- data = self.wf.readframes(self.chunk)
- while data and not self.stop:
- self.stream.write(data)
- data = self.wf.readframes(self.chunk)
- self.wf = wave.open(self.fl, 'rb')
-
- def close(self):
- self.stream.close()
- self.p.terminate()
-
- self.a = AudioFile(curr_directory() + '/sounds/call.wav')
- self.a.play()
- self.a.close()
-
- if settings.Settings.get_instance()['calls_sound']:
- self.thread = SoundPlay()
- self.thread.start()
- else:
- self.thread = None
-
- def stop(self):
- if self.thread is not None:
- self.thread.a.stop = True
- self.thread.wait()
- self.close()
-
- def accept_call_with_audio(self):
- if self._processing:
- return
- self._processing = True
- pr = profile.Profile.get_instance()
- pr.accept_call(self._friend_number, True, False)
- self.stop()
-
- def accept_call_with_video(self):
- if self._processing:
- return
- self._processing = True
- pr = profile.Profile.get_instance()
- pr.accept_call(self._friend_number, True, True)
- self.stop()
-
- def decline_call(self):
- if self._processing:
- return
- self._processing = True
- pr = profile.Profile.get_instance()
- pr.stop_call(self._friend_number, False)
- self.stop()
-
- def set_pixmap(self, pixmap):
- self.avatar_label.setPixmap(pixmap)
diff --git a/toxygen/basecontact.py b/toxygen/basecontact.py
deleted file mode 100644
index e1243a4..0000000
--- a/toxygen/basecontact.py
+++ /dev/null
@@ -1,118 +0,0 @@
-from settings import *
-from PyQt5 import QtCore, QtGui
-from toxcore_enums_and_consts import TOX_PUBLIC_KEY_SIZE
-
-
-class BaseContact:
- """
- Class encapsulating TOX contact
- Properties: name (alias of contact or name), status_message, status (connection status)
- widget - widget for update, tox id (or public key)
- Base class for all contacts.
- """
-
- def __init__(self, name, status_message, widget, tox_id):
- """
- :param name: name, example: 'Toxygen user'
- :param status_message: status message, example: 'Toxing on Toxygen'
- :param widget: ContactItem instance
- :param tox_id: tox id of contact
- """
- self._name, self._status_message = name, status_message
- self._status, self._widget = None, widget
- self._tox_id = tox_id
- self.init_widget()
-
- # -----------------------------------------------------------------------------------------------------------------
- # Name - current name or alias of user
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_name(self):
- return self._name
-
- def set_name(self, value):
- self._name = str(value, 'utf-8')
- self._widget.name.setText(self._name)
- self._widget.name.repaint()
-
- name = property(get_name, set_name)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Status message
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_status_message(self):
- return self._status_message
-
- def set_status_message(self, value):
- self._status_message = str(value, 'utf-8')
- self._widget.status_message.setText(self._status_message)
- self._widget.status_message.repaint()
-
- status_message = property(get_status_message, set_status_message)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Status
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_status(self):
- return self._status
-
- def set_status(self, value):
- self._status = value
- self._widget.connection_status.update(value)
-
- status = property(get_status, set_status)
-
- # -----------------------------------------------------------------------------------------------------------------
- # TOX ID. WARNING: for friend it will return public key, for profile - full address
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_tox_id(self):
- return self._tox_id
-
- tox_id = property(get_tox_id)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Avatars
- # -----------------------------------------------------------------------------------------------------------------
-
- def load_avatar(self):
- """
- Tries to load avatar of contact or uses default avatar
- """
- prefix = ProfileHelper.get_path() + 'avatars/'
- avatar_path = prefix + '{}.png'.format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
- if not os.path.isfile(avatar_path) or not os.path.getsize(avatar_path): # load default image
- avatar_path = curr_directory() + '/images/avatar.png'
- width = self._widget.avatar_label.width()
- pixmap = QtGui.QPixmap(avatar_path)
- self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio,
- QtCore.Qt.SmoothTransformation))
- self._widget.avatar_label.repaint()
-
- def reset_avatar(self):
- avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
- if os.path.isfile(avatar_path):
- os.remove(avatar_path)
- self.load_avatar()
-
- def set_avatar(self, avatar):
- avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
- with open(avatar_path, 'wb') as f:
- f.write(avatar)
- self.load_avatar()
-
- def get_pixmap(self):
- return self._widget.avatar_label.pixmap()
-
- # -----------------------------------------------------------------------------------------------------------------
- # Widgets
- # -----------------------------------------------------------------------------------------------------------------
-
- def init_widget(self):
- if self._widget is not None:
- self._widget.name.setText(self._name)
- self._widget.status_message.setText(self._status_message)
- self._widget.connection_status.update(self._status)
- self.load_avatar()
diff --git a/toxygen/bootstrap.py b/toxygen/bootstrap.py
deleted file mode 100644
index 0589940..0000000
--- a/toxygen/bootstrap.py
+++ /dev/null
@@ -1,75 +0,0 @@
-import random
-import urllib.request
-from util import log, curr_directory
-import settings
-from PyQt5 import QtNetwork, QtCore
-import json
-
-
-class Node:
-
- def __init__(self, node):
- self._ip, self._port, self._tox_key = node['ipv4'], node['port'], node['public_key']
- self._priority = random.randint(1, 1000000) if node['status_tcp'] and node['status_udp'] else 0
-
- def get_priority(self):
- return self._priority
-
- priority = property(get_priority)
-
- def get_data(self):
- return bytes(self._ip, 'utf-8'), self._port, self._tox_key
-
-
-def generate_nodes():
- with open(curr_directory() + '/nodes.json', 'rt') as fl:
- json_nodes = json.loads(fl.read())['nodes']
- nodes = map(lambda json_node: Node(json_node), json_nodes)
- sorted_nodes = sorted(nodes, key=lambda x: x.priority)[-4:]
- for node in sorted_nodes:
- yield node.get_data()
-
-
-def save_nodes(nodes):
- if not nodes:
- return
- print('Saving nodes...')
- with open(curr_directory() + '/nodes.json', 'wb') as fl:
- fl.write(nodes)
-
-
-def download_nodes_list():
- url = 'https://nodes.tox.chat/json'
- s = settings.Settings.get_instance()
- if not s['download_nodes_list']:
- return
-
- if not s['proxy_type']: # no proxy
- try:
- req = urllib.request.Request(url)
- req.add_header('Content-Type', 'application/json')
- response = urllib.request.urlopen(req)
- result = response.read()
- save_nodes(result)
- except Exception as ex:
- log('TOX nodes loading error: ' + str(ex))
- else: # proxy
- netman = QtNetwork.QNetworkAccessManager()
- proxy = QtNetwork.QNetworkProxy()
- proxy.setType(
- QtNetwork.QNetworkProxy.Socks5Proxy if s['proxy_type'] == 2 else QtNetwork.QNetworkProxy.HttpProxy)
- proxy.setHostName(s['proxy_host'])
- proxy.setPort(s['proxy_port'])
- netman.setProxy(proxy)
- try:
- request = QtNetwork.QNetworkRequest()
- request.setUrl(QtCore.QUrl(url))
- reply = netman.get(request)
-
- while not reply.isFinished():
- QtCore.QThread.msleep(1)
- QtCore.QCoreApplication.processEvents()
- data = bytes(reply.readAll().data())
- save_nodes(data)
- except Exception as ex:
- log('TOX nodes loading error: ' + str(ex))
diff --git a/toxygen/callbacks.py b/toxygen/callbacks.py
deleted file mode 100644
index b59d17c..0000000
--- a/toxygen/callbacks.py
+++ /dev/null
@@ -1,469 +0,0 @@
-from PyQt5 import QtCore, QtGui, QtWidgets
-from notifications import *
-from settings import Settings
-from profile import Profile
-from toxcore_enums_and_consts import *
-from toxav_enums import *
-from tox import bin_to_string
-from plugin_support import PluginLoader
-import queue
-import threading
-import util
-import cv2
-import numpy as np
-
-# -----------------------------------------------------------------------------------------------------------------
-# Threads
-# -----------------------------------------------------------------------------------------------------------------
-
-
-class InvokeEvent(QtCore.QEvent):
- EVENT_TYPE = QtCore.QEvent.Type(QtCore.QEvent.registerEventType())
-
- def __init__(self, fn, *args, **kwargs):
- QtCore.QEvent.__init__(self, InvokeEvent.EVENT_TYPE)
- self.fn = fn
- self.args = args
- self.kwargs = kwargs
-
-
-class Invoker(QtCore.QObject):
-
- def event(self, event):
- event.fn(*event.args, **event.kwargs)
- return True
-
-
-_invoker = Invoker()
-
-
-def invoke_in_main_thread(fn, *args, **kwargs):
- QtCore.QCoreApplication.postEvent(_invoker, InvokeEvent(fn, *args, **kwargs))
-
-
-class FileTransfersThread(threading.Thread):
-
- def __init__(self):
- self._queue = queue.Queue()
- self._timeout = 0.01
- self._continue = True
- super().__init__()
-
- def execute(self, function, *args, **kwargs):
- self._queue.put((function, args, kwargs))
-
- def stop(self):
- self._continue = False
-
- def run(self):
- while self._continue:
- try:
- function, args, kwargs = self._queue.get(timeout=self._timeout)
- function(*args, **kwargs)
- except queue.Empty:
- pass
- except queue.Full:
- util.log('Queue is Full in _thread')
- except Exception as ex:
- util.log('Exception in _thread: ' + str(ex))
-
-
-_thread = FileTransfersThread()
-
-
-def start():
- _thread.start()
-
-
-def stop():
- _thread.stop()
- _thread.join()
-
-# -----------------------------------------------------------------------------------------------------------------
-# Callbacks - current user
-# -----------------------------------------------------------------------------------------------------------------
-
-
-def self_connection_status(tox_link):
- """
- Current user changed connection status (offline, UDP, TCP)
- """
- def wrapped(tox, connection, user_data):
- print('Connection status: ', str(connection))
- profile = Profile.get_instance()
- if profile.status is None:
- status = tox_link.self_get_status()
- invoke_in_main_thread(profile.set_status, status)
- elif connection == TOX_CONNECTION['NONE']:
- invoke_in_main_thread(profile.set_status, None)
- return wrapped
-
-
-# -----------------------------------------------------------------------------------------------------------------
-# Callbacks - friends
-# -----------------------------------------------------------------------------------------------------------------
-
-
-def friend_status(tox, friend_num, new_status, user_data):
- """
- Check friend's status (none, busy, away)
- """
- print("Friend's #{} status changed!".format(friend_num))
- profile = Profile.get_instance()
- friend = profile.get_friend_by_number(friend_num)
- if friend.status is None and Settings.get_instance()['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
- sound_notification(SOUND_NOTIFICATION['FRIEND_CONNECTION_STATUS'])
- invoke_in_main_thread(friend.set_status, new_status)
- invoke_in_main_thread(QtCore.QTimer.singleShot, 5000, lambda: profile.send_files(friend_num))
- invoke_in_main_thread(profile.update_filtration)
-
-
-def friend_connection_status(tox, friend_num, new_status, user_data):
- """
- Check friend's connection status (offline, udp, tcp)
- """
- print("Friend #{} connection status: {}".format(friend_num, new_status))
- profile = Profile.get_instance()
- friend = profile.get_friend_by_number(friend_num)
- if new_status == TOX_CONNECTION['NONE']:
- invoke_in_main_thread(profile.friend_exit, friend_num)
- invoke_in_main_thread(profile.update_filtration)
- if Settings.get_instance()['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
- sound_notification(SOUND_NOTIFICATION['FRIEND_CONNECTION_STATUS'])
- elif friend.status is None:
- invoke_in_main_thread(profile.send_avatar, friend_num)
- invoke_in_main_thread(PluginLoader.get_instance().friend_online, friend_num)
-
-
-def friend_name(tox, friend_num, name, size, user_data):
- """
- Friend changed his name
- """
- profile = Profile.get_instance()
- print('New name friend #' + str(friend_num))
- invoke_in_main_thread(profile.new_name, friend_num, name)
-
-
-def friend_status_message(tox, friend_num, status_message, size, user_data):
- """
- :return: function for callback friend_status_message. It updates friend's status message
- and calls window repaint
- """
- profile = Profile.get_instance()
- friend = profile.get_friend_by_number(friend_num)
- invoke_in_main_thread(friend.set_status_message, status_message)
- print('User #{} has new status'.format(friend_num))
- invoke_in_main_thread(profile.send_messages, friend_num)
- if profile.get_active_number() == friend_num:
- invoke_in_main_thread(profile.set_active)
-
-
-def friend_message(window, tray):
- """
- New message from friend
- """
- def wrapped(tox, friend_number, message_type, message, size, user_data):
- profile = Profile.get_instance()
- settings = Settings.get_instance()
- message = str(message, 'utf-8')
- invoke_in_main_thread(profile.new_message, friend_number, message_type, message)
- if not window.isActiveWindow():
- friend = profile.get_friend_by_number(friend_number)
- if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and not settings.locked:
- invoke_in_main_thread(tray_notification, friend.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'))
- return wrapped
-
-
-def friend_request(tox, public_key, message, message_size, user_data):
- """
- Called when user get new friend request
- """
- print('Friend request')
- profile = Profile.get_instance()
- key = ''.join(chr(x) for x in public_key[:TOX_PUBLIC_KEY_SIZE])
- tox_id = bin_to_string(key, TOX_PUBLIC_KEY_SIZE)
- if tox_id not in Settings.get_instance()['blocked']:
- invoke_in_main_thread(profile.process_friend_request, tox_id, str(message, 'utf-8'))
-
-
-def friend_typing(tox, friend_number, typing, user_data):
- invoke_in_main_thread(Profile.get_instance().friend_typing, friend_number, typing)
-
-
-def friend_read_receipt(tox, friend_number, message_id, user_data):
- profile = Profile.get_instance()
- profile.get_friend_by_number(friend_number).dec_receipt()
- if friend_number == profile.get_active_number():
- invoke_in_main_thread(profile.receipt)
-
-# -----------------------------------------------------------------------------------------------------------------
-# Callbacks - file transfers
-# -----------------------------------------------------------------------------------------------------------------
-
-
-def tox_file_recv(window, tray):
- """
- New incoming file
- """
- def wrapped(tox, friend_number, file_number, file_type, size, file_name, file_name_size, user_data):
- profile = Profile.get_instance()
- settings = Settings.get_instance()
- if file_type == TOX_FILE_KIND['DATA']:
- print('File')
- try:
- file_name = str(file_name[:file_name_size], 'utf-8')
- except:
- file_name = 'toxygen_file'
- invoke_in_main_thread(profile.incoming_file_transfer,
- friend_number,
- file_number,
- size,
- file_name)
- if not window.isActiveWindow():
- friend = profile.get_friend_by_number(friend_number)
- if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and not settings.locked:
- file_from = QtWidgets.QApplication.translate("Callback", "File from")
- invoke_in_main_thread(tray_notification, file_from + ' ' + friend.name, file_name, tray, window)
- if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
- sound_notification(SOUND_NOTIFICATION['FILE_TRANSFER'])
- invoke_in_main_thread(tray.setIcon, QtGui.QIcon(curr_directory() + '/images/icon_new_messages.png'))
- else: # AVATAR
- print('Avatar')
- invoke_in_main_thread(profile.incoming_avatar,
- friend_number,
- file_number,
- size)
- return wrapped
-
-
-def file_recv_chunk(tox, friend_number, file_number, position, chunk, length, user_data):
- """
- Incoming chunk
- """
- _thread.execute(Profile.get_instance().incoming_chunk, friend_number, file_number, position,
- chunk[:length] if length else None)
-
-
-def file_chunk_request(tox, friend_number, file_number, position, size, user_data):
- """
- Outgoing chunk
- """
- Profile.get_instance().outgoing_chunk(friend_number, file_number, position, size)
-
-
-def file_recv_control(tox, friend_number, file_number, file_control, user_data):
- """
- Friend cancelled, paused or resumed file transfer
- """
- if file_control == TOX_FILE_CONTROL['CANCEL']:
- invoke_in_main_thread(Profile.get_instance().cancel_transfer, friend_number, file_number, True)
- elif file_control == TOX_FILE_CONTROL['PAUSE']:
- invoke_in_main_thread(Profile.get_instance().pause_transfer, friend_number, file_number, True)
- elif file_control == TOX_FILE_CONTROL['RESUME']:
- invoke_in_main_thread(Profile.get_instance().resume_transfer, friend_number, file_number, True)
-
-# -----------------------------------------------------------------------------------------------------------------
-# Callbacks - custom packets
-# -----------------------------------------------------------------------------------------------------------------
-
-
-def lossless_packet(tox, friend_number, data, length, user_data):
- """
- Incoming lossless packet
- """
- data = data[:length]
- plugin = PluginLoader.get_instance()
- invoke_in_main_thread(plugin.callback_lossless, friend_number, data)
-
-
-def lossy_packet(tox, friend_number, data, length, user_data):
- """
- Incoming lossy packet
- """
- data = data[:length]
- plugin = PluginLoader.get_instance()
- invoke_in_main_thread(plugin.callback_lossy, friend_number, data)
-
-
-# -----------------------------------------------------------------------------------------------------------------
-# Callbacks - audio
-# -----------------------------------------------------------------------------------------------------------------
-
-def call_state(toxav, friend_number, mask, user_data):
- """
- New call state
- """
- print(friend_number, mask)
- if mask == TOXAV_FRIEND_CALL_STATE['FINISHED'] or mask == TOXAV_FRIEND_CALL_STATE['ERROR']:
- invoke_in_main_thread(Profile.get_instance().stop_call, friend_number, True)
- else:
- Profile.get_instance().call.toxav_call_state_cb(friend_number, mask)
-
-
-def call(toxav, friend_number, audio, video, user_data):
- """
- Incoming call from friend
- """
- print(friend_number, audio, video)
- invoke_in_main_thread(Profile.get_instance().incoming_call, audio, video, friend_number)
-
-
-def callback_audio(toxav, friend_number, samples, audio_samples_per_channel, audio_channels_count, rate, user_data):
- """
- New audio chunk
- """
- Profile.get_instance().call.audio_chunk(
- bytes(samples[:audio_samples_per_channel * 2 * audio_channels_count]),
- audio_channels_count,
- rate)
-
-# -----------------------------------------------------------------------------------------------------------------
-# Callbacks - video
-# -----------------------------------------------------------------------------------------------------------------
-
-
-def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, ustride, vstride, user_data):
- """
- Creates yuv frame from y, u, v and shows it using OpenCV
- For yuv => bgr we need this YUV420 frame:
-
- width
- -------------------------
- | |
- | Y | height
- | |
- -------------------------
- | | |
- | U even | U odd | height // 4
- | | |
- -------------------------
- | | |
- | V even | V odd | height // 4
- | | |
- -------------------------
-
- width // 2 width // 2
-
- It can be created from initial y, u, v using slices
- """
- try:
- y_size = abs(max(width, abs(ystride)))
- u_size = abs(max(width // 2, abs(ustride)))
- v_size = abs(max(width // 2, abs(vstride)))
-
- y = np.asarray(y[:y_size * height], dtype=np.uint8).reshape(height, y_size)
- u = np.asarray(u[:u_size * height // 2], dtype=np.uint8).reshape(height // 2, u_size)
- v = np.asarray(v[:v_size * height // 2], dtype=np.uint8).reshape(height // 2, v_size)
-
- width -= width % 4
- height -= height % 4
-
- frame = np.zeros((int(height * 1.5), width), dtype=np.uint8)
-
- frame[:height, :] = y[:height, :width]
- frame[height:height * 5 // 4, :width // 2] = u[:height // 2:2, :width // 2]
- frame[height:height * 5 // 4, width // 2:] = u[1:height // 2:2, :width // 2]
-
- frame[height * 5 // 4:, :width // 2] = v[:height // 2:2, :width // 2]
- frame[height * 5 // 4:, width // 2:] = v[1:height // 2:2, :width // 2]
-
- frame = cv2.cvtColor(frame, cv2.COLOR_YUV2BGR_I420)
-
- invoke_in_main_thread(cv2.imshow, str(friend_number), frame)
- except Exception as ex:
- print(ex)
-
-# -----------------------------------------------------------------------------------------------------------------
-# Callbacks - groups
-# -----------------------------------------------------------------------------------------------------------------
-
-
-def group_invite(tox, friend_number, gc_type, data, length, user_data):
- invoke_in_main_thread(Profile.get_instance().group_invite, friend_number, gc_type,
- bytes(data[:length]))
-
-
-def show_gc_notification(window, tray, message, group_number, peer_number):
- profile = Profile.get_instance()
- settings = Settings.get_instance()
- chat = profile.get_group_by_number(group_number)
- peer_name = chat.get_peer_name(peer_number)
- if not window.isActiveWindow() and (profile.name in message or settings['group_notifications']):
- if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and not settings.locked:
- invoke_in_main_thread(tray_notification, chat.name + ' ' + peer_name, message, tray, window)
- if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
- sound_notification(SOUND_NOTIFICATION['MESSAGE'])
- invoke_in_main_thread(tray.setIcon, QtGui.QIcon(curr_directory() + '/images/icon_new_messages.png'))
-
-
-def group_message(window, tray):
- def wrapped(tox, group_number, peer_number, message, length, user_data):
- message = str(message[:length], 'utf-8')
- invoke_in_main_thread(Profile.get_instance().new_gc_message, group_number,
- peer_number, TOX_MESSAGE_TYPE['NORMAL'], message)
- show_gc_notification(window, tray, message, group_number, peer_number)
- return wrapped
-
-
-def group_action(window, tray):
- def wrapped(tox, group_number, peer_number, message, length, user_data):
- message = str(message[:length], 'utf-8')
- invoke_in_main_thread(Profile.get_instance().new_gc_message, group_number,
- peer_number, TOX_MESSAGE_TYPE['ACTION'], message)
- show_gc_notification(window, tray, message, group_number, peer_number)
- return wrapped
-
-
-def group_title(tox, group_number, peer_number, title, length, user_data):
- invoke_in_main_thread(Profile.get_instance().new_gc_title, group_number,
- title[:length])
-
-
-def group_namelist_change(tox, group_number, peer_number, change, user_data):
- invoke_in_main_thread(Profile.get_instance().update_gc, group_number)
-
-# -----------------------------------------------------------------------------------------------------------------
-# Callbacks - initialization
-# -----------------------------------------------------------------------------------------------------------------
-
-
-def init_callbacks(tox, window, tray):
- """
- Initialization of all callbacks.
- :param tox: tox instance
- :param window: main window
- :param tray: tray (for notifications)
- """
- tox.callback_self_connection_status(self_connection_status(tox), 0)
-
- tox.callback_friend_status(friend_status, 0)
- tox.callback_friend_message(friend_message(window, tray), 0)
- tox.callback_friend_connection_status(friend_connection_status, 0)
- tox.callback_friend_name(friend_name, 0)
- tox.callback_friend_status_message(friend_status_message, 0)
- tox.callback_friend_request(friend_request, 0)
- tox.callback_friend_typing(friend_typing, 0)
- tox.callback_friend_read_receipt(friend_read_receipt, 0)
-
- tox.callback_file_recv(tox_file_recv(window, tray), 0)
- tox.callback_file_recv_chunk(file_recv_chunk, 0)
- tox.callback_file_chunk_request(file_chunk_request, 0)
- tox.callback_file_recv_control(file_recv_control, 0)
-
- toxav = tox.AV
- toxav.callback_call_state(call_state, 0)
- toxav.callback_call(call, 0)
- toxav.callback_audio_receive_frame(callback_audio, 0)
- toxav.callback_video_receive_frame(video_receive_frame, 0)
-
- tox.callback_friend_lossless_packet(lossless_packet, 0)
- tox.callback_friend_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)
diff --git a/toxygen/calls.py b/toxygen/calls.py
deleted file mode 100644
index 3d02110..0000000
--- a/toxygen/calls.py
+++ /dev/null
@@ -1,339 +0,0 @@
-import pyaudio
-import time
-import threading
-import settings
-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
-
-
-class Call:
-
- def __init__(self, out_audio, out_video, in_audio=False, in_video=False):
- self._in_audio = in_audio
- self._in_video = in_video
- self._out_audio = out_audio
- self._out_video = out_video
- self._is_active = False
-
- def get_is_active(self):
- return self._is_active
-
- def set_is_active(self, value):
- self._is_active = value
-
- is_active = property(get_is_active, set_is_active)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Audio
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_in_audio(self):
- return self._in_audio
-
- def set_in_audio(self, value):
- self._in_audio = value
-
- in_audio = property(get_in_audio, set_in_audio)
-
- def get_out_audio(self):
- return self._out_audio
-
- def set_out_audio(self, value):
- self._out_audio = value
-
- out_audio = property(get_out_audio, set_out_audio)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Video
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_in_video(self):
- return self._in_video
-
- def set_in_video(self, value):
- self._in_video = value
-
- in_video = property(get_in_video, set_in_video)
-
- def get_out_video(self):
- return self._out_video
-
- def set_out_video(self, value):
- self._out_video = value
-
- out_video = property(get_out_video, set_out_video)
-
-
-class AV:
-
- def __init__(self, toxav):
- self._toxav = toxav
- self._running = True
-
- self._calls = {} # dict: key - friend number, value - Call instance
-
- self._audio = None
- self._audio_stream = None
- self._audio_thread = None
- self._audio_running = False
- self._out_stream = None
-
- self._audio_rate = 8000
- self._audio_channels = 1
- self._audio_duration = 60
- self._audio_sample_count = self._audio_rate * self._audio_channels * self._audio_duration // 1000
-
- self._video = None
- self._video_thread = None
- self._video_running = False
-
- self._video_width = 640
- self._video_height = 480
-
- def stop(self):
- self._running = False
- self.stop_audio_thread()
- self.stop_video_thread()
-
- def __contains__(self, friend_number):
- return friend_number in self._calls
-
- # -----------------------------------------------------------------------------------------------------------------
- # Calls
- # -----------------------------------------------------------------------------------------------------------------
-
- def __call__(self, friend_number, audio, video):
- """Call friend with specified number"""
- self._toxav.call(friend_number, 32 if audio else 0, 5000 if video else 0)
- self._calls[friend_number] = Call(audio, video)
- threading.Timer(30.0, lambda: self.finish_not_started_call(friend_number)).start()
-
- def accept_call(self, friend_number, audio_enabled, video_enabled):
- if self._running:
- self._calls[friend_number] = Call(audio_enabled, video_enabled)
- self._toxav.answer(friend_number, 32 if audio_enabled else 0, 5000 if video_enabled else 0)
- if audio_enabled:
- self.start_audio_thread()
- if video_enabled:
- self.start_video_thread()
-
- def finish_call(self, friend_number, by_friend=False):
- if not by_friend:
- self._toxav.call_control(friend_number, TOXAV_CALL_CONTROL['CANCEL'])
- if friend_number in self._calls:
- del self._calls[friend_number]
- if not len(list(filter(lambda c: c.out_audio, self._calls))):
- self.stop_audio_thread()
- if not len(list(filter(lambda c: c.out_video, self._calls))):
- self.stop_video_thread()
-
- def finish_not_started_call(self, friend_number):
- if friend_number in self:
- call = self._calls[friend_number]
- if not call.is_active:
- self.finish_call(friend_number)
-
- def toxav_call_state_cb(self, friend_number, state):
- """
- New call state
- """
- call = self._calls[friend_number]
- call.is_active = True
-
- call.in_audio = state | TOXAV_FRIEND_CALL_STATE['SENDING_A'] > 0
- call.in_video = state | TOXAV_FRIEND_CALL_STATE['SENDING_V'] > 0
-
- if state | TOXAV_FRIEND_CALL_STATE['ACCEPTING_A'] and call.out_audio:
- self.start_audio_thread()
-
- if state | TOXAV_FRIEND_CALL_STATE['ACCEPTING_V'] and call.out_video:
- self.start_video_thread()
-
- def is_video_call(self, number):
- return number in self and self._calls[number].in_video
-
- # -----------------------------------------------------------------------------------------------------------------
- # Threads
- # -----------------------------------------------------------------------------------------------------------------
-
- def start_audio_thread(self):
- """
- Start audio sending
- """
- if self._audio_thread is not None:
- return
-
- self._audio_running = True
-
- self._audio = pyaudio.PyAudio()
- self._audio_stream = self._audio.open(format=pyaudio.paInt16,
- rate=self._audio_rate,
- channels=self._audio_channels,
- input=True,
- input_device_index=settings.Settings.get_instance().audio['input'],
- frames_per_buffer=self._audio_sample_count * 10)
-
- self._audio_thread = threading.Thread(target=self.send_audio)
- self._audio_thread.start()
-
- def stop_audio_thread(self):
-
- if self._audio_thread is None:
- return
-
- self._audio_running = False
-
- self._audio_thread.join()
-
- self._audio_thread = None
- self._audio_stream = None
- self._audio = None
-
- if self._out_stream is not None:
- self._out_stream.stop_stream()
- self._out_stream.close()
- self._out_stream = None
-
- def start_video_thread(self):
- if self._video_thread is not None:
- return
-
- self._video_running = True
- s = settings.Settings.get_instance()
- self._video_width = s.video['width']
- self._video_height = s.video['height']
-
- if s.video['device'] == -1:
- self._video = screen_sharing.DesktopGrabber(s.video['x'], s.video['y'],
- s.video['width'], s.video['height'])
- else:
- self._video = cv2.VideoCapture(s.video['device'])
- self._video.set(cv2.CAP_PROP_FPS, 25)
- self._video.set(cv2.CAP_PROP_FRAME_WIDTH, self._video_width)
- self._video.set(cv2.CAP_PROP_FRAME_HEIGHT, self._video_height)
-
- self._video_thread = threading.Thread(target=self.send_video)
- self._video_thread.start()
-
- def stop_video_thread(self):
- if self._video_thread is None:
- return
-
- self._video_running = False
- self._video_thread.join()
- self._video_thread = None
- self._video = None
-
- # -----------------------------------------------------------------------------------------------------------------
- # Incoming chunks
- # -----------------------------------------------------------------------------------------------------------------
-
- def audio_chunk(self, samples, channels_count, rate):
- """
- Incoming chunk
- """
-
- if self._out_stream is None:
- self._out_stream = self._audio.open(format=pyaudio.paInt16,
- channels=channels_count,
- rate=rate,
- output_device_index=settings.Settings.get_instance().audio['output'],
- output=True)
- self._out_stream.write(samples)
-
- # -----------------------------------------------------------------------------------------------------------------
- # AV sending
- # -----------------------------------------------------------------------------------------------------------------
-
- def send_audio(self):
- """
- This method sends audio to friends
- """
-
- while self._audio_running:
- try:
- pcm = self._audio_stream.read(self._audio_sample_count)
- if pcm:
- for friend_num in self._calls:
- if self._calls[friend_num].out_audio:
- try:
- self._toxav.audio_send_frame(friend_num, pcm, self._audio_sample_count,
- self._audio_channels, self._audio_rate)
- except:
- pass
- except:
- pass
-
- time.sleep(0.01)
-
- def send_video(self):
- """
- This method sends video to friends
- """
- while self._video_running:
- try:
- result, frame = self._video.read()
- if result:
- height, width, channels = frame.shape
- for friend_num in self._calls:
- if self._calls[friend_num].out_video:
- try:
- y, u, v = self.convert_bgr_to_yuv(frame)
- self._toxav.video_send_frame(friend_num, width, height, y, u, v)
- except:
- pass
- except:
- pass
-
- time.sleep(0.01)
-
- def convert_bgr_to_yuv(self, frame):
- """
- :param frame: input bgr frame
- :return y, u, v: y, u, v values of frame
-
- How this function works:
- OpenCV creates YUV420 frame from BGR
- This frame has following structure and size:
- width, height - dim of input frame
- width, height * 1.5 - dim of output frame
-
- width
- -------------------------
- | |
- | Y | height
- | |
- -------------------------
- | | |
- | U even | U odd | height // 4
- | | |
- -------------------------
- | | |
- | V even | V odd | height // 4
- | | |
- -------------------------
-
- width // 2 width // 2
-
- Y, U, V can be extracted using slices and joined in one list using itertools.chain.from_iterable()
- Function returns bytes(y), bytes(u), bytes(v), because it is required for ctypes
- """
- frame = cv2.cvtColor(frame, cv2.COLOR_BGR2YUV_I420)
-
- y = frame[:self._video_height, :]
- y = list(itertools.chain.from_iterable(y))
-
- u = np.zeros((self._video_height // 2, self._video_width // 2), dtype=np.int)
- u[::2, :] = frame[self._video_height:self._video_height * 5 // 4, :self._video_width // 2]
- u[1::2, :] = frame[self._video_height:self._video_height * 5 // 4, self._video_width // 2:]
- u = list(itertools.chain.from_iterable(u))
- v = np.zeros((self._video_height // 2, self._video_width // 2), dtype=np.int)
- v[::2, :] = frame[self._video_height * 5 // 4:, :self._video_width // 2]
- v[1::2, :] = frame[self._video_height * 5 // 4:, self._video_width // 2:]
- v = list(itertools.chain.from_iterable(v))
-
- return bytes(y), bytes(u), bytes(v)
diff --git a/toxygen/contact.py b/toxygen/contact.py
deleted file mode 100644
index 9f27a1d..0000000
--- a/toxygen/contact.py
+++ /dev/null
@@ -1,288 +0,0 @@
-from PyQt5 import QtCore, QtGui
-from history import *
-import basecontact
-import util
-from messages import *
-import file_transfers as ft
-import re
-
-
-class Contact(basecontact.BaseContact):
- """
- Class encapsulating TOX contact
- Properties: number, message getter, history etc. Base class for friend and gc classes
- """
-
- def __init__(self, message_getter, number, name, status_message, widget, tox_id):
- """
- :param message_getter: gets messages from db
- :param number: number of friend.
- """
- super().__init__(name, status_message, widget, tox_id)
- self._number = number
- self._new_messages = False
- self._visible = True
- self._alias = False
- self._message_getter = message_getter
- self._corr = []
- self._unsaved_messages = 0
- self._history_loaded = self._new_actions = False
- self._curr_text = self._search_string = ''
- self._search_index = 0
-
- def __del__(self):
- self.set_visibility(False)
- del self._widget
- if hasattr(self, '_message_getter'):
- del self._message_getter
-
- # -----------------------------------------------------------------------------------------------------------------
- # History support
- # -----------------------------------------------------------------------------------------------------------------
-
- def load_corr(self, first_time=True):
- """
- :param first_time: friend became active, load first part of messages
- """
- if (first_time and self._history_loaded) or (not hasattr(self, '_message_getter')):
- return
- if self._message_getter is None:
- return
- data = list(self._message_getter.get(PAGE_SIZE))
- if data is not None and len(data):
- data.reverse()
- else:
- return
- data = list(map(lambda tupl: TextMessage(*tupl), data))
- self._corr = data + self._corr
- self._history_loaded = True
-
- def load_all_corr(self):
- """
- 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()
- data = list(map(lambda tupl: TextMessage(*tupl), data))
- self._corr = data + self._corr
- self._history_loaded = True
-
- def get_corr_for_saving(self):
- """
- Get data to save in db
- :return: list of unsaved messages or []
- """
- messages = list(filter(lambda x: x.get_type() <= 1, self._corr))
- return list(map(lambda x: x.get_data(), messages[-self._unsaved_messages:])) if self._unsaved_messages else []
-
- def get_corr(self):
- return self._corr[:]
-
- def append_message(self, message):
- """
- :param message: text or file transfer message
- """
- self._corr.append(message)
- if message.get_type() <= 1:
- self._unsaved_messages += 1
-
- def get_last_message_text(self):
- messages = list(filter(lambda x: x.get_type() <= 1 and x.get_owner() != MESSAGE_OWNER['FRIEND'], self._corr))
- if messages:
- return messages[-1].get_data()[0]
- else:
- return ''
-
- # -----------------------------------------------------------------------------------------------------------------
- # Unsent messages
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_unsent_messages(self):
- """
- :return list of unsent messages
- """
- messages = filter(lambda x: x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr)
- return list(messages)
-
- def get_unsent_messages_for_saving(self):
- """
- :return list of unsent messages for saving
- """
- messages = filter(lambda x: x.get_type() <= 1 and x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr)
- return list(map(lambda x: x.get_data(), messages))
-
- def mark_as_sent(self):
- try:
- message = list(filter(lambda x: x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr))[0]
- message.mark_as_sent()
- except Exception as ex:
- util.log('Mark as sent ex: ' + str(ex))
-
- # -----------------------------------------------------------------------------------------------------------------
- # Message deletion
- # -----------------------------------------------------------------------------------------------------------------
-
- def delete_message(self, time):
- 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
- self._corr.remove(elem)
- self._message_getter.delete_one()
- self._search_index = 0
-
- def delete_old_messages(self):
- """
- Delete old messages (reduces RAM usage if messages saving is not enabled)
- """
- def save_message(x):
- if x.get_type() == 2 and (x.get_status() >= 2 or x.get_status() is None):
- return True
- return x.get_owner() == MESSAGE_OWNER['NOT_SENT']
-
- old = filter(save_message, self._corr[:-SAVE_MESSAGES])
- self._corr = list(old) + self._corr[-SAVE_MESSAGES:]
- text_messages = filter(lambda x: x.get_type() <= 1, self._corr)
- self._unsaved_messages = min(self._unsaved_messages, len(list(text_messages)))
- self._search_index = 0
-
- def clear_corr(self, save_unsent=False):
- """
- Clear messages list
- """
- if hasattr(self, '_message_getter'):
- del self._message_getter
- self._search_index = 0
- # don't delete data about active file transfer
- if not save_unsent:
- self._corr = list(filter(lambda x: x.get_type() == 2 and
- x.get_status() in ft.ACTIVE_FILE_TRANSFERS, self._corr))
- self._unsaved_messages = 0
- else:
- self._corr = list(filter(lambda x: (x.get_type() == 2 and x.get_status() in ft.ACTIVE_FILE_TRANSFERS)
- or (x.get_type() <= 1 and x.get_owner() == MESSAGE_OWNER['NOT_SENT']),
- self._corr))
- self._unsaved_messages = len(self.get_unsent_messages())
-
- # -----------------------------------------------------------------------------------------------------------------
- # Chat history search
- # -----------------------------------------------------------------------------------------------------------------
-
- def search_string(self, search_string):
- self._search_string, self._search_index = search_string, 0
- return self.search_prev()
-
- def search_prev(self):
- while True:
- l = len(self._corr)
- for i in range(self._search_index - 1, -l - 1, -1):
- if self._corr[i].get_type() > 1:
- continue
- message = self._corr[i].get_data()[0]
- if re.search(self._search_string, message, re.IGNORECASE) is not None:
- self._search_index = i
- return i
- self._search_index = -l
- self.load_corr(False)
- if len(self._corr) == l:
- return None # not found
-
- def search_next(self):
- if not self._search_index:
- return None
- for i in range(self._search_index + 1, 0):
- if self._corr[i].get_type() > 1:
- continue
- message = self._corr[i].get_data()[0]
- if re.search(self._search_string, message, re.IGNORECASE) is not None:
- self._search_index = i
- return i
- return None # not found
-
- # -----------------------------------------------------------------------------------------------------------------
- # Current text - text from message area
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_curr_text(self):
- return self._curr_text
-
- def set_curr_text(self, value):
- self._curr_text = value
-
- curr_text = property(get_curr_text, set_curr_text)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Alias support
- # -----------------------------------------------------------------------------------------------------------------
-
- def set_name(self, value):
- """
- Set new name or ignore if alias exists
- :param value: new name
- """
- if not self._alias:
- super().set_name(value)
-
- def set_alias(self, alias):
- self._alias = bool(alias)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Visibility in friends' list
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_visibility(self):
- return self._visible
-
- def set_visibility(self, value):
- self._visible = value
-
- visibility = property(get_visibility, set_visibility)
-
- def set_widget(self, widget):
- self._widget = widget
- self.init_widget()
-
- # -----------------------------------------------------------------------------------------------------------------
- # Unread messages and other actions from friend
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_actions(self):
- return self._new_actions
-
- def set_actions(self, value):
- self._new_actions = value
- self._widget.connection_status.update(self.status, value)
-
- actions = property(get_actions, set_actions) # unread messages, incoming files, av calls
-
- def get_messages(self):
- return self._new_messages
-
- def inc_messages(self):
- self._new_messages += 1
- self._new_actions = True
- self._widget.connection_status.update(self.status, True)
- self._widget.messages.update(self._new_messages)
-
- def reset_messages(self):
- self._new_actions = False
- self._new_messages = 0
- self._widget.messages.update(self._new_messages)
- self._widget.connection_status.update(self.status, False)
-
- messages = property(get_messages)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Friend's number (can be used in toxcore)
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_number(self):
- return self._number
-
- def set_number(self, value):
- self._number = value
-
- number = property(get_number, set_number)
diff --git a/toxygen/file_transfers.py b/toxygen/file_transfers.py
deleted file mode 100644
index 1bbabe5..0000000
--- a/toxygen/file_transfers.py
+++ /dev/null
@@ -1,347 +0,0 @@
-from toxcore_enums_and_consts import TOX_FILE_KIND, TOX_FILE_CONTROL
-from os.path import basename, getsize, exists, dirname
-from os import remove, rename, chdir
-from time import time, sleep
-from tox import Tox
-import settings
-from PyQt5 import QtCore
-
-
-TOX_FILE_TRANSFER_STATE = {
- 'RUNNING': 0,
- 'PAUSED_BY_USER': 1,
- 'CANCELLED': 2,
- 'FINISHED': 3,
- 'PAUSED_BY_FRIEND': 4,
- 'INCOMING_NOT_STARTED': 5,
- 'OUTGOING_NOT_STARTED': 6
-}
-
-ACTIVE_FILE_TRANSFERS = (0, 1, 4, 5, 6)
-
-PAUSED_FILE_TRANSFERS = (1, 4, 5, 6)
-
-DO_NOT_SHOW_ACCEPT_BUTTON = (2, 3, 4, 6)
-
-SHOW_PROGRESS_BAR = (0, 1, 4)
-
-ALLOWED_FILES = ('toxygen_inline.png', 'utox-inline.png', 'sticker.png')
-
-
-def is_inline(file_name):
- return file_name in ALLOWED_FILES or file_name.startswith('qTox_Screenshot_') or file_name.startswith('qTox_Image_')
-
-
-class StateSignal(QtCore.QObject):
-
- signal = QtCore.pyqtSignal(int, float, int) # state, progress, time in sec
-
-
-class TransferFinishedSignal(QtCore.QObject):
-
- signal = QtCore.pyqtSignal(int, int) # friend number, file number
-
-
-class FileTransfer(QtCore.QObject):
- """
- Superclass for file transfers
- """
-
- def __init__(self, path, tox, friend_number, size, file_number=None):
- QtCore.QObject.__init__(self)
- self._path = path
- self._tox = tox
- self._friend_number = friend_number
- self.state = TOX_FILE_TRANSFER_STATE['RUNNING']
- self._file_number = file_number
- self._creation_time = None
- self._size = float(size)
- self._done = 0
- self._state_changed = StateSignal()
- self._finished = TransferFinishedSignal()
- self._file_id = None
-
- def set_tox(self, tox):
- self._tox = tox
-
- def set_state_changed_handler(self, handler):
- self._state_changed.signal.connect(handler)
-
- def set_transfer_finished_handler(self, handler):
- self._finished.signal.connect(handler)
-
- def signal(self):
- percentage = self._done / self._size if self._size else 0
- if self._creation_time is None or not percentage:
- t = -1
- else:
- t = ((time() - self._creation_time) / percentage) * (1 - percentage)
- self._state_changed.signal.emit(self.state, percentage, int(t))
-
- def finished(self):
- self._finished.signal.emit(self._friend_number, self._file_number)
-
- def get_file_number(self):
- return self._file_number
-
- def get_friend_number(self):
- return self._friend_number
-
- def get_id(self):
- return self._file_id
-
- def get_path(self):
- return self._path
-
- def cancel(self):
- self.send_control(TOX_FILE_CONTROL['CANCEL'])
- if hasattr(self, '_file'):
- self._file.close()
- self.signal()
-
- def cancelled(self):
- if hasattr(self, '_file'):
- sleep(0.1)
- self._file.close()
- self.state = TOX_FILE_TRANSFER_STATE['CANCELLED']
- self.signal()
-
- def pause(self, by_friend):
- if not by_friend:
- self.send_control(TOX_FILE_CONTROL['PAUSE'])
- else:
- self.state = TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND']
- self.signal()
-
- def send_control(self, control):
- if self._tox.file_control(self._friend_number, self._file_number, control):
- self.state = control
- self.signal()
-
- def get_file_id(self):
- return self._tox.file_get_file_id(self._friend_number, self._file_number)
-
-# -----------------------------------------------------------------------------------------------------------------
-# Send file
-# -----------------------------------------------------------------------------------------------------------------
-
-
-class SendTransfer(FileTransfer):
-
- def __init__(self, path, tox, friend_number, kind=TOX_FILE_KIND['DATA'], file_id=None):
- if path is not None:
- self._file = open(path, 'rb')
- size = getsize(path)
- else:
- size = 0
- super(SendTransfer, self).__init__(path, tox, friend_number, size)
- self.state = TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']
- self._file_number = tox.file_send(friend_number, kind, size, file_id,
- bytes(basename(path), 'utf-8') if path else b'')
- self._file_id = self.get_file_id()
-
- def send_chunk(self, position, size):
- """
- Send chunk
- :param position: start position in file
- :param size: chunk max size
- """
- if self._creation_time is None:
- self._creation_time = time()
- if size:
- self._file.seek(position)
- data = self._file.read(size)
- self._tox.file_send_chunk(self._friend_number, self._file_number, position, data)
- self._done += size
- else:
- if hasattr(self, '_file'):
- self._file.close()
- self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
- self.finished()
- self.signal()
-
-
-class SendAvatar(SendTransfer):
- """
- Send avatar to friend. Doesn't need file transfer item
- """
-
- def __init__(self, path, tox, friend_number):
- if path is None:
- hash = None
- else:
- with open(path, 'rb') as fl:
- hash = Tox.hash(fl.read())
- super(SendAvatar, self).__init__(path, tox, friend_number, TOX_FILE_KIND['AVATAR'], hash)
-
-
-class SendFromBuffer(FileTransfer):
- """
- Send inline image
- """
-
- def __init__(self, tox, friend_number, data, file_name):
- super(SendFromBuffer, self).__init__(None, tox, friend_number, len(data))
- self.state = TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']
- self._data = data
- self._file_number = tox.file_send(friend_number, TOX_FILE_KIND['DATA'],
- len(data), None, bytes(file_name, 'utf-8'))
-
- def get_data(self):
- return self._data
-
- def send_chunk(self, position, size):
- if self._creation_time is None:
- self._creation_time = time()
- if size:
- data = self._data[position:position + size]
- self._tox.file_send_chunk(self._friend_number, self._file_number, position, data)
- self._done += size
- else:
- self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
- self.finished()
- self.signal()
-
-
-class SendFromFileBuffer(SendTransfer):
-
- def __init__(self, *args):
- super(SendFromFileBuffer, self).__init__(*args)
-
- def send_chunk(self, position, size):
- super(SendFromFileBuffer, self).send_chunk(position, size)
- if not size:
- chdir(dirname(self._path))
- remove(self._path)
-
-# -----------------------------------------------------------------------------------------------------------------
-# Receive file
-# -----------------------------------------------------------------------------------------------------------------
-
-
-class ReceiveTransfer(FileTransfer):
-
- def __init__(self, path, tox, friend_number, size, file_number, position=0):
- super(ReceiveTransfer, self).__init__(path, tox, friend_number, size, file_number)
- self._file = open(self._path, 'wb')
- self._file_size = position
- self._file.truncate(position)
- self._missed = set()
- self._file_id = self.get_file_id()
- self._done = position
-
- def cancel(self):
- super(ReceiveTransfer, self).cancel()
- remove(self._path)
-
- def total_size(self):
- self._missed.add(self._file_size)
- return min(self._missed)
-
- def write_chunk(self, position, data):
- """
- Incoming chunk
- :param position: position in file to save data
- :param data: raw data (string)
- """
- if self._creation_time is None:
- self._creation_time = time()
- if data is None:
- self._file.close()
- self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
- self.finished()
- else:
- data = bytearray(data)
- if self._file_size < position:
- self._file.seek(0, 2)
- self._file.write(b'\0' * (position - self._file_size))
- self._missed.add(self._file_size)
- else:
- self._missed.discard(position)
- self._file.seek(position)
- self._file.write(data)
- l = len(data)
- if position + l > self._file_size:
- self._file_size = position + l
- self._done += l
- self.signal()
-
-
-class ReceiveToBuffer(FileTransfer):
- """
- Inline image - save in buffer not in file system
- """
-
- def __init__(self, tox, friend_number, size, file_number):
- super(ReceiveToBuffer, self).__init__(None, tox, friend_number, size, file_number)
- self._data = bytes()
- self._data_size = 0
-
- def get_data(self):
- return self._data
-
- def write_chunk(self, position, data):
- if self._creation_time is None:
- self._creation_time = time()
- if data is None:
- self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
- self.finished()
- else:
- data = bytes(data)
- l = len(data)
- if self._data_size < position:
- self._data += (b'\0' * (position - self._data_size))
- self._data = self._data[:position] + data + self._data[position + l:]
- if position + l > self._data_size:
- self._data_size = position + l
- self._done += l
- self.signal()
-
-
-class ReceiveAvatar(ReceiveTransfer):
- """
- Get friend's avatar. Doesn't need file transfer item
- """
- MAX_AVATAR_SIZE = 512 * 1024
-
- def __init__(self, tox, friend_number, size, file_number):
- path = settings.ProfileHelper.get_path() + 'avatars/{}.png'.format(tox.friend_get_public_key(friend_number))
- super(ReceiveAvatar, self).__init__(path + '.tmp', tox, friend_number, size, file_number)
- if size > self.MAX_AVATAR_SIZE:
- self.send_control(TOX_FILE_CONTROL['CANCEL'])
- self._file.close()
- remove(path + '.tmp')
- elif not size:
- self.send_control(TOX_FILE_CONTROL['CANCEL'])
- self._file.close()
- if exists(path):
- remove(path)
- self._file.close()
- remove(path + '.tmp')
- elif exists(path):
- hash = self.get_file_id()
- with open(path, 'rb') as fl:
- data = fl.read()
- existing_hash = Tox.hash(data)
- if hash == existing_hash:
- self.send_control(TOX_FILE_CONTROL['CANCEL'])
- self._file.close()
- remove(path + '.tmp')
- else:
- self.send_control(TOX_FILE_CONTROL['RESUME'])
- else:
- self.send_control(TOX_FILE_CONTROL['RESUME'])
-
- def write_chunk(self, position, data):
- super(ReceiveAvatar, self).write_chunk(position, data)
- if self.state:
- avatar_path = self._path[:-4]
- if exists(avatar_path):
- chdir(dirname(avatar_path))
- remove(avatar_path)
- rename(self._path, avatar_path)
- self.finished(True)
-
- def finished(self, emit=False):
- if emit:
- super().finished()
diff --git a/toxygen/friend.py b/toxygen/friend.py
deleted file mode 100644
index d912708..0000000
--- a/toxygen/friend.py
+++ /dev/null
@@ -1,75 +0,0 @@
-import contact
-from messages import *
-import os
-
-
-class Friend(contact.Contact):
- """
- Friend in list of friends.
- """
-
- def __init__(self, message_getter, number, name, status_message, widget, tox_id):
- super().__init__(message_getter, number, name, status_message, widget, tox_id)
- self._receipts = 0
-
- # -----------------------------------------------------------------------------------------------------------------
- # File transfers support
- # -----------------------------------------------------------------------------------------------------------------
-
- def update_transfer_data(self, file_number, status, inline=None):
- """
- Update status of active transfer and load inline if needed
- """
- try:
- tr = list(filter(lambda x: x.get_type() == MESSAGE_TYPE['FILE_TRANSFER'] and x.is_active(file_number),
- self._corr))[0]
- tr.set_status(status)
- i = self._corr.index(tr)
- if inline: # inline was loaded
- self._corr.insert(i, inline)
- return i - len(self._corr)
- except:
- pass
-
- def get_unsent_files(self):
- messages = filter(lambda x: type(x) is UnsentFile, self._corr)
- return messages
-
- def clear_unsent_files(self):
- self._corr = list(filter(lambda x: type(x) is not UnsentFile, self._corr))
-
- def remove_invalid_unsent_files(self):
- def is_valid(message):
- if type(message) is not UnsentFile:
- return True
- if message.get_data()[1] is not None:
- return True
- return os.path.exists(message.get_data()[0])
- self._corr = list(filter(is_valid, self._corr))
-
- def delete_one_unsent_file(self, time):
- self._corr = list(filter(lambda x: not (type(x) is UnsentFile and x.get_data()[2] == time), self._corr))
-
- # -----------------------------------------------------------------------------------------------------------------
- # History support
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_receipts(self):
- return self._receipts
-
- receipts = property(get_receipts) # read receipts
-
- def inc_receipts(self):
- self._receipts += 1
-
- def dec_receipt(self):
- if self._receipts:
- self._receipts -= 1
- self.mark_as_sent()
-
- # -----------------------------------------------------------------------------------------------------------------
- # Full status
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_full_status(self):
- return self._status_message
diff --git a/toxygen/group_chat.py b/toxygen/group_chat.py
deleted file mode 100644
index f7921a1..0000000
--- a/toxygen/group_chat.py
+++ /dev/null
@@ -1,49 +0,0 @@
-import contact
-import util
-from PyQt5 import QtGui, QtCore
-import toxcore_enums_and_consts as constants
-
-
-class GroupChat(contact.Contact):
-
- def __init__(self, name, status_message, widget, tox, group_number):
- super().__init__(None, group_number, name, status_message, widget, None)
- self._tox = tox
- self.set_status(constants.TOX_USER_STATUS['NONE'])
-
- def set_name(self, name):
- self._tox.group_set_title(self._number, name)
- super().set_name(name)
-
- def send_message(self, message):
- self._tox.group_message_send(self._number, message.encode('utf-8'))
-
- def new_title(self, title):
- super().set_name(title)
-
- def load_avatar(self):
- path = util.curr_directory() + '/images/group.png'
- width = self._widget.avatar_label.width()
- pixmap = QtGui.QPixmap(path)
- self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio,
- QtCore.Qt.SmoothTransformation))
- self._widget.avatar_label.repaint()
-
- def remove_invalid_unsent_files(self):
- pass
-
- def get_names(self):
- peers_count = self._tox.group_number_peers(self._number)
- names = []
- for i in range(peers_count):
- name = self._tox.group_peername(self._number, i)
- names.append(name)
- names = sorted(names, key=lambda n: n.lower())
- return names
-
- def get_full_status(self):
- names = self.get_names()
- return '\n'.join(names)
-
- def get_peer_name(self, peer_number):
- return self._tox.group_peername(self._number, peer_number)
diff --git a/toxygen/history.py b/toxygen/history.py
deleted file mode 100644
index 586981a..0000000
--- a/toxygen/history.py
+++ /dev/null
@@ -1,215 +0,0 @@
-from sqlite3 import connect
-import settings
-from os import chdir
-import os.path
-from toxes import ToxES
-
-
-PAGE_SIZE = 42
-
-TIMEOUT = 11
-
-SAVE_MESSAGES = 250
-
-MESSAGE_OWNER = {
- 'ME': 0,
- 'FRIEND': 1,
- 'NOT_SENT': 2
-}
-
-
-class History:
-
- def __init__(self, name):
- self._name = name
- chdir(settings.ProfileHelper.get_path())
- path = settings.ProfileHelper.get_path() + self._name + '.hstr'
- if os.path.exists(path):
- decr = ToxES.get_instance()
- try:
- with open(path, 'rb') as fin:
- data = fin.read()
- if decr.is_data_encrypted(data):
- data = decr.pass_decrypt(data)
- with open(path, 'wb') as fout:
- fout.write(data)
- except:
- os.remove(path)
- db = connect(name + '.hstr', timeout=TIMEOUT)
- cursor = db.cursor()
- cursor.execute('CREATE TABLE IF NOT EXISTS friends('
- ' tox_id TEXT PRIMARY KEY'
- ')')
- db.close()
-
- def save(self):
- encr = ToxES.get_instance()
- if encr.has_password():
- path = settings.ProfileHelper.get_path() + self._name + '.hstr'
- with open(path, 'rb') as fin:
- data = fin.read()
- data = encr.pass_encrypt(bytes(data))
- with open(path, 'wb') as fout:
- fout.write(data)
-
- def export(self, directory):
- path = settings.ProfileHelper.get_path() + self._name + '.hstr'
- new_path = directory + self._name + '.hstr'
- with open(path, 'rb') as fin:
- data = fin.read()
- encr = ToxES.get_instance()
- if encr.has_password():
- data = encr.pass_encrypt(data)
- with open(new_path, 'wb') as fout:
- fout.write(data)
-
- def add_friend_to_db(self, tox_id):
- chdir(settings.ProfileHelper.get_path())
- db = connect(self._name + '.hstr', timeout=TIMEOUT)
- try:
- cursor = db.cursor()
- cursor.execute('INSERT INTO friends VALUES (?);', (tox_id, ))
- cursor.execute('CREATE TABLE id' + tox_id + '('
- ' id INTEGER PRIMARY KEY,'
- ' message TEXT,'
- ' owner INTEGER,'
- ' unix_time REAL,'
- ' message_type INTEGER'
- ')')
- db.commit()
- except:
- print('Database is locked!')
- db.rollback()
- finally:
- db.close()
-
- def delete_friend_from_db(self, tox_id):
- chdir(settings.ProfileHelper.get_path())
- db = connect(self._name + '.hstr', timeout=TIMEOUT)
- try:
- cursor = db.cursor()
- cursor.execute('DELETE FROM friends WHERE tox_id=?;', (tox_id, ))
- cursor.execute('DROP TABLE id' + tox_id + ';')
- db.commit()
- except:
- print('Database is locked!')
- db.rollback()
- finally:
- db.close()
-
- def friend_exists_in_db(self, tox_id):
- chdir(settings.ProfileHelper.get_path())
- db = connect(self._name + '.hstr', timeout=TIMEOUT)
- cursor = db.cursor()
- cursor.execute('SELECT 0 FROM friends WHERE tox_id=?', (tox_id, ))
- result = cursor.fetchone()
- db.close()
- return result is not None
-
- def save_messages_to_db(self, tox_id, messages_iter):
- chdir(settings.ProfileHelper.get_path())
- db = connect(self._name + '.hstr', timeout=TIMEOUT)
- try:
- cursor = db.cursor()
- cursor.executemany('INSERT INTO id' + tox_id + '(message, owner, unix_time, message_type) '
- 'VALUES (?, ?, ?, ?);', messages_iter)
- db.commit()
- except:
- print('Database is locked!')
- db.rollback()
- finally:
- db.close()
-
- def update_messages(self, tox_id, unsent_time):
- chdir(settings.ProfileHelper.get_path())
- db = connect(self._name + '.hstr', timeout=TIMEOUT)
- try:
- cursor = db.cursor()
- cursor.execute('UPDATE id' + tox_id + ' SET owner = 0 '
- 'WHERE unix_time < ' + str(unsent_time) + ' AND owner = 2;')
- db.commit()
- except:
- print('Database is locked!')
- db.rollback()
- finally:
- db.close()
-
- def delete_message(self, tox_id, time):
- start, end = str(time - 0.01), str(time + 0.01)
- chdir(settings.ProfileHelper.get_path())
- db = connect(self._name + '.hstr', timeout=TIMEOUT)
- try:
- cursor = db.cursor()
- cursor.execute('DELETE FROM id' + tox_id + ' WHERE unix_time < ' + end + ' AND unix_time > ' +
- start + ';')
- db.commit()
- except:
- print('Database is locked!')
- db.rollback()
- finally:
- db.close()
-
- def delete_messages(self, tox_id):
- chdir(settings.ProfileHelper.get_path())
- db = connect(self._name + '.hstr', timeout=TIMEOUT)
- try:
- cursor = db.cursor()
- cursor.execute('DELETE FROM id' + tox_id + ';')
- db.commit()
- except:
- print('Database is locked!')
- db.rollback()
- finally:
- db.close()
-
- def messages_getter(self, tox_id):
- return History.MessageGetter(self._name, tox_id)
-
- class MessageGetter:
-
- def __init__(self, name, tox_id):
- self._count = 0
- self._name = name
- self._tox_id = tox_id
- self._db = self._cursor = None
-
- def connect(self):
- chdir(settings.ProfileHelper.get_path())
- self._db = connect(self._name + '.hstr', timeout=TIMEOUT)
- self._cursor = self._db.cursor()
- self._cursor.execute('SELECT message, owner, unix_time, message_type FROM id' + self._tox_id +
- ' ORDER BY unix_time DESC;')
-
- def disconnect(self):
- self._db.close()
-
- def get_one(self):
- self.connect()
- self.skip()
- data = self._cursor.fetchone()
- self._count += 1
- self.disconnect()
- return data
-
- def get_all(self):
- self.connect()
- data = self._cursor.fetchall()
- self.disconnect()
- self._count = len(data)
- return data
-
- def get(self, count):
- self.connect()
- self.skip()
- data = self._cursor.fetchmany(count)
- self.disconnect()
- self._count += len(data)
- return data
-
- def skip(self):
- if self._count:
- self._cursor.fetchmany(self._count)
-
- def delete_one(self):
- if self._count:
- self._count -= 1
diff --git a/toxygen/items_factory.py b/toxygen/items_factory.py
deleted file mode 100644
index 44a00ad..0000000
--- a/toxygen/items_factory.py
+++ /dev/null
@@ -1,68 +0,0 @@
-from PyQt5 import QtWidgets, QtCore
-from list_items import *
-
-
-class ItemsFactory:
-
- def __init__(self, friends_list, messages):
- self._friends = friends_list
- self._messages = messages
-
- def friend_item(self):
- item = ContactItem()
- elem = QtWidgets.QListWidgetItem(self._friends)
- elem.setSizeHint(QtCore.QSize(250, item.height()))
- self._friends.addItem(elem)
- self._friends.setItemWidget(elem, item)
- return item
-
- def message_item(self, text, time, name, sent, message_type, append, pixmap):
- item = MessageItem(text, time, name, sent, message_type, self._messages)
- if pixmap is not None:
- item.set_avatar(pixmap)
- elem = QtWidgets.QListWidgetItem()
- elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height()))
- if append:
- self._messages.addItem(elem)
- else:
- self._messages.insertItem(0, elem)
- self._messages.setItemWidget(elem, item)
- return item
-
- def inline_item(self, data, append):
- elem = QtWidgets.QListWidgetItem()
- item = InlineImageItem(data, self._messages.width(), elem)
- elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height()))
- if append:
- self._messages.addItem(elem)
- else:
- self._messages.insertItem(0, elem)
- self._messages.setItemWidget(elem, item)
- return item
-
- def unsent_file_item(self, file_name, size, name, time, append):
- item = UnsentFileItem(file_name,
- size,
- name,
- time,
- self._messages.width())
- elem = QtWidgets.QListWidgetItem()
- elem.setSizeHint(QtCore.QSize(self._messages.width() - 30, 34))
- if append:
- self._messages.addItem(elem)
- else:
- self._messages.insertItem(0, elem)
- self._messages.setItemWidget(elem, item)
- return item
-
- def file_transfer_item(self, data, append):
- data.append(self._messages.width())
- item = FileTransferItem(*data)
- elem = QtWidgets.QListWidgetItem()
- elem.setSizeHint(QtCore.QSize(self._messages.width() - 30, 34))
- if append:
- self._messages.addItem(elem)
- else:
- self._messages.insertItem(0, elem)
- self._messages.setItemWidget(elem, item)
- return item
diff --git a/toxygen/libtox.py b/toxygen/libtox.py
deleted file mode 100644
index 752798f..0000000
--- a/toxygen/libtox.py
+++ /dev/null
@@ -1,59 +0,0 @@
-from platform import system
-from ctypes import CDLL
-import util
-
-
-class LibToxCore:
-
- def __init__(self):
- if system() == 'Windows':
- self._libtoxcore = CDLL(util.curr_directory() + '/libs/libtox.dll')
- elif system() == 'Darwin':
- self._libtoxcore = CDLL('libtoxcore.dylib')
- else:
- # libtoxcore and libsodium must be installed in your os
- try:
- self._libtoxcore = CDLL('libtoxcore.so')
- except:
- self._libtoxcore = CDLL(util.curr_directory() + '/libs/libtoxcore.so')
-
- def __getattr__(self, item):
- return self._libtoxcore.__getattr__(item)
-
-
-class LibToxAV:
-
- def __init__(self):
- if system() == 'Windows':
- # on Windows av api is in libtox.dll
- self._libtoxav = CDLL(util.curr_directory() + '/libs/libtox.dll')
- elif system() == 'Darwin':
- self._libtoxav = CDLL('libtoxav.dylib')
- else:
- # /usr/lib/libtoxav.so must exists
- try:
- self._libtoxav = CDLL('libtoxav.so')
- except:
- self._libtoxav = CDLL(util.curr_directory() + '/libs/libtoxav.so')
-
- def __getattr__(self, item):
- return self._libtoxav.__getattr__(item)
-
-
-class LibToxEncryptSave:
-
- def __init__(self):
- if system() == 'Windows':
- # on Windows profile encryption api is in libtox.dll
- self._lib_tox_encrypt_save = CDLL(util.curr_directory() + '/libs/libtox.dll')
- elif system() == 'Darwin':
- self._lib_tox_encrypt_save = CDLL('libtoxencryptsave.dylib')
- else:
- # /usr/lib/libtoxencryptsave.so must exists
- try:
- self._lib_tox_encrypt_save = CDLL('libtoxencryptsave.so')
- except:
- self._lib_tox_encrypt_save = CDLL(util.curr_directory() + '/libs/libtoxencryptsave.so')
-
- def __getattr__(self, item):
- return self._lib_tox_encrypt_save.__getattr__(item)
diff --git a/toxygen/list_items.py b/toxygen/list_items.py
deleted file mode 100644
index 9b92f2a..0000000
--- a/toxygen/list_items.py
+++ /dev/null
@@ -1,545 +0,0 @@
-from toxcore_enums_and_consts import *
-from PyQt5 import QtCore, QtGui, QtWidgets
-import profile
-from file_transfers import TOX_FILE_TRANSFER_STATE, PAUSED_FILE_TRANSFERS, DO_NOT_SHOW_ACCEPT_BUTTON, ACTIVE_FILE_TRANSFERS, SHOW_PROGRESS_BAR
-from util import curr_directory, convert_time, curr_time
-from widgets import DataLabel, create_menu
-import html as h
-import smileys
-import settings
-import re
-
-
-class MessageEdit(QtWidgets.QTextBrowser):
-
- def __init__(self, text, width, message_type, parent=None):
- super(MessageEdit, self).__init__(parent)
- self.urls = {}
- self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
- self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
- self.setWordWrapMode(QtGui.QTextOption.WrapAtWordBoundaryOrAnywhere)
- self.document().setTextWidth(width)
- self.setOpenExternalLinks(True)
- self.setAcceptRichText(True)
- self.setOpenLinks(False)
- path = smileys.SmileyLoader.get_instance().get_smileys_path()
- if path is not None:
- self.setSearchPaths([path])
- self.document().setDefaultStyleSheet('a { color: #306EFF; }')
- text = self.decoratedText(text)
- if message_type != TOX_MESSAGE_TYPE['NORMAL']:
- self.setHtml('
' + text + '
')
- else:
- self.setHtml(text)
- font = QtGui.QFont()
- font.setFamily(settings.Settings.get_instance()['font'])
- font.setPixelSize(settings.Settings.get_instance()['message_font_size'])
- font.setBold(False)
- self.setFont(font)
- self.resize(width, self.document().size().height())
- self.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse | QtCore.Qt.LinksAccessibleByMouse)
- self.anchorClicked.connect(self.on_anchor_clicked)
-
- def contextMenuEvent(self, event):
- menu = create_menu(self.createStandardContextMenu(event.pos()))
- quote = menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Quote selected text'))
- quote.triggered.connect(self.quote_text)
- text = self.textCursor().selection().toPlainText()
- if not text:
- quote.setEnabled(False)
- else:
- import plugin_support
- submenu = plugin_support.PluginLoader.get_instance().get_message_menu(menu, text)
- if len(submenu):
- plug = menu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Plugins'))
- plug.addActions(submenu)
- menu.popup(event.globalPos())
- menu.exec_(event.globalPos())
- del menu
-
- def quote_text(self):
- text = self.textCursor().selection().toPlainText()
- if text:
- import mainscreen
- window = mainscreen.MainWindow.get_instance()
- text = '>' + '\n>'.join(text.split('\n'))
- if window.messageEdit.toPlainText():
- text = '\n' + text
- window.messageEdit.appendPlainText(text)
-
- def on_anchor_clicked(self, url):
- text = str(url.toString())
- if text.startswith('tox:'):
- import menu
- self.add_contact = menu.AddContact(text[4:])
- self.add_contact.show()
- else:
- QtGui.QDesktopServices.openUrl(url)
- self.clearFocus()
-
- def addAnimation(self, url, fileName):
- movie = QtGui.QMovie(self)
- movie.setFileName(fileName)
- self.urls[movie] = url
- movie.frameChanged[int].connect(lambda x: self.animate(movie))
- movie.start()
-
- def animate(self, movie):
- self.document().addResource(QtGui.QTextDocument.ImageResource,
- self.urls[movie],
- movie.currentPixmap())
- self.setLineWrapColumnOrWidth(self.lineWrapColumnOrWidth())
-
- def decoratedText(self, text):
- text = h.escape(text) # replace < and >
- exp = QtCore.QRegExp(
- '('
- '(?:\\b)((www\\.)|(http[s]?|ftp)://)'
- '\\w+\\S+)'
- '|(?:\\b)(file:///)([\\S| ]*)'
- '|(?:\\b)(tox:[a-zA-Z\\d]{76}$)'
- '|(?:\\b)(mailto:\\S+@\\S+\\.\\S+)'
- '|(?:\\b)(tox:\\S+@\\S+)')
- offset = exp.indexIn(text, 0)
- while offset != -1: # add links
- url = exp.cap()
- if exp.cap(2) == 'www.':
- html = '{0}'.format(url)
- else:
- html = '{0}'.format(url)
- text = text[:offset] + html + text[offset + len(exp.cap()):]
- offset += len(html)
- offset = exp.indexIn(text, offset)
- arr = text.split('\n')
- for i in range(len(arr)): # quotes
- if arr[i].startswith('>'):
- arr[i] = '' + arr[i][4:] + ''
- text = '
'.join(arr)
- text = smileys.SmileyLoader.get_instance().add_smileys_to_text(text, self) # smileys
- return text
-
-
-class MessageItem(QtWidgets.QWidget):
- """
- Message in messages list
- """
- def __init__(self, text, time, user='', sent=True, message_type=TOX_MESSAGE_TYPE['NORMAL'], parent=None):
- QtWidgets.QWidget.__init__(self, parent)
- self.name = DataLabel(self)
- self.name.setGeometry(QtCore.QRect(2, 2, 95, 23))
- self.name.setTextFormat(QtCore.Qt.PlainText)
- font = QtGui.QFont()
- font.setFamily(settings.Settings.get_instance()['font'])
- font.setPointSize(11)
- font.setBold(True)
- self.name.setFont(font)
- self.name.setText(user)
-
- self.time = QtWidgets.QLabel(self)
- self.time.setGeometry(QtCore.QRect(parent.width() - 60, 0, 50, 25))
- font.setPointSize(10)
- font.setBold(False)
- self.time.setFont(font)
- self._time = time
- if not sent:
- movie = QtGui.QMovie(curr_directory() + '/images/spinner.gif')
- self.time.setMovie(movie)
- movie.start()
- self.t = True
- else:
- self.time.setText(convert_time(time))
- self.t = False
-
- self.message = MessageEdit(text, parent.width() - 160, message_type, self)
- if message_type != TOX_MESSAGE_TYPE['NORMAL']:
- self.name.setStyleSheet("QLabel { color: #5CB3FF; }")
- self.message.setAlignment(QtCore.Qt.AlignCenter)
- self.time.setStyleSheet("QLabel { color: #5CB3FF; }")
- self.message.setGeometry(QtCore.QRect(100, 0, parent.width() - 160, self.message.height()))
- self.setFixedHeight(self.message.height())
-
- def mouseReleaseEvent(self, event):
- if event.button() == QtCore.Qt.RightButton and event.x() > self.time.x():
- self.listMenu = QtWidgets.QMenu()
- delete_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Delete message'))
- delete_item.triggered.connect(self.delete)
- parent_position = self.time.mapToGlobal(QtCore.QPoint(0, 0))
- self.listMenu.move(parent_position)
- self.listMenu.show()
-
- def delete(self):
- pr = profile.Profile.get_instance()
- pr.delete_message(self._time)
-
- def mark_as_sent(self):
- if self.t:
- self.time.setText(convert_time(self._time))
- self.t = False
- return True
- return False
-
- def set_avatar(self, pixmap):
- self.name.setAlignment(QtCore.Qt.AlignCenter)
- self.message.setAlignment(QtCore.Qt.AlignVCenter)
- self.setFixedHeight(max(self.height(), 36))
- self.name.setFixedHeight(self.height())
- self.message.setFixedHeight(self.height())
- self.name.setPixmap(pixmap.scaled(30, 30, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation))
-
- def select_text(self, text):
- tmp = self.message.toHtml()
- text = h.escape(text)
- strings = re.findall(text, tmp, flags=re.IGNORECASE)
- for s in strings:
- tmp = self.replace_all(tmp, s)
- self.message.setHtml(tmp)
-
- @staticmethod
- def replace_all(text, substring):
- i, l = 0, len(substring)
- while i < len(text) - l + 1:
- index = text[i:].find(substring)
- if index == -1:
- break
- i += index
- lgt, rgt = text[i:].find('<'), text[i:].find('>')
- if rgt < lgt:
- i += rgt + 1
- continue
- sub = '{}'.format(substring)
- text = text[:i] + sub + text[i + l:]
- i += len(sub)
- return text
-
-
-class ContactItem(QtWidgets.QWidget):
- """
- Contact in friends list
- """
-
- def __init__(self, parent=None):
- QtWidgets.QWidget.__init__(self, parent)
- mode = settings.Settings.get_instance()['compact_mode']
- self.setBaseSize(QtCore.QSize(250, 40 if mode else 70))
- self.avatar_label = QtWidgets.QLabel(self)
- size = 32 if mode else 64
- self.avatar_label.setGeometry(QtCore.QRect(3, 4, size, size))
- self.avatar_label.setScaledContents(False)
- self.avatar_label.setAlignment(QtCore.Qt.AlignCenter)
- self.name = DataLabel(self)
- self.name.setGeometry(QtCore.QRect(50 if mode else 75, 3 if mode else 10, 150, 15 if mode else 25))
- font = QtGui.QFont()
- font.setFamily(settings.Settings.get_instance()['font'])
- font.setPointSize(10 if mode else 12)
- font.setBold(True)
- self.name.setFont(font)
- self.status_message = DataLabel(self)
- self.status_message.setGeometry(QtCore.QRect(50 if mode else 75, 20 if mode else 30, 170, 15 if mode else 20))
- font.setPointSize(10)
- font.setBold(False)
- self.status_message.setFont(font)
- self.connection_status = StatusCircle(self)
- self.connection_status.setGeometry(QtCore.QRect(230, -2 if mode else 5, 32, 32))
- self.messages = UnreadMessagesCount(self)
- self.messages.setGeometry(QtCore.QRect(20 if mode else 52, 20 if mode else 50, 30, 20))
-
-
-class StatusCircle(QtWidgets.QWidget):
- """
- Connection status
- """
- def __init__(self, parent):
- QtWidgets.QWidget.__init__(self, parent)
- self.setGeometry(0, 0, 32, 32)
- self.label = QtWidgets.QLabel(self)
- self.label.setGeometry(QtCore.QRect(0, 0, 32, 32))
- self.unread = False
-
- def update(self, status, unread_messages=None):
- if unread_messages is None:
- unread_messages = self.unread
- else:
- self.unread = unread_messages
- if status == TOX_USER_STATUS['NONE']:
- name = 'online'
- elif status == TOX_USER_STATUS['AWAY']:
- name = 'idle'
- elif status == TOX_USER_STATUS['BUSY']:
- name = 'busy'
- else:
- name = 'offline'
- if unread_messages:
- name += '_notification'
- self.label.setGeometry(QtCore.QRect(0, 0, 32, 32))
- else:
- self.label.setGeometry(QtCore.QRect(2, 0, 32, 32))
- pixmap = QtGui.QPixmap(curr_directory() + '/images/{}.png'.format(name))
- self.label.setPixmap(pixmap)
-
-
-class UnreadMessagesCount(QtWidgets.QWidget):
-
- def __init__(self, parent=None):
- super(UnreadMessagesCount, self).__init__(parent)
- self.resize(30, 20)
- self.label = QtWidgets.QLabel(self)
- self.label.setGeometry(QtCore.QRect(0, 0, 30, 20))
- self.label.setVisible(False)
- font = QtGui.QFont()
- font.setFamily(settings.Settings.get_instance()['font'])
- font.setPointSize(12)
- font.setBold(True)
- self.label.setFont(font)
- self.label.setAlignment(QtCore.Qt.AlignVCenter | QtCore.Qt.AlignCenter)
- color = settings.Settings.get_instance()['unread_color']
- self.label.setStyleSheet('QLabel { color: white; background-color: ' + color + '; border-radius: 10; }')
-
- def update(self, messages_count):
- color = settings.Settings.get_instance()['unread_color']
- self.label.setStyleSheet('QLabel { color: white; background-color: ' + color + '; border-radius: 10; }')
- if messages_count:
- self.label.setVisible(True)
- self.label.setText(str(messages_count))
- else:
- self.label.setVisible(False)
-
-
-class FileTransferItem(QtWidgets.QListWidget):
-
- def __init__(self, file_name, size, time, user, friend_number, file_number, state, width, parent=None):
-
- QtWidgets.QListWidget.__init__(self, parent)
- self.resize(QtCore.QSize(width, 34))
- if state == TOX_FILE_TRANSFER_STATE['CANCELLED']:
- self.setStyleSheet('QListWidget { border: 1px solid #B40404; }')
- elif state in PAUSED_FILE_TRANSFERS:
- self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }')
- else:
- self.setStyleSheet('QListWidget { border: 1px solid green; }')
- self.state = state
-
- self.name = DataLabel(self)
- self.name.setGeometry(QtCore.QRect(3, 7, 95, 25))
- self.name.setTextFormat(QtCore.Qt.PlainText)
- font = QtGui.QFont()
- font.setFamily(settings.Settings.get_instance()['font'])
- font.setPointSize(11)
- font.setBold(True)
- self.name.setFont(font)
- self.name.setText(user)
-
- self.time = QtWidgets.QLabel(self)
- self.time.setGeometry(QtCore.QRect(width - 60, 7, 50, 25))
- font.setPointSize(10)
- font.setBold(False)
- self.time.setFont(font)
- self.time.setText(convert_time(time))
-
- self.cancel = QtWidgets.QPushButton(self)
- self.cancel.setGeometry(QtCore.QRect(width - 125, 2, 30, 30))
- pixmap = QtGui.QPixmap(curr_directory() + '/images/decline.png')
- icon = QtGui.QIcon(pixmap)
- self.cancel.setIcon(icon)
- self.cancel.setIconSize(QtCore.QSize(30, 30))
- self.cancel.setVisible(state in ACTIVE_FILE_TRANSFERS)
- self.cancel.clicked.connect(lambda: self.cancel_transfer(friend_number, file_number))
- self.cancel.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none;}')
-
- self.accept_or_pause = QtWidgets.QPushButton(self)
- self.accept_or_pause.setGeometry(QtCore.QRect(width - 170, 2, 30, 30))
- if state == TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']:
- self.accept_or_pause.setVisible(True)
- self.button_update('accept')
- elif state in DO_NOT_SHOW_ACCEPT_BUTTON:
- self.accept_or_pause.setVisible(False)
- elif state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']: # setup for continue
- self.accept_or_pause.setVisible(True)
- self.button_update('resume')
- else: # pause
- self.accept_or_pause.setVisible(True)
- self.button_update('pause')
- self.accept_or_pause.clicked.connect(lambda: self.accept_or_pause_transfer(friend_number, file_number, size))
-
- self.accept_or_pause.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none}')
-
- self.pb = QtWidgets.QProgressBar(self)
- self.pb.setGeometry(QtCore.QRect(100, 7, 100, 20))
- self.pb.setValue(0)
- self.pb.setStyleSheet('QProgressBar { background-color: #302F2F; }')
- self.pb.setVisible(state in SHOW_PROGRESS_BAR)
-
- self.file_name = DataLabel(self)
- self.file_name.setGeometry(QtCore.QRect(210, 7, width - 420, 20))
- font.setPointSize(12)
- self.file_name.setFont(font)
- file_size = size // 1024
- if not file_size:
- file_size = '{}B'.format(size)
- elif file_size >= 1024:
- file_size = '{}MB'.format(file_size // 1024)
- else:
- file_size = '{}KB'.format(file_size)
- file_data = '{} {}'.format(file_size, file_name)
- self.file_name.setText(file_data)
- self.file_name.setToolTip(file_name)
- self.saved_name = file_name
- self.time_left = QtWidgets.QLabel(self)
- self.time_left.setGeometry(QtCore.QRect(width - 92, 7, 30, 20))
- font.setPointSize(10)
- self.time_left.setFont(font)
- self.time_left.setVisible(state == TOX_FILE_TRANSFER_STATE['RUNNING'])
- self.setFocusPolicy(QtCore.Qt.NoFocus)
- self.paused = False
-
- def cancel_transfer(self, friend_number, file_number):
- pr = profile.Profile.get_instance()
- pr.cancel_transfer(friend_number, file_number)
- self.setStyleSheet('QListWidget { border: 1px solid #B40404; }')
- self.cancel.setVisible(False)
- self.accept_or_pause.setVisible(False)
- self.pb.setVisible(False)
-
- def accept_or_pause_transfer(self, friend_number, file_number, size):
- if self.state == TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']:
- directory = QtWidgets.QFileDialog.getExistingDirectory(self,
- QtWidgets.QApplication.translate("MainWindow", 'Choose folder'),
- curr_directory(),
- QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog)
- self.pb.setVisible(True)
- if directory:
- pr = profile.Profile.get_instance()
- pr.accept_transfer(self, directory + '/' + self.saved_name, friend_number, file_number, size)
- self.button_update('pause')
- elif self.state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']: # resume
- self.paused = False
- profile.Profile.get_instance().resume_transfer(friend_number, file_number)
- self.button_update('pause')
- self.state = TOX_FILE_TRANSFER_STATE['RUNNING']
- else: # pause
- self.paused = True
- self.state = TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']
- profile.Profile.get_instance().pause_transfer(friend_number, file_number)
- self.button_update('resume')
- self.accept_or_pause.clearFocus()
-
- def button_update(self, path):
- pixmap = QtGui.QPixmap(curr_directory() + '/images/{}.png'.format(path))
- icon = QtGui.QIcon(pixmap)
- self.accept_or_pause.setIcon(icon)
- self.accept_or_pause.setIconSize(QtCore.QSize(30, 30))
-
- def update_transfer_state(self, state, progress, time):
- self.pb.setValue(int(progress * 100))
- if time + 1:
- m, s = divmod(time, 60)
- self.time_left.setText('{0:02d}:{1:02d}'.format(m, s))
- if self.state != state and self.state in ACTIVE_FILE_TRANSFERS:
- if state == TOX_FILE_TRANSFER_STATE['CANCELLED']:
- self.setStyleSheet('QListWidget { border: 1px solid #B40404; }')
- self.cancel.setVisible(False)
- self.accept_or_pause.setVisible(False)
- self.pb.setVisible(False)
- self.state = state
- self.time_left.setVisible(False)
- elif state == TOX_FILE_TRANSFER_STATE['FINISHED']:
- self.accept_or_pause.setVisible(False)
- self.pb.setVisible(False)
- self.cancel.setVisible(False)
- self.setStyleSheet('QListWidget { border: 1px solid green; }')
- self.state = state
- self.time_left.setVisible(False)
- elif state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND']:
- self.accept_or_pause.setVisible(False)
- self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }')
- self.state = state
- self.time_left.setVisible(False)
- elif state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']:
- self.button_update('resume') # setup button continue
- self.setStyleSheet('QListWidget { border: 1px solid green; }')
- self.state = state
- self.time_left.setVisible(False)
- elif state == TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']:
- self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }')
- self.accept_or_pause.setVisible(False)
- self.time_left.setVisible(False)
- self.pb.setVisible(False)
- elif not self.paused: # active
- self.pb.setVisible(True)
- self.accept_or_pause.setVisible(True) # setup to pause
- self.button_update('pause')
- self.setStyleSheet('QListWidget { border: 1px solid green; }')
- self.state = state
- self.time_left.setVisible(True)
-
- def mark_as_sent(self):
- return False
-
-
-class UnsentFileItem(FileTransferItem):
-
- def __init__(self, file_name, size, user, time, width, parent=None):
- super(UnsentFileItem, self).__init__(file_name, size, time, user, -1, -1,
- TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND'], width, parent)
- self._time = time
- self.pb.setVisible(False)
- movie = QtGui.QMovie(curr_directory() + '/images/spinner.gif')
- self.time.setMovie(movie)
- movie.start()
-
- def cancel_transfer(self, *args):
- pr = profile.Profile.get_instance()
- pr.cancel_not_started_transfer(self._time)
-
-
-class InlineImageItem(QtWidgets.QScrollArea):
-
- def __init__(self, data, width, elem):
-
- QtWidgets.QScrollArea.__init__(self)
- self.setFocusPolicy(QtCore.Qt.NoFocus)
- self._elem = elem
- self._image_label = QtWidgets.QLabel(self)
- self._image_label.raise_()
- self.setWidget(self._image_label)
- self._image_label.setScaledContents(False)
- self._pixmap = QtGui.QPixmap()
- self._pixmap.loadFromData(data, 'PNG')
- self._max_size = width - 30
- self._resize_needed = not (self._pixmap.width() <= self._max_size)
- self._full_size = not self._resize_needed
- if not self._resize_needed:
- self._image_label.setPixmap(self._pixmap)
- self.resize(QtCore.QSize(self._max_size + 5, self._pixmap.height() + 5))
- self._image_label.setGeometry(5, 0, self._pixmap.width(), self._pixmap.height())
- else:
- pixmap = self._pixmap.scaled(self._max_size, self._max_size, QtCore.Qt.KeepAspectRatio)
- self._image_label.setPixmap(pixmap)
- self.resize(QtCore.QSize(self._max_size + 5, pixmap.height()))
- self._image_label.setGeometry(5, 0, self._max_size + 5, pixmap.height())
- self._elem.setSizeHint(QtCore.QSize(self.width(), self.height()))
-
- def mouseReleaseEvent(self, event):
- if event.button() == QtCore.Qt.LeftButton and self._resize_needed: # scale inline
- if self._full_size:
- pixmap = self._pixmap.scaled(self._max_size, self._max_size, QtCore.Qt.KeepAspectRatio)
- self._image_label.setPixmap(pixmap)
- self.resize(QtCore.QSize(self._max_size, pixmap.height()))
- self._image_label.setGeometry(5, 0, pixmap.width(), pixmap.height())
- else:
- self._image_label.setPixmap(self._pixmap)
- self.resize(QtCore.QSize(self._max_size, self._pixmap.height() + 17))
- self._image_label.setGeometry(5, 0, self._pixmap.width(), self._pixmap.height())
- self._full_size = not self._full_size
- self._elem.setSizeHint(QtCore.QSize(self.width(), self.height()))
- elif event.button() == QtCore.Qt.RightButton: # save inline
- directory = QtWidgets.QFileDialog.getExistingDirectory(self,
- QtWidgets.QApplication.translate("MainWindow",
- 'Choose folder'),
- curr_directory(),
- QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog)
- if directory:
- fl = QtCore.QFile(directory + '/toxygen_inline_' + curr_time().replace(':', '_') + '.png')
- self._pixmap.save(fl, 'PNG')
-
- def mark_as_sent(self):
- return False
diff --git a/toxygen/loginscreen.py b/toxygen/loginscreen.py
deleted file mode 100644
index 77aa5ba..0000000
--- a/toxygen/loginscreen.py
+++ /dev/null
@@ -1,103 +0,0 @@
-from PyQt5 import QtWidgets, QtCore
-from widgets import *
-
-
-class NickEdit(LineEdit):
-
- def __init__(self, parent):
- super(NickEdit, self).__init__(parent)
- self.parent = parent
-
- def keyPressEvent(self, event):
- if event.key() == QtCore.Qt.Key_Return:
- self.parent.create_profile()
- else:
- super(NickEdit, self).keyPressEvent(event)
-
-
-class LoginScreen(CenteredWidget):
-
- def __init__(self):
- super(LoginScreen, self).__init__()
- self.initUI()
- self.center()
-
- def initUI(self):
- self.resize(400, 200)
- self.setMinimumSize(QtCore.QSize(400, 200))
- self.setMaximumSize(QtCore.QSize(400, 200))
- self.new_profile = QtWidgets.QPushButton(self)
- self.new_profile.setGeometry(QtCore.QRect(20, 150, 171, 27))
- self.new_profile.clicked.connect(self.create_profile)
- self.label = QtWidgets.QLabel(self)
- self.label.setGeometry(QtCore.QRect(20, 70, 101, 17))
- self.new_name = NickEdit(self)
- self.new_name.setGeometry(QtCore.QRect(20, 100, 171, 31))
- self.load_profile = QtWidgets.QPushButton(self)
- self.load_profile.setGeometry(QtCore.QRect(220, 150, 161, 27))
- self.load_profile.clicked.connect(self.load_ex_profile)
- self.default = QtWidgets.QCheckBox(self)
- self.default.setGeometry(QtCore.QRect(220, 110, 131, 22))
- self.groupBox = QtWidgets.QGroupBox(self)
- self.groupBox.setGeometry(QtCore.QRect(210, 40, 181, 151))
- self.comboBox = QtWidgets.QComboBox(self.groupBox)
- self.comboBox.setGeometry(QtCore.QRect(10, 30, 161, 27))
- self.groupBox_2 = QtWidgets.QGroupBox(self)
- self.groupBox_2.setGeometry(QtCore.QRect(10, 40, 191, 151))
- self.toxygen = QtWidgets.QLabel(self)
- self.groupBox.raise_()
- self.groupBox_2.raise_()
- self.comboBox.raise_()
- self.default.raise_()
- self.load_profile.raise_()
- self.new_name.raise_()
- self.new_profile.raise_()
- self.toxygen.setGeometry(QtCore.QRect(160, 8, 90, 25))
- font = QtGui.QFont()
- font.setFamily("Impact")
- font.setPointSize(16)
- self.toxygen.setFont(font)
- self.toxygen.setObjectName("toxygen")
- self.type = 0
- self.number = -1
- self.load_as_default = False
- self.name = None
- self.retranslateUi()
- QtCore.QMetaObject.connectSlotsByName(self)
-
- def retranslateUi(self):
- self.new_name.setPlaceholderText(QtWidgets.QApplication.translate("login", "Profile name"))
- self.setWindowTitle(QtWidgets.QApplication.translate("login", "Log in"))
- self.new_profile.setText(QtWidgets.QApplication.translate("login", "Create"))
- self.label.setText(QtWidgets.QApplication.translate("login", "Profile name:"))
- self.load_profile.setText(QtWidgets.QApplication.translate("login", "Load profile"))
- self.default.setText(QtWidgets.QApplication.translate("login", "Use as default"))
- self.groupBox.setTitle(QtWidgets.QApplication.translate("login", "Load existing profile"))
- self.groupBox_2.setTitle(QtWidgets.QApplication.translate("login", "Create new profile"))
- self.toxygen.setText(QtWidgets.QApplication.translate("login", "toxygen"))
-
- def create_profile(self):
- self.type = 1
- self.name = self.new_name.text()
- self.close()
-
- def load_ex_profile(self):
- if not self.create_only:
- self.type = 2
- self.number = self.comboBox.currentIndex()
- self.load_as_default = self.default.isChecked()
- self.close()
-
- def update_select(self, data):
- list_of_profiles = []
- for elem in data:
- list_of_profiles.append(elem)
- self.comboBox.addItems(list_of_profiles)
- self.create_only = not list_of_profiles
-
- def update_on_close(self, func):
- self.onclose = func
-
- def closeEvent(self, event):
- self.onclose(self.type, self.number, self.load_as_default, self.name)
- event.accept()
diff --git a/toxygen/mainscreen.py b/toxygen/mainscreen.py
deleted file mode 100644
index 93ec72d..0000000
--- a/toxygen/mainscreen.py
+++ /dev/null
@@ -1,757 +0,0 @@
-from menu import *
-from profile import *
-from list_items import *
-from widgets import MultilineEdit, ComboBox
-import plugin_support
-from mainscreen_widgets import *
-import settings
-import toxes
-
-
-class MainWindow(QtWidgets.QMainWindow, Singleton):
-
- def __init__(self, tox, reset, tray):
- super().__init__()
- Singleton.__init__(self)
- self.reset = reset
- self.tray = tray
- self.setAcceptDrops(True)
- self.initUI(tox)
- self._saved = False
- if settings.Settings.get_instance()['show_welcome_screen']:
- self.ws = WelcomeScreen()
-
- 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.menubar)
- self.menuProfile.setObjectName("menuProfile")
- self.menuSettings = QtWidgets.QMenu(self.menubar)
- self.menuSettings.setObjectName("menuSettings")
- self.menuPlugins = QtWidgets.QMenu(self.menubar)
- self.menuPlugins.setObjectName("menuPlugins")
- self.menuAbout = QtWidgets.QMenu(self.menubar)
- self.menuAbout.setObjectName("menuAbout")
-
- self.actionAdd_friend = QtWidgets.QAction(window)
- self.actionAdd_gc = QtWidgets.QAction(window)
- self.actionAdd_friend.setObjectName("actionAdd_friend")
- self.actionprofilesettings = QtWidgets.QAction(window)
- self.actionprofilesettings.setObjectName("actionprofilesettings")
- self.actionPrivacy_settings = QtWidgets.QAction(window)
- self.actionPrivacy_settings.setObjectName("actionPrivacy_settings")
- self.actionInterface_settings = QtWidgets.QAction(window)
- self.actionInterface_settings.setObjectName("actionInterface_settings")
- self.actionNotifications = QtWidgets.QAction(window)
- self.actionNotifications.setObjectName("actionNotifications")
- self.actionNetwork = QtWidgets.QAction(window)
- self.actionNetwork.setObjectName("actionNetwork")
- self.actionAbout_program = QtWidgets.QAction(window)
- self.actionAbout_program.setObjectName("actionAbout_program")
- self.updateSettings = QtWidgets.QAction(window)
- self.actionSettings = QtWidgets.QAction(window)
- self.actionSettings.setObjectName("actionSettings")
- 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)
- self.menuSettings.addAction(self.actionInterface_settings)
- self.menuSettings.addAction(self.actionNotifications)
- self.menuSettings.addAction(self.actionNetwork)
- self.menuSettings.addAction(self.audioSettings)
- self.menuSettings.addAction(self.videoSettings)
- self.menuSettings.addAction(self.updateSettings)
- self.menuPlugins.addAction(self.pluginData)
- self.menuPlugins.addAction(self.importPlugin)
- self.menuPlugins.addAction(self.reloadPlugins)
- self.menuAbout.addAction(self.actionAbout_program)
-
- 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)
- self.actionNotifications.triggered.connect(self.notification_settings)
- self.audioSettings.triggered.connect(self.audio_settings)
- self.videoSettings.triggered.connect(self.video_settings)
- self.updateSettings.triggered.connect(self.update_settings)
- self.pluginData.triggered.connect(self.plugins_menu)
- self.lockApp.triggered.connect(self.lock_app)
- self.importPlugin.triggered.connect(self.import_plugin)
- self.reloadPlugins.triggered.connect(self.reload_plugins)
-
- def languageChange(self, *args, **kwargs):
- self.retranslateUi()
-
- def event(self, event):
- if event.type() == QtCore.QEvent.WindowActivate:
- self.tray.setIcon(QtGui.QIcon(curr_directory() + '/images/icon.png'))
- self.messages.repaint()
- return super(MainWindow, self).event(event)
-
- def retranslateUi(self):
- self.lockApp.setText(QtWidgets.QApplication.translate("MainWindow", "Lock"))
- self.menuPlugins.setTitle(QtWidgets.QApplication.translate("MainWindow", "Plugins"))
- self.pluginData.setText(QtWidgets.QApplication.translate("MainWindow", "List of plugins"))
- 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"))
- self.actionNotifications.setText(QtWidgets.QApplication.translate("MainWindow", "Notifications"))
- self.actionNetwork.setText(QtWidgets.QApplication.translate("MainWindow", "Network"))
- self.actionAbout_program.setText(QtWidgets.QApplication.translate("MainWindow", "About program"))
- self.actionSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Settings"))
- self.audioSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Audio"))
- self.videoSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Video"))
- self.updateSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Updates"))
- self.contact_name.setPlaceholderText(QtWidgets.QApplication.translate("MainWindow", "Search"))
- self.sendMessageButton.setToolTip(QtWidgets.QApplication.translate("MainWindow", "Send message"))
- self.callButton.setToolTip(QtWidgets.QApplication.translate("MainWindow", "Start audio call with friend"))
- self.online_contacts.clear()
- self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "All"))
- self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "Online"))
- self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "Online first"))
- self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "Name"))
- self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "Online and by name"))
- self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "Online first and by name"))
- ind = Settings.get_instance()['sorting']
- d = {0: 0, 1: 1, 2: 2, 3: 4, 1 | 4: 4, 2 | 4: 5}
- self.online_contacts.setCurrentIndex(d[ind])
- self.importPlugin.setText(QtWidgets.QApplication.translate("MainWindow", "Import plugin"))
- self.reloadPlugins.setText(QtWidgets.QApplication.translate("MainWindow", "Reload plugins"))
-
- def setup_right_bottom(self, Form):
- Form.resize(650, 60)
- self.messageEdit = MessageArea(Form, self)
- self.messageEdit.setGeometry(QtCore.QRect(0, 3, 450, 55))
- self.messageEdit.setObjectName("messageEdit")
- font = QtGui.QFont()
- font.setPointSize(11)
- font.setFamily(settings.Settings.get_instance()['font'])
- self.messageEdit.setFont(font)
-
- self.sendMessageButton = QtWidgets.QPushButton(Form)
- self.sendMessageButton.setGeometry(QtCore.QRect(565, 3, 60, 55))
- self.sendMessageButton.setObjectName("sendMessageButton")
-
- self.menuButton = MenuButton(Form, self.show_menu)
- self.menuButton.setGeometry(QtCore.QRect(QtCore.QRect(455, 3, 55, 55)))
-
- pixmap = QtGui.QPixmap('send.png')
- icon = QtGui.QIcon(pixmap)
- self.sendMessageButton.setIcon(icon)
- self.sendMessageButton.setIconSize(QtCore.QSize(45, 60))
-
- pixmap = QtGui.QPixmap('menu.png')
- icon = QtGui.QIcon(pixmap)
- self.menuButton.setIcon(icon)
- self.menuButton.setIconSize(QtCore.QSize(40, 40))
-
- self.sendMessageButton.clicked.connect(self.send_message)
-
- QtCore.QMetaObject.connectSlotsByName(Form)
-
- def setup_left_center_menu(self, Form):
- Form.resize(270, 25)
- self.search_label = QtWidgets.QLabel(Form)
- self.search_label.setGeometry(QtCore.QRect(3, 2, 20, 20))
- pixmap = QtGui.QPixmap()
- pixmap.load(curr_directory() + '/images/search.png')
- self.search_label.setScaledContents(False)
- self.search_label.setPixmap(pixmap)
-
- self.contact_name = LineEdit(Form)
- self.contact_name.setGeometry(QtCore.QRect(0, 0, 150, 25))
- self.contact_name.setObjectName("contact_name")
- self.contact_name.textChanged.connect(self.filtering)
-
- self.online_contacts = ComboBox(Form)
- self.online_contacts.setGeometry(QtCore.QRect(150, 0, 120, 25))
- self.online_contacts.activated[int].connect(lambda x: self.filtering())
- self.search_label.raise_()
-
- QtCore.QMetaObject.connectSlotsByName(Form)
-
- def setup_left_top(self, Form):
- Form.setCursor(QtCore.Qt.PointingHandCursor)
- Form.setMinimumSize(QtCore.QSize(270, 75))
- Form.setMaximumSize(QtCore.QSize(270, 75))
- Form.setBaseSize(QtCore.QSize(270, 75))
- self.avatar_label = Form.avatar_label = QtWidgets.QLabel(Form)
- self.avatar_label.setGeometry(QtCore.QRect(5, 5, 64, 64))
- self.avatar_label.setScaledContents(False)
- self.avatar_label.setAlignment(QtCore.Qt.AlignCenter)
- self.name = Form.name = DataLabel(Form)
- Form.name.setGeometry(QtCore.QRect(75, 15, 150, 25))
- font = QtGui.QFont()
- font.setFamily(settings.Settings.get_instance()['font'])
- font.setPointSize(14)
- font.setBold(True)
- Form.name.setFont(font)
- Form.name.setObjectName("name")
- self.status_message = Form.status_message = DataLabel(Form)
- Form.status_message.setGeometry(QtCore.QRect(75, 35, 170, 25))
- font.setPointSize(12)
- font.setBold(False)
- Form.status_message.setFont(font)
- Form.status_message.setObjectName("status_message")
- self.connection_status = Form.connection_status = StatusCircle(Form)
- Form.connection_status.setGeometry(QtCore.QRect(230, 10, 32, 32))
- self.avatar_label.mouseReleaseEvent = self.profile_settings
- self.status_message.mouseReleaseEvent = self.profile_settings
- self.name.mouseReleaseEvent = self.profile_settings
- self.connection_status.raise_()
- Form.connection_status.setObjectName("connection_status")
-
- def setup_right_top(self, Form):
- Form.resize(650, 75)
- self.account_avatar = QtWidgets.QLabel(Form)
- self.account_avatar.setGeometry(QtCore.QRect(10, 5, 64, 64))
- self.account_avatar.setScaledContents(False)
- self.account_name = DataLabel(Form)
- self.account_name.setGeometry(QtCore.QRect(100, 0, 400, 25))
- self.account_name.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse)
- font = QtGui.QFont()
- font.setFamily(settings.Settings.get_instance()['font'])
- font.setPointSize(14)
- font.setBold(True)
- self.account_name.setFont(font)
- self.account_name.setObjectName("account_name")
- self.account_status = DataLabel(Form)
- self.account_status.setGeometry(QtCore.QRect(100, 20, 400, 25))
- self.account_status.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse)
- font.setPointSize(12)
- font.setBold(False)
- self.account_status.setFont(font)
- self.account_status.setObjectName("account_status")
- self.callButton = QtWidgets.QPushButton(Form)
- self.callButton.setGeometry(QtCore.QRect(550, 5, 50, 50))
- self.callButton.setObjectName("callButton")
- self.callButton.clicked.connect(lambda: self.profile.call_click(True))
- self.videocallButton = QtWidgets.QPushButton(Form)
- self.videocallButton.setGeometry(QtCore.QRect(550, 5, 50, 50))
- self.videocallButton.setObjectName("videocallButton")
- self.videocallButton.clicked.connect(lambda: self.profile.call_click(True, True))
- self.update_call_state('call')
- self.typing = QtWidgets.QLabel(Form)
- self.typing.setGeometry(QtCore.QRect(500, 25, 50, 30))
- pixmap = QtGui.QPixmap(QtCore.QSize(50, 30))
- pixmap.load(curr_directory() + '/images/typing.png')
- self.typing.setScaledContents(False)
- self.typing.setPixmap(pixmap.scaled(50, 30, QtCore.Qt.KeepAspectRatio))
- self.typing.setVisible(False)
- QtCore.QMetaObject.connectSlotsByName(Form)
-
- def setup_left_center(self, widget):
- self.friends_list = QtWidgets.QListWidget(widget)
- self.friends_list.setObjectName("friends_list")
- self.friends_list.setGeometry(0, 0, 270, 310)
- self.friends_list.clicked.connect(self.friend_click)
- self.friends_list.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
- self.friends_list.customContextMenuRequested.connect(self.friend_right_click)
- self.friends_list.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
- self.friends_list.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
- self.friends_list.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
- self.friends_list.verticalScrollBar().setContextMenuPolicy(QtCore.Qt.NoContextMenu)
-
- def setup_right_center(self, widget):
- self.messages = QtWidgets.QListWidget(widget)
- self.messages.setGeometry(0, 0, 620, 310)
- self.messages.setObjectName("messages")
- self.messages.setSpacing(1)
- self.messages.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
- self.messages.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
- self.messages.focusOutEvent = lambda event: self.messages.clearSelection()
- self.messages.verticalScrollBar().setContextMenuPolicy(QtCore.Qt.NoContextMenu)
-
- def load(pos):
- if not pos:
- self.profile.load_history()
- self.messages.verticalScrollBar().setValue(1)
- self.messages.verticalScrollBar().valueChanged.connect(load)
- self.messages.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
- self.messages.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
-
- def initUI(self, tox):
- self.setMinimumSize(920, 500)
- s = Settings.get_instance()
- self.setGeometry(s['x'], s['y'], s['width'], s['height'])
- self.setWindowTitle('Toxygen')
- os.chdir(curr_directory() + '/images/')
- menu = QtWidgets.QWidget()
- main = QtWidgets.QWidget()
- grid = QtWidgets.QGridLayout()
- search = QtWidgets.QWidget()
- name = QtWidgets.QWidget()
- info = QtWidgets.QWidget()
- main_list = QtWidgets.QWidget()
- messages = QtWidgets.QWidget()
- message_buttons = QtWidgets.QWidget()
- self.setup_left_center_menu(search)
- self.setup_left_top(name)
- self.setup_right_center(messages)
- self.setup_right_top(info)
- self.setup_right_bottom(message_buttons)
- self.setup_left_center(main_list)
- self.setup_menu(menu)
- if not Settings.get_instance()['mirror_mode']:
- grid.addWidget(search, 2, 0)
- grid.addWidget(name, 1, 0)
- grid.addWidget(messages, 2, 1, 2, 1)
- grid.addWidget(info, 1, 1)
- grid.addWidget(message_buttons, 4, 1)
- grid.addWidget(main_list, 3, 0, 2, 1)
- grid.setColumnMinimumWidth(1, 500)
- grid.setColumnMinimumWidth(0, 270)
- else:
- grid.addWidget(search, 2, 1)
- grid.addWidget(name, 1, 1)
- grid.addWidget(messages, 2, 0, 2, 1)
- grid.addWidget(info, 1, 0)
- grid.addWidget(message_buttons, 4, 0)
- grid.addWidget(main_list, 3, 1, 2, 1)
- grid.setColumnMinimumWidth(0, 500)
- grid.setColumnMinimumWidth(1, 270)
-
- grid.addWidget(menu, 0, 0, 1, 2)
- grid.setSpacing(0)
- grid.setContentsMargins(0, 0, 0, 0)
- grid.setRowMinimumHeight(0, 25)
- grid.setRowMinimumHeight(1, 75)
- grid.setRowMinimumHeight(2, 25)
- grid.setRowMinimumHeight(3, 320)
- grid.setRowMinimumHeight(4, 55)
- grid.setColumnStretch(1, 1)
- grid.setRowStretch(3, 1)
- main.setLayout(grid)
- self.setCentralWidget(main)
- self.messageEdit.setFocus()
- self.user_info = name
- self.friend_info = info
- self.retranslateUi()
- self.profile = Profile(tox, self)
-
- def closeEvent(self, event):
- s = Settings.get_instance()
- if not s['close_to_tray'] or s.closing:
- if not self._saved:
- self._saved = True
- self.profile.save_history()
- self.profile.close()
- s['x'] = self.geometry().x()
- s['y'] = self.geometry().y()
- s['width'] = self.width()
- s['height'] = self.height()
- s.save()
- QtWidgets.QApplication.closeAllWindows()
- event.accept()
- elif QtWidgets.QSystemTrayIcon.isSystemTrayAvailable():
- event.ignore()
- self.hide()
-
- def close_window(self):
- Settings.get_instance().closing = True
- self.close()
-
- def resizeEvent(self, *args, **kwargs):
- self.messages.setGeometry(0, 0, self.width() - 270, self.height() - 155)
- self.friends_list.setGeometry(0, 0, 270, self.height() - 125)
-
- self.videocallButton.setGeometry(QtCore.QRect(self.width() - 330, 10, 50, 50))
- self.callButton.setGeometry(QtCore.QRect(self.width() - 390, 10, 50, 50))
- self.typing.setGeometry(QtCore.QRect(self.width() - 450, 20, 50, 30))
-
- self.messageEdit.setGeometry(QtCore.QRect(55, 0, self.width() - 395, 55))
- self.menuButton.setGeometry(QtCore.QRect(0, 0, 55, 55))
- self.sendMessageButton.setGeometry(QtCore.QRect(self.width() - 340, 0, 70, 55))
-
- self.account_name.setGeometry(QtCore.QRect(100, 15, self.width() - 560, 25))
- self.account_status.setGeometry(QtCore.QRect(100, 35, self.width() - 560, 25))
- self.messageEdit.setFocus()
- self.profile.update()
-
- def keyPressEvent(self, event):
- if event.key() == QtCore.Qt.Key_Escape and QtWidgets.QSystemTrayIcon.isSystemTrayAvailable():
- self.hide()
- elif event.key() == QtCore.Qt.Key_C and event.modifiers() & QtCore.Qt.ControlModifier and self.messages.selectedIndexes():
- rows = list(map(lambda x: self.messages.row(x), self.messages.selectedItems()))
- indexes = (rows[0] - self.messages.count(), rows[-1] - self.messages.count())
- s = self.profile.export_history(self.profile.active_friend, True, indexes)
- clipboard = QtWidgets.QApplication.clipboard()
- clipboard.setText(s)
- elif event.key() == QtCore.Qt.Key_Z and event.modifiers() & QtCore.Qt.ControlModifier and self.messages.selectedIndexes():
- self.messages.clearSelection()
- elif event.key() == QtCore.Qt.Key_F and event.modifiers() & QtCore.Qt.ControlModifier:
- self.show_search_field()
- else:
- super(MainWindow, self).keyPressEvent(event)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Functions which called when user click in menu
- # -----------------------------------------------------------------------------------------------------------------
-
- def about_program(self):
- import util
- msgBox = QtWidgets.QMessageBox()
- msgBox.setWindowTitle(QtWidgets.QApplication.translate("MainWindow", "About"))
- text = (QtWidgets.QApplication.translate("MainWindow", 'Toxygen is Tox client written on Python.
Version: '))
- github = '
Github'
- submit_a_bug = '
Submit a bug'
- msgBox.setText(text + util.program_version + github + submit_a_bug)
- msgBox.exec_()
-
- def network_settings(self):
- self.n_s = NetworkSettings(self.reset)
- self.n_s.show()
-
- def plugins_menu(self):
- self.p_s = PluginsSettings()
- self.p_s.show()
-
- def add_contact(self, link=''):
- 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()
-
- def privacy_settings(self):
- self.priv_s = PrivacySettings()
- self.priv_s.show()
-
- def notification_settings(self):
- self.notif_s = NotificationsSettings()
- self.notif_s.show()
-
- def interface_settings(self):
- self.int_s = InterfaceSettings()
- self.int_s.show()
-
- def audio_settings(self):
- self.audio_s = AudioSettings()
- self.audio_s.show()
-
- def video_settings(self):
- self.video_s = VideoSettings()
- self.video_s.show()
-
- def update_settings(self):
- self.update_s = UpdateSettings()
- self.update_s.show()
-
- def reload_plugins(self):
- plugin_loader = plugin_support.PluginLoader.get_instance()
- if plugin_loader is not None:
- plugin_loader.reload()
-
- def import_plugin(self):
- import util
- directory = QtWidgets.QFileDialog.getExistingDirectory(self,
- QtWidgets.QApplication.translate("MainWindow", 'Choose folder with plugin'),
- util.curr_directory(),
- QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog)
- if directory:
- src = directory + '/'
- dest = curr_directory() + '/plugins/'
- util.copy(src, dest)
- msgBox = QtWidgets.QMessageBox()
- msgBox.setWindowTitle(
- QtWidgets.QApplication.translate("MainWindow", "Restart Toxygen"))
- msgBox.setText(
- QtWidgets.QApplication.translate("MainWindow", 'Plugin will be loaded after restart'))
- msgBox.exec_()
-
- def lock_app(self):
- if toxes.ToxES.get_instance().has_password():
- Settings.get_instance().locked = True
- self.hide()
- else:
- msgBox = QtWidgets.QMessageBox()
- msgBox.setWindowTitle(
- QtWidgets.QApplication.translate("MainWindow", "Cannot lock app"))
- msgBox.setText(
- QtWidgets.QApplication.translate("MainWindow", 'Error. Profile password is not set.'))
- msgBox.exec_()
-
- def show_menu(self):
- if not hasattr(self, 'menu'):
- self.menu = DropdownMenu(self)
- self.menu.setGeometry(QtCore.QRect(0 if Settings.get_instance()['mirror_mode'] else 270,
- self.height() - 120,
- 180,
- 120))
- self.menu.show()
-
- # -----------------------------------------------------------------------------------------------------------------
- # Messages, calls and file transfers
- # -----------------------------------------------------------------------------------------------------------------
-
- def send_message(self):
- text = self.messageEdit.toPlainText()
- self.profile.send_message(text)
-
- def send_file(self):
- self.menu.hide()
- 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]:
- self.profile.send_file(name[0])
-
- def send_screenshot(self, hide=False):
- self.menu.hide()
- if self.profile.active_friend + 1 and self.profile.is_active_a_friend():
- self.sw = ScreenShotWindow(self)
- self.sw.show()
- if hide:
- self.hide()
-
- def send_smiley(self):
- self.menu.hide()
- if self.profile.active_friend + 1:
- self.smiley = SmileyWindow(self)
- self.smiley.setGeometry(QtCore.QRect(self.x() if Settings.get_instance()['mirror_mode'] else 270 + self.x(),
- self.y() + self.height() - 200,
- self.smiley.width(),
- self.smiley.height()))
- self.smiley.show()
-
- def send_sticker(self):
- self.menu.hide()
- 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,
- self.sticker.width(),
- self.sticker.height()))
- self.sticker.show()
-
- def active_call(self):
- self.update_call_state('finish_call')
-
- def incoming_call(self):
- self.update_call_state('incoming_call')
-
- def call_finished(self):
- self.update_call_state('call')
-
- def update_call_state(self, state):
- os.chdir(curr_directory() + '/images/')
-
- pixmap = QtGui.QPixmap(curr_directory() + '/images/{}.png'.format(state))
- icon = QtGui.QIcon(pixmap)
- self.callButton.setIcon(icon)
- self.callButton.setIconSize(QtCore.QSize(50, 50))
-
- pixmap = QtGui.QPixmap(curr_directory() + '/images/{}_video.png'.format(state))
- icon = QtGui.QIcon(pixmap)
- self.videocallButton.setIcon(icon)
- self.videocallButton.setIconSize(QtCore.QSize(35, 35))
-
- # -----------------------------------------------------------------------------------------------------------------
- # Functions which called when user open context menu in friends list
- # -----------------------------------------------------------------------------------------------------------------
-
- def friend_right_click(self, pos):
- item = self.friends_list.itemAt(pos)
- num = self.friends_list.indexFromItem(item).row()
- friend = Profile.get_instance().get_friend(num)
- if friend is None:
- return
- settings = Settings.get_instance()
- allowed = friend.tox_id in settings['auto_accept_from_friends']
- 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'))
- export_to_text_item = history_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Export as text'))
- export_to_html_item = history_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Export as HTML'))
-
- 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)
- remove_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Remove friend'))
- 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 number=number: 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)
- copy_key_item.triggered.connect(lambda: self.copy_friend_key(num))
- remove_item.triggered.connect(lambda: self.remove_friend(num))
- block_item.triggered.connect(lambda: self.block_friend(num))
- auto_accept_item.triggered.connect(lambda: self.auto_accept(num, not allowed))
- notes_item.triggered.connect(lambda: self.show_note(friend))
- else:
- leave_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Leave chat'))
- set_title_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Set title'))
- leave_item.triggered.connect(lambda: self.leave_gc(num))
- set_title_item.triggered.connect(lambda: self.set_title(num))
- clear_history_item.triggered.connect(lambda: self.clear_history(num))
- 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))
- export_to_html_item.triggered.connect(lambda: self.export_history(num, False))
- parent_position = self.friends_list.mapToGlobal(QtCore.QPoint(0, 0))
- self.listMenu.move(parent_position + pos)
- self.listMenu.show()
-
- def show_note(self, friend):
- s = Settings.get_instance()
- note = s['notes'][friend.tox_id] if friend.tox_id in s['notes'] else ''
- user = QtWidgets.QApplication.translate("MainWindow", 'Notes about user')
- user = '{} {}'.format(user, friend.name)
-
- def save_note(text):
- if friend.tox_id in s['notes']:
- del s['notes'][friend.tox_id]
- if text:
- s['notes'][friend.tox_id] = text
- s.save()
- self.note = MultilineEdit(user, note, save_note)
- self.note.show()
-
- def export_history(self, num, as_text=True):
- s = self.profile.export_history(num, as_text)
- extension = 'txt' if as_text else 'html'
- file_name, _ = QtWidgets.QFileDialog.getSaveFileName(None,
- QtWidgets.QApplication.translate("MainWindow",
- 'Choose file name'),
- curr_directory(),
- filter=extension,
- options=QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog)
-
- if file_name:
- if not file_name.endswith('.' + extension):
- file_name += '.' + extension
- with open(file_name, 'wt') as fl:
- fl.write(s)
-
- def set_alias(self, num):
- self.profile.set_alias(num)
-
- def remove_friend(self, num):
- self.profile.delete_friend(num)
-
- def block_friend(self, num):
- friend = self.profile.get_friend(num)
- self.profile.block_user(friend.tox_id)
-
- def copy_friend_key(self, num):
- tox_id = self.profile.friend_public_key(num)
- clipboard = QtWidgets.QApplication.clipboard()
- clipboard.setText(tox_id)
-
- def copy_name(self, friend):
- clipboard = QtWidgets.QApplication.clipboard()
- clipboard.setText(friend.name)
-
- def copy_status(self, friend):
- clipboard = QtWidgets.QApplication.clipboard()
- clipboard.setText(friend.status_message)
-
- 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)
- if value:
- settings['auto_accept_from_friends'].append(tox_id)
- else:
- 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
- # -----------------------------------------------------------------------------------------------------------------
-
- def friend_click(self, index):
- num = index.row()
- self.profile.set_active(num)
-
- def mouseReleaseEvent(self, event):
- pos = self.connection_status.pos()
- x, y = pos.x() + self.user_info.pos().x(), pos.y() + self.user_info.pos().y()
- if (x < event.x() < x + 32) and (y < event.y() < y + 32):
- self.profile.change_status()
- else:
- super(MainWindow, self).mouseReleaseEvent(event)
-
- def show(self):
- super().show()
- self.profile.update()
-
- def filtering(self):
- ind = self.online_contacts.currentIndex()
- d = {0: 0, 1: 1, 2: 2, 3: 4, 4: 1 | 4, 5: 2 | 4}
- self.profile.filtration_and_sorting(d[ind], self.contact_name.text())
-
- def show_search_field(self):
- if hasattr(self, 'search_field') and self.search_field.isVisible():
- return
- if self.profile.get_curr_friend() is None:
- return
- self.search_field = SearchScreen(self.messages, self.messages.width(), self.messages.parent())
- x, y = self.messages.x(), self.messages.y() + self.messages.height() - 40
- self.search_field.setGeometry(x, y, self.messages.width(), 40)
- self.messages.setGeometry(x, self.messages.y(), self.messages.width(), self.messages.height() - 40)
- self.search_field.show()
diff --git a/toxygen/mainscreen_widgets.py b/toxygen/mainscreen_widgets.py
deleted file mode 100644
index dcbc075..0000000
--- a/toxygen/mainscreen_widgets.py
+++ /dev/null
@@ -1,492 +0,0 @@
-from PyQt5 import QtCore, QtGui, QtWidgets
-from widgets import RubberBandWindow, create_menu, QRightClickButton, CenteredWidget, LineEdit
-from profile import Profile
-import smileys
-import util
-import platform
-
-
-class MessageArea(QtWidgets.QPlainTextEdit):
- """User types messages here"""
-
- def __init__(self, parent, form):
- super(MessageArea, self).__init__(parent)
- self.parent = form
- self.setAcceptDrops(True)
- self.timer = QtCore.QTimer(self)
- self.timer.timeout.connect(lambda: self.parent.profile.send_typing(False))
-
- def keyPressEvent(self, event):
- if event.matches(QtGui.QKeySequence.Paste):
- mimeData = QtWidgets.QApplication.clipboard().mimeData()
- if mimeData.hasUrls():
- for url in mimeData.urls():
- self.pasteEvent(url.toString())
- else:
- self.pasteEvent()
- elif event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
- modifiers = event.modifiers()
- if modifiers & QtCore.Qt.ControlModifier or modifiers & QtCore.Qt.ShiftModifier:
- self.insertPlainText('\n')
- else:
- if self.timer.isActive():
- self.timer.stop()
- self.parent.profile.send_typing(False)
- 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():
- self.timer.stop()
- self.timer.start(5000)
- super(MessageArea, self).keyPressEvent(event)
-
- def contextMenuEvent(self, event):
- menu = create_menu(self.createStandardContextMenu())
- menu.exec_(event.globalPos())
- del menu
-
- def dragEnterEvent(self, e):
- e.accept()
-
- def dragMoveEvent(self, e):
- e.accept()
-
- def dropEvent(self, e):
- if e.mimeData().hasFormat('text/plain') or e.mimeData().hasFormat('text/html'):
- e.accept()
- self.pasteEvent(e.mimeData().text())
- elif e.mimeData().hasUrls():
- for url in e.mimeData().urls():
- self.pasteEvent(url.toString())
- e.accept()
- else:
- e.ignore()
-
- def pasteEvent(self, text=None):
- text = text or QtWidgets.QApplication.clipboard().text()
- if text.startswith('file://'):
- file_name = self.parse_file_name(text)
- self.parent.profile.send_file(file_name)
- elif text:
- self.insertPlainText(text)
- else:
- image = QtWidgets.QApplication.clipboard().image()
- if image is not None:
- byte_array = QtCore.QByteArray()
- buffer = QtCore.QBuffer(byte_array)
- buffer.open(QtCore.QIODevice.WriteOnly)
- image.save(buffer, 'PNG')
- self.parent.profile.send_screenshot(bytes(byte_array.data()))
-
- def parse_file_name(self, file_name):
- import urllib
- if file_name.endswith('\r\n'):
- file_name = file_name[:-2]
- file_name = urllib.parse.unquote(file_name)
- return file_name[8 if platform.system() == 'Windows' else 7:]
-
-
-class ScreenShotWindow(RubberBandWindow):
-
- def closeEvent(self, *args):
- if self.parent.isHidden():
- self.parent.show()
-
- def mouseReleaseEvent(self, event):
- if self.rubberband.isVisible():
- self.rubberband.hide()
- rect = self.rubberband.geometry()
- if rect.width() and rect.height():
- screen = QtWidgets.QApplication.primaryScreen()
- p = screen.grabWindow(0,
- rect.x() + 4,
- rect.y() + 4,
- rect.width() - 8,
- rect.height() - 8)
- byte_array = QtCore.QByteArray()
- buffer = QtCore.QBuffer(byte_array)
- buffer.open(QtCore.QIODevice.WriteOnly)
- p.save(buffer, 'PNG')
- Profile.get_instance().send_screenshot(bytes(byte_array.data()))
- self.close()
-
-
-class SmileyWindow(QtWidgets.QWidget):
- """
- Smiley selection window
- """
-
- def __init__(self, parent):
- super(SmileyWindow, self).__init__()
- self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
- inst = smileys.SmileyLoader.get_instance()
- self.data = inst.get_smileys()
- count = len(self.data)
- if not count:
- self.close()
- self.page_size = int(pow(count / 8, 0.5) + 1) * 8 # smileys per page
- if count % self.page_size == 0:
- self.page_count = count // self.page_size
- else:
- self.page_count = round(count / self.page_size + 0.5)
- self.page = -1
- self.radio = []
- self.parent = parent
- 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 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)
- self.setMinimumSize(width, 200)
- self.buttons = []
- 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 c, t=i: self.clicked(t))
- self.buttons.append(b)
- self.checked(0)
-
- def checked(self, pos): # new page opened
- self.radio[self.page].setChecked(False)
- self.radio[pos].setChecked(True)
- self.page = pos
- start = self.page * self.page_size
- for i in range(self.page_size):
- try:
- self.buttons[i].setVisible(True)
- pixmap = QtGui.QPixmap(self.data[start + i][1])
- icon = QtGui.QIcon(pixmap)
- self.buttons[i].setIcon(icon)
- except:
- self.buttons[i].setVisible(False)
-
- def clicked(self, pos): # smiley selected
- pos += self.page * self.page_size
- smiley = self.data[pos][0]
- self.parent.messageEdit.insertPlainText(smiley)
- self.close()
-
- def leaveEvent(self, event):
- self.close()
-
-
-class MenuButton(QtWidgets.QPushButton):
-
- def __init__(self, parent, enter):
- super(MenuButton, self).__init__(parent)
- self.enter = enter
-
- def enterEvent(self, event):
- self.enter()
- super(MenuButton, self).enterEvent(event)
-
-
-class DropdownMenu(QtWidgets.QWidget):
-
- def __init__(self, parent):
- super(DropdownMenu, self).__init__(parent)
- self.installEventFilter(self)
- self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
- self.setMaximumSize(120, 120)
- self.setMinimumSize(120, 120)
- self.screenshotButton = QRightClickButton(self)
- self.screenshotButton.setGeometry(QtCore.QRect(0, 60, 60, 60))
- self.screenshotButton.setObjectName("screenshotButton")
-
- self.fileTransferButton = QtWidgets.QPushButton(self)
- self.fileTransferButton.setGeometry(QtCore.QRect(60, 60, 60, 60))
- self.fileTransferButton.setObjectName("fileTransferButton")
-
- self.smileyButton = QtWidgets.QPushButton(self)
- self.smileyButton.setGeometry(QtCore.QRect(0, 0, 60, 60))
-
- self.stickerButton = QtWidgets.QPushButton(self)
- self.stickerButton.setGeometry(QtCore.QRect(60, 0, 60, 60))
-
- pixmap = QtGui.QPixmap(util.curr_directory() + '/images/file.png')
- icon = QtGui.QIcon(pixmap)
- self.fileTransferButton.setIcon(icon)
- self.fileTransferButton.setIconSize(QtCore.QSize(50, 50))
-
- pixmap = QtGui.QPixmap(util.curr_directory() + '/images/screenshot.png')
- icon = QtGui.QIcon(pixmap)
- self.screenshotButton.setIcon(icon)
- self.screenshotButton.setIconSize(QtCore.QSize(50, 60))
-
- pixmap = QtGui.QPixmap(util.curr_directory() + '/images/smiley.png')
- icon = QtGui.QIcon(pixmap)
- self.smileyButton.setIcon(icon)
- self.smileyButton.setIconSize(QtCore.QSize(50, 50))
-
- pixmap = QtGui.QPixmap(util.curr_directory() + '/images/sticker.png')
- icon = QtGui.QIcon(pixmap)
- self.stickerButton.setIcon(icon)
- self.stickerButton.setIconSize(QtCore.QSize(55, 55))
-
- self.screenshotButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send screenshot"))
- self.fileTransferButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send file"))
- self.smileyButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Add smiley"))
- self.stickerButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send sticker"))
-
- self.fileTransferButton.clicked.connect(parent.send_file)
- self.screenshotButton.clicked.connect(parent.send_screenshot)
- self.screenshotButton.rightClicked.connect(lambda: parent.send_screenshot(True))
- self.smileyButton.clicked.connect(parent.send_smiley)
- self.stickerButton.clicked.connect(parent.send_sticker)
-
- def leaveEvent(self, event):
- self.close()
-
- def eventFilter(self, obj, event):
- if event.type() == QtCore.QEvent.WindowDeactivate:
- self.close()
- return False
-
-
-class StickerItem(QtWidgets.QWidget):
-
- def __init__(self, fl):
- super(StickerItem, self).__init__()
- self._image_label = QtWidgets.QLabel(self)
- self.path = fl
- self.pixmap = QtGui.QPixmap()
- self.pixmap.load(fl)
- if self.pixmap.width() > 150:
- self.pixmap = self.pixmap.scaled(150, 200, QtCore.Qt.KeepAspectRatio)
- self.setFixedSize(150, self.pixmap.height())
- self._image_label.setPixmap(self.pixmap)
-
-
-class StickerWindow(QtWidgets.QWidget):
- """Sticker selection window"""
-
- def __init__(self, parent):
- super(StickerWindow, self).__init__()
- self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
- self.setMaximumSize(250, 200)
- self.setMinimumSize(250, 200)
- self.list = QtWidgets.QListWidget(self)
- self.list.setGeometry(QtCore.QRect(0, 0, 250, 200))
- self.arr = smileys.sticker_loader()
- for sticker in self.arr:
- item = StickerItem(sticker)
- elem = QtWidgets.QListWidgetItem()
- elem.setSizeHint(QtCore.QSize(250, item.height()))
- self.list.addItem(elem)
- self.list.setItemWidget(elem, item)
- self.list.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
- self.list.setSpacing(3)
- self.list.clicked.connect(self.click)
- self.parent = parent
-
- def click(self, index):
- num = index.row()
- self.parent.profile.send_sticker(self.arr[num])
- self.close()
-
- def leaveEvent(self, event):
- self.close()
-
-
-class WelcomeScreen(CenteredWidget):
-
- def __init__(self):
- super().__init__()
- self.setMaximumSize(250, 200)
- self.setMinimumSize(250, 200)
- self.center()
- self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
- self.text = QtWidgets.QTextBrowser(self)
- self.text.setGeometry(QtCore.QRect(0, 0, 250, 170))
- self.text.setOpenExternalLinks(True)
- self.checkbox = QtWidgets.QCheckBox(self)
- self.checkbox.setGeometry(QtCore.QRect(5, 170, 240, 30))
- self.checkbox.setText(QtWidgets.QApplication.translate('WelcomeScreen', "Don't show again"))
- self.setWindowTitle(QtWidgets.QApplication.translate('WelcomeScreen', 'Tip of the day'))
- import random
- num = random.randint(0, 10)
- if num == 0:
- text = QtWidgets.QApplication.translate('WelcomeScreen', 'Press Esc if you want hide app to tray.')
- elif num == 1:
- text = QtWidgets.QApplication.translate('WelcomeScreen',
- 'Right click on screenshot button hides app to tray during screenshot.')
- elif num == 2:
- text = QtWidgets.QApplication.translate('WelcomeScreen',
- 'You can use Tox over Tor. For more info read this post')
- elif num == 3:
- text = QtWidgets.QApplication.translate('WelcomeScreen',
- 'Use Settings -> Interface to customize interface.')
- elif num == 4:
- text = QtWidgets.QApplication.translate('WelcomeScreen',
- '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. Read more')
- 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.4.1:
Downloading nodes from tox.chat
Bug fixes')
- 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')
- elif num == 9:
- text = QtWidgets.QApplication.translate('WelcomeScreen',
- 'Use right click on inline image to save it')
- else:
- text = QtWidgets.QApplication.translate('WelcomeScreen',
- 'Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam.')
- self.text.setHtml(text)
- self.checkbox.stateChanged.connect(self.not_show)
- QtCore.QTimer.singleShot(1000, self.show)
-
- def not_show(self):
- import settings
- s = settings.Settings.get_instance()
- s['show_welcome_screen'] = False
- s.save()
-
-
-class MainMenuButton(QtWidgets.QPushButton):
-
- def __init__(self, *args):
- super().__init__(*args)
- self.setObjectName("mainmenubutton")
-
- def setText(self, text):
- metrics = QtGui.QFontMetrics(self.font())
- self.setFixedWidth(metrics.size(QtCore.Qt.TextSingleLine, text).width() + 20)
- super().setText(text)
-
-
-class ClickableLabel(QtWidgets.QLabel):
-
- clicked = QtCore.pyqtSignal()
-
- def __init__(self, *args):
- super().__init__(*args)
-
- def mouseReleaseEvent(self, ev):
- self.clicked.emit()
-
-
-class SearchScreen(QtWidgets.QWidget):
-
- def __init__(self, messages, width, *args):
- super().__init__(*args)
- self.setMaximumSize(width, 40)
- self.setMinimumSize(width, 40)
- self._messages = messages
-
- self.search_text = LineEdit(self)
- self.search_text.setGeometry(0, 0, width - 160, 40)
-
- self.search_button = ClickableLabel(self)
- self.search_button.setGeometry(width - 160, 0, 40, 40)
- pixmap = QtGui.QPixmap()
- pixmap.load(util.curr_directory() + '/images/search.png')
- self.search_button.setScaledContents(False)
- self.search_button.setAlignment(QtCore.Qt.AlignCenter)
- self.search_button.setPixmap(pixmap)
- self.search_button.clicked.connect(self.search)
-
- font = QtGui.QFont()
- font.setPointSize(32)
- font.setBold(True)
-
- self.prev_button = QtWidgets.QPushButton(self)
- self.prev_button.setGeometry(width - 120, 0, 40, 40)
- self.prev_button.clicked.connect(self.prev)
- self.prev_button.setText('\u25B2')
-
- self.next_button = QtWidgets.QPushButton(self)
- self.next_button.setGeometry(width - 80, 0, 40, 40)
- self.next_button.clicked.connect(self.next)
- self.next_button.setText('\u25BC')
-
- self.close_button = QtWidgets.QPushButton(self)
- self.close_button.setGeometry(width - 40, 0, 40, 40)
- self.close_button.clicked.connect(self.close)
- self.close_button.setText('×')
- self.close_button.setFont(font)
-
- font.setPointSize(18)
- self.next_button.setFont(font)
- self.prev_button.setFont(font)
-
- self.retranslateUi()
-
- def retranslateUi(self):
- self.search_text.setPlaceholderText(QtWidgets.QApplication.translate("MainWindow", "Search"))
-
- def show(self):
- super().show()
- self.search_text.setFocus()
-
- def search(self):
- Profile.get_instance().update()
- text = self.search_text.text()
- friend = Profile.get_instance().get_curr_friend()
- if text and friend and util.is_re_valid(text):
- index = friend.search_string(text)
- self.load_messages(index)
-
- def prev(self):
- friend = Profile.get_instance().get_curr_friend()
- if friend is not None:
- index = friend.search_prev()
- self.load_messages(index)
-
- def next(self):
- friend = Profile.get_instance().get_curr_friend()
- text = self.search_text.text()
- if friend is not None:
- index = friend.search_next()
- if index is not None:
- count = self._messages.count()
- index += count
- item = self._messages.item(index)
- self._messages.scrollToItem(item)
- self._messages.itemWidget(item).select_text(text)
- else:
- self.not_found(text)
-
- def load_messages(self, index):
- text = self.search_text.text()
- if index is not None:
- profile = Profile.get_instance()
- count = self._messages.count()
- while count + index < 0:
- profile.load_history()
- count = self._messages.count()
- index += count
- item = self._messages.item(index)
- self._messages.scrollToItem(item)
- self._messages.itemWidget(item).select_text(text)
- else:
- self.not_found(text)
-
- def closeEvent(self, *args):
- Profile.get_instance().update()
- self._messages.setGeometry(0, 0, self._messages.width(), self._messages.height() + 40)
- super().closeEvent(*args)
-
- @staticmethod
- def not_found(text):
- mbox = QtWidgets.QMessageBox()
- mbox_text = QtWidgets.QApplication.translate("MainWindow",
- 'Text "{}" was not found')
-
- mbox.setText(mbox_text.format(text))
- mbox.setWindowTitle(QtWidgets.QApplication.translate("MainWindow",
- 'Not found'))
- mbox.exec_()
diff --git a/toxygen/menu.py b/toxygen/menu.py
deleted file mode 100644
index 17f4e17..0000000
--- a/toxygen/menu.py
+++ /dev/null
@@ -1,1095 +0,0 @@
-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, RubberBandWindow
-import pyaudio
-import toxes
-import plugin_support
-import updater
-
-
-class AddContact(CenteredWidget):
- """Add contact form"""
-
- def __init__(self, tox_id=''):
- super(AddContact, self).__init__()
- self.initUI(tox_id)
- self._adding = False
-
- def initUI(self, tox_id):
- self.setObjectName('AddContact')
- self.resize(568, 306)
- self.sendRequestButton = QtWidgets.QPushButton(self)
- self.sendRequestButton.setGeometry(QtCore.QRect(50, 270, 471, 31))
- self.sendRequestButton.setMinimumSize(QtCore.QSize(0, 0))
- self.sendRequestButton.setBaseSize(QtCore.QSize(0, 0))
- self.sendRequestButton.setObjectName("sendRequestButton")
- self.sendRequestButton.clicked.connect(self.add_friend)
- self.tox_id = LineEdit(self)
- self.tox_id.setGeometry(QtCore.QRect(50, 40, 471, 27))
- self.tox_id.setObjectName("lineEdit")
- self.tox_id.setText(tox_id)
- self.label = QtWidgets.QLabel(self)
- self.label.setGeometry(QtCore.QRect(50, 10, 80, 20))
- self.error_label = DataLabel(self)
- self.error_label.setGeometry(QtCore.QRect(120, 10, 420, 20))
- font = QtGui.QFont()
- font.setFamily(Settings.get_instance()['font'])
- font.setPointSize(10)
- font.setWeight(30)
- self.error_label.setFont(font)
- self.error_label.setStyleSheet("QLabel { color: #BC1C1C; }")
- self.label.setObjectName("label")
- self.message_edit = QtWidgets.QTextEdit(self)
- self.message_edit.setGeometry(QtCore.QRect(50, 110, 471, 151))
- self.message_edit.setObjectName("textEdit")
- self.message = QtWidgets.QLabel(self)
- self.message.setGeometry(QtCore.QRect(50, 70, 101, 31))
- self.message.setFont(font)
- self.message.setObjectName("label_2")
- self.retranslateUi()
- self.message_edit.setText('Hello! Add me to your contact list please')
- font.setPointSize(12)
- font.setBold(True)
- self.label.setFont(font)
- self.message.setFont(font)
- QtCore.QMetaObject.connectSlotsByName(self)
-
- def add_friend(self):
- if self._adding:
- return
- self._adding = True
- profile = Profile.get_instance()
- send = profile.send_friend_request(self.tox_id.text().strip(), self.message_edit.toPlainText())
- self._adding = False
- if send is True:
- # request was successful
- self.close()
- else: # print error data
- self.error_label.setText(send)
-
- def retranslateUi(self):
- self.setWindowTitle(QtWidgets.QApplication.translate('AddContact', "Add contact"))
- self.sendRequestButton.setText(QtWidgets.QApplication.translate("Form", "Send request"))
- self.label.setText(QtWidgets.QApplication.translate('AddContact', "TOX ID:"))
- self.message.setText(QtWidgets.QApplication.translate('AddContact', "Message:"))
- self.tox_id.setPlaceholderText(QtWidgets.QApplication.translate('AddContact', "TOX ID or public key of contact"))
-
-
-class ProfileSettings(CenteredWidget):
- """Form with profile settings such as name, status, TOX ID"""
- def __init__(self):
- super(ProfileSettings, self).__init__()
- self.initUI()
- self.center()
-
- def initUI(self):
- self.setObjectName("ProfileSettingsForm")
- self.setMinimumSize(QtCore.QSize(700, 600))
- self.setMaximumSize(QtCore.QSize(700, 600))
- self.nick = LineEdit(self)
- self.nick.setGeometry(QtCore.QRect(30, 60, 350, 27))
- profile = Profile.get_instance()
- self.nick.setText(profile.name)
- self.status = QtWidgets.QComboBox(self)
- self.status.setGeometry(QtCore.QRect(400, 60, 200, 27))
- self.status_message = LineEdit(self)
- self.status_message.setGeometry(QtCore.QRect(30, 130, 350, 27))
- self.status_message.setText(profile.status_message)
- self.label = QtWidgets.QLabel(self)
- self.label.setGeometry(QtCore.QRect(40, 30, 91, 25))
- font = QtGui.QFont()
- font.setFamily(Settings.get_instance()['font'])
- font.setPointSize(18)
- font.setWeight(75)
- font.setBold(True)
- self.label.setFont(font)
- self.label_2 = QtWidgets.QLabel(self)
- self.label_2.setGeometry(QtCore.QRect(40, 100, 100, 25))
- self.label_2.setFont(font)
- self.label_3 = QtWidgets.QLabel(self)
- self.label_3.setGeometry(QtCore.QRect(40, 180, 100, 25))
- self.label_3.setFont(font)
- self.tox_id = QtWidgets.QLabel(self)
- self.tox_id.setGeometry(QtCore.QRect(15, 210, 685, 21))
- font.setPointSize(10)
- self.tox_id.setFont(font)
- s = profile.tox_id
- self.tox_id.setText(s)
- self.copyId = QtWidgets.QPushButton(self)
- self.copyId.setGeometry(QtCore.QRect(40, 250, 180, 30))
- self.copyId.clicked.connect(self.copy)
- self.export = QtWidgets.QPushButton(self)
- self.export.setGeometry(QtCore.QRect(230, 250, 180, 30))
- self.export.clicked.connect(self.export_profile)
- self.new_nospam = QtWidgets.QPushButton(self)
- self.new_nospam.setGeometry(QtCore.QRect(420, 250, 180, 30))
- self.new_nospam.clicked.connect(self.new_no_spam)
- self.copy_pk = QtWidgets.QPushButton(self)
- self.copy_pk.setGeometry(QtCore.QRect(40, 300, 180, 30))
- self.copy_pk.clicked.connect(self.copy_public_key)
- self.new_avatar = QtWidgets.QPushButton(self)
- self.new_avatar.setGeometry(QtCore.QRect(230, 300, 180, 30))
- self.delete_avatar = QtWidgets.QPushButton(self)
- self.delete_avatar.setGeometry(QtCore.QRect(420, 300, 180, 30))
- self.delete_avatar.clicked.connect(self.reset_avatar)
- self.new_avatar.clicked.connect(self.set_avatar)
- self.profilepass = QtWidgets.QLabel(self)
- self.profilepass.setGeometry(QtCore.QRect(40, 340, 300, 30))
- font.setPointSize(18)
- self.profilepass.setFont(font)
- self.password = LineEdit(self)
- self.password.setGeometry(QtCore.QRect(40, 380, 300, 30))
- self.password.setEchoMode(QtWidgets.QLineEdit.Password)
- self.leave_blank = QtWidgets.QLabel(self)
- self.leave_blank.setGeometry(QtCore.QRect(350, 380, 300, 30))
- self.confirm_password = LineEdit(self)
- self.confirm_password.setGeometry(QtCore.QRect(40, 420, 300, 30))
- self.confirm_password.setEchoMode(QtWidgets.QLineEdit.Password)
- self.set_password = QtWidgets.QPushButton(self)
- self.set_password.setGeometry(QtCore.QRect(40, 470, 300, 30))
- self.set_password.clicked.connect(self.new_password)
- self.not_match = QtWidgets.QLabel(self)
- self.not_match.setGeometry(QtCore.QRect(350, 420, 300, 30))
- self.not_match.setVisible(False)
- self.not_match.setStyleSheet('QLabel { color: #BC1C1C; }')
- self.warning = QtWidgets.QLabel(self)
- self.warning.setGeometry(QtCore.QRect(40, 510, 500, 30))
- self.warning.setStyleSheet('QLabel { color: #BC1C1C; }')
- self.default = QtWidgets.QPushButton(self)
- self.default.setGeometry(QtCore.QRect(40, 550, 620, 30))
- path, name = Settings.get_auto_profile()
- self.auto = path + name == ProfileHelper.get_path() + Settings.get_instance().name
- self.default.clicked.connect(self.auto_profile)
- self.retranslateUi()
- if profile.status is not None:
- self.status.setCurrentIndex(profile.status)
- else:
- self.status.setVisible(False)
- QtCore.QMetaObject.connectSlotsByName(self)
-
- def retranslateUi(self):
- self.export.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Export profile"))
- self.setWindowTitle(QtWidgets.QApplication.translate("ProfileSettingsForm", "Profile settings"))
- self.label.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Name:"))
- self.label_2.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Status:"))
- self.label_3.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "TOX ID:"))
- self.copyId.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Copy TOX ID"))
- self.new_avatar.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "New avatar"))
- self.delete_avatar.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Reset avatar"))
- self.new_nospam.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "New NoSpam"))
- self.profilepass.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Profile password"))
- self.password.setPlaceholderText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Password (at least 8 symbols)"))
- self.confirm_password.setPlaceholderText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Confirm password"))
- self.set_password.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Set password"))
- self.not_match.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Passwords do not match"))
- self.leave_blank.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Leaving blank will reset current password"))
- self.warning.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "There is no way to recover lost passwords"))
- self.status.addItem(QtWidgets.QApplication.translate("ProfileSettingsForm", "Online"))
- self.status.addItem(QtWidgets.QApplication.translate("ProfileSettingsForm", "Away"))
- self.status.addItem(QtWidgets.QApplication.translate("ProfileSettingsForm", "Busy"))
- self.copy_pk.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Copy public key"))
- if self.auto:
- self.default.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Mark as not default profile"))
- else:
- self.default.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Mark as default profile"))
-
- def auto_profile(self):
- if self.auto:
- Settings.reset_auto_profile()
- else:
- Settings.set_auto_profile(ProfileHelper.get_path(), Settings.get_instance().name)
- self.auto = not self.auto
- if self.auto:
- self.default.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Mark as not default profile"))
- else:
- self.default.setText(
- QtWidgets.QApplication.translate("ProfileSettingsForm", "Mark as default profile"))
-
- def new_password(self):
- if self.password.text() == self.confirm_password.text():
- if not len(self.password.text()) or len(self.password.text()) >= 8:
- e = toxes.ToxES.get_instance()
- e.set_password(self.password.text())
- self.close()
- else:
- self.not_match.setText(
- QtWidgets.QApplication.translate("ProfileSettingsForm", "Password must be at least 8 symbols"))
- self.not_match.setVisible(True)
- else:
- self.not_match.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Passwords do not match"))
- self.not_match.setVisible(True)
-
- def copy(self):
- clipboard = QtWidgets.QApplication.clipboard()
- profile = Profile.get_instance()
- clipboard.setText(profile.tox_id)
- pixmap = QtGui.QPixmap(curr_directory() + '/images/accept.png')
- icon = QtGui.QIcon(pixmap)
- self.copyId.setIcon(icon)
- self.copyId.setIconSize(QtCore.QSize(10, 10))
-
- def copy_public_key(self):
- clipboard = QtWidgets.QApplication.clipboard()
- profile = Profile.get_instance()
- clipboard.setText(profile.tox_id[:64])
- pixmap = QtGui.QPixmap(curr_directory() + '/images/accept.png')
- icon = QtGui.QIcon(pixmap)
- self.copy_pk.setIcon(icon)
- self.copy_pk.setIconSize(QtCore.QSize(10, 10))
-
- def new_no_spam(self):
- self.tox_id.setText(Profile.get_instance().new_nospam())
-
- def reset_avatar(self):
- Profile.get_instance().reset_avatar()
-
- def set_avatar(self):
- choose = QtWidgets.QApplication.translate("ProfileSettingsForm", "Choose avatar")
- name = QtWidgets.QFileDialog.getOpenFileName(self, choose, None, 'Images (*.png)',
- options=QtWidgets.QFileDialog.DontUseNativeDialog)
- if name[0]:
- bitmap = QtGui.QPixmap(name[0])
- bitmap.scaled(QtCore.QSize(128, 128), aspectRatioMode=QtCore.Qt.KeepAspectRatio,
- transformMode=QtCore.Qt.SmoothTransformation)
-
- byte_array = QtCore.QByteArray()
- buffer = QtCore.QBuffer(byte_array)
- buffer.open(QtCore.QIODevice.WriteOnly)
- bitmap.save(buffer, 'PNG')
- Profile.get_instance().set_avatar(bytes(byte_array.data()))
-
- def export_profile(self):
- directory = QtWidgets.QFileDialog.getExistingDirectory(self, '', curr_directory(),
- QtWidgets.QFileDialog.DontUseNativeDialog) + '/'
- if directory != '/':
- reply = QtWidgets.QMessageBox.question(None,
- QtWidgets.QApplication.translate("ProfileSettingsForm",
- 'Use new path'),
- QtWidgets.QApplication.translate("ProfileSettingsForm",
- 'Do you want to move your profile to this location?'),
- QtWidgets.QMessageBox.Yes,
- QtWidgets.QMessageBox.No)
- settings = Settings.get_instance()
- settings.export(directory)
- profile = Profile.get_instance()
- profile.export_db(directory)
- ProfileHelper.get_instance().export_profile(directory, reply == QtWidgets.QMessageBox.Yes)
-
- def closeEvent(self, event):
- profile = Profile.get_instance()
- profile.set_name(self.nick.text())
- profile.set_status_message(self.status_message.text().encode('utf-8'))
- profile.set_status(self.status.currentIndex())
-
-
-class NetworkSettings(CenteredWidget):
- """Network settings form: UDP, Ipv6 and proxy"""
- def __init__(self, reset):
- super(NetworkSettings, self).__init__()
- self.reset = reset
- self.initUI()
- self.center()
-
- def initUI(self):
- self.setObjectName("NetworkSettings")
- self.resize(300, 400)
- self.setMinimumSize(QtCore.QSize(300, 400))
- self.setMaximumSize(QtCore.QSize(300, 400))
- self.setBaseSize(QtCore.QSize(300, 400))
- self.ipv = QtWidgets.QCheckBox(self)
- self.ipv.setGeometry(QtCore.QRect(20, 10, 97, 22))
- self.ipv.setObjectName("ipv")
- self.udp = QtWidgets.QCheckBox(self)
- self.udp.setGeometry(QtCore.QRect(150, 10, 97, 22))
- self.udp.setObjectName("udp")
- self.proxy = QtWidgets.QCheckBox(self)
- self.proxy.setGeometry(QtCore.QRect(20, 40, 97, 22))
- self.http = QtWidgets.QCheckBox(self)
- self.http.setGeometry(QtCore.QRect(20, 70, 97, 22))
- self.proxy.setObjectName("proxy")
- self.proxyip = LineEdit(self)
- self.proxyip.setGeometry(QtCore.QRect(40, 130, 231, 27))
- self.proxyip.setObjectName("proxyip")
- self.proxyport = LineEdit(self)
- self.proxyport.setGeometry(QtCore.QRect(40, 190, 231, 27))
- self.proxyport.setObjectName("proxyport")
- self.label = QtWidgets.QLabel(self)
- self.label.setGeometry(QtCore.QRect(40, 100, 66, 17))
- self.label_2 = QtWidgets.QLabel(self)
- self.label_2.setGeometry(QtCore.QRect(40, 165, 66, 17))
- self.reconnect = QtWidgets.QPushButton(self)
- self.reconnect.setGeometry(QtCore.QRect(40, 230, 231, 30))
- self.reconnect.clicked.connect(self.restart_core)
- settings = Settings.get_instance()
- self.ipv.setChecked(settings['ipv6_enabled'])
- self.udp.setChecked(settings['udp_enabled'])
- self.proxy.setChecked(settings['proxy_type'])
- self.proxyip.setText(settings['proxy_host'])
- self.proxyport.setText(str(settings['proxy_port']))
- self.http.setChecked(settings['proxy_type'] == 1)
- self.warning = QtWidgets.QLabel(self)
- self.warning.setGeometry(QtCore.QRect(5, 270, 290, 60))
- self.warning.setStyleSheet('QLabel { color: #BC1C1C; }')
- self.nodes = QtWidgets.QCheckBox(self)
- self.nodes.setGeometry(QtCore.QRect(20, 350, 270, 22))
- self.nodes.setChecked(settings['download_nodes_list'])
- self.retranslateUi()
- self.proxy.stateChanged.connect(lambda x: self.activate())
- self.activate()
- QtCore.QMetaObject.connectSlotsByName(self)
-
- def retranslateUi(self):
- self.setWindowTitle(QtWidgets.QApplication.translate("NetworkSettings", "Network settings"))
- self.ipv.setText(QtWidgets.QApplication.translate("Form", "IPv6"))
- self.udp.setText(QtWidgets.QApplication.translate("Form", "UDP"))
- self.proxy.setText(QtWidgets.QApplication.translate("Form", "Proxy"))
- self.label.setText(QtWidgets.QApplication.translate("Form", "IP:"))
- self.label_2.setText(QtWidgets.QApplication.translate("Form", "Port:"))
- self.reconnect.setText(QtWidgets.QApplication.translate("NetworkSettings", "Restart TOX core"))
- self.http.setText(QtWidgets.QApplication.translate("Form", "HTTP"))
- self.nodes.setText(QtWidgets.QApplication.translate("Form", "Download nodes list from tox.chat"))
- self.warning.setText(QtWidgets.QApplication.translate("Form", "WARNING:\nusing proxy with enabled UDP\ncan produce IP leak"))
-
- def activate(self):
- bl = self.proxy.isChecked()
- self.proxyip.setEnabled(bl)
- self.http.setEnabled(bl)
- self.proxyport.setEnabled(bl)
-
- def restart_core(self):
- try:
- settings = Settings.get_instance()
- settings['ipv6_enabled'] = self.ipv.isChecked()
- settings['udp_enabled'] = self.udp.isChecked()
- settings['proxy_type'] = 2 - int(self.http.isChecked()) if self.proxy.isChecked() else 0
- settings['proxy_host'] = str(self.proxyip.text())
- settings['proxy_port'] = int(self.proxyport.text())
- settings['download_nodes_list'] = self.nodes.isChecked()
- settings.save()
- # recreate tox instance
- Profile.get_instance().reset(self.reset)
- self.close()
- except Exception as ex:
- log('Exception in restart: ' + str(ex))
-
-
-class PrivacySettings(CenteredWidget):
- """Privacy settings form: history, typing notifications"""
-
- def __init__(self):
- super(PrivacySettings, self).__init__()
- self.initUI()
- self.center()
-
- def initUI(self):
- self.setObjectName("privacySettings")
- self.resize(370, 600)
- self.setMinimumSize(QtCore.QSize(370, 600))
- self.setMaximumSize(QtCore.QSize(370, 600))
- self.saveHistory = QtWidgets.QCheckBox(self)
- self.saveHistory.setGeometry(QtCore.QRect(10, 20, 350, 22))
- self.saveUnsentOnly = QtWidgets.QCheckBox(self)
- self.saveUnsentOnly.setGeometry(QtCore.QRect(10, 60, 350, 22))
-
- self.fileautoaccept = QtWidgets.QCheckBox(self)
- self.fileautoaccept.setGeometry(QtCore.QRect(10, 100, 350, 22))
-
- self.typingNotifications = QtWidgets.QCheckBox(self)
- self.typingNotifications.setGeometry(QtCore.QRect(10, 140, 350, 30))
- self.inlines = QtWidgets.QCheckBox(self)
- self.inlines.setGeometry(QtCore.QRect(10, 180, 350, 30))
- self.auto_path = QtWidgets.QLabel(self)
- self.auto_path.setGeometry(QtCore.QRect(10, 230, 350, 30))
- self.path = QtWidgets.QPlainTextEdit(self)
- self.path.setGeometry(QtCore.QRect(10, 265, 350, 45))
- self.change_path = QtWidgets.QPushButton(self)
- self.change_path.setGeometry(QtCore.QRect(10, 320, 350, 30))
- settings = Settings.get_instance()
- self.typingNotifications.setChecked(settings['typing_notifications'])
- self.fileautoaccept.setChecked(settings['allow_auto_accept'])
- self.saveHistory.setChecked(settings['save_history'])
- self.inlines.setChecked(settings['allow_inline'])
- self.saveUnsentOnly.setChecked(settings['save_unsent_only'])
- self.saveUnsentOnly.setEnabled(settings['save_history'])
- self.saveHistory.stateChanged.connect(self.update)
- self.path.setPlainText(settings['auto_accept_path'] or curr_directory())
- self.change_path.clicked.connect(self.new_path)
- self.block_user_label = QtWidgets.QLabel(self)
- self.block_user_label.setGeometry(QtCore.QRect(10, 360, 350, 30))
- self.block_id = QtWidgets.QPlainTextEdit(self)
- self.block_id.setGeometry(QtCore.QRect(10, 390, 350, 30))
- self.block = QtWidgets.QPushButton(self)
- self.block.setGeometry(QtCore.QRect(10, 430, 350, 30))
- self.block.clicked.connect(lambda: Profile.get_instance().block_user(self.block_id.toPlainText()) or self.close())
- self.blocked_users_label = QtWidgets.QLabel(self)
- self.blocked_users_label.setGeometry(QtCore.QRect(10, 470, 350, 30))
- self.comboBox = QtWidgets.QComboBox(self)
- self.comboBox.setGeometry(QtCore.QRect(10, 500, 350, 30))
- self.comboBox.addItems(settings['blocked'])
- self.unblock = QtWidgets.QPushButton(self)
- self.unblock.setGeometry(QtCore.QRect(10, 540, 350, 30))
- self.unblock.clicked.connect(lambda: self.unblock_user())
- self.retranslateUi()
- QtCore.QMetaObject.connectSlotsByName(self)
-
- def retranslateUi(self):
- self.setWindowTitle(QtWidgets.QApplication.translate("privacySettings", "Privacy settings"))
- self.saveHistory.setText(QtWidgets.QApplication.translate("privacySettings", "Save chat history"))
- self.fileautoaccept.setText(QtWidgets.QApplication.translate("privacySettings", "Allow file auto accept"))
- self.typingNotifications.setText(QtWidgets.QApplication.translate("privacySettings", "Send typing notifications"))
- self.auto_path.setText(QtWidgets.QApplication.translate("privacySettings", "Auto accept default path:"))
- self.change_path.setText(QtWidgets.QApplication.translate("privacySettings", "Change"))
- self.inlines.setText(QtWidgets.QApplication.translate("privacySettings", "Allow inlines"))
- self.block_user_label.setText(QtWidgets.QApplication.translate("privacySettings", "Block by public key:"))
- self.blocked_users_label.setText(QtWidgets.QApplication.translate("privacySettings", "Blocked users:"))
- self.unblock.setText(QtWidgets.QApplication.translate("privacySettings", "Unblock"))
- self.block.setText(QtWidgets.QApplication.translate("privacySettings", "Block user"))
- self.saveUnsentOnly.setText(QtWidgets.QApplication.translate("privacySettings", "Save unsent messages only"))
-
- def update(self, new_state):
- self.saveUnsentOnly.setEnabled(new_state)
- if not new_state:
- self.saveUnsentOnly.setChecked(False)
-
- def unblock_user(self):
- if not self.comboBox.count():
- return
- title = QtWidgets.QApplication.translate("privacySettings", "Add to friend list")
- info = QtWidgets.QApplication.translate("privacySettings", "Do you want to add this user to friend list?")
- reply = QtWidgets.QMessageBox.question(None, title, info, QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)
- Profile.get_instance().unblock_user(self.comboBox.currentText(), reply == QtWidgets.QMessageBox.Yes)
- self.close()
-
- def closeEvent(self, event):
- settings = Settings.get_instance()
- settings['typing_notifications'] = self.typingNotifications.isChecked()
- settings['allow_auto_accept'] = self.fileautoaccept.isChecked()
-
- if settings['save_history'] and not self.saveHistory.isChecked(): # clear history
- reply = QtWidgets.QMessageBox.question(None,
- QtWidgets.QApplication.translate("privacySettings",
- 'Chat history'),
- QtWidgets.QApplication.translate("privacySettings",
- 'History will be cleaned! Continue?'),
- QtWidgets.QMessageBox.Yes,
- QtWidgets.QMessageBox.No)
- if reply == QtWidgets.QMessageBox.Yes:
- Profile.get_instance().clear_history()
- settings['save_history'] = self.saveHistory.isChecked()
- else:
- settings['save_history'] = self.saveHistory.isChecked()
- if self.saveUnsentOnly.isChecked() and not settings['save_unsent_only']:
- reply = QtWidgets.QMessageBox.question(None,
- QtWidgets.QApplication.translate("privacySettings",
- 'Chat history'),
- QtWidgets.QApplication.translate("privacySettings",
- 'History will be cleaned! Continue?'),
- QtWidgets.QMessageBox.Yes,
- QtWidgets.QMessageBox.No)
- if reply == QtWidgets.QMessageBox.Yes:
- Profile.get_instance().clear_history(None, True)
- settings['save_unsent_only'] = self.saveUnsentOnly.isChecked()
- else:
- settings['save_unsent_only'] = self.saveUnsentOnly.isChecked()
- settings['auto_accept_path'] = self.path.toPlainText()
- settings['allow_inline'] = self.inlines.isChecked()
- settings.save()
-
- def new_path(self):
- directory = QtWidgets.QFileDialog.getExistingDirectory(options=QtWidgets.QFileDialog.DontUseNativeDialog) + '/'
- if directory != '/':
- self.path.setPlainText(directory)
-
-
-class NotificationsSettings(CenteredWidget):
- """Notifications settings form"""
-
- def __init__(self):
- super(NotificationsSettings, self).__init__()
- self.initUI()
- self.center()
-
- def initUI(self):
- self.setObjectName("notificationsForm")
- 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, 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'])
- font.setPointSize(12)
- 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)
-
- 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"))
-
- def closeEvent(self, *args, **kwargs):
- 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()
-
-
-class InterfaceSettings(CenteredWidget):
- """Interface settings form"""
- def __init__(self):
- super(InterfaceSettings, self).__init__()
- self.initUI()
- self.center()
-
- def initUI(self):
- self.setObjectName("interfaceForm")
- self.setMinimumSize(QtCore.QSize(400, 650))
- self.setMaximumSize(QtCore.QSize(400, 650))
- self.label = QtWidgets.QLabel(self)
- self.label.setGeometry(QtCore.QRect(30, 10, 370, 20))
- settings = Settings.get_instance()
- font = QtGui.QFont()
- font.setPointSize(14)
- font.setBold(True)
- font.setFamily(settings['font'])
- self.label.setFont(font)
- self.themeSelect = QtWidgets.QComboBox(self)
- self.themeSelect.setGeometry(QtCore.QRect(30, 40, 120, 30))
- self.themeSelect.addItems(list(settings.built_in_themes().keys()))
- theme = settings['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)
- self.lang_choose = QtWidgets.QComboBox(self)
- self.lang_choose.setGeometry(QtCore.QRect(30, 110, 120, 30))
- supported = sorted(Settings.supported_languages().keys(), reverse=True)
- for key in supported:
- self.lang_choose.insertItem(0, key)
- if settings['language'] == key:
- self.lang_choose.setCurrentIndex(0)
- self.lang = QtWidgets.QLabel(self)
- self.lang.setGeometry(QtCore.QRect(30, 80, 370, 20))
- self.lang.setFont(font)
- self.mirror_mode = QtWidgets.QCheckBox(self)
- self.mirror_mode.setGeometry(QtCore.QRect(30, 160, 370, 20))
- self.mirror_mode.setChecked(settings['mirror_mode'])
- self.smileys = QtWidgets.QCheckBox(self)
- self.smileys.setGeometry(QtCore.QRect(30, 190, 120, 20))
- self.smileys.setChecked(settings['smileys'])
- self.smiley_pack_label = QtWidgets.QLabel(self)
- self.smiley_pack_label.setGeometry(QtCore.QRect(30, 230, 370, 20))
- self.smiley_pack_label.setFont(font)
- self.smiley_pack = QtWidgets.QComboBox(self)
- self.smiley_pack.setGeometry(QtCore.QRect(30, 260, 160, 30))
- sm = smileys.SmileyLoader.get_instance()
- self.smiley_pack.addItems(sm.get_packs_list())
- try:
- ind = sm.get_packs_list().index(settings['smiley_pack'])
- except:
- ind = sm.get_packs_list().index('default')
- self.smiley_pack.setCurrentIndex(ind)
- self.messages_font_size_label = QtWidgets.QLabel(self)
- self.messages_font_size_label.setGeometry(QtCore.QRect(30, 300, 370, 20))
- self.messages_font_size_label.setFont(font)
- self.messages_font_size = QtWidgets.QComboBox(self)
- self.messages_font_size.setGeometry(QtCore.QRect(30, 330, 160, 30))
- self.messages_font_size.addItems([str(x) for x in range(10, 25)])
- self.messages_font_size.setCurrentIndex(settings['message_font_size'] - 10)
-
- self.unread = QtWidgets.QPushButton(self)
- self.unread.setGeometry(QtCore.QRect(30, 470, 340, 30))
- self.unread.clicked.connect(self.select_color)
-
- self.compact_mode = QtWidgets.QCheckBox(self)
- self.compact_mode.setGeometry(QtCore.QRect(30, 380, 370, 20))
- self.compact_mode.setChecked(settings['compact_mode'])
-
- self.close_to_tray = QtWidgets.QCheckBox(self)
- self.close_to_tray.setGeometry(QtCore.QRect(30, 410, 370, 20))
- self.close_to_tray.setChecked(settings['close_to_tray'])
-
- self.show_avatars = QtWidgets.QCheckBox(self)
- self.show_avatars.setGeometry(QtCore.QRect(30, 440, 370, 20))
- self.show_avatars.setChecked(settings['show_avatars'])
-
- self.choose_font = QtWidgets.QPushButton(self)
- self.choose_font.setGeometry(QtCore.QRect(30, 510, 340, 30))
- self.choose_font.clicked.connect(self.new_font)
-
- self.import_smileys = QtWidgets.QPushButton(self)
- self.import_smileys.setGeometry(QtCore.QRect(30, 550, 340, 30))
- self.import_smileys.clicked.connect(self.import_sm)
-
- self.import_stickers = QtWidgets.QPushButton(self)
- self.import_stickers.setGeometry(QtCore.QRect(30, 590, 340, 30))
- self.import_stickers.clicked.connect(self.import_st)
-
- self.retranslateUi()
- QtCore.QMetaObject.connectSlotsByName(self)
-
- def retranslateUi(self):
- self.show_avatars.setText(QtWidgets.QApplication.translate("interfaceForm", "Show avatars in chat"))
- self.setWindowTitle(QtWidgets.QApplication.translate("interfaceForm", "Interface settings"))
- self.label.setText(QtWidgets.QApplication.translate("interfaceForm", "Theme:"))
- self.lang.setText(QtWidgets.QApplication.translate("interfaceForm", "Language:"))
- self.smileys.setText(QtWidgets.QApplication.translate("interfaceForm", "Smileys"))
- self.smiley_pack_label.setText(QtWidgets.QApplication.translate("interfaceForm", "Smiley pack:"))
- self.mirror_mode.setText(QtWidgets.QApplication.translate("interfaceForm", "Mirror mode"))
- self.messages_font_size_label.setText(QtWidgets.QApplication.translate("interfaceForm", "Messages font size:"))
- self.unread.setText(QtWidgets.QApplication.translate("interfaceForm", "Select unread messages notification color"))
- self.compact_mode.setText(QtWidgets.QApplication.translate("interfaceForm", "Compact contact list"))
- self.import_smileys.setText(QtWidgets.QApplication.translate("interfaceForm", "Import smiley pack"))
- self.import_stickers.setText(QtWidgets.QApplication.translate("interfaceForm", "Import sticker pack"))
- self.close_to_tray.setText(QtWidgets.QApplication.translate("interfaceForm", "Close to tray"))
- self.choose_font.setText(QtWidgets.QApplication.translate("interfaceForm", "Select font"))
-
- def import_st(self):
- directory = QtWidgets.QFileDialog.getExistingDirectory(self,
- QtWidgets.QApplication.translate("MainWindow",
- 'Choose folder with sticker pack'),
- curr_directory(),
- QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog)
-
- if directory:
- src = directory + '/'
- dest = curr_directory() + '/stickers/' + os.path.basename(directory) + '/'
- copy(src, dest)
-
- def import_sm(self):
- directory = QtWidgets.QFileDialog.getExistingDirectory(self,
- QtWidgets.QApplication.translate("MainWindow",
- 'Choose folder with smiley pack'),
- curr_directory(),
- QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog)
-
- if directory:
- src = directory + '/'
- dest = curr_directory() + '/smileys/' + os.path.basename(directory) + '/'
- copy(src, dest)
-
- def new_font(self):
- settings = Settings.get_instance()
- font, ok = QtWidgets.QFontDialog.getFont(QtGui.QFont(settings['font'], 10), self)
- if ok:
- settings['font'] = font.family()
- settings.save()
- msgBox = QtWidgets.QMessageBox()
- text = QtWidgets.QApplication.translate("interfaceForm", 'Restart app to apply settings')
- msgBox.setWindowTitle(QtWidgets.QApplication.translate("interfaceForm", 'Restart required'))
- msgBox.setText(text)
- msgBox.exec_()
-
- def select_color(self):
- settings = Settings.get_instance()
- col = QtWidgets.QColorDialog.getColor(QtGui.QColor(settings['unread_color']))
-
- if col.isValid():
- name = col.name()
- settings['unread_color'] = name
- settings.save()
-
- 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():
- settings['mirror_mode'] = self.mirror_mode.isChecked()
- restart = True
- if settings['compact_mode'] != self.compact_mode.isChecked():
- settings['compact_mode'] = self.compact_mode.isChecked()
- restart = True
- if settings['show_avatars'] != self.show_avatars.isChecked():
- settings['show_avatars'] = self.show_avatars.isChecked()
- restart = True
- settings['smiley_pack'] = self.smiley_pack.currentText()
- settings['close_to_tray'] = self.close_to_tray.isChecked()
- smileys.SmileyLoader.get_instance().load_pack()
- language = self.lang_choose.currentText()
- if settings['language'] != language:
- settings['language'] = language
- text = self.lang_choose.currentText()
- path = Settings.supported_languages()[text]
- app = QtWidgets.QApplication.instance()
- app.removeTranslator(app.translator)
- app.translator.load(curr_directory() + '/translations/' + path)
- app.installTranslator(app.translator)
- settings['message_font_size'] = self.messages_font_size.currentIndex() + 10
- Profile.get_instance().update()
- settings.save()
- if restart:
- msgBox = QtWidgets.QMessageBox()
- text = QtWidgets.QApplication.translate("interfaceForm", 'Restart app to apply settings')
- msgBox.setWindowTitle(QtWidgets.QApplication.translate("interfaceForm", 'Restart required'))
- msgBox.setText(text)
- msgBox.exec_()
-
-
-class AudioSettings(CenteredWidget):
- """
- Audio calls settings form
- """
-
- def __init__(self):
- super(AudioSettings, self).__init__()
- self.initUI()
- self.retranslateUi()
- self.center()
-
- def initUI(self):
- self.setObjectName("audioSettingsForm")
- self.resize(400, 150)
- self.setMinimumSize(QtCore.QSize(400, 150))
- self.setMaximumSize(QtCore.QSize(400, 150))
- self.in_label = QtWidgets.QLabel(self)
- self.in_label.setGeometry(QtCore.QRect(25, 5, 350, 20))
- self.out_label = QtWidgets.QLabel(self)
- self.out_label.setGeometry(QtCore.QRect(25, 65, 350, 20))
- settings = Settings.get_instance()
- font = QtGui.QFont()
- font.setPointSize(16)
- font.setBold(True)
- font.setFamily(settings['font'])
- self.in_label.setFont(font)
- self.out_label.setFont(font)
- self.input = QtWidgets.QComboBox(self)
- self.input.setGeometry(QtCore.QRect(25, 30, 350, 30))
- self.output = QtWidgets.QComboBox(self)
- self.output.setGeometry(QtCore.QRect(25, 90, 350, 30))
- p = pyaudio.PyAudio()
- self.in_indexes, self.out_indexes = [], []
- for i in range(p.get_device_count()):
- device = p.get_device_info_by_index(i)
- if device["maxInputChannels"]:
- self.input.addItem(str(device["name"]))
- self.in_indexes.append(i)
- if device["maxOutputChannels"]:
- self.output.addItem(str(device["name"]))
- self.out_indexes.append(i)
- self.input.setCurrentIndex(self.in_indexes.index(settings.audio['input']))
- self.output.setCurrentIndex(self.out_indexes.index(settings.audio['output']))
- QtCore.QMetaObject.connectSlotsByName(self)
-
- def retranslateUi(self):
- self.setWindowTitle(QtWidgets.QApplication.translate("audioSettingsForm", "Audio settings"))
- self.in_label.setText(QtWidgets.QApplication.translate("audioSettingsForm", "Input device:"))
- self.out_label.setText(QtWidgets.QApplication.translate("audioSettingsForm", "Output device:"))
-
- def closeEvent(self, event):
- settings = Settings.get_instance()
- settings.audio['input'] = self.in_indexes[self.input.currentIndex()]
- settings.audio['output'] = self.out_indexes[self.output.currentIndex()]
- settings.save()
-
-
-class DesktopAreaSelectionWindow(RubberBandWindow):
-
- def mouseReleaseEvent(self, event):
- if self.rubberband.isVisible():
- self.rubberband.hide()
- rect = self.rubberband.geometry()
- width, height = rect.width(), rect.height()
- if width >= 8 and height >= 8:
- self.parent.save(rect.x(), rect.y(), width - (width % 4), height - (height % 4))
- self.close()
-
-
-class VideoSettings(CenteredWidget):
- """
- Audio calls settings form
- """
-
- def __init__(self):
- super().__init__()
- self.initUI()
- self.retranslateUi()
- self.center()
- self.desktopAreaSelection = None
-
- def initUI(self):
- self.setObjectName("videoSettingsForm")
- self.resize(400, 120)
- self.setMinimumSize(QtCore.QSize(400, 120))
- self.setMaximumSize(QtCore.QSize(400, 120))
- self.in_label = QtWidgets.QLabel(self)
- self.in_label.setGeometry(QtCore.QRect(25, 5, 350, 20))
- settings = Settings.get_instance()
- font = QtGui.QFont()
- font.setPointSize(16)
- font.setBold(True)
- font.setFamily(settings['font'])
- self.in_label.setFont(font)
- self.video_size = QtWidgets.QComboBox(self)
- self.video_size.setGeometry(QtCore.QRect(25, 70, 350, 30))
- self.input = QtWidgets.QComboBox(self)
- self.input.setGeometry(QtCore.QRect(25, 30, 350, 30))
- self.input.currentIndexChanged.connect(self.selectionChanged)
- self.button = QtWidgets.QPushButton(self)
- self.button.clicked.connect(self.button_clicked)
- self.button.setGeometry(QtCore.QRect(25, 70, 350, 30))
- import cv2
- self.devices = [-1]
- screen = QtWidgets.QApplication.primaryScreen()
- size = screen.size()
- self.frame_max_sizes = [(size.width(), size.height())]
- desktop = QtWidgets.QApplication.translate("videoSettingsForm", "Desktop")
- self.input.addItem(desktop)
- for i in range(10):
- v = cv2.VideoCapture(i)
- if v.isOpened():
- v.set(cv2.CAP_PROP_FRAME_WIDTH, 10000)
- v.set(cv2.CAP_PROP_FRAME_HEIGHT, 10000)
-
- width = int(v.get(cv2.CAP_PROP_FRAME_WIDTH))
- height = int(v.get(cv2.CAP_PROP_FRAME_HEIGHT))
- del v
- self.devices.append(i)
- self.frame_max_sizes.append((width, height))
- self.input.addItem('Device #' + str(i))
- try:
- index = self.devices.index(settings.video['device'])
- self.input.setCurrentIndex(index)
- except:
- print('Video devices error!')
-
- def retranslateUi(self):
- self.setWindowTitle(QtWidgets.QApplication.translate("videoSettingsForm", "Video settings"))
- self.in_label.setText(QtWidgets.QApplication.translate("videoSettingsForm", "Device:"))
- self.button.setText(QtWidgets.QApplication.translate("videoSettingsForm", "Select region"))
-
- def button_clicked(self):
- self.desktopAreaSelection = DesktopAreaSelectionWindow(self)
-
- def closeEvent(self, event):
- if self.input.currentIndex() == 0:
- return
- try:
- settings = Settings.get_instance()
- settings.video['device'] = self.devices[self.input.currentIndex()]
- text = self.video_size.currentText()
- settings.video['width'] = int(text.split(' ')[0])
- settings.video['height'] = int(text.split(' ')[-1])
- settings.save()
- except Exception as ex:
- print('Saving video settings error: ' + str(ex))
-
- def save(self, x, y, width, height):
- self.desktopAreaSelection = None
- settings = Settings.get_instance()
- settings.video['device'] = -1
- settings.video['width'] = width
- settings.video['height'] = height
- settings.video['x'] = x
- settings.video['y'] = y
- settings.save()
-
- def selectionChanged(self):
- if self.input.currentIndex() == 0:
- self.button.setVisible(True)
- self.video_size.setVisible(False)
- else:
- self.button.setVisible(False)
- self.video_size.setVisible(True)
- width, height = self.frame_max_sizes[self.input.currentIndex()]
- self.video_size.clear()
- dims = [
- (320, 240),
- (640, 360),
- (640, 480),
- (720, 480),
- (1280, 720),
- (1920, 1080),
- (2560, 1440)
- ]
- for w, h in dims:
- if w <= width and h <= height:
- self.video_size.addItem(str(w) + ' * ' + str(h))
-
-
-class PluginsSettings(CenteredWidget):
- """
- Plugins settings form
- """
-
- def __init__(self):
- super(PluginsSettings, self).__init__()
- self.initUI()
- self.center()
- self.retranslateUi()
-
- def initUI(self):
- self.resize(400, 210)
- self.setMinimumSize(QtCore.QSize(400, 210))
- self.setMaximumSize(QtCore.QSize(400, 210))
- self.comboBox = QtWidgets.QComboBox(self)
- self.comboBox.setGeometry(QtCore.QRect(30, 10, 340, 30))
- self.label = QtWidgets.QLabel(self)
- self.label.setGeometry(QtCore.QRect(30, 40, 340, 90))
- self.label.setWordWrap(True)
- self.button = QtWidgets.QPushButton(self)
- self.button.setGeometry(QtCore.QRect(30, 130, 340, 30))
- self.button.clicked.connect(self.button_click)
- self.open = QtWidgets.QPushButton(self)
- self.open.setGeometry(QtCore.QRect(30, 170, 340, 30))
- self.open.clicked.connect(self.open_plugin)
- self.pl_loader = plugin_support.PluginLoader.get_instance()
- self.update_list()
- self.comboBox.currentIndexChanged.connect(self.show_data)
- self.show_data()
-
- def retranslateUi(self):
- self.setWindowTitle(QtWidgets.QApplication.translate('PluginsForm', "Plugins"))
- self.open.setText(QtWidgets.QApplication.translate('PluginsForm', "Open selected plugin"))
-
- def open_plugin(self):
- ind = self.comboBox.currentIndex()
- plugin = self.data[ind]
- window = self.pl_loader.plugin_window(plugin[-1])
- if window is not None:
- self.window = window
- self.window.show()
- else:
- msgBox = QtWidgets.QMessageBox()
- text = QtWidgets.QApplication.translate("PluginsForm", 'No GUI found for this plugin')
- msgBox.setWindowTitle(QtWidgets.QApplication.translate("PluginsForm", 'Error'))
- msgBox.setText(text)
- msgBox.exec_()
-
- def update_list(self):
- self.comboBox.clear()
- data = self.pl_loader.get_plugins_list()
- self.comboBox.addItems(list(map(lambda x: x[0], data)))
- self.data = data
-
- def show_data(self):
- ind = self.comboBox.currentIndex()
- if len(self.data):
- plugin = self.data[ind]
- descr = plugin[2] or QtWidgets.QApplication.translate("PluginsForm", "No description available")
- self.label.setText(descr)
- if plugin[1]:
- self.button.setText(QtWidgets.QApplication.translate("PluginsForm", "Disable plugin"))
- else:
- self.button.setText(QtWidgets.QApplication.translate("PluginsForm", "Enable plugin"))
- else:
- self.open.setVisible(False)
- self.button.setVisible(False)
- self.label.setText(QtWidgets.QApplication.translate("PluginsForm", "No plugins found"))
-
- def button_click(self):
- ind = self.comboBox.currentIndex()
- plugin = self.data[ind]
- self.pl_loader.toggle_plugin(plugin[-1])
- plugin[1] = not plugin[1]
- if plugin[1]:
- self.button.setText(QtWidgets.QApplication.translate("PluginsForm", "Disable plugin"))
- else:
- self.button.setText(QtWidgets.QApplication.translate("PluginsForm", "Enable plugin"))
-
-
-class UpdateSettings(CenteredWidget):
- """
- Updates settings form
- """
-
- def __init__(self):
- super(UpdateSettings, self).__init__()
- self.initUI()
- self.center()
-
- def initUI(self):
- self.setObjectName("updateSettingsForm")
- self.resize(400, 150)
- self.setMinimumSize(QtCore.QSize(400, 120))
- self.setMaximumSize(QtCore.QSize(400, 120))
- self.in_label = QtWidgets.QLabel(self)
- self.in_label.setGeometry(QtCore.QRect(25, 5, 350, 20))
- settings = Settings.get_instance()
- font = QtGui.QFont()
- font.setPointSize(16)
- font.setBold(True)
- font.setFamily(settings['font'])
- self.in_label.setFont(font)
- self.autoupdate = QtWidgets.QComboBox(self)
- self.autoupdate.setGeometry(QtCore.QRect(25, 30, 350, 30))
- self.button = QtWidgets.QPushButton(self)
- self.button.setGeometry(QtCore.QRect(25, 70, 350, 30))
- self.button.setEnabled(settings['update'])
- self.button.clicked.connect(self.update_client)
-
- self.retranslateUi()
- self.autoupdate.setCurrentIndex(settings['update'])
- QtCore.QMetaObject.connectSlotsByName(self)
-
- def retranslateUi(self):
- self.setWindowTitle(QtWidgets.QApplication.translate("updateSettingsForm", "Update settings"))
- self.in_label.setText(QtWidgets.QApplication.translate("updateSettingsForm", "Select update mode:"))
- self.button.setText(QtWidgets.QApplication.translate("updateSettingsForm", "Update Toxygen"))
- self.autoupdate.addItem(QtWidgets.QApplication.translate("updateSettingsForm", "Disabled"))
- self.autoupdate.addItem(QtWidgets.QApplication.translate("updateSettingsForm", "Manual"))
- self.autoupdate.addItem(QtWidgets.QApplication.translate("updateSettingsForm", "Auto"))
-
- def closeEvent(self, event):
- settings = Settings.get_instance()
- settings['update'] = self.autoupdate.currentIndex()
- settings.save()
-
- def update_client(self):
- if not updater.connection_available():
- msgBox = QtWidgets.QMessageBox()
- msgBox.setWindowTitle(
- QtWidgets.QApplication.translate("updateSettingsForm", "Error"))
- text = (QtWidgets.QApplication.translate("updateSettingsForm", 'Problems with internet connection'))
- msgBox.setText(text)
- msgBox.exec_()
- return
- if not updater.updater_available():
- msgBox = QtWidgets.QMessageBox()
- msgBox.setWindowTitle(
- QtWidgets.QApplication.translate("updateSettingsForm", "Error"))
- text = (QtWidgets.QApplication.translate("updateSettingsForm", 'Updater not found'))
- msgBox.setText(text)
- msgBox.exec_()
- return
- version = updater.check_for_updates()
- if version is not None:
- updater.download(version)
- QtWidgets.QApplication.closeAllWindows()
- else:
- msgBox = QtWidgets.QMessageBox()
- msgBox.setWindowTitle(
- QtWidgets.QApplication.translate("updateSettingsForm", "No updates found"))
- text = (QtWidgets.QApplication.translate("updateSettingsForm", 'Toxygen is up to date'))
- msgBox.setText(text)
- msgBox.exec_()
diff --git a/toxygen/messages.py b/toxygen/messages.py
deleted file mode 100644
index 8d9f4a3..0000000
--- a/toxygen/messages.py
+++ /dev/null
@@ -1,113 +0,0 @@
-
-
-MESSAGE_TYPE = {
- 'TEXT': 0,
- 'ACTION': 1,
- 'FILE_TRANSFER': 2,
- 'INLINE': 3,
- 'INFO_MESSAGE': 4,
- 'GC_TEXT': 5,
- 'GC_ACTION': 6
-}
-
-
-class Message:
-
- def __init__(self, message_type, owner, time):
- self._time = time
- self._type = message_type
- self._owner = owner
-
- def get_type(self):
- return self._type
-
- def get_owner(self):
- return self._owner
-
- def mark_as_sent(self):
- self._owner = 0
-
-
-class TextMessage(Message):
- """
- Plain text or action message
- """
-
- def __init__(self, message, owner, time, message_type):
- super(TextMessage, self).__init__(message_type, owner, time)
- self._message = message
-
- def get_data(self):
- 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
- """
-
- def __init__(self, owner, time, status, size, name, friend_number, file_number):
- super(TransferMessage, self).__init__(MESSAGE_TYPE['FILE_TRANSFER'], owner, time)
- self._status = status
- self._size = size
- self._file_name = name
- self._friend_number, self._file_number = friend_number, file_number
-
- def is_active(self, file_number):
- return self._file_number == file_number and self._status not in (2, 3)
-
- def get_friend_number(self):
- return self._friend_number
-
- def get_file_number(self):
- return self._file_number
-
- def get_status(self):
- return self._status
-
- def set_status(self, value):
- self._status = value
-
- def get_data(self):
- return self._file_name, self._size, self._time, self._owner, self._friend_number, self._file_number, self._status
-
-
-class UnsentFile(Message):
- def __init__(self, path, data, time):
- super(UnsentFile, self).__init__(MESSAGE_TYPE['FILE_TRANSFER'], 0, time)
- self._data, self._path = data, path
-
- def get_data(self):
- return self._path, self._data, self._time
-
- def get_status(self):
- return None
-
-
-class InlineImage(Message):
- """
- Inline image
- """
-
- def __init__(self, data):
- super(InlineImage, self).__init__(MESSAGE_TYPE['INLINE'], None, None)
- self._data = data
-
- def get_data(self):
- return self._data
-
-
-class InfoMessage(TextMessage):
-
- def __init__(self, message, time):
- super(InfoMessage, self).__init__(message, None, time, MESSAGE_TYPE['INFO_MESSAGE'])
diff --git a/toxygen/nodes.json b/toxygen/nodes.json
deleted file mode 100644
index 003bbc0..0000000
--- a/toxygen/nodes.json
+++ /dev/null
@@ -1 +0,0 @@
-{"last_scan":1516822981,"last_refresh":1516822982,"nodes":[{"ipv4":"node.tox.biribiri.org","ipv6":"-","port":33445,"tcp_ports":[3389,33445],"public_key":"F404ABAA1C99A9D37D61AB54898F56793E1DEF8BD46B1038B9D822E8460FAB67","maintainer":"nurupo","location":"US","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Welcome, stranger #7985. I'm up for 5d 14h 34m 34s, running since Jan 19 05:08:27 UTC. If I get outdated, please ping my maintainer at nurupo.contributions@gmail.com","last_ping":1516822981},{"ipv4":"nodes.tox.chat","ipv6":"-","port":33445,"tcp_ports":[3389,33445],"public_key":"6FC41E2BD381D37E9748FC0E0328CE086AF9598BECC8FEB7DDF2E440475F300E","maintainer":"Impyy","location":"NL","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Straps boots like no other","last_ping":1516822981},{"ipv4":"130.133.110.14","ipv6":"2001:6f8:1c3c:babe::14:1","port":33445,"tcp_ports":[33445],"public_key":"461FA3776EF0FA655F1A05477DF1B3B614F7D6B124F7DB1DD4FE3C08B03B640F","maintainer":"Manolis","location":"DE","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Spline tox bootstrap node","last_ping":1516822981},{"ipv4":"205.185.116.116","ipv6":"-","port":33445,"tcp_ports":[3389,33445],"public_key":"A179B09749AC826FF01F37A9613F6B57118AE014D4196A0E1105A98F93A54702","maintainer":"Busindre","location":"US","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"198.98.51.198","ipv6":"2605:6400:1:fed5:22:45af:ec10:f329","port":33445,"tcp_ports":[33445,3389],"public_key":"1D5A5F2F5D6233058BF0259B09622FB40B482E4FA0931EB8FD3AB8E7BF7DAF6F","maintainer":"Busindre","location":"US","status_udp":true,"status_tcp":true,"version":"2014101200","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"85.172.30.117","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"8E7D0B859922EF569298B4D261A8CCB5FEA14FB91ED412A7603A585A25698832","maintainer":"ray65536","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Ray's Tox Node","last_ping":1516822981},{"ipv4":"194.249.212.109","ipv6":"2001:1470:fbfe::109","port":33445,"tcp_ports":[33445,3389],"public_key":"3CEE1F054081E7A011234883BC4FC39F661A55B73637A5AC293DDF1251D9432B","maintainer":"fluke571","location":"SI","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"185.25.116.107","ipv6":"2a00:7a60:0:746b::3","port":33445,"tcp_ports":[33445,3389],"public_key":"DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43","maintainer":"MAH69K","location":"UA","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Saluton! Mia Tox ID: B229B7BD68FC66C2716EAB8671A461906321C764782D7B3EDBB650A315F6C458EF744CE89F07. Scribu! ;)","last_ping":1516822981},{"ipv4":"5.189.176.217","ipv6":"2a02:c200:1:10:3:1:605:1337","port":5190,"tcp_ports":[3389,33445,5190],"public_key":"2B2137E094F743AC8BD44652C55F41DFACC502F125E99E4FE24D40537489E32F","maintainer":"tastytea","location":"DE","status_udp":true,"status_tcp":true,"version":"","motd":"","last_ping":1516822981},{"ipv4":"217.182.143.254","ipv6":"2001:41d0:302:1000::e111","port":2306,"tcp_ports":[33445,2306,443],"public_key":"7AED21F94D82B05774F697B209628CD5A9AD17E0C073D9329076A4C28ED28147","maintainer":"pucetox","location":"FR","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"by pucetox,\nipv4/ipv6 UDP:2306 TCP:21/80/443/2306/33445\nsync your nodes here tox.0x10k.com/bootstrapd-conf , \n for communication: 1D1C0B992DEB6D7F18561176F7F5E572BCC7F2BA5CFA7E9E437B9134122CE96D906A6119F9D2","last_ping":1516822981},{"ipv4":"104.223.122.15","ipv6":"2607:ff48:aa81:800::35eb:1","port":33445,"tcp_ports":[3389,33445],"public_key":"0FB96EEBFB1650DDB52E70CF773DDFCABE25A95CC3BB50FC251082E4B63EF82A","maintainer":"ru_maniac","location":"US","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"built on: Tue Feb 21st 2017, 10:52:30 UTC+3\nplease note: running on TokTox Toxcore!\nmore info on the matter: goo.gl/Gz5KhK \u0026 goo.gl/i2TZJr\n\ntox id for queries and general info: EBD2A7B649ABB10ED9F47E5113F04000F39D46F087CEB62FCCE1069471FD6915256D197F2A97","last_ping":1516822981},{"ipv4":"tox.verdict.gg","ipv6":"-","port":33445,"tcp_ports":[33445,3389],"public_key":"1C5293AEF2114717547B39DA8EA6F1E331E5E358B35F9B6B5F19317911C5F976","maintainer":"Deliran","location":"DE","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Praise The Sun!","last_ping":1516822981},{"ipv4":"d4rk4.ru","ipv6":"-","port":1813,"tcp_ports":[1813],"public_key":"53737F6D47FA6BD2808F378E339AF45BF86F39B64E79D6D491C53A1D522E7039","maintainer":"D4rk4","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"TOX ID: 35EDC07AEB18B163E07EE33F6CDDA63969F394FF6A617CEAB22A7EBBEAAAF854C0EDFBD46898","last_ping":1516822981},{"ipv4":"51.254.84.212","ipv6":"2001:41d0:a:1a3b::18","port":33445,"tcp_ports":[3389,33445],"public_key":"AEC204B9A4501412D5F0BB67D9C81B5DB3EE6ADA64122D32A3E9B093D544327D","maintainer":"a68366","location":"FR","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Since 26.12.2015","last_ping":1516822981},{"ipv4":"88.99.133.52","ipv6":"-","port":33445,"tcp_ports":[3389,33445],"public_key":"2D320F971EF2CA18004416C2AAE7BA52BF7949DB34EA8E2E21AF67BD367BE211","maintainer":"Skey","location":"FR","status_udp":true,"status_tcp":true,"version":"2014101200","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"92.54.84.70","ipv6":"-","port":33445,"tcp_ports":[],"public_key":"5625A62618CB4FCA70E147A71B29695F38CC65FF0CBD68AD46254585BE564802","maintainer":"t3mp","location":"RU","status_udp":true,"status_tcp":false,"version":"2016010100","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"tox.uplinklabs.net","ipv6":"tox.uplinklabs.net","port":33445,"tcp_ports":[3389,33445],"public_key":"1A56EA3EDF5DF4C0AEABBF3C2E4E603890F87E983CAC8A0D532A335F2C6E3E1F","maintainer":"AbacusAvenger","location":"US","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"i don't know what this is for","last_ping":1516822981},{"ipv4":"toxnode.nek0.net","ipv6":"toxnode.nek0.net","port":33445,"tcp_ports":[3389,33445],"public_key":"20965721D32CE50C3E837DD75B33908B33037E6225110BFF209277AEAF3F9639","maintainer":"Phsm","location":"UA","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"95.215.44.78","ipv6":"2a02:7aa0:1619::c6fe:d0cb","port":33445,"tcp_ports":[33445,3389],"public_key":"672DBE27B4ADB9D5FB105A6BB648B2F8FDB89B3323486A7A21968316E012023C","maintainer":"HooinKyoma","location":"SE","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Thanx to Hooin Kyoma","last_ping":1516822981},{"ipv4":"163.172.136.118","ipv6":"2001:bc8:4400:2100::1c:50f","port":33445,"tcp_ports":[33445,3389],"public_key":"2C289F9F37C20D09DA83565588BF496FAB3764853FA38141817A72E3F18ACA0B","maintainer":"LittleVulpix","location":"FR","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"LittleTox - your friendly neighbourhood tox node!","last_ping":1516822981},{"ipv4":"sorunome.de","ipv6":"sorunome.de","port":33445,"tcp_ports":[3389,33445],"public_key":"02807CF4F8BB8FB390CC3794BDF1E8449E9A8392C5D3F2200019DA9F1E812E46","maintainer":"Sorunome","location":"DE","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Keep calm and pony on","last_ping":1516822981},{"ipv4":"37.97.185.116","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"E59A0E71ADA20D35BD1B0957059D7EF7E7792B3D680AE25C6F4DBBA09114D165","maintainer":"Yani","location":"NL","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Yani's node of pleasure and leisure","last_ping":1516822981},{"ipv4":"80.87.193.193","ipv6":"2a01:230:2:6::46a8","port":33445,"tcp_ports":[3389,33445],"public_key":"B38255EE4B054924F6D79A5E6E5889EC94B6ADF6FE9906F97A3D01E3D083223A","maintainer":"linxon","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Tox DHT node by Linxon. Author ToxID: EC774ED05A7E71EEE2EBA939A27CD4FF403D7D79E1E685CFD0394B1770498217C6107E4D3C26","last_ping":1516822981},{"ipv4":"initramfs.io","ipv6":"-","port":33445,"tcp_ports":[3389,33445],"public_key":"3F0A45A268367C1BEA652F258C85F4A66DA76BCAA667A49E770BCC4917AB6A25","maintainer":"initramfs","location":"TW","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"initramfs' Tox DHT Node","last_ping":1516822981},{"ipv4":"hibiki.eve.moe","ipv6":"hibiki.eve.moe","port":33445,"tcp_ports":[33445],"public_key":"D3EB45181B343C2C222A5BCF72B760638E15ED87904625AAD351C594EEFAE03E","maintainer":"EveNeko","location":"FR","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"tox-bootstrapd@hibiki.eve.moe","last_ping":1516822981},{"ipv4":"tox.deadteam.org","ipv6":"tox.deadteam.org","port":33445,"tcp_ports":[33445],"public_key":"C7D284129E83877D63591F14B3F658D77FF9BA9BA7293AEB2BDFBFE1A803AF47","maintainer":"DeadTeam","location":"DE","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Vive le TOX","last_ping":1516822981},{"ipv4":"46.229.52.198","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"813C8F4187833EF0655B10F7752141A352248462A567529A38B6BBF73E979307","maintainer":"Stranger","location":"UA","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Freedom to parrots!","last_ping":1516822981},{"ipv4":"node.tox.ngc.network","ipv6":"node.tox.ngc.network","port":33445,"tcp_ports":[3389,33445],"public_key":"A856243058D1DE633379508ADCAFCF944E40E1672FF402750EF712E30C42012A","maintainer":"Nolz","location":"DE","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Unlike Others","last_ping":1516822981},{"ipv4":"149.56.140.5","ipv6":"2607:5300:0201:3100:0000:0000:0000:3ec2","port":33445,"tcp_ports":[3389,33445],"public_key":"7E5668E0EE09E19F320AD47902419331FFEE147BB3606769CFBE921A2A2FD34C","maintainer":"velusip","location":"CA","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Jera","last_ping":1516822981},{"ipv4":"185.14.30.213","ipv6":"2a00:1ca8:a7::e8b","port":443,"tcp_ports":[33445,3389,443],"public_key":"2555763C8C460495B14157D234DD56B86300A2395554BCAE4621AC345B8C1B1B","maintainer":"dvor","location":"NL","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Just another tox node.","last_ping":1516822981},{"ipv4":"tox.natalenko.name","ipv6":"tox.natalenko.name","port":33445,"tcp_ports":[33445],"public_key":"1CB6EBFD9D85448FA70D3CAE1220B76BF6FCE911B46ACDCF88054C190589650B","maintainer":"post-factum","location":"DE","status_udp":true,"status_tcp":true,"version":"","motd":"","last_ping":1516822981},{"ipv4":"136.243.141.187","ipv6":"2a01:4f8:212:2459::a:1337","port":443,"tcp_ports":[33445,3389,443],"public_key":"6EE1FADE9F55CC7938234CC07C864081FC606D8FE7B751EDA217F268F1078A39","maintainer":"CeBe","location":"DE","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"uTox is the future! - maintained by CeBe - contact: tox@cebe.cc - tox: 7F50119368DC8FD3B1ECAF5D18E3F8854F0484CEC5BBF625D420B8E38638733C02486E387AF8","last_ping":1516822981},{"ipv4":"tox.abilinski.com","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"0E9D7FEE2AA4B42A4C18FE81C038E32FFD8D907AAA7896F05AA76C8D31A20065","maintainer":"flobe","location":"CA","status_udp":true,"status_tcp":true,"version":"","motd":"","last_ping":1516822981},{"ipv4":"m.loskiq.it","ipv6":"-","port":33445,"tcp_ports":[33445,3389],"public_key":"88124F3C18C6CFA8778B7679B7329A333616BD27A4DFB562D476681315CF143D","maintainer":"loskiq","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"https://t.me/loskiq","last_ping":1516822981},{"ipv4":"192.99.232.158","ipv6":"-","port":33445,"tcp_ports":[],"public_key":"7B6CB208C811DEA8782711CE0CAD456AAC0C7B165A0498A1AA7010D2F2EC996C","maintainer":"basiljose","location":"CA","status_udp":true,"status_tcp":false,"version":"2016010100","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"tmux.ru","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"7467AFA626D3246343170B309BA5BDC975DF3924FC9D7A5917FBFA9F5CD5CD38","maintainer":"nrn","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"https://t.me/nyoroon","last_ping":1516822981},{"ipv4":"37.48.122.22","ipv6":"2001:1af8:4700:a115:6::b","port":33445,"tcp_ports":[33445,3389],"public_key":"1B5A8AB25FFFB66620A531C4646B47F0F32B74C547B30AF8BD8266CA50A3AB59","maintainer":"Pokemon","location":"NL","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Those who would give up essential Liberty, to purchase a little temporary Safety, deserve neither Liberty nor Safety","last_ping":1516822981},{"ipv4":"tox.novg.net","ipv6":"-","port":33445,"tcp_ports":[33445,3389],"public_key":"D527E5847F8330D628DAB1814F0A422F6DC9D0A300E6C357634EE2DA88C35463","maintainer":"blind_oracle","location":"NL","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"t0x-node1.weba.ru","ipv6":"-","port":33445,"tcp_ports":[3389,33445],"public_key":"5A59705F86B9FC0671FDF72ED9BB5E55015FF20B349985543DDD4B0656CA1C63","maintainer":"Amin","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"T0X-Node #1","last_ping":1516822981},{"ipv4":"109.195.99.39","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"EF937F61B4979B60BBF306752D8F32029A2A05CD2615B2E9FBFFEADD8E7D5032","maintainer":"NaCl","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"NaCl node respond","last_ping":1516822981},{"ipv4":"79.140.30.52","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"FFAC871E85B1E1487F87AE7C76726AE0E60318A85F6A1669E04C47EB8DC7C72D","maintainer":"warlomak","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"tox-easy-bootstrap","last_ping":1516822981},{"ipv4":"94.41.167.70","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"E519B2C1098999B60190012C7B53E8C43A73C535721036CD9DEC7CCA06741A7D","maintainer":"warlomak","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"tox-easy-bootstrap","last_ping":1516822981},{"ipv4":"104.223.122.204","ipv6":"-","port":33445,"tcp_ports":[3389],"public_key":"3925752E43BF2F8EB4E12B0E9414311064FF2D76707DC7D5D2CCB43F75081F6B","maintainer":"ru_maniac","location":"US","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"rmnc_third_node","last_ping":1516822981},{"ipv4":"77.55.211.53","ipv6":"-","port":53,"tcp_ports":[443,33445,3389],"public_key":"B9D109CC820C69A5D97A4A1A15708107C6BA85C13BC6188CC809D374AFF18E63","maintainer":"GDR!","location":"PL","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"GDR!'s tox-bootstrapd https://gdr.name/","last_ping":1516822922},{"ipv4":"boseburo.ddns.net","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"AF3FC9FC3D121E82E362B4FA84A53E63F58C11C2BA61D988855289B8CABC9B18","maintainer":"LowEel","location":"DE","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"This is the Bose Buro bootstrap daemon","last_ping":1516822981},{"ipv4":"46.101.197.175","ipv6":"2a03:b0c0:3:d0::ac:5001","port":443,"tcp_ports":[443,33445,3389],"public_key":"CD133B521159541FB1D326DE9850F5E56A6C724B5B8E5EB5CD8D950408E95707","maintainer":"clearmartin","location":"DE","status_udp":false,"status_tcp":true,"version":"2014101200","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"104.233.104.126","ipv6":"-","port":33445,"tcp_ports":[],"public_key":"EDEE8F2E839A57820DE3DA4156D88350E53D4161447068A3457EE8F59F362414","maintainer":"wildermesser","location":"CA","status_udp":false,"status_tcp":false,"version":"","motd":"","last_ping":0},{"ipv4":"195.93.190.6","ipv6":"2a01:d0:ffff:a8a::2","port":33445,"tcp_ports":[],"public_key":"FB4CE0DDEFEED45F26917053E5D24BDDA0FA0A3D83A672A9DA2375928B37023D","maintainer":"strngr","location":"UA","status_udp":false,"status_tcp":false,"version":"2016010100","motd":"tox node at strngr.name","last_ping":1516816803},{"ipv4":"193.124.186.205","ipv6":"2a02:f680:1:1100::542a","port":5228,"tcp_ports":[],"public_key":"9906D65F2A4751068A59D30505C5FC8AE1A95E0843AE9372EAFA3BAB6AC16C2C","maintainer":"Cactus","location":"RU","status_udp":false,"status_tcp":false,"version":"","motd":"","last_ping":0},{"ipv4":"85.21.144.224","ipv6":"-","port":33445,"tcp_ports":[],"public_key":"8F738BBC8FA9394670BCAB146C67A507B9907C8E564E28C2B59BEBB2FF68711B","maintainer":"himura","location":"RU","status_udp":false,"status_tcp":false,"version":"","motd":"","last_ping":0},{"ipv4":"37.187.122.30","ipv6":"-","port":33445,"tcp_ports":[],"public_key":"BEB71F97ED9C99C04B8489BB75579EB4DC6AB6F441B603D63533122F1858B51D","maintainer":"dolohow","location":"FR","status_udp":false,"status_tcp":false,"version":"2016010100","motd":"#stay frosty 8218DB335926393789859EDF2D79AC4CC805ADF73472D08165FEA51555502A58AE84FCE7C3D4","last_ping":1515853621},{"ipv4":"95.215.46.114","ipv6":"2a02:7aa0:1619::bdbd:17b8","port":33445,"tcp_ports":[],"public_key":"5823FB947FF24CF83DDFAC3F3BAA18F96EA2018B16CC08429CB97FA502F40C23","maintainer":"isotoxin","location":"SE","status_udp":false,"status_tcp":false,"version":"","motd":"","last_ping":0},{"ipv4":"tox.dumalogiya.ru","ipv6":"-","port":33445,"tcp_ports":[],"public_key":"2DAE6EB8C16131761A675D7C723F618FBA9D29DD8B4E0A39E7E3E8D7055EF113","maintainer":"mikhailnov","location":"RU","status_udp":false,"status_tcp":false,"version":"","motd":"","last_ping":0}]}
\ No newline at end of file
diff --git a/toxygen/notifications.py b/toxygen/notifications.py
deleted file mode 100644
index 26a29ec..0000000
--- a/toxygen/notifications.py
+++ /dev/null
@@ -1,71 +0,0 @@
-from PyQt5 import QtCore, QtWidgets
-from util import curr_directory
-import wave
-import pyaudio
-
-
-SOUND_NOTIFICATION = {
- 'MESSAGE': 0,
- 'FRIEND_CONNECTION_STATUS': 1,
- 'FILE_TRANSFER': 2
-}
-
-
-def tray_notification(title, text, tray, window):
- """
- Show tray notification and activate window icon
- NOTE: different behaviour on different OS
- :param title: Name of user who sent message or file
- :param text: text of message or file info
- :param tray: ref to tray icon
- :param window: main window
- """
- if QtWidgets.QSystemTrayIcon.isSystemTrayAvailable():
- if len(text) > 30:
- text = text[:27] + '...'
- tray.showMessage(title, text, QtWidgets.QSystemTrayIcon.NoIcon, 3000)
- QtWidgets.QApplication.alert(window, 0)
-
- def message_clicked():
- window.setWindowState(window.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
- window.activateWindow()
- tray.messageClicked.connect(message_clicked)
-
-
-class AudioFile:
- chunk = 1024
-
- def __init__(self, fl):
- self.wf = wave.open(fl, 'rb')
- self.p = pyaudio.PyAudio()
- self.stream = self.p.open(
- format=self.p.get_format_from_width(self.wf.getsampwidth()),
- channels=self.wf.getnchannels(),
- rate=self.wf.getframerate(),
- output=True)
-
- def play(self):
- data = self.wf.readframes(self.chunk)
- while data:
- self.stream.write(data)
- data = self.wf.readframes(self.chunk)
-
- def close(self):
- self.stream.close()
- self.p.terminate()
-
-
-def sound_notification(t):
- """
- Plays sound notification
- :param t: type of notification
- """
- if t == SOUND_NOTIFICATION['MESSAGE']:
- f = curr_directory() + '/sounds/message.wav'
- elif t == SOUND_NOTIFICATION['FILE_TRANSFER']:
- f = curr_directory() + '/sounds/file.wav'
- else:
- f = curr_directory() + '/sounds/contact.wav'
- a = AudioFile(f)
- a.play()
- a.close()
diff --git a/toxygen/passwordscreen.py b/toxygen/passwordscreen.py
deleted file mode 100644
index ca721e5..0000000
--- a/toxygen/passwordscreen.py
+++ /dev/null
@@ -1,154 +0,0 @@
-from widgets import CenteredWidget, LineEdit
-from PyQt5 import QtCore, QtWidgets
-
-
-class PasswordArea(LineEdit):
-
- def __init__(self, parent):
- super(PasswordArea, self).__init__(parent)
- self.parent = parent
- self.setEchoMode(QtWidgets.QLineEdit.Password)
-
- def keyPressEvent(self, event):
- if event.key() == QtCore.Qt.Key_Return:
- self.parent.button_click()
- else:
- super(PasswordArea, self).keyPressEvent(event)
-
-
-class PasswordScreenBase(CenteredWidget):
-
- def __init__(self, encrypt):
- super(PasswordScreenBase, self).__init__()
- self._encrypt = encrypt
- self.initUI()
-
- def initUI(self):
- self.resize(360, 170)
- self.setMinimumSize(QtCore.QSize(360, 170))
- self.setMaximumSize(QtCore.QSize(360, 170))
-
- self.enter_pass = QtWidgets.QLabel(self)
- self.enter_pass.setGeometry(QtCore.QRect(30, 10, 300, 30))
-
- self.password = PasswordArea(self)
- self.password.setGeometry(QtCore.QRect(30, 50, 300, 30))
-
- self.button = QtWidgets.QPushButton(self)
- self.button.setGeometry(QtCore.QRect(30, 90, 300, 30))
- self.button.setText('OK')
- self.button.clicked.connect(self.button_click)
-
- self.warning = QtWidgets.QLabel(self)
- self.warning.setGeometry(QtCore.QRect(30, 130, 300, 30))
- self.warning.setStyleSheet('QLabel { color: #F70D1A; }')
- self.warning.setVisible(False)
-
- self.retranslateUi()
- self.center()
- QtCore.QMetaObject.connectSlotsByName(self)
-
- def button_click(self):
- pass
-
- def keyPressEvent(self, event):
- if event.key() == QtCore.Qt.Key_Enter:
- self.button_click()
- else:
- super(PasswordScreenBase, self).keyPressEvent(event)
-
- def retranslateUi(self):
- self.setWindowTitle(QtWidgets.QApplication.translate("pass", "Enter password"))
- self.enter_pass.setText(QtWidgets.QApplication.translate("pass", "Password:"))
- self.warning.setText(QtWidgets.QApplication.translate("pass", "Incorrect password"))
-
-
-class PasswordScreen(PasswordScreenBase):
-
- def __init__(self, encrypt, data):
- super(PasswordScreen, self).__init__(encrypt)
- self._data = data
-
- def button_click(self):
- if self.password.text():
- try:
- self._encrypt.set_password(self.password.text())
- new_data = self._encrypt.pass_decrypt(self._data[0])
- except Exception as ex:
- self.warning.setVisible(True)
- print('Decryption error:', ex)
- else:
- self._data[0] = new_data
- self.close()
-
-
-class UnlockAppScreen(PasswordScreenBase):
-
- def __init__(self, encrypt, callback):
- super(UnlockAppScreen, self).__init__(encrypt)
- self._callback = callback
- self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
-
- def button_click(self):
- if self.password.text():
- if self._encrypt.is_password(self.password.text()):
- self._callback()
- self.close()
- else:
- self.warning.setVisible(True)
- print('Wrong password!')
-
-
-class SetProfilePasswordScreen(CenteredWidget):
-
- def __init__(self, encrypt):
- super(SetProfilePasswordScreen, self).__init__()
- self._encrypt = encrypt
- self.initUI()
- self.retranslateUi()
- self.center()
-
- def initUI(self):
- self.setMinimumSize(QtCore.QSize(700, 200))
- self.setMaximumSize(QtCore.QSize(700, 200))
- self.password = LineEdit(self)
- self.password.setGeometry(QtCore.QRect(40, 10, 300, 30))
- self.password.setEchoMode(QtWidgets.QLineEdit.Password)
- self.confirm_password = LineEdit(self)
- self.confirm_password.setGeometry(QtCore.QRect(40, 50, 300, 30))
- self.confirm_password.setEchoMode(QtWidgets.QLineEdit.Password)
- self.set_password = QtWidgets.QPushButton(self)
- self.set_password.setGeometry(QtCore.QRect(40, 100, 300, 30))
- self.set_password.clicked.connect(self.new_password)
- self.not_match = QtWidgets.QLabel(self)
- self.not_match.setGeometry(QtCore.QRect(350, 50, 300, 30))
- self.not_match.setVisible(False)
- self.not_match.setStyleSheet('QLabel { color: #BC1C1C; }')
- self.warning = QtWidgets.QLabel(self)
- self.warning.setGeometry(QtCore.QRect(40, 160, 500, 30))
- self.warning.setStyleSheet('QLabel { color: #BC1C1C; }')
-
- def retranslateUi(self):
- self.setWindowTitle(QtWidgets.QApplication.translate("PasswordScreen", "Profile password"))
- self.password.setPlaceholderText(
- QtWidgets.QApplication.translate("PasswordScreen", "Password (at least 8 symbols)"))
- self.confirm_password.setPlaceholderText(
- QtWidgets.QApplication.translate("PasswordScreen", "Confirm password"))
- self.set_password.setText(
- QtWidgets.QApplication.translate("PasswordScreen", "Set password"))
- self.not_match.setText(QtWidgets.QApplication.translate("PasswordScreen", "Passwords do not match"))
- self.warning.setText(
- QtWidgets.QApplication.translate("PasswordScreen", "There is no way to recover lost passwords"))
-
- def new_password(self):
- if self.password.text() == self.confirm_password.text():
- if len(self.password.text()) >= 8:
- self._encrypt.set_password(self.password.text())
- self.close()
- else:
- self.not_match.setText(
- QtWidgets.QApplication.translate("PasswordScreen", "Password must be at least 8 symbols"))
- self.not_match.setVisible(True)
- else:
- self.not_match.setText(QtWidgets.QApplication.translate("PasswordScreen", "Passwords do not match"))
- self.not_match.setVisible(True)
diff --git a/toxygen/plugin_support.py b/toxygen/plugin_support.py
deleted file mode 100644
index 0ff7421..0000000
--- a/toxygen/plugin_support.py
+++ /dev/null
@@ -1,176 +0,0 @@
-import util
-import profile
-import os
-import importlib
-import inspect
-import plugins.plugin_super_class as pl
-import toxes
-import sys
-
-
-class PluginLoader(util.Singleton):
-
- def __init__(self, tox, settings):
- super().__init__()
- self._profile = profile.Profile.get_instance()
- self._settings = settings
- self._plugins = {} # dict. key - plugin unique short name, value - tuple (plugin instance, is active)
- self._tox = tox
- self._encr = toxes.ToxES.get_instance()
-
- def set_tox(self, tox):
- """
- New tox instance
- """
- self._tox = tox
- for value in self._plugins.values():
- value[0].set_tox(tox)
-
- def load(self):
- """
- Load all plugins in plugins folder
- """
- path = util.curr_directory() + '/plugins/'
- if not os.path.exists(path):
- util.log('Plugin dir not found')
- return
- else:
- sys.path.append(path)
- files = [f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f))]
- for fl in files:
- if fl in ('plugin_super_class.py', '__init__.py') or not fl.endswith('.py'):
- continue
- name = fl[:-3] # module name without .py
- try:
- module = importlib.import_module(name) # import plugin
- except ImportError:
- util.log('Import error in module ' + name)
- continue
- except Exception as ex:
- util.log('Exception in module ' + name + ' Exception: ' + str(ex))
- continue
- for elem in dir(module):
- obj = getattr(module, elem)
- # looking for plugin class in module
- if inspect.isclass(obj) and hasattr(obj, 'is_plugin') and obj.is_plugin:
- print('Plugin', elem)
- try: # create instance of plugin class
- inst = obj(self._tox, self._profile, self._settings, self._encr)
- autostart = inst.get_short_name() in self._settings['plugins']
- if autostart:
- inst.start()
- except Exception as ex:
- util.log('Exception in module ' + name + ' Exception: ' + str(ex))
- continue
- self._plugins[inst.get_short_name()] = [inst, autostart] # (inst, is active)
- break
-
- def callback_lossless(self, friend_number, data):
- """
- New incoming custom lossless packet (callback)
- """
- l = data[0] - pl.LOSSLESS_FIRST_BYTE
- name = ''.join(chr(x) for x in data[1:l + 1])
- if name in self._plugins and self._plugins[name][1]:
- self._plugins[name][0].lossless_packet(''.join(chr(x) for x in data[l + 1:]), friend_number)
-
- def callback_lossy(self, friend_number, data):
- """
- New incoming custom lossy packet (callback)
- """
- l = data[0] - pl.LOSSY_FIRST_BYTE
- name = ''.join(chr(x) for x in data[1:l + 1])
- if name in self._plugins and self._plugins[name][1]:
- self._plugins[name][0].lossy_packet(''.join(chr(x) for x in data[l + 1:]), friend_number)
-
- def friend_online(self, friend_number):
- """
- Friend with specified number is online
- """
- for elem in self._plugins.values():
- if elem[1]:
- elem[0].friend_connected(friend_number)
-
- def get_plugins_list(self):
- """
- Returns list of all plugins
- """
- result = []
- for data in self._plugins.values():
- try:
- result.append([data[0].get_name(), # plugin full name
- data[1], # is enabled
- data[0].get_description(), # plugin description
- data[0].get_short_name()]) # key - short unique name
- except:
- continue
- return result
-
- def plugin_window(self, key):
- """
- Return window or None for specified plugin
- """
- return self._plugins[key][0].get_window()
-
- def toggle_plugin(self, key):
- """
- Enable/disable plugin
- :param key: plugin short name
- """
- plugin = self._plugins[key]
- if plugin[1]:
- plugin[0].stop()
- else:
- plugin[0].start()
- plugin[1] = not plugin[1]
- if plugin[1]:
- self._settings['plugins'].append(key)
- else:
- self._settings['plugins'].remove(key)
- self._settings.save()
-
- def command(self, text):
- """
- New command for plugin
- """
- text = text.strip()
- name = text.split()[0]
- if name in self._plugins and self._plugins[name][1]:
- self._plugins[name][0].command(text[len(name) + 1:])
-
- def get_menu(self, menu, num):
- """
- Return list of items for menu
- """
- result = []
- for elem in self._plugins.values():
- if elem[1]:
- try:
- result.extend(elem[0].get_menu(menu, num))
- except:
- continue
- return result
-
- def get_message_menu(self, menu, selected_text):
- result = []
- for elem in self._plugins.values():
- if elem[1]:
- try:
- result.extend(elem[0].get_message_menu(menu, selected_text))
- except:
- continue
- return result
-
- def stop(self):
- """
- App is closing, stop all plugins
- """
- for key in list(self._plugins.keys()):
- if self._plugins[key][1]:
- self._plugins[key][0].close()
- del self._plugins[key]
-
- def reload(self):
- print('Reloading plugins')
- self.stop()
- self.load()
diff --git a/toxygen/profile.py b/toxygen/profile.py
deleted file mode 100644
index a0d8cd4..0000000
--- a/toxygen/profile.py
+++ /dev/null
@@ -1,1466 +0,0 @@
-from list_items import *
-from PyQt5 import QtGui, QtWidgets
-from friend import *
-from settings import *
-from toxcore_enums_and_consts import *
-from ctypes import *
-from util import log, Singleton, curr_directory
-from tox_dns import tox_dns
-from history import *
-from file_transfers import *
-import time
-import calls
-import avwidgets
-import plugin_support
-import basecontact
-import items_factory
-import cv2
-import threading
-from group_chat import *
-import re
-
-
-class Profile(basecontact.BaseContact, Singleton):
- """
- Profile of current toxygen user. Contains friends list, tox instance
- """
- def __init__(self, tox, screen):
- """
- :param tox: tox instance
- :param screen: ref to main screen
- """
- basecontact.BaseContact.__init__(self,
- tox.self_get_name(),
- tox.self_get_status_message(),
- screen.user_info,
- tox.self_get_address())
- Singleton.__init__(self)
- self._screen = screen
- self._messages = screen.messages
- self._tox = tox
- self._file_transfers = {} # dict of file transfers. key - tuple (friend_number, file_number)
- self._call = calls.AV(tox.AV) # object with data about calls
- self._call_widgets = {} # dict of incoming call widgets
- self._incoming_calls = set()
- self._load_history = True
- self._waiting_for_reconnection = False
- self._factory = items_factory.ItemsFactory(self._screen.friends_list, self._messages)
- settings = Settings.get_instance()
- self._sorting = settings['sorting']
- self._show_avatars = settings['show_avatars']
- self._filter_string = ''
- self._friend_item_height = 40 if settings['compact_mode'] else 70
- self._paused_file_transfers = dict(settings['paused_file_transfers'])
- # key - file id, value: [path, friend number, is incoming, start position]
- screen.online_contacts.setCurrentIndex(int(self._sorting))
- aliases = settings['friends_aliases']
- data = tox.self_get_friend_list()
- self._history = History(tox.self_get_public_key()) # connection to db
- self._contacts, self._active_friend = [], -1
- for i in data: # creates list of friends
- tox_id = tox.friend_get_public_key(i)
- try:
- alias = list(filter(lambda x: x[0] == tox_id, aliases))[0][1]
- except:
- alias = ''
- item = self.create_friend_item()
- name = alias or tox.friend_get_name(i) or tox_id
- status_message = tox.friend_get_status_message(i)
- if not self._history.friend_exists_in_db(tox_id):
- self._history.add_friend_to_db(tox_id)
- message_getter = self._history.messages_getter(tox_id)
- friend = Friend(message_getter, i, name, status_message, item, tox_id)
- friend.set_alias(alias)
- self._contacts.append(friend)
- if len(self._contacts):
- self.set_active(0)
- self.filtration_and_sorting(self._sorting)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Edit current user's data
- # -----------------------------------------------------------------------------------------------------------------
-
- def change_status(self):
- """
- Changes status of user (online, away, busy)
- """
- if self._status is not None:
- self.set_status((self._status + 1) % 3)
-
- def set_status(self, status):
- super(Profile, self).set_status(status)
- 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):
- if self.name == value:
- return
- tmp = self.name
- super(Profile, self).set_name(value.encode('utf-8'))
- self._tox.self_set_name(self._name.encode('utf-8'))
- message = QtWidgets.QApplication.translate("MainWindow", 'User {} is now known as {}')
- message = message.format(tmp, value)
- for friend in self._contacts:
- friend.append_message(InfoMessage(message, time.time()))
- if self._active_friend + 1:
- self.create_message_item(message, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
- self._messages.scrollToBottom()
-
- def set_status_message(self, value):
- super(Profile, self).set_status_message(value)
- self._tox.self_set_status_message(self._status_message.encode('utf-8'))
-
- def new_nospam(self):
- """Sets new nospam part of tox id"""
- import random
- self._tox.self_set_nospam(random.randint(0, 4294967295)) # no spam - uint32
- self._tox_id = self._tox.self_get_address()
- return self._tox_id
-
- # -----------------------------------------------------------------------------------------------------------------
- # Filtration
- # -----------------------------------------------------------------------------------------------------------------
-
- def filtration_and_sorting(self, sorting=0, filter_str=''):
- """
- Filtration of friends list
- :param sorting: 0 - no sort, 1 - online only, 2 - online first, 4 - by name
- :param filter_str: show contacts which name contains this substring
- """
- 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)
- if sorting & 4:
- if not sorting & 2:
- self._contacts = sorted(self._contacts, key=lambda x: x.name.lower())
- else: # save results of prev sorting
- online_friends = filter(lambda x: x.status is not None, self._contacts)
- count = len(list(online_friends))
- part1 = self._contacts[:count]
- part2 = self._contacts[count:]
- part1 = sorted(part1, key=lambda x: x.name.lower())
- part2 = sorted(part2, key=lambda x: x.name.lower())
- self._contacts = part1 + part2
- else: # sort by number
- online_friends = filter(lambda x: x.status is not None, self._contacts)
- count = len(list(online_friends))
- part1 = self._contacts[:count]
- part2 = self._contacts[count:]
- part1 = sorted(part1, key=lambda x: x.number)
- part2 = sorted(part2, key=lambda x: x.number)
- self._contacts = part1 + part2
- self._screen.friends_list.clear()
- for contact in self._contacts:
- contact.set_widget(self.create_friend_item())
- for index, friend in enumerate(self._contacts):
- friend.visibility = (friend.status is not None or not (sorting & 1)) and (filter_str in friend.name.lower())
- friend.visibility = friend.visibility or friend.messages or friend.actions
- if friend.visibility:
- self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250, self._friend_item_height))
- else:
- self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250, 0))
- self._sorting, self._filter_string = sorting, filter_str
- settings['sorting'] = self._sorting
- settings.save()
- self.set_active_by_number_and_type(number, is_friend)
-
- def update_filtration(self):
- """
- Update list of contacts when 1 of friends change connection status
- """
- self.filtration_and_sorting(self._sorting, self._filter_string)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Friend getters
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_friend_by_number(self, num):
- 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):
- return None
- return self._contacts[num]
-
- def get_curr_friend(self):
- return self._contacts[self._active_friend] if self._active_friend + 1 else None
-
- # -----------------------------------------------------------------------------------------------------------------
- # Work with active friend
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_active(self):
- return self._active_friend
-
- def set_active(self, value=None):
- """
- Change current active friend or update info
- :param value: number of new active friend in friend's list or None to update active user's data
- """
- if value is None and self._active_friend == -1: # nothing to update
- return
- 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()
- self._screen.messageEdit.clear()
- return
- try:
- self.send_typing(False)
- self._screen.typing.setVisible(False)
- if value is not None:
- if self._active_friend + 1 and self._active_friend != value:
- try:
- self.get_curr_friend().curr_text = self._screen.messageEdit.toPlainText()
- except:
- pass
- friend = self._contacts[value]
- friend.remove_invalid_unsent_files()
- if self._active_friend != value:
- self._screen.messageEdit.setPlainText(friend.curr_text)
- self._active_friend = value
- friend.reset_messages()
- if not Settings.get_instance()['save_history']:
- friend.delete_old_messages()
- self._messages.clear()
- friend.load_corr()
- messages = friend.get_corr()[-PAGE_SIZE:]
- self._load_history = False
- for message in messages:
- if message.get_type() <= 1:
- data = message.get_data()
- self.create_message_item(data[0],
- data[2],
- data[1],
- data[3])
- elif message.get_type() == MESSAGE_TYPE['FILE_TRANSFER']:
- if message.get_status() is None:
- self.create_unsent_file_item(message)
- continue
- item = self.create_file_transfer_item(message)
- if message.get_status() in ACTIVE_FILE_TRANSFERS: # active file transfer
- try:
- ft = self._file_transfers[(message.get_friend_number(), message.get_file_number())]
- ft.set_state_changed_handler(item.update_transfer_state)
- ft.signal()
- except:
- print('Incoming not started transfer - no info found')
- elif message.get_type() == MESSAGE_TYPE['INLINE']: # inline
- self.create_inline_item(message.get_data())
- 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:
- self._screen.active_call()
- elif value in self._incoming_calls:
- self._screen.incoming_call()
- else:
- self._screen.call_finished()
- else:
- friend = self.get_curr_friend()
-
- 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'
- os.chdir(os.path.dirname(avatar_path))
- pixmap = QtGui.QPixmap(avatar_path)
- self._screen.account_avatar.setPixmap(pixmap.scaled(64, 64, QtCore.Qt.KeepAspectRatio,
- QtCore.Qt.SmoothTransformation))
- except Exception as ex: # no friend found. ignore
- log('Friend value: ' + str(value))
- log('Error in set active: ' + str(ex))
- raise
-
- def set_active_by_number_and_type(self, number, is_friend):
- for i in range(len(self._contacts)):
- c = self._contacts[i]
- if c.number == number and (type(c) is Friend == is_friend):
- self._active_friend = i
- break
-
- active_friend = property(get_active, set_active)
-
- def get_last_message(self):
- if self._active_friend + 1:
- return self.get_curr_friend().get_last_message_text()
- else:
- return ''
-
- def get_active_number(self):
- return self.get_curr_friend().number if self._active_friend + 1 else -1
-
- def get_active_name(self):
- return self.get_curr_friend().name if self._active_friend + 1 else ''
-
- def is_active_online(self):
- return self._active_friend + 1 and self.get_curr_friend().status is not None
-
- def new_name(self, number, name):
- friend = self.get_friend_by_number(number)
- tmp = friend.name
- friend.set_name(name)
- name = str(name, 'utf-8')
- if friend.name == name and tmp != name:
- message = QtWidgets.QApplication.translate("MainWindow", 'User {} is now known as {}')
- message = message.format(tmp, name)
- friend.append_message(InfoMessage(message, time.time()))
- friend.actions = True
- if number == self.get_active_number():
- self.create_message_item(message, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
- self._messages.scrollToBottom()
- self.set_active(None)
-
- def update(self):
- if self._active_friend + 1:
- self.set_active(self._active_friend)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Friend connection status callbacks
- # -----------------------------------------------------------------------------------------------------------------
-
- def send_files(self, friend_number):
- friend = self.get_friend_by_number(friend_number)
- friend.remove_invalid_unsent_files()
- files = friend.get_unsent_files()
- try:
- for fl in files:
- data = fl.get_data()
- if data[1] is not None:
- self.send_inline(data[1], data[0], friend_number, True)
- else:
- self.send_file(data[0], friend_number, True)
- friend.clear_unsent_files()
- for key in list(self._paused_file_transfers.keys()):
- data = self._paused_file_transfers[key]
- if not os.path.exists(data[0]):
- del self._paused_file_transfers[key]
- 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() and self.is_active_a_friend():
- self.update()
- except Exception as ex:
- print('Exception in file sending: ' + str(ex))
-
- def friend_exit(self, friend_number):
- """
- Friend with specified number quit
- """
- self.get_friend_by_number(friend_number).status = None
- self.friend_typing(friend_number, False)
- if friend_number in self._call:
- self._call.finish_call(friend_number, True)
- for friend_num, file_num in list(self._file_transfers.keys()):
- if friend_num == friend_number:
- ft = self._file_transfers[(friend_num, file_num)]
- if type(ft) is SendTransfer:
- self._paused_file_transfers[ft.get_id()] = [ft.get_path(), friend_num, False, -1]
- elif type(ft) is ReceiveTransfer and ft.state != TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']:
- self._paused_file_transfers[ft.get_id()] = [ft.get_path(), friend_num, True, ft.total_size()]
- self.cancel_transfer(friend_num, file_num, True)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Typing notifications
- # -----------------------------------------------------------------------------------------------------------------
-
- def send_typing(self, typing):
- """
- Send typing notification to a friend
- """
- if Settings.get_instance()['typing_notifications'] and self._active_friend + 1:
- try:
- friend = self.get_curr_friend()
- if friend.status is not None:
- self._tox.self_set_typing(friend.number, typing)
- except:
- pass
-
- def friend_typing(self, friend_number, typing):
- """
- Display incoming typing notification
- """
- if friend_number == self.get_active_number() and self.is_active_a_friend():
- self._screen.typing.setVisible(typing)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Private messages
- # -----------------------------------------------------------------------------------------------------------------
-
- def receipt(self):
- i = 0
- while i < self._messages.count() and not self._messages.itemWidget(self._messages.item(i)).mark_as_sent():
- i += 1
-
- def send_messages(self, friend_number):
- """
- Send 'offline' messages to friend
- """
- friend = self.get_friend_by_number(friend_number)
- friend.load_corr()
- messages = friend.get_unsent_messages()
- try:
- for message in messages:
- self.split_and_send(friend_number, message.get_data()[-1], message.get_data()[0].encode('utf-8'))
- friend.inc_receipts()
- except Exception as ex:
- log('Sending pending messages failed with ' + str(ex))
-
- def split_and_send(self, number, message_type, message):
- """
- Message splitting. Message length cannot be > TOX_MAX_MESSAGE_LENGTH
- :param number: friend's number
- :param message_type: type of message
- :param message: message text
- """
- while len(message) > TOX_MAX_MESSAGE_LENGTH:
- size = TOX_MAX_MESSAGE_LENGTH * 4 // 5
- last_part = message[size:TOX_MAX_MESSAGE_LENGTH]
- if b' ' in last_part:
- index = last_part.index(b' ')
- elif b',' in last_part:
- index = last_part.index(b',')
- elif b'.' in last_part:
- index = last_part.index(b'.')
- else:
- index = TOX_MAX_MESSAGE_LENGTH - size - 1
- index += size + 1
- self._tox.friend_send_message(number, message_type, message[:index])
- message = message[index:]
- self._tox.friend_send_message(number, message_type, message)
-
- def new_message(self, friend_num, message_type, message):
- """
- Current user gets new message
- :param friend_num: friend_num of friend who sent message
- :param message_type: message type - plain text or action message (/me)
- :param message: text of message
- """
- 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()
- self.get_curr_friend().append_message(
- TextMessage(message, MESSAGE_OWNER['FRIEND'], t, message_type))
- else:
- friend = self.get_friend_by_number(friend_num)
- friend.inc_messages()
- friend.append_message(
- TextMessage(message, MESSAGE_OWNER['FRIEND'], time.time(), message_type))
- if not friend.visibility:
- self.update_filtration()
-
- def send_message(self, text, friend_num=None):
- """
- Send message
- :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 '):
- plugin_support.PluginLoader.get_instance().command(text[8:])
- self._screen.messageEdit.clear()
- elif text and friend_num + 1:
- if text.startswith('/me '):
- message_type = TOX_MESSAGE_TYPE['ACTION']
- text = text[4:]
- else:
- message_type = TOX_MESSAGE_TYPE['NORMAL']
- friend = self.get_friend_by_number(friend_num)
- friend.inc_receipts()
- if friend.status is not None:
- self.split_and_send(friend.number, message_type, text.encode('utf-8'))
- 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()
- friend.append_message(TextMessage(text, MESSAGE_OWNER['NOT_SENT'], t, message_type))
-
- def delete_message(self, time):
- friend = self.get_curr_friend()
- friend.delete_message(time)
- self._history.delete_message(friend.tox_id, time)
- self.update()
-
- # -----------------------------------------------------------------------------------------------------------------
- # History support
- # -----------------------------------------------------------------------------------------------------------------
-
- def save_history(self):
- """
- Save history to db
- """
- s = Settings.get_instance()
- if hasattr(self, '_history'):
- if s['save_history']:
- 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']:
- messages = friend.get_corr_for_saving()
- else:
- messages = friend.get_unsent_messages_for_saving()
- self._history.delete_messages(friend.tox_id)
- self._history.save_messages_to_db(friend.tox_id, messages)
- unsent_messages = friend.get_unsent_messages()
- unsent_time = unsent_messages[0].get_data()[2] if len(unsent_messages) else time.time() + 1
- self._history.update_messages(friend.tox_id, unsent_time)
- self._history.save()
- del self._history
-
- def clear_history(self, num=None, save_unsent=False):
- """
- Clear chat history
- """
- if num is not None:
- friend = self._contacts[num]
- friend.clear_corr(save_unsent)
- if self._history.friend_exists_in_db(friend.tox_id):
- self._history.delete_messages(friend.tox_id)
- self._history.delete_friend_from_db(friend.tox_id)
- else: # clear all history
- for number in range(len(self._contacts)):
- self.clear_history(number, save_unsent)
- if num is None or num == self.get_active_number():
- self.update()
-
- def load_history(self):
- """
- Tries to load next part of messages
- """
- if not self._load_history:
- return
- self._load_history = False
- friend = self.get_curr_friend()
- friend.load_corr(False)
- data = friend.get_corr()
- if not data:
- return
- data.reverse()
- data = data[self._messages.count():self._messages.count() + PAGE_SIZE]
- for message in data:
- if message.get_type() <= 1: # text message
- data = message.get_data()
- self.create_message_item(data[0],
- data[2],
- data[1],
- data[3],
- False)
- elif message.get_type() == MESSAGE_TYPE['FILE_TRANSFER']: # file transfer
- if message.get_status() is None:
- self.create_unsent_file_item(message)
- continue
- item = self.create_file_transfer_item(message, False)
- if message.get_status() in ACTIVE_FILE_TRANSFERS: # active file transfer
- try:
- ft = self._file_transfers[(message.get_friend_number(), message.get_file_number())]
- ft.set_state_changed_handler(item.update_transfer_state)
- ft.signal()
- except:
- print('Incoming not started transfer - no info found')
- elif message.get_type() == MESSAGE_TYPE['INLINE']: # inline image
- self.create_inline_item(message.get_data(), False)
- elif message.get_type() < 5: # info message
- data = message.get_data()
- self.create_message_item(data[0],
- data[2],
- '',
- data[3],
- False)
- else:
- data = message.get_data()
- self.create_gc_message_item(data[0], data[2], data[1], data[4], data[3], False)
- self._load_history = True
-
- def export_db(self, directory):
- self._history.export(directory)
-
- def export_history(self, num, as_text=True, _range=None):
- friend = self._contacts[num]
- if _range is None:
- friend.load_all_corr()
- corr = friend.get_corr()
- elif _range[1] + 1:
- corr = friend.get_corr()[_range[0]:_range[1] + 1]
- else:
- corr = friend.get_corr()[_range[0]:]
- arr = []
- new_line = '\n' if as_text else '
'
- for message in corr:
- if type(message) is TextMessage:
- data = message.get_data()
- if as_text:
- x = '[{}] {}: {}\n'
- else:
- x = '[{}] {}: {}
'
- arr.append(x.format(convert_time(data[2]) if data[1] != MESSAGE_OWNER['NOT_SENT'] else 'Unsent',
- friend.name if data[1] == MESSAGE_OWNER['FRIEND'] else self.name,
- data[0]))
- s = new_line.join(arr)
- if not as_text:
- s = '{}{}'.format(friend.name, s)
- return s
-
- # -----------------------------------------------------------------------------------------------------------------
- # Friend, message and file transfer items creation
- # -----------------------------------------------------------------------------------------------------------------
-
- def create_friend_item(self):
- """
- Method-factory
- :return: new widget for friend instance
- """
- return self._factory.friend_item()
-
- def create_message_item(self, text, time, owner, message_type, append=True):
- if message_type == MESSAGE_TYPE['INFO_MESSAGE']:
- name = ''
- elif owner == MESSAGE_OWNER['FRIEND']:
- name = self.get_active_name()
- else:
- name = self._name
- 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, 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
- return self._factory.file_transfer_item(data, append)
-
- def create_unsent_file_item(self, message, append=True):
- data = message.get_data()
- return self._factory.unsent_file_item(os.path.basename(data[0]),
- os.path.getsize(data[0]) if data[1] is None else len(data[1]),
- self.name,
- data[2],
- append)
-
- def create_inline_item(self, data, append=True):
- return self._factory.inline_item(data, append)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Work with friends (remove, block, set alias, get public key)
- # -----------------------------------------------------------------------------------------------------------------
-
- def set_alias(self, num):
- """
- Set new alias for friend
- """
- friend = self._contacts[num]
- name = friend.name
- dialog = QtWidgets.QApplication.translate('MainWindow',
- "Enter new alias for friend {} or leave empty to use friend's name:")
- dialog = dialog.format(name)
- title = QtWidgets.QApplication.translate('MainWindow',
- 'Set alias')
- text, ok = QtWidgets.QInputDialog.getText(None,
- title,
- dialog,
- QtWidgets.QLineEdit.Normal,
- name)
- if ok:
- settings = Settings.get_instance()
- aliases = settings['friends_aliases']
- if text:
- friend.name = bytes(text, 'utf-8')
- try:
- index = list(map(lambda x: x[0], aliases)).index(friend.tox_id)
- aliases[index] = (friend.tox_id, text)
- except:
- aliases.append((friend.tox_id, text))
- friend.set_alias(text)
- else: # use default name
- friend.name = bytes(self._tox.friend_get_name(friend.number), 'utf-8')
- friend.set_alias('')
- try:
- index = list(map(lambda x: x[0], aliases)).index(friend.tox_id)
- del aliases[index]
- except:
- pass
- settings.save()
- if num == self.get_active_number() and self.is_active_a_friend():
- self.update()
-
- def friend_public_key(self, num):
- return self._contacts[num].tox_id
-
- def delete_friend(self, num):
- """
- Removes friend from contact list
- :param num: number of friend in list
- """
- friend = self._contacts[num]
- settings = Settings.get_instance()
- try:
- index = list(map(lambda x: x[0], settings['friends_aliases'])).index(friend.tox_id)
- del settings['friends_aliases'][index]
- except:
- pass
- if friend.tox_id in settings['notes']:
- del settings['notes'][friend.tox_id]
- settings.save()
- self.clear_history(num)
- if self._history.friend_exists_in_db(friend.tox_id):
- self._history.delete_friend_from_db(friend.tox_id)
- self._tox.friend_delete(friend.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)
- data = self._tox.get_savedata()
- ProfileHelper.get_instance().save_profile(data)
-
- def add_friend(self, tox_id):
- """
- Adds friend to list
- """
- num = self._tox.friend_add_norequest(tox_id) # num - friend number
- item = self.create_friend_item()
- try:
- if not self._history.friend_exists_in_db(tox_id):
- self._history.add_friend_to_db(tox_id)
- message_getter = self._history.messages_getter(tox_id)
- except Exception as ex: # something is wrong
- log('Accept friend request failed! ' + str(ex))
- message_getter = None
- friend = Friend(message_getter, num, tox_id, '', item, tox_id)
- self._contacts.append(friend)
-
- def block_user(self, tox_id):
- """
- Block user with specified tox id (or public key) - delete from friends list and ignore friend requests
- """
- tox_id = tox_id[:TOX_PUBLIC_KEY_SIZE * 2]
- if tox_id == self.tox_id[:TOX_PUBLIC_KEY_SIZE * 2]:
- return
- settings = Settings.get_instance()
- if tox_id not in settings['blocked']:
- settings['blocked'].append(tox_id)
- settings.save()
- try:
- num = self._tox.friend_by_public_key(tox_id)
- self.delete_friend(num)
- data = self._tox.get_savedata()
- ProfileHelper.get_instance().save_profile(data)
- except: # not in friend list
- pass
-
- def unblock_user(self, tox_id, add_to_friend_list):
- """
- Unblock user
- :param tox_id: tox id of contact
- :param add_to_friend_list: add this contact to friend list or not
- """
- s = Settings.get_instance()
- s['blocked'].remove(tox_id)
- s.save()
- if add_to_friend_list:
- self.add_friend(tox_id)
- data = self._tox.get_savedata()
- ProfileHelper.get_instance().save_profile(data)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Friend requests
- # -----------------------------------------------------------------------------------------------------------------
-
- def send_friend_request(self, tox_id, message):
- """
- Function tries to send request to contact with specified id
- :param tox_id: id of new contact or tox dns 4 value
- :param message: additional message
- :return: True on success else error string
- """
- try:
- message = message or 'Hello! Add me to your contact list please'
- if '@' in tox_id: # value like groupbot@toxme.io
- tox_id = tox_dns(tox_id)
- if tox_id is None:
- raise Exception('TOX DNS lookup failed')
- if len(tox_id) == TOX_PUBLIC_KEY_SIZE * 2: # public key
- self.add_friend(tox_id)
- msgBox = QtWidgets.QMessageBox()
- msgBox.setWindowTitle(QtWidgets.QApplication.translate("MainWindow", "Friend added"))
- text = (QtWidgets.QApplication.translate("MainWindow", 'Friend added without sending friend request'))
- msgBox.setText(text)
- msgBox.exec_()
- else:
- result = self._tox.friend_add(tox_id, message.encode('utf-8'))
- tox_id = tox_id[:TOX_PUBLIC_KEY_SIZE * 2]
- item = self.create_friend_item()
- if not self._history.friend_exists_in_db(tox_id):
- self._history.add_friend_to_db(tox_id)
- message_getter = self._history.messages_getter(tox_id)
- friend = Friend(message_getter, result, tox_id, '', item, tox_id)
- self._contacts.append(friend)
- data = self._tox.get_savedata()
- ProfileHelper.get_instance().save_profile(data)
- return True
- except Exception as ex: # wrong data
- log('Friend request failed with ' + str(ex))
- return str(ex)
-
- def process_friend_request(self, tox_id, message):
- """
- Accept or ignore friend request
- :param tox_id: tox id of contact
- :param message: message
- """
- try:
- text = QtWidgets.QApplication.translate('MainWindow', 'User {} wants to add you to contact list. Message:\n{}')
- info = text.format(tox_id, message)
- fr_req = QtWidgets.QApplication.translate('MainWindow', 'Friend request')
- reply = QtWidgets.QMessageBox.question(None, fr_req, info, QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)
- if reply == QtWidgets.QMessageBox.Yes: # accepted
- self.add_friend(tox_id)
- data = self._tox.get_savedata()
- ProfileHelper.get_instance().save_profile(data)
- except Exception as ex: # something is wrong
- log('Accept friend request failed! ' + str(ex))
-
- # -----------------------------------------------------------------------------------------------------------------
- # Reset
- # -----------------------------------------------------------------------------------------------------------------
-
- def reset(self, restart):
- """
- Recreate tox instance
- :param restart: method which calls restart and returns new tox instance
- """
- for contact in self._contacts:
- if type(contact) is Friend:
- self.friend_exit(contact.number)
- else:
- self.leave_gc(contact.number)
- self._call.stop()
- del self._call
- del self._tox
- self._tox = restart()
- self._call = calls.AV(self._tox.AV)
- self.status = None
- for friend in self._contacts:
- friend.number = self._tox.friend_by_public_key(friend.tox_id) # numbers update
- self.update_filtration()
-
- def reconnect(self):
- self._waiting_for_reconnection = False
- if self.status is None or all(list(map(lambda x: x.status is None, self._contacts))) and len(self._contacts):
- self._waiting_for_reconnection = True
- self.reset(self._screen.reset)
- QtCore.QTimer.singleShot(50000, self.reconnect)
-
- def close(self):
- 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]
- if hasattr(self, '_call'):
- self._call.stop()
- del self._call
- s = Settings.get_instance()
- s['paused_file_transfers'] = dict(self._paused_file_transfers) if s['resend_files'] else {}
- s.save()
-
- # -----------------------------------------------------------------------------------------------------------------
- # File transfers support
- # -----------------------------------------------------------------------------------------------------------------
-
- def incoming_file_transfer(self, friend_number, file_number, size, file_name):
- """
- New transfer
- :param friend_number: number of friend who sent file
- :param file_number: file number
- :param size: file size in bytes
- :param file_name: file name without path
- """
- settings = Settings.get_instance()
- friend = self.get_friend_by_number(friend_number)
- auto = settings['allow_auto_accept'] and friend.tox_id in settings['auto_accept_from_friends']
- inline = is_inline(file_name) and settings['allow_inline']
- file_id = self._tox.file_get_file_id(friend_number, file_number)
- accepted = True
- if file_id in self._paused_file_transfers:
- data = self._paused_file_transfers[file_id]
- pos = data[-1] if os.path.exists(data[0]) else 0
- if pos >= size:
- self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL'])
- return
- self._tox.file_seek(friend_number, file_number, pos)
- self.accept_transfer(None, data[0], friend_number, file_number, size, False, pos)
- tm = TransferMessage(MESSAGE_OWNER['FRIEND'],
- time.time(),
- TOX_FILE_TRANSFER_STATE['RUNNING'],
- size,
- file_name,
- friend_number,
- file_number)
- elif inline and size < 1024 * 1024:
- self.accept_transfer(None, '', friend_number, file_number, size, True)
- tm = TransferMessage(MESSAGE_OWNER['FRIEND'],
- time.time(),
- TOX_FILE_TRANSFER_STATE['RUNNING'],
- size,
- file_name,
- friend_number,
- file_number)
-
- elif auto:
- path = settings['auto_accept_path']
- if not path or not os.path.exists(path):
- path = curr_directory()
- self.accept_transfer(None, path + '/' + file_name, friend_number, file_number, size)
- tm = TransferMessage(MESSAGE_OWNER['FRIEND'],
- time.time(),
- TOX_FILE_TRANSFER_STATE['RUNNING'],
- size,
- file_name,
- friend_number,
- file_number)
- else:
- tm = TransferMessage(MESSAGE_OWNER['FRIEND'],
- time.time(),
- TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED'],
- size,
- file_name,
- friend_number,
- file_number)
- accepted = False
- 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)
- self._messages.scrollToBottom()
- else:
- friend.actions = True
-
- friend.append_message(tm)
-
- def cancel_transfer(self, friend_number, file_number, already_cancelled=False):
- """
- Stop transfer
- :param friend_number: number of friend
- :param file_number: file number
- :param already_cancelled: was cancelled by friend
- """
- i = self.get_friend_by_number(friend_number).update_transfer_data(file_number,
- TOX_FILE_TRANSFER_STATE['CANCELLED'])
- if (friend_number, file_number) in self._file_transfers:
- tr = self._file_transfers[(friend_number, file_number)]
- if not already_cancelled:
- tr.cancel()
- else:
- tr.cancelled()
- if (friend_number, file_number) in self._file_transfers:
- del tr
- del self._file_transfers[(friend_number, file_number)]
- else:
- if not already_cancelled:
- self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL'])
- if friend_number == self.get_active_number() and self.is_active_a_friend():
- tmp = self._messages.count() + i
- if tmp >= 0:
- self._messages.itemWidget(
- self._messages.item(tmp)).update_transfer_state(TOX_FILE_TRANSFER_STATE['CANCELLED'],
- 0, -1)
-
- def cancel_not_started_transfer(self, cancel_time):
- self.get_curr_friend().delete_one_unsent_file(cancel_time)
- self.update()
-
- def pause_transfer(self, friend_number, file_number, by_friend=False):
- """
- Pause transfer with specified data
- """
- tr = self._file_transfers[(friend_number, file_number)]
- tr.pause(by_friend)
- t = TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND'] if by_friend else TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']
- self.get_friend_by_number(friend_number).update_transfer_data(file_number, t)
-
- def resume_transfer(self, friend_number, file_number, by_friend=False):
- """
- Resume transfer with specified data
- """
- self.get_friend_by_number(friend_number).update_transfer_data(file_number,
- TOX_FILE_TRANSFER_STATE['RUNNING'])
- tr = self._file_transfers[(friend_number, file_number)]
- if by_friend:
- tr.state = TOX_FILE_TRANSFER_STATE['RUNNING']
- tr.signal()
- else:
- tr.send_control(TOX_FILE_CONTROL['RESUME'])
-
- def accept_transfer(self, item, path, friend_number, file_number, size, inline=False, from_position=0):
- """
- :param item: transfer item.
- :param path: path for saving
- :param friend_number: friend number
- :param file_number: file number
- :param size: file size
- :param inline: is inline image
- :param from_position: position for start
- """
- path, file_name = os.path.split(path)
- new_file_name, i = file_name, 1
- if not from_position:
- while os.path.isfile(path + '/' + new_file_name): # file with same name already exists
- if '.' in file_name: # has extension
- d = file_name.rindex('.')
- else: # no extension
- d = len(file_name)
- new_file_name = file_name[:d] + ' ({})'.format(i) + file_name[d:]
- i += 1
- path = os.path.join(path, new_file_name)
- if not inline:
- rt = ReceiveTransfer(path, self._tox, friend_number, size, file_number, from_position)
- else:
- rt = ReceiveToBuffer(self._tox, friend_number, size, file_number)
- rt.set_transfer_finished_handler(self.transfer_finished)
- self._file_transfers[(friend_number, file_number)] = rt
- self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['RESUME'])
- if item is not None:
- rt.set_state_changed_handler(item.update_transfer_state)
- self.get_friend_by_number(friend_number).update_transfer_data(file_number,
- TOX_FILE_TRANSFER_STATE['RUNNING'])
-
- def send_screenshot(self, data):
- """
- Send screenshot to current active friend
- :param data: raw data - png
- """
- self.send_inline(data, 'toxygen_inline.png')
- self._messages.repaint()
-
- def send_sticker(self, path):
- with open(path, 'rb') as fl:
- data = fl.read()
- self.send_inline(data, 'sticker.png')
-
- def send_inline(self, data, file_name, friend_number=None, is_resend=False):
- friend_number = friend_number or self.get_active_number()
- friend = self.get_friend_by_number(friend_number)
- if friend.status is None and not is_resend:
- m = UnsentFile(file_name, data, time.time())
- friend.append_message(m)
- self.update()
- return
- elif friend.status is None and is_resend:
- raise RuntimeError()
- st = SendFromBuffer(self._tox, friend.number, data, file_name)
- st.set_transfer_finished_handler(self.transfer_finished)
- self._file_transfers[(friend.number, st.get_file_number())] = st
- tm = TransferMessage(MESSAGE_OWNER['ME'],
- time.time(),
- TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED'],
- len(data),
- file_name,
- friend.number,
- st.get_file_number())
- friend.append_message(tm)
- if friend_number == self.get_active_number():
- item = self.create_file_transfer_item(tm)
- st.set_state_changed_handler(item.update_transfer_state)
- self._messages.scrollToBottom()
-
- def send_file(self, path, number=None, is_resend=False, file_id=None):
- """
- Send file to current active friend
- :param path: file path
- :param number: friend_number
- :param is_resend: is 'offline' message
- :param file_id: file id of transfer
- """
- friend_number = self.get_active_number() if number is None else number
- friend = self.get_friend_by_number(friend_number)
- if friend.status is None and not is_resend:
- m = UnsentFile(path, None, time.time())
- friend.append_message(m)
- self.update()
- return
- elif friend.status is None and is_resend:
- print('Error in sending')
- raise RuntimeError()
- st = SendTransfer(path, self._tox, friend_number, TOX_FILE_KIND['DATA'], file_id)
- st.set_transfer_finished_handler(self.transfer_finished)
- self._file_transfers[(friend_number, st.get_file_number())] = st
- tm = TransferMessage(MESSAGE_OWNER['ME'],
- time.time(),
- TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED'],
- os.path.getsize(path),
- os.path.basename(path),
- friend_number,
- st.get_file_number())
- if friend_number == self.get_active_number():
- item = self.create_file_transfer_item(tm)
- st.set_state_changed_handler(item.update_transfer_state)
- self._messages.scrollToBottom()
- self._contacts[friend_number].append_message(tm)
-
- def incoming_chunk(self, friend_number, file_number, position, data):
- """
- Incoming chunk
- """
- self._file_transfers[(friend_number, file_number)].write_chunk(position, data)
-
- def outgoing_chunk(self, friend_number, file_number, position, size):
- """
- Outgoing chunk
- """
- self._file_transfers[(friend_number, file_number)].send_chunk(position, size)
-
- def transfer_finished(self, friend_number, file_number):
- transfer = self._file_transfers[(friend_number, file_number)]
- t = type(transfer)
- if t is ReceiveAvatar:
- self.get_friend_by_number(friend_number).load_avatar()
- 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')
- inline = InlineImage(transfer.get_data())
- 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() and self.is_active_a_friend():
- count = self._messages.count()
- if count + i + 1 >= 0:
- elem = QtWidgets.QListWidgetItem()
- item = InlineImageItem(transfer.get_data(), self._messages.width(), elem)
- elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height()))
- self._messages.insertItem(count + i + 1, elem)
- self._messages.setItemWidget(elem, item)
- self._messages.scrollToBottom()
- elif t is not SendAvatar:
- self.get_friend_by_number(friend_number).update_transfer_data(file_number,
- TOX_FILE_TRANSFER_STATE['FINISHED'])
- del self._file_transfers[(friend_number, file_number)]
- del transfer
-
- # -----------------------------------------------------------------------------------------------------------------
- # Avatars support
- # -----------------------------------------------------------------------------------------------------------------
-
- def send_avatar(self, friend_number):
- """
- :param friend_number: number of friend who should get new avatar
- """
- avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
- if not os.path.isfile(avatar_path): # reset image
- avatar_path = None
- sa = SendAvatar(avatar_path, self._tox, friend_number)
- self._file_transfers[(friend_number, sa.get_file_number())] = sa
-
- def incoming_avatar(self, friend_number, file_number, size):
- """
- Friend changed avatar
- :param friend_number: friend number
- :param file_number: file number
- :param size: size of avatar or 0 (default avatar)
- """
- ra = ReceiveAvatar(self._tox, friend_number, size, file_number)
- if ra.state != TOX_FILE_TRANSFER_STATE['CANCELLED']:
- self._file_transfers[(friend_number, file_number)] = ra
- 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 and self.is_active_a_friend():
- self.set_active(None)
-
- def reset_avatar(self):
- super(Profile, self).reset_avatar()
- for friend in filter(lambda x: x.status is not None, self._contacts):
- self.send_avatar(friend.number)
-
- def set_avatar(self, data):
- super(Profile, self).set_avatar(data)
- for friend in filter(lambda x: x.status is not None, self._contacts):
- self.send_avatar(friend.number)
-
- # -----------------------------------------------------------------------------------------------------------------
- # AV support
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_call(self):
- return self._call
-
- call = property(get_call)
-
- 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
- self._call(num, audio, video)
- self._screen.active_call()
- if video:
- text = QtWidgets.QApplication.translate("incoming_call", "Outgoing video call")
- else:
- text = QtWidgets.QApplication.translate("incoming_call", "Outgoing audio call")
- self.get_curr_friend().append_message(InfoMessage(text, time.time()))
- self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
- self._messages.scrollToBottom()
- elif num in self._call: # finish or cancel call if you call with active friend
- self.stop_call(num, False)
-
- def incoming_call(self, audio, video, friend_number):
- """
- Incoming call from friend.
- """
- if not Settings.get_instance().audio['enabled']:
- return
- friend = self.get_friend_by_number(friend_number)
- if video:
- text = QtWidgets.QApplication.translate("incoming_call", "Incoming video call")
- else:
- text = QtWidgets.QApplication.translate("incoming_call", "Incoming audio call")
- friend.append_message(InfoMessage(text, time.time()))
- self._incoming_calls.add(friend_number)
- if friend_number == self.get_active_number():
- self._screen.incoming_call()
- self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
- self._messages.scrollToBottom()
- else:
- friend.actions = True
- self._call_widgets[friend_number] = avwidgets.IncomingCallWidget(friend_number, text, friend.name)
- self._call_widgets[friend_number].set_pixmap(friend.get_pixmap())
- self._call_widgets[friend_number].show()
-
- def accept_call(self, friend_number, audio, video):
- """
- Accept incoming call with audio or video
- """
- self._call.accept_call(friend_number, audio, video)
- self._screen.active_call()
- if friend_number in self._incoming_calls:
- self._incoming_calls.remove(friend_number)
- del self._call_widgets[friend_number]
-
- def stop_call(self, friend_number, by_friend):
- """
- Stop call with friend
- """
- if friend_number in self._incoming_calls:
- self._incoming_calls.remove(friend_number)
- text = QtWidgets.QApplication.translate("incoming_call", "Call declined")
- else:
- text = QtWidgets.QApplication.translate("incoming_call", "Call finished")
- self._screen.call_finished()
- is_video = self._call.is_video_call(friend_number)
- self._call.finish_call(friend_number, by_friend) # finish or decline call
- if hasattr(self, '_call_widget'):
- self._call_widget[friend_number].close()
- del self._call_widget[friend_number]
-
- def destroy_window():
- if is_video:
- cv2.destroyWindow(str(friend_number))
-
- threading.Timer(2.0, destroy_window).start()
- friend = self.get_friend_by_number(friend_number)
- friend.append_message(InfoMessage(text, time.time()))
- if friend_number == self.get_active_number():
- 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):
- if number == -1:
- return
- widget = self.create_friend_item()
- gc = GroupChat('Group chat #' + str(number), '', widget, self._tox, number)
- self._contacts.append(gc)
-
- def create_group_chat(self):
- number = self._tox.add_av_groupchat()
- self.add_gc(number)
-
- def leave_gc(self, num):
- gc = self._contacts[num]
- self._tox.del_groupchat(gc.number)
- del self._contacts[num]
- self._screen.friends_list.takeItem(num)
- if num == self._active_friend: # active friend was deleted
- if not len(self._contacts): # last friend was deleted
- self.set_active(-1)
- else:
- self.set_active(0)
-
- def group_invite(self, friend_number, gc_type, data):
- text = QtWidgets.QApplication.translate('MainWindow', 'User {} invites you to group chat. Accept?')
- title = QtWidgets.QApplication.translate('MainWindow', 'Group chat invite')
- friend = self.get_friend_by_number(friend_number)
- reply = QtWidgets.QMessageBox.question(None, title, text.format(friend.name), QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)
- if reply == QtWidgets.QMessageBox.Yes: # accepted
- if gc_type == TOX_GROUPCHAT_TYPE['TEXT']:
- number = self._tox.join_groupchat(friend_number, data)
- else:
- number = self._tox.join_av_groupchat(friend_number, data)
- self.add_gc(number)
-
- def new_gc_message(self, group_number, peer_number, message_type, message):
- name = self._tox.group_peername(group_number, peer_number)
- message_type += 5
- if group_number == self.get_active_number() and not self.is_active_a_friend(): # add message to list
- t = time.time()
- self.create_gc_message_item(message, t, MESSAGE_OWNER['FRIEND'], name, message_type)
- self._messages.scrollToBottom()
- self.get_curr_friend().append_message(
- GroupChatMessage(message, MESSAGE_OWNER['FRIEND'], t, message_type, name))
- else:
- gc = self.get_group_by_number(group_number)
- gc.inc_messages()
- gc.append_message(
- GroupChatMessage(message, MESSAGE_OWNER['FRIEND'], time.time(), message_type, name))
- if not gc.visibility:
- self.update_filtration()
-
- def new_gc_title(self, group_number, title):
- gc = self.get_group_by_number(group_number)
- gc.new_title(title)
- if not self.is_active_a_friend() and self.get_active_number() == group_number:
- self.update()
-
- def update_gc(self, group_number):
- count = self._tox.group_number_peers(group_number)
- gc = self.get_group_by_number(group_number)
- text = QtWidgets.QApplication.translate('MainWindow', '{} users in chat')
- gc.status_message = text.format(str(count)).encode('utf-8')
- if not self.is_active_a_friend() and self.get_active_number() == group_number:
- self.update()
-
- def send_gc_message(self, text):
- group_number = self.get_active_number()
- if text.startswith('/me '):
- text = text[4:]
- self._tox.group_action_send(group_number, text.encode('utf-8'))
- else:
- self._tox.group_message_send(group_number, text.encode('utf-8'))
- self._screen.messageEdit.clear()
-
- def set_title(self, num):
- """
- Set new title for gc
- """
- gc = self._contacts[num]
- name = gc.name
- dialog = QtWidgets.QApplication.translate('MainWindow',
- "Enter new title for group {}:")
- dialog = dialog.format(name)
- title = QtWidgets.QApplication.translate('MainWindow',
- 'Set title')
- text, ok = QtWidgets.QInputDialog.getText(None,
- title,
- dialog,
- QtWidgets.QLineEdit.Normal,
- name)
- if ok:
- text = text.encode('utf-8')
- self._tox.group_set_title(gc.number, text)
- self.new_gc_title(gc.number, text)
-
- def get_group_chats(self):
- chats = filter(lambda x: type(x) is GroupChat, self._contacts)
- chats = map(lambda c: (c.name, c.number), chats)
- return list(chats)
-
- def invite_friend(self, friend_num, group_number):
- friend = self._contacts[friend_num]
- self._tox.invite_friend(friend.number, group_number)
-
- def get_gc_peer_name(self, text):
- gc = self.get_curr_friend()
- if type(gc) is not GroupChat:
- return '\t'
- names = gc.get_names()
- name = re.split("\s+", text)[-1]
- suggested_names = list(filter(lambda x: x.startswith(name), names))
- if not len(suggested_names):
- return '\t'
- return suggested_names[0][len(name):] + ': '
-
-
-def tox_factory(data=None, settings=None):
- """
- :param data: user data from .tox file. None = no saved data, create new profile
- :param settings: current profile settings. None = default settings will be used
- :return: new tox instance
- """
- if settings is None:
- settings = Settings.get_default_settings()
- tox_options = Tox.options_new()
- # see lines 393-401
- tox_options.contents.ipv6_enabled = settings['ipv6_enabled']
- tox_options.contents.udp_enabled = settings['udp_enabled']
- tox_options.contents.proxy_type = settings['proxy_type']
- tox_options.contents.proxy_host = bytes(settings['proxy_host'], 'UTF-8')
- tox_options.contents.proxy_port = settings['proxy_port']
- tox_options.contents.start_port = settings['start_port']
- tox_options.contents.end_port = settings['end_port']
- tox_options.contents.tcp_port = settings['tcp_port']
- if data: # load existing profile
- tox_options.contents.savedata_type = TOX_SAVEDATA_TYPE['TOX_SAVE']
- tox_options.contents.savedata_data = c_char_p(data)
- tox_options.contents.savedata_length = len(data)
- else: # create new profile
- tox_options.contents.savedata_type = TOX_SAVEDATA_TYPE['NONE']
- tox_options.contents.savedata_data = None
- tox_options.contents.savedata_length = 0
- return Tox(tox_options)
diff --git a/toxygen/screen_sharing.py b/toxygen/screen_sharing.py
deleted file mode 100644
index 265658c..0000000
--- a/toxygen/screen_sharing.py
+++ /dev/null
@@ -1,22 +0,0 @@
-import numpy as np
-from PyQt5 import QtWidgets
-
-
-class DesktopGrabber:
-
- def __init__(self, x, y, width, height):
- self._x = x
- self._y = y
- self._width = width
- self._height = height
- self._width -= width % 4
- self._height -= height % 4
- self._screen = QtWidgets.QApplication.primaryScreen()
-
- def read(self):
- pixmap = self._screen.grabWindow(0, self._x, self._y, self._width, self._height)
- image = pixmap.toImage()
- s = image.bits().asstring(self._width * self._height * 4)
- arr = np.fromstring(s, dtype=np.uint8).reshape((self._height, self._width, 4))
-
- return True, arr
diff --git a/toxygen/settings.py b/toxygen/settings.py
deleted file mode 100644
index 101f372..0000000
--- a/toxygen/settings.py
+++ /dev/null
@@ -1,293 +0,0 @@
-from platform import system
-import json
-import os
-from util import Singleton, curr_directory, log, copy, append_slash
-import pyaudio
-from toxes import ToxES
-import smileys
-
-
-class Settings(dict, Singleton):
- """
- Settings of current profile + global app settings
- """
-
- def __init__(self, name):
- Singleton.__init__(self)
- self.path = ProfileHelper.get_path() + str(name) + '.json'
- self.name = name
- if os.path.isfile(self.path):
- with open(self.path, 'rb') as fl:
- data = fl.read()
- inst = ToxES.get_instance()
- try:
- if inst.is_data_encrypted(data):
- data = inst.pass_decrypt(data)
- info = json.loads(str(data, 'utf-8'))
- except Exception as ex:
- info = Settings.get_default_settings()
- log('Parsing settings error: ' + str(ex))
- super(Settings, self).__init__(info)
- self.upgrade()
- else:
- super(Settings, self).__init__(Settings.get_default_settings())
- self.save()
- smileys.SmileyLoader(self)
- self.locked = False
- self.closing = False
- self.unlockScreen = False
- p = pyaudio.PyAudio()
- input_devices = output_devices = 0
- for i in range(p.get_device_count()):
- device = p.get_device_info_by_index(i)
- if device["maxInputChannels"]:
- input_devices += 1
- if device["maxOutputChannels"]:
- output_devices += 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,
- 'enabled': input_devices and output_devices}
- self.video = {'device': -1, 'width': 640, 'height': 480, 'x': 0, 'y': 0}
-
- @staticmethod
- def get_auto_profile():
- p = Settings.get_global_settings_path()
- if os.path.isfile(p):
- with open(p) as fl:
- data = fl.read()
- auto = json.loads(data)
- if 'path' in auto and 'name' in auto:
- path = str(auto['path'])
- name = str(auto['name'])
- if os.path.isfile(append_slash(path) + name + '.tox'):
- return path, name
- return '', ''
-
- @staticmethod
- def set_auto_profile(path, name):
- p = Settings.get_global_settings_path()
- if os.path.isfile(p):
- with open(p) as fl:
- data = fl.read()
- data = json.loads(data)
- else:
- data = {}
- data['path'] = str(path)
- data['name'] = str(name)
- with open(p, 'w') as fl:
- fl.write(json.dumps(data))
-
- @staticmethod
- def reset_auto_profile():
- p = Settings.get_global_settings_path()
- if os.path.isfile(p):
- with open(p) as fl:
- data = fl.read()
- data = json.loads(data)
- else:
- data = {}
- if 'path' in data:
- del data['path']
- del data['name']
- with open(p, 'w') as fl:
- fl.write(json.dumps(data))
-
- @staticmethod
- def is_active_profile(path, name):
- path = path + name + '.lock'
- return os.path.isfile(path)
-
- @staticmethod
- def get_default_settings():
- """
- Default profile settings
- """
- return {
- 'theme': 'dark',
- 'ipv6_enabled': False,
- 'udp_enabled': True,
- 'proxy_type': 0,
- 'proxy_host': '127.0.0.1',
- 'proxy_port': 9050,
- 'start_port': 0,
- 'end_port': 0,
- 'tcp_port': 0,
- 'notifications': True,
- 'sound_notifications': False,
- 'language': 'English',
- 'save_history': False,
- 'allow_inline': True,
- 'allow_auto_accept': True,
- 'auto_accept_path': None,
- 'sorting': 0,
- 'auto_accept_from_friends': [],
- 'paused_file_transfers': {},
- 'resend_files': True,
- 'friends_aliases': [],
- 'show_avatars': False,
- 'typing_notifications': False,
- 'calls_sound': True,
- 'blocked': [],
- 'plugins': [],
- 'notes': {},
- 'smileys': True,
- 'smiley_pack': 'default',
- 'mirror_mode': False,
- 'width': 920,
- 'height': 500,
- 'x': 400,
- 'y': 400,
- 'message_font_size': 14,
- 'unread_color': 'red',
- 'save_unsent_only': False,
- 'compact_mode': False,
- 'show_welcome_screen': True,
- 'close_to_tray': False,
- 'font': 'Times New Roman',
- 'update': 1,
- 'group_notifications': True,
- 'download_nodes_list': False
- }
-
- @staticmethod
- def supported_languages():
- return {
- 'English': 'en_EN',
- 'French': 'fr_FR',
- 'Russian': 'ru_RU',
- 'Ukrainian': 'uk_UA'
- }
-
- @staticmethod
- def built_in_themes():
- return {
- 'dark': '/styles/dark_style.qss',
- 'default': '/styles/style.qss'
- }
-
- def upgrade(self):
- default = Settings.get_default_settings()
- for key in default:
- if key not in self:
- print(key)
- self[key] = default[key]
- self.save()
-
- def save(self):
- text = json.dumps(self)
- inst = ToxES.get_instance()
- if inst.has_password():
- text = bytes(inst.pass_encrypt(bytes(text, 'utf-8')))
- else:
- text = bytes(text, 'utf-8')
- with open(self.path, 'wb') as fl:
- fl.write(text)
-
- def close(self):
- profile_path = ProfileHelper.get_path()
- path = str(profile_path + str(self.name) + '.lock')
- if os.path.isfile(path):
- os.remove(path)
-
- def set_active_profile(self):
- """
- Mark current profile as active
- """
- profile_path = ProfileHelper.get_path()
- path = str(profile_path + str(self.name) + '.lock')
- with open(path, 'w') as fl:
- fl.write('active')
-
- def export(self, path):
- text = json.dumps(self)
- with open(path + str(self.name) + '.json', 'w') as fl:
- fl.write(text)
-
- def update_path(self):
- self.path = ProfileHelper.get_path() + self.name + '.json'
-
- @staticmethod
- def get_global_settings_path():
- return curr_directory() + '/toxygen.json'
-
- @staticmethod
- def get_default_path():
- if system() == 'Windows':
- return os.getenv('APPDATA') + '/Tox/'
- elif system() == 'Darwin':
- return os.getenv('HOME') + '/Library/Application Support/Tox/'
- else:
- return os.getenv('HOME') + '/.config/tox/'
-
-
-class ProfileHelper(Singleton):
- """
- Class with methods for search, load and save profiles
- """
- def __init__(self, path, name):
- Singleton.__init__(self)
- path = append_slash(path)
- self._path = path + name + '.tox'
- self._directory = path
- # create /avatars if not exists:
- directory = path + 'avatars'
- if not os.path.exists(directory):
- os.makedirs(directory)
-
- def open_profile(self):
- with open(self._path, 'rb') as fl:
- data = fl.read()
- if data:
- return data
- else:
- raise IOError('Save file has zero size!')
-
- def get_dir(self):
- return self._directory
-
- def save_profile(self, data):
- inst = ToxES.get_instance()
- if inst.has_password():
- data = inst.pass_encrypt(data)
- with open(self._path, 'wb') as fl:
- fl.write(data)
- print('Profile saved successfully')
-
- def export_profile(self, new_path, use_new_path):
- path = new_path + os.path.basename(self._path)
- with open(self._path, 'rb') as fin:
- data = fin.read()
- with open(path, 'wb') as fout:
- fout.write(data)
- print('Profile exported successfully')
- copy(self._directory + 'avatars', new_path + 'avatars')
- if use_new_path:
- self._path = new_path + os.path.basename(self._path)
- self._directory = new_path
- Settings.get_instance().update_path()
-
- @staticmethod
- def find_profiles():
- """
- Find available tox profiles
- """
- path = Settings.get_default_path()
- result = []
- # check default path
- if not os.path.exists(path):
- os.makedirs(path)
- for fl in os.listdir(path):
- if fl.endswith('.tox'):
- name = fl[:-4]
- result.append((path, name))
- path = curr_directory()
- # check current directory
- for fl in os.listdir(path):
- if fl.endswith('.tox'):
- name = fl[:-4]
- result.append((path + '/', name))
- return result
-
- @staticmethod
- def get_path():
- return ProfileHelper.get_instance().get_dir()
diff --git a/toxygen/smileys.py b/toxygen/smileys.py
deleted file mode 100644
index 52cb603..0000000
--- a/toxygen/smileys.py
+++ /dev/null
@@ -1,88 +0,0 @@
-import util
-import json
-import os
-from collections import OrderedDict
-from PyQt5 import QtCore
-
-
-class SmileyLoader(util.Singleton):
- """
- Class which loads smileys packs and insert smileys into messages
- """
-
- def __init__(self, settings):
- super().__init__()
- self._settings = settings
- self._curr_pack = None # current pack name
- self._smileys = {} # smileys dict. key - smiley (str), value - path to image (str)
- self._list = [] # smileys list without duplicates
- self.load_pack()
-
- def load_pack(self):
- """
- Loads smiley pack
- """
- pack_name = self._settings['smiley_pack']
- if self._settings['smileys'] and self._curr_pack != pack_name:
- self._curr_pack = pack_name
- path = self.get_smileys_path() + 'config.json'
- try:
- with open(path, encoding='utf8') as fl:
- self._smileys = json.loads(fl.read())
- fl.seek(0)
- tmp = json.loads(fl.read(), object_pairs_hook=OrderedDict)
- print('Smiley pack {} loaded'.format(pack_name))
- keys, values, self._list = [], [], []
- for key, value in tmp.items():
- value = self.get_smileys_path() + value
- if value not in values:
- keys.append(key)
- values.append(value)
- self._list = list(zip(keys, values))
- except Exception as ex:
- self._smileys = {}
- self._list = []
- print('Smiley pack {} was not loaded. Error: {}'.format(pack_name, ex))
-
- def get_smileys_path(self):
- return util.curr_directory() + '/smileys/' + self._curr_pack + '/' if self._curr_pack is not None else None
-
- def get_packs_list(self):
- d = util.curr_directory() + '/smileys/'
- return [x[1] for x in os.walk(d)][0]
-
- def get_smileys(self):
- return list(self._list)
-
- def add_smileys_to_text(self, text, edit):
- """
- Adds smileys to text
- :param text: message
- :param edit: MessageEdit instance
- :return text with smileys
- """
- if not self._settings['smileys'] or not len(self._smileys):
- return text
- arr = text.split(' ')
- for i in range(len(arr)):
- if arr[i] in self._smileys:
- file_name = self._smileys[arr[i]] # image name
- arr[i] = ''.format(arr[i], file_name)
- if file_name.endswith('.gif'): # animated smiley
- edit.addAnimation(QtCore.QUrl(file_name), self.get_smileys_path() + file_name)
- return ' '.join(arr)
-
-
-def sticker_loader():
- """
- :return list of stickers
- """
- result = []
- d = util.curr_directory() + '/stickers/'
- keys = [x[1] for x in os.walk(d)][0]
- for key in keys:
- path = d + key + '/'
- files = filter(lambda f: f.endswith('.png'), os.listdir(path))
- files = map(lambda f: str(path + f), files)
- result.extend(files)
- return result
diff --git a/toxygen/tox.py b/toxygen/tox.py
deleted file mode 100644
index ef4e44c..0000000
--- a/toxygen/tox.py
+++ /dev/null
@@ -1,1601 +0,0 @@
-from ctypes import *
-from toxcore_enums_and_consts import *
-from toxav import ToxAV
-from libtox import LibToxCore
-
-
-class ToxOptions(Structure):
- _fields_ = [
- ('ipv6_enabled', c_bool),
- ('udp_enabled', c_bool),
- ('proxy_type', c_int),
- ('proxy_host', c_char_p),
- ('proxy_port', c_uint16),
- ('start_port', c_uint16),
- ('end_port', c_uint16),
- ('tcp_port', c_uint16),
- ('savedata_type', c_int),
- ('savedata_data', c_char_p),
- ('savedata_length', c_size_t)
- ]
-
-
-def string_to_bin(tox_id):
- return c_char_p(bytes.fromhex(tox_id)) if tox_id is not None else None
-
-
-def bin_to_string(raw_id, length):
- res = ''.join('{:02x}'.format(ord(raw_id[i])) for i in range(length))
- return res.upper()
-
-
-class Tox:
-
- libtoxcore = LibToxCore()
-
- def __init__(self, tox_options=None, tox_pointer=None):
- """
- Creates and initialises a new Tox instance with the options passed.
-
- This function will bring the instance into a valid state. Running the event loop with a new instance will
- operate correctly.
-
- :param tox_options: An options object. If this parameter is None, the default options are used.
- :param tox_pointer: Tox instance pointer. If this parameter is not None, tox_options will be ignored.
- """
- if tox_pointer is not None:
- self._tox_pointer = tox_pointer
- else:
- tox_err_new = c_int()
- Tox.libtoxcore.tox_new.restype = POINTER(c_void_p)
- self._tox_pointer = Tox.libtoxcore.tox_new(tox_options, byref(tox_err_new))
- tox_err_new = tox_err_new.value
- if tox_err_new == TOX_ERR_NEW['NULL']:
- raise ArgumentError('One of the arguments to the function was NULL when it was not expected.')
- elif tox_err_new == TOX_ERR_NEW['MALLOC']:
- raise MemoryError('The function was unable to allocate enough '
- 'memory to store the internal structures for the Tox object.')
- elif tox_err_new == TOX_ERR_NEW['PORT_ALLOC']:
- raise RuntimeError('The function was unable to bind to a port. This may mean that all ports have '
- 'already been bound, e.g. by other Tox instances, or it may mean a permission error.'
- ' You may be able to gather more information from errno.')
- elif tox_err_new == TOX_ERR_NEW['PROXY_BAD_TYPE']:
- raise ArgumentError('proxy_type was invalid.')
- elif tox_err_new == TOX_ERR_NEW['PROXY_BAD_HOST']:
- raise ArgumentError('proxy_type was valid but the proxy_host passed had an invalid format or was NULL.')
- elif tox_err_new == TOX_ERR_NEW['PROXY_BAD_PORT']:
- raise ArgumentError('proxy_type was valid, but the proxy_port was invalid.')
- elif tox_err_new == TOX_ERR_NEW['PROXY_NOT_FOUND']:
- raise ArgumentError('The proxy address passed could not be resolved.')
- elif tox_err_new == TOX_ERR_NEW['LOAD_ENCRYPTED']:
- raise ArgumentError('The byte array to be loaded contained an encrypted save.')
- elif tox_err_new == TOX_ERR_NEW['LOAD_BAD_FORMAT']:
- raise ArgumentError('The data format was invalid. This can happen when loading data that was saved by'
- ' an older version of Tox, or when the data has been corrupted. When loading from'
- ' badly formatted data, some data may have been loaded, and the rest is discarded.'
- ' Passing an invalid length parameter also causes this error.')
-
- self.self_connection_status_cb = None
- self.friend_name_cb = None
- self.friend_status_message_cb = None
- self.friend_status_cb = None
- self.friend_connection_status_cb = None
- self.friend_request_cb = None
- self.friend_read_receipt_cb = None
- self.friend_typing_cb = None
- self.friend_message_cb = None
- self.file_recv_control_cb = None
- self.file_chunk_request_cb = None
- self.file_recv_cb = None
- 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)
-
- def __del__(self):
- del self.AV
- Tox.libtoxcore.tox_kill(self._tox_pointer)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Startup options
- # -----------------------------------------------------------------------------------------------------------------
-
- @staticmethod
- def options_default(tox_options):
- """
- Initialises a Tox_Options object with the default options.
-
- The result of this function is independent of the original options. All values will be overwritten, no values
- will be read (so it is permissible to pass an uninitialised object).
-
- If options is NULL, this function has no effect.
-
- :param tox_options: A pointer to options object to be filled with default options.
- """
- Tox.libtoxcore.tox_options_default(tox_options)
-
- @staticmethod
- def options_new():
- """
- Allocates a new Tox_Options object and initialises it with the default options. This function can be used to
- preserve long term ABI compatibility by giving the responsibility of allocation and deallocation to the Tox
- library.
-
- Objects returned from this function must be freed using the tox_options_free function.
-
- :return: A pointer to new ToxOptions object with default options or raise MemoryError.
- """
- tox_err_options_new = c_int()
- f = Tox.libtoxcore.tox_options_new
- f.restype = POINTER(ToxOptions)
- result = f(byref(tox_err_options_new))
- tox_err_options_new = tox_err_options_new.value
- if tox_err_options_new == TOX_ERR_OPTIONS_NEW['OK']:
- return result
- elif tox_err_options_new == TOX_ERR_OPTIONS_NEW['MALLOC']:
- raise MemoryError('The function failed to allocate enough memory for the options struct.')
-
- @staticmethod
- def options_free(tox_options):
- """
- Releases all resources associated with an options objects.
-
- Passing a pointer that was not returned by tox_options_new results in undefined behaviour.
-
- :param tox_options: A pointer to new ToxOptions object
- """
- Tox.libtoxcore.tox_options_free(tox_options)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Creation and destruction
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_savedata_size(self):
- """
- Calculates the number of bytes required to store the tox instance with tox_get_savedata.
- This function cannot fail. The result is always greater than 0.
-
- :return: number of bytes
- """
- return Tox.libtoxcore.tox_get_savedata_size(self._tox_pointer)
-
- def get_savedata(self, savedata=None):
- """
- Store all information associated with the tox instance to a byte array.
-
- :param savedata: pointer (c_char_p) to a memory region large enough to store the tox instance data.
- Call tox_get_savedata_size to find the number of bytes required. If this parameter is None, this function
- allocates memory for the tox instance data.
- :return: pointer (c_char_p) to a memory region with the tox instance data
- """
- if savedata is None:
- savedata_size = self.get_savedata_size()
- savedata = create_string_buffer(savedata_size)
- Tox.libtoxcore.tox_get_savedata(self._tox_pointer, savedata)
- return savedata[:]
-
- # -----------------------------------------------------------------------------------------------------------------
- # Connection lifecycle and event loop
- # -----------------------------------------------------------------------------------------------------------------
-
- def bootstrap(self, address, port, public_key):
- """
- Sends a "get nodes" request to the given bootstrap node with IP, port, and public key to setup connections.
-
- This function will attempt to connect to the node using UDP. You must use this function even if
- Tox_Options.udp_enabled was set to false.
-
- :param address: The hostname or IP address (IPv4 or IPv6) of the node.
- :param port: The port on the host on which the bootstrap Tox instance is listening.
- :param public_key: The long term public key of the bootstrap node (TOX_PUBLIC_KEY_SIZE bytes).
- :return: True on success.
- """
- tox_err_bootstrap = c_int()
- result = Tox.libtoxcore.tox_bootstrap(self._tox_pointer, c_char_p(address), c_uint16(port),
- string_to_bin(public_key), byref(tox_err_bootstrap))
- tox_err_bootstrap = tox_err_bootstrap.value
- if tox_err_bootstrap == TOX_ERR_BOOTSTRAP['OK']:
- return bool(result)
- elif tox_err_bootstrap == TOX_ERR_BOOTSTRAP['NULL']:
- raise ArgumentError('One of the arguments to the function was NULL when it was not expected.')
- elif tox_err_bootstrap == TOX_ERR_BOOTSTRAP['BAD_HOST']:
- raise ArgumentError('The address could not be resolved to an IP '
- 'address, or the IP address passed was invalid.')
- elif tox_err_bootstrap == TOX_ERR_BOOTSTRAP['BAD_PORT']:
- raise ArgumentError('The port passed was invalid. The valid port range is (1, 65535).')
-
- def add_tcp_relay(self, address, port, public_key):
- """
- Adds additional host:port pair as TCP relay.
-
- This function can be used to initiate TCP connections to different ports on the same bootstrap node, or to add
- TCP relays without using them as bootstrap nodes.
-
- :param address: The hostname or IP address (IPv4 or IPv6) of the TCP relay.
- :param port: The port on the host on which the TCP relay is listening.
- :param public_key: The long term public key of the TCP relay (TOX_PUBLIC_KEY_SIZE bytes).
- :return: True on success.
- """
- tox_err_bootstrap = c_int()
- result = Tox.libtoxcore.tox_add_tcp_relay(self._tox_pointer, c_char_p(address), c_uint16(port),
- string_to_bin(public_key), byref(tox_err_bootstrap))
- tox_err_bootstrap = tox_err_bootstrap.value
- if tox_err_bootstrap == TOX_ERR_BOOTSTRAP['OK']:
- return bool(result)
- elif tox_err_bootstrap == TOX_ERR_BOOTSTRAP['NULL']:
- raise ArgumentError('One of the arguments to the function was NULL when it was not expected.')
- elif tox_err_bootstrap == TOX_ERR_BOOTSTRAP['BAD_HOST']:
- raise ArgumentError('The address could not be resolved to an IP '
- 'address, or the IP address passed was invalid.')
- elif tox_err_bootstrap == TOX_ERR_BOOTSTRAP['BAD_PORT']:
- raise ArgumentError('The port passed was invalid. The valid port range is (1, 65535).')
-
- def self_get_connection_status(self):
- """
- Return whether we are connected to the DHT. The return value is equal to the last value received through the
- `self_connection_status` callback.
-
- :return: TOX_CONNECTION
- """
- return Tox.libtoxcore.tox_self_get_connection_status(self._tox_pointer)
-
- def callback_self_connection_status(self, callback, user_data):
- """
- Set the callback for the `self_connection_status` event. Pass None to unset.
-
- This event is triggered whenever there is a change in the DHT connection state. When disconnected, a client may
- choose to call tox_bootstrap again, to reconnect to the DHT. Note that this state may frequently change for
- short amounts of time. Clients should therefore not immediately bootstrap on receiving a disconnect.
-
- :param callback: Python function. Should take pointer (c_void_p) to Tox object,
- TOX_CONNECTION (c_int),
- pointer (c_void_p) to user_data
- :param user_data: pointer (c_void_p) to user data
- """
- c_callback = CFUNCTYPE(None, c_void_p, c_int, c_void_p)
- self.self_connection_status_cb = c_callback(callback)
- Tox.libtoxcore.tox_callback_self_connection_status(self._tox_pointer,
- self.self_connection_status_cb, user_data)
-
- def iteration_interval(self):
- """
- Return the time in milliseconds before tox_iterate() should be called again for optimal performance.
- :return: time in milliseconds
- """
- return Tox.libtoxcore.tox_iteration_interval(self._tox_pointer)
-
- def iterate(self):
- """
- The main loop that needs to be run in intervals of tox_iteration_interval() milliseconds.
- """
- Tox.libtoxcore.tox_iterate(self._tox_pointer)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Internal client information (Tox address/id)
- # -----------------------------------------------------------------------------------------------------------------
-
- def self_get_address(self, address=None):
- """
- Writes the Tox friend address of the client to a byte array. The address is not in human-readable format. If a
- client wants to display the address, formatting is required.
-
- :param address: pointer (c_char_p) to a memory region of at least TOX_ADDRESS_SIZE bytes. If this parameter is
- None, this function allocates memory for address.
- :return: Tox friend address
- """
- if address is None:
- address = create_string_buffer(TOX_ADDRESS_SIZE)
- Tox.libtoxcore.tox_self_get_address(self._tox_pointer, address)
- return bin_to_string(address, TOX_ADDRESS_SIZE)
-
- def self_set_nospam(self, nospam):
- """
- Set the 4-byte nospam part of the address.
-
- :param nospam: Any 32 bit unsigned integer.
- """
- Tox.libtoxcore.tox_self_set_nospam(self._tox_pointer, c_uint32(nospam))
-
- def self_get_nospam(self):
- """
- Get the 4-byte nospam part of the address.
-
- :return: nospam part of the address
- """
- return Tox.libtoxcore.tox_self_get_nospam(self._tox_pointer)
-
- def self_get_public_key(self, public_key=None):
- """
- Copy the Tox Public Key (long term) from the Tox object.
-
- :param public_key: A memory region of at least TOX_PUBLIC_KEY_SIZE bytes. If this parameter is NULL, this
- function allocates memory for Tox Public Key.
- :return: Tox Public Key
- """
- if public_key is None:
- public_key = create_string_buffer(TOX_PUBLIC_KEY_SIZE)
- Tox.libtoxcore.tox_self_get_public_key(self._tox_pointer, public_key)
- return bin_to_string(public_key, TOX_PUBLIC_KEY_SIZE)
-
- def self_get_secret_key(self, secret_key=None):
- """
- Copy the Tox Secret Key from the Tox object.
-
- :param secret_key: pointer (c_char_p) to a memory region of at least TOX_SECRET_KEY_SIZE bytes. If this
- parameter is NULL, this function allocates memory for Tox Secret Key.
- :return: Tox Secret Key
- """
- if secret_key is None:
- secret_key = create_string_buffer(TOX_SECRET_KEY_SIZE)
- Tox.libtoxcore.tox_self_get_secret_key(self._tox_pointer, secret_key)
- return bin_to_string(secret_key, TOX_SECRET_KEY_SIZE)
-
- # -----------------------------------------------------------------------------------------------------------------
- # User-visible client information (nickname/status)
- # -----------------------------------------------------------------------------------------------------------------
-
- def self_set_name(self, name):
- """
- Set the nickname for the Tox client.
-
- Nickname length cannot exceed TOX_MAX_NAME_LENGTH. If length is 0, the name parameter is ignored
- (it can be None), and the nickname is set back to empty.
- :param name: New nickname.
- :return: True on success.
- """
- tox_err_set_info = c_int()
- result = Tox.libtoxcore.tox_self_set_name(self._tox_pointer, c_char_p(name),
- c_size_t(len(name)), byref(tox_err_set_info))
- tox_err_set_info = tox_err_set_info.value
- if tox_err_set_info == TOX_ERR_SET_INFO['OK']:
- return bool(result)
- elif tox_err_set_info == TOX_ERR_SET_INFO['NULL']:
- raise ArgumentError('One of the arguments to the function was NULL when it was not expected.')
- elif tox_err_set_info == TOX_ERR_SET_INFO['TOO_LONG']:
- raise ArgumentError('Information length exceeded maximum permissible size.')
-
- def self_get_name_size(self):
- """
- Return the length of the current nickname as passed to tox_self_set_name.
-
- If no nickname was set before calling this function, the name is empty, and this function returns 0.
-
- :return: length of the current nickname
- """
- return Tox.libtoxcore.tox_self_get_name_size(self._tox_pointer)
-
- def self_get_name(self, name=None):
- """
- Write the nickname set by tox_self_set_name to a byte array.
-
- If no nickname was set before calling this function, the name is empty, and this function has no effect.
-
- Call tox_self_get_name_size to find out how much memory to allocate for the result.
-
- :param name: pointer (c_char_p) to a memory region location large enough to hold the nickname. If this parameter
- is NULL, the function allocates memory for the nickname.
- :return: nickname
- """
- if name is None:
- name = create_string_buffer(self.self_get_name_size())
- Tox.libtoxcore.tox_self_get_name(self._tox_pointer, name)
- return str(name.value, 'utf-8')
-
- def self_set_status_message(self, status_message):
- """
- Set the client's status message.
-
- Status message length cannot exceed TOX_MAX_STATUS_MESSAGE_LENGTH. If length is 0, the status parameter is
- ignored, and the user status is set back to empty.
-
- :param status_message: new status message
- :return: True on success.
- """
- tox_err_set_info = c_int()
- result = Tox.libtoxcore.tox_self_set_status_message(self._tox_pointer, c_char_p(status_message),
- c_size_t(len(status_message)), byref(tox_err_set_info))
- tox_err_set_info = tox_err_set_info.value
- if tox_err_set_info == TOX_ERR_SET_INFO['OK']:
- return bool(result)
- elif tox_err_set_info == TOX_ERR_SET_INFO['NULL']:
- raise ArgumentError('One of the arguments to the function was NULL when it was not expected.')
- elif tox_err_set_info == TOX_ERR_SET_INFO['TOO_LONG']:
- raise ArgumentError('Information length exceeded maximum permissible size.')
-
- def self_get_status_message_size(self):
- """
- Return the length of the current status message as passed to tox_self_set_status_message.
-
- If no status message was set before calling this function, the status is empty, and this function returns 0.
-
- :return: length of the current status message
- """
- return Tox.libtoxcore.tox_self_get_status_message_size(self._tox_pointer)
-
- def self_get_status_message(self, status_message=None):
- """
- Write the status message set by tox_self_set_status_message to a byte array.
-
- If no status message was set before calling this function, the status is empty, and this function has no effect.
-
- Call tox_self_get_status_message_size to find out how much memory to allocate for the result.
-
- :param status_message: pointer (c_char_p) to a valid memory location large enough to hold the status message.
- If this parameter is None, the function allocates memory for the status message.
- :return: status message
- """
- if status_message is None:
- status_message = create_string_buffer(self.self_get_status_message_size())
- Tox.libtoxcore.tox_self_get_status_message(self._tox_pointer, status_message)
- return str(status_message.value, 'utf-8')
-
- def self_set_status(self, status):
- """
- Set the client's user status.
-
- :param status: One of the user statuses listed in the enumeration TOX_USER_STATUS.
- """
- Tox.libtoxcore.tox_self_set_status(self._tox_pointer, c_int(status))
-
- def self_get_status(self):
- """
- Returns the client's user status.
-
- :return: client's user status
- """
- return Tox.libtoxcore.tox_self_get_status(self._tox_pointer)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Friend list management
- # -----------------------------------------------------------------------------------------------------------------
-
- def friend_add(self, address, message):
- """
- Add a friend to the friend list and send a friend request.
-
- A friend request message must be at least 1 byte long and at most TOX_MAX_FRIEND_REQUEST_LENGTH.
-
- Friend numbers are unique identifiers used in all functions that operate on friends. Once added, a friend number
- is stable for the lifetime of the Tox object. After saving the state and reloading it, the friend numbers may
- not be the same as before. Deleting a friend creates a gap in the friend number set, which is filled by the next
- adding of a friend. Any pattern in friend numbers should not be relied on.
-
- If more than INT32_MAX friends are added, this function causes undefined behaviour.
-
- :param address: The address of the friend (returned by tox_self_get_address of the friend you wish to add) it
- must be TOX_ADDRESS_SIZE bytes.
- :param message: The message that will be sent along with the friend request.
- :return: the friend number on success, UINT32_MAX on failure.
- """
- tox_err_friend_add = c_int()
- result = Tox.libtoxcore.tox_friend_add(self._tox_pointer, string_to_bin(address), c_char_p(message),
- c_size_t(len(message)), byref(tox_err_friend_add))
- tox_err_friend_add = tox_err_friend_add.value
- if tox_err_friend_add == TOX_ERR_FRIEND_ADD['OK']:
- return result
- elif tox_err_friend_add == TOX_ERR_FRIEND_ADD['NULL']:
- raise ArgumentError('One of the arguments to the function was NULL when it was not expected.')
- elif tox_err_friend_add == TOX_ERR_FRIEND_ADD['TOO_LONG']:
- raise ArgumentError('The length of the friend request message exceeded TOX_MAX_FRIEND_REQUEST_LENGTH.')
- elif tox_err_friend_add == TOX_ERR_FRIEND_ADD['NO_MESSAGE']:
- raise ArgumentError('The friend request message was empty. This, and the TOO_LONG code will never be'
- ' returned from tox_friend_add_norequest.')
- elif tox_err_friend_add == TOX_ERR_FRIEND_ADD['OWN_KEY']:
- raise ArgumentError('The friend address belongs to the sending client.')
- elif tox_err_friend_add == TOX_ERR_FRIEND_ADD['ALREADY_SENT']:
- raise ArgumentError('A friend request has already been sent, or the address belongs to a friend that is'
- ' already on the friend list.')
- elif tox_err_friend_add == TOX_ERR_FRIEND_ADD['BAD_CHECKSUM']:
- raise ArgumentError('The friend address checksum failed.')
- elif tox_err_friend_add == TOX_ERR_FRIEND_ADD['SET_NEW_NOSPAM']:
- raise ArgumentError('The friend was already there, but the nospam value was different.')
- elif tox_err_friend_add == TOX_ERR_FRIEND_ADD['MALLOC']:
- raise MemoryError('A memory allocation failed when trying to increase the friend list size.')
-
- def friend_add_norequest(self, public_key):
- """
- Add a friend without sending a friend request.
-
- This function is used to add a friend in response to a friend request. If the client receives a friend request,
- it can be reasonably sure that the other client added this client as a friend, eliminating the need for a friend
- request.
-
- This function is also useful in a situation where both instances are controlled by the same entity, so that this
- entity can perform the mutual friend adding. In this case, there is no need for a friend request, either.
-
- :param public_key: A byte array of length TOX_PUBLIC_KEY_SIZE containing the Public Key (not the Address) of the
- friend to add.
- :return: the friend number on success, UINT32_MAX on failure.
- """
- tox_err_friend_add = c_int()
- result = Tox.libtoxcore.tox_friend_add_norequest(self._tox_pointer, string_to_bin(public_key),
- byref(tox_err_friend_add))
- tox_err_friend_add = tox_err_friend_add.value
- if tox_err_friend_add == TOX_ERR_FRIEND_ADD['OK']:
- return result
- elif tox_err_friend_add == TOX_ERR_FRIEND_ADD['NULL']:
- raise ArgumentError('One of the arguments to the function was NULL when it was not expected.')
- elif tox_err_friend_add == TOX_ERR_FRIEND_ADD['TOO_LONG']:
- raise ArgumentError('The length of the friend request message exceeded TOX_MAX_FRIEND_REQUEST_LENGTH.')
- elif tox_err_friend_add == TOX_ERR_FRIEND_ADD['NO_MESSAGE']:
- raise ArgumentError('The friend request message was empty. This, and the TOO_LONG code will never be'
- ' returned from tox_friend_add_norequest.')
- elif tox_err_friend_add == TOX_ERR_FRIEND_ADD['OWN_KEY']:
- raise ArgumentError('The friend address belongs to the sending client.')
- elif tox_err_friend_add == TOX_ERR_FRIEND_ADD['ALREADY_SENT']:
- raise ArgumentError('A friend request has already been sent, or the address belongs to a friend that is'
- ' already on the friend list.')
- elif tox_err_friend_add == TOX_ERR_FRIEND_ADD['BAD_CHECKSUM']:
- raise ArgumentError('The friend address checksum failed.')
- elif tox_err_friend_add == TOX_ERR_FRIEND_ADD['SET_NEW_NOSPAM']:
- raise ArgumentError('The friend was already there, but the nospam value was different.')
- elif tox_err_friend_add == TOX_ERR_FRIEND_ADD['MALLOC']:
- raise MemoryError('A memory allocation failed when trying to increase the friend list size.')
-
- def friend_delete(self, friend_number):
- """
- Remove a friend from the friend list.
-
- This does not notify the friend of their deletion. After calling this function, this client will appear offline
- to the friend and no communication can occur between the two.
-
- :param friend_number: Friend number for the friend to be deleted.
- :return: True on success.
- """
- tox_err_friend_delete = c_int()
- result = Tox.libtoxcore.tox_friend_delete(self._tox_pointer, c_uint32(friend_number),
- byref(tox_err_friend_delete))
- tox_err_friend_delete = tox_err_friend_delete.value
- if tox_err_friend_delete == TOX_ERR_FRIEND_DELETE['OK']:
- return bool(result)
- elif tox_err_friend_delete == TOX_ERR_FRIEND_DELETE['FRIEND_NOT_FOUND']:
- raise ArgumentError('There was no friend with the given friend number. No friends were deleted.')
-
- # -----------------------------------------------------------------------------------------------------------------
- # Friend list queries
- # -----------------------------------------------------------------------------------------------------------------
-
- def friend_by_public_key(self, public_key):
- """
- Return the friend number associated with that Public Key.
-
- :param public_key: A byte array containing the Public Key.
- :return: friend number
- """
- tox_err_friend_by_public_key = c_int()
- result = Tox.libtoxcore.tox_friend_by_public_key(self._tox_pointer, string_to_bin(public_key),
- byref(tox_err_friend_by_public_key))
- tox_err_friend_by_public_key = tox_err_friend_by_public_key.value
- if tox_err_friend_by_public_key == TOX_ERR_FRIEND_BY_PUBLIC_KEY['OK']:
- return result
- elif tox_err_friend_by_public_key == TOX_ERR_FRIEND_BY_PUBLIC_KEY['NULL']:
- raise ArgumentError('One of the arguments to the function was NULL when it was not expected.')
- elif tox_err_friend_by_public_key == TOX_ERR_FRIEND_BY_PUBLIC_KEY['NOT_FOUND']:
- raise ArgumentError('No friend with the given Public Key exists on the friend list.')
-
- def friend_exists(self, friend_number):
- """
- Checks if a friend with the given friend number exists and returns true if it does.
- """
- return bool(Tox.libtoxcore.tox_friend_exists(self._tox_pointer, c_uint32(friend_number)))
-
- def self_get_friend_list_size(self):
- """
- Return the number of friends on the friend list.
-
- This function can be used to determine how much memory to allocate for tox_self_get_friend_list.
-
- :return: number of friends
- """
- return Tox.libtoxcore.tox_self_get_friend_list_size(self._tox_pointer)
-
- def self_get_friend_list(self, friend_list=None):
- """
- Copy a list of valid friend numbers into an array.
-
- Call tox_self_get_friend_list_size to determine the number of elements to allocate.
-
- :param friend_list: pointer (c_char_p) to a memory region with enough space to hold the friend list. If this
- parameter is None, this function allocates memory for the friend list.
- :return: friend list
- """
- friend_list_size = self.self_get_friend_list_size()
- if friend_list is None:
- friend_list = create_string_buffer(sizeof(c_uint32) * friend_list_size)
- friend_list = POINTER(c_uint32)(friend_list)
- Tox.libtoxcore.tox_self_get_friend_list(self._tox_pointer, friend_list)
- return friend_list[0:friend_list_size]
-
- def friend_get_public_key(self, friend_number, public_key=None):
- """
- Copies the Public Key associated with a given friend number to a byte array.
-
- :param friend_number: The friend number you want the Public Key of.
- :param public_key: pointer (c_char_p) to a memory region of at least TOX_PUBLIC_KEY_SIZE bytes. If this
- parameter is None, this function allocates memory for Tox Public Key.
- :return: Tox Public Key
- """
- if public_key is None:
- public_key = create_string_buffer(TOX_PUBLIC_KEY_SIZE)
- tox_err_friend_get_public_key = c_int()
- Tox.libtoxcore.tox_friend_get_public_key(self._tox_pointer, c_uint32(friend_number), public_key,
- byref(tox_err_friend_get_public_key))
- tox_err_friend_get_public_key = tox_err_friend_get_public_key.value
- if tox_err_friend_get_public_key == TOX_ERR_FRIEND_GET_PUBLIC_KEY['OK']:
- return bin_to_string(public_key, TOX_PUBLIC_KEY_SIZE)
- elif tox_err_friend_get_public_key == TOX_ERR_FRIEND_GET_PUBLIC_KEY['FRIEND_NOT_FOUND']:
- raise ArgumentError('No friend with the given number exists on the friend list.')
-
- def friend_get_last_online(self, friend_number):
- """
- Return a unix-time timestamp of the last time the friend associated with a given friend number was seen online.
- This function will return UINT64_MAX on error.
-
- :param friend_number: The friend number you want to query.
- :return: unix-time timestamp
- """
- tox_err_last_online = c_int()
- result = Tox.libtoxcore.tox_friend_get_last_online(self._tox_pointer, c_uint32(friend_number),
- byref(tox_err_last_online))
- tox_err_last_online = tox_err_last_online.value
- if tox_err_last_online == TOX_ERR_FRIEND_GET_LAST_ONLINE['OK']:
- return result
- elif tox_err_last_online == TOX_ERR_FRIEND_GET_LAST_ONLINE['FRIEND_NOT_FOUND']:
- raise ArgumentError('No friend with the given number exists on the friend list.')
-
- # -----------------------------------------------------------------------------------------------------------------
- # Friend-specific state queries (can also be received through callbacks)
- # -----------------------------------------------------------------------------------------------------------------
-
- def friend_get_name_size(self, friend_number):
- """
- Return the length of the friend's name. If the friend number is invalid, the return value is unspecified.
-
- The return value is equal to the `length` argument received by the last `friend_name` callback.
- """
- tox_err_friend_query = c_int()
- result = Tox.libtoxcore.tox_friend_get_name_size(self._tox_pointer, c_uint32(friend_number),
- byref(tox_err_friend_query))
- tox_err_friend_query = tox_err_friend_query.value
- if tox_err_friend_query == TOX_ERR_FRIEND_QUERY['OK']:
- return result
- elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['NULL']:
- raise ArgumentError('The pointer parameter for storing the query result (name, message) was NULL. Unlike'
- ' the `_self_` variants of these functions, which have no effect when a parameter is'
- ' NULL, these functions return an error in that case.')
- elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['FRIEND_NOT_FOUND']:
- raise ArgumentError('The friend_number did not designate a valid friend.')
-
- def friend_get_name(self, friend_number, name=None):
- """
- Write the name of the friend designated by the given friend number to a byte array.
-
- Call tox_friend_get_name_size to determine the allocation size for the `name` parameter.
-
- The data written to `name` is equal to the data received by the last `friend_name` callback.
-
- :param friend_number: number of friend
- :param name: pointer (c_char_p) to a valid memory region large enough to store the friend's name.
- :return: name of the friend
- """
- if name is None:
- name = create_string_buffer(self.friend_get_name_size(friend_number))
- tox_err_friend_query = c_int()
- Tox.libtoxcore.tox_friend_get_name(self._tox_pointer, c_uint32(friend_number), name,
- byref(tox_err_friend_query))
- tox_err_friend_query = tox_err_friend_query.value
- if tox_err_friend_query == TOX_ERR_FRIEND_QUERY['OK']:
- return str(name.value, 'utf-8')
- elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['NULL']:
- raise ArgumentError('The pointer parameter for storing the query result (name, message) was NULL. Unlike'
- ' the `_self_` variants of these functions, which have no effect when a parameter is'
- ' NULL, these functions return an error in that case.')
- elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['FRIEND_NOT_FOUND']:
- raise ArgumentError('The friend_number did not designate a valid friend.')
-
- def callback_friend_name(self, callback, user_data):
- """
- Set the callback for the `friend_name` event. Pass None to unset.
-
- This event is triggered when a friend changes their name.
-
- :param callback: Python function. Should take pointer (c_void_p) to Tox object,
- The friend number (c_uint32) of the friend whose name changed,
- A byte array (c_char_p) containing the same data as tox_friend_get_name would write to its `name` parameter,
- A value (c_size_t) equal to the return value of tox_friend_get_name_size,
- pointer (c_void_p) to user_data
- :param user_data: pointer (c_void_p) to user data
- """
- c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_char_p, c_size_t, c_void_p)
- self.friend_name_cb = c_callback(callback)
- Tox.libtoxcore.tox_callback_friend_name(self._tox_pointer, self.friend_name_cb, user_data)
-
- def friend_get_status_message_size(self, friend_number):
- """
- Return the length of the friend's status message. If the friend number is invalid, the return value is SIZE_MAX.
-
- :return: length of the friend's status message
- """
- tox_err_friend_query = c_int()
- result = Tox.libtoxcore.tox_friend_get_status_message_size(self._tox_pointer, c_uint32(friend_number),
- byref(tox_err_friend_query))
- tox_err_friend_query = tox_err_friend_query.value
- if tox_err_friend_query == TOX_ERR_FRIEND_QUERY['OK']:
- return result
- elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['NULL']:
- raise ArgumentError('The pointer parameter for storing the query result (name, message) was NULL. Unlike'
- ' the `_self_` variants of these functions, which have no effect when a parameter is'
- ' NULL, these functions return an error in that case.')
- elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['FRIEND_NOT_FOUND']:
- raise ArgumentError('The friend_number did not designate a valid friend.')
-
- def friend_get_status_message(self, friend_number, status_message=None):
- """
- Write the status message of the friend designated by the given friend number to a byte array.
-
- Call tox_friend_get_status_message_size to determine the allocation size for the `status_name` parameter.
-
- The data written to `status_message` is equal to the data received by the last `friend_status_message` callback.
-
- :param friend_number:
- :param status_message: pointer (c_char_p) to a valid memory region large enough to store the friend's status
- message.
- :return: status message of the friend
- """
- if status_message is None:
- status_message = create_string_buffer(self.friend_get_status_message_size(friend_number))
- tox_err_friend_query = c_int()
- Tox.libtoxcore.tox_friend_get_status_message(self._tox_pointer, c_uint32(friend_number), status_message,
- byref(tox_err_friend_query))
- tox_err_friend_query = tox_err_friend_query.value
- if tox_err_friend_query == TOX_ERR_FRIEND_QUERY['OK']:
- return str(status_message.value, 'utf-8')
- elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['NULL']:
- raise ArgumentError('The pointer parameter for storing the query result (name, message) was NULL. Unlike'
- ' the `_self_` variants of these functions, which have no effect when a parameter is'
- ' NULL, these functions return an error in that case.')
- elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['FRIEND_NOT_FOUND']:
- raise ArgumentError('The friend_number did not designate a valid friend.')
-
- def callback_friend_status_message(self, callback, user_data):
- """
- Set the callback for the `friend_status_message` event. Pass NULL to unset.
-
- This event is triggered when a friend changes their status message.
-
- :param callback: Python function. Should take pointer (c_void_p) to Tox object,
- The friend number (c_uint32) of the friend whose status message changed,
- A byte array (c_char_p) containing the same data as tox_friend_get_status_message would write to its
- `status_message` parameter,
- A value (c_size_t) equal to the return value of tox_friend_get_status_message_size,
- pointer (c_void_p) to user_data
- :param user_data: pointer (c_void_p) to user data
- """
- c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_char_p, c_size_t, c_void_p)
- self.friend_status_message_cb = c_callback(callback)
- Tox.libtoxcore.tox_callback_friend_status_message(self._tox_pointer,
- self.friend_status_message_cb, c_void_p(user_data))
-
- def friend_get_status(self, friend_number):
- """
- Return the friend's user status (away/busy/...). If the friend number is invalid, the return value is
- unspecified.
-
- The status returned is equal to the last status received through the `friend_status` callback.
-
- :return: TOX_USER_STATUS
- """
- tox_err_friend_query = c_int()
- result = Tox.libtoxcore.tox_friend_get_status(self._tox_pointer, c_uint32(friend_number),
- byref(tox_err_friend_query))
- tox_err_friend_query = tox_err_friend_query.value
- if tox_err_friend_query == TOX_ERR_FRIEND_QUERY['OK']:
- return result
- elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['NULL']:
- raise ArgumentError('The pointer parameter for storing the query result (name, message) was NULL. Unlike'
- ' the `_self_` variants of these functions, which have no effect when a parameter is'
- ' NULL, these functions return an error in that case.')
- elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['FRIEND_NOT_FOUND']:
- raise ArgumentError('The friend_number did not designate a valid friend.')
-
- def callback_friend_status(self, callback, user_data):
- """
- Set the callback for the `friend_status` event. Pass None to unset.
-
- This event is triggered when a friend changes their user status.
-
- :param callback: Python function. Should take pointer (c_void_p) to Tox object,
- The friend number (c_uint32) of the friend whose user status changed,
- The new user status (TOX_USER_STATUS),
- pointer (c_void_p) to user_data
- :param user_data: pointer (c_void_p) to user data
- """
- c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_int, c_void_p)
- self.friend_status_cb = c_callback(callback)
- Tox.libtoxcore.tox_callback_friend_status(self._tox_pointer, self.friend_status_cb, c_void_p(user_data))
-
- def friend_get_connection_status(self, friend_number):
- """
- Check whether a friend is currently connected to this client.
-
- The result of this function is equal to the last value received by the `friend_connection_status` callback.
-
- :param friend_number: The friend number for which to query the connection status.
- :return: the friend's connection status (TOX_CONNECTION) as it was received through the
- `friend_connection_status` event.
- """
- tox_err_friend_query = c_int()
- result = Tox.libtoxcore.tox_friend_get_connection_status(self._tox_pointer, c_uint32(friend_number),
- byref(tox_err_friend_query))
- tox_err_friend_query = tox_err_friend_query.value
- if tox_err_friend_query == TOX_ERR_FRIEND_QUERY['OK']:
- return result
- elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['NULL']:
- raise ArgumentError('The pointer parameter for storing the query result (name, message) was NULL. Unlike'
- ' the `_self_` variants of these functions, which have no effect when a parameter is'
- ' NULL, these functions return an error in that case.')
- elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['FRIEND_NOT_FOUND']:
- raise ArgumentError('The friend_number did not designate a valid friend.')
-
- def callback_friend_connection_status(self, callback, user_data):
- """
- Set the callback for the `friend_connection_status` event. Pass NULL to unset.
-
- This event is triggered when a friend goes offline after having been online, or when a friend goes online.
-
- This callback is not called when adding friends. It is assumed that when adding friends, their connection status
- is initially offline.
-
- :param callback: Python function. Should take pointer (c_void_p) to Tox object,
- The friend number (c_uint32) of the friend whose connection status changed,
- The result of calling tox_friend_get_connection_status (TOX_CONNECTION) on the passed friend_number,
- pointer (c_void_p) to user_data
- :param user_data: pointer (c_void_p) to user data
- """
- c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_int, c_void_p)
- self.friend_connection_status_cb = c_callback(callback)
- Tox.libtoxcore.tox_callback_friend_connection_status(self._tox_pointer,
- self.friend_connection_status_cb, c_void_p(user_data))
-
- def friend_get_typing(self, friend_number):
- """
- Check whether a friend is currently typing a message.
-
- :param friend_number: The friend number for which to query the typing status.
- :return: true if the friend is typing.
- """
- tox_err_friend_query = c_int()
- result = Tox.libtoxcore.tox_friend_get_typing(self._tox_pointer, c_uint32(friend_number),
- byref(tox_err_friend_query))
- tox_err_friend_query = tox_err_friend_query.value
- if tox_err_friend_query == TOX_ERR_FRIEND_QUERY['OK']:
- return bool(result)
- elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['NULL']:
- raise ArgumentError('The pointer parameter for storing the query result (name, message) was NULL. Unlike'
- ' the `_self_` variants of these functions, which have no effect when a parameter is'
- ' NULL, these functions return an error in that case.')
- elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['FRIEND_NOT_FOUND']:
- raise ArgumentError('The friend_number did not designate a valid friend.')
-
- def callback_friend_typing(self, callback, user_data):
- """
- Set the callback for the `friend_typing` event. Pass NULL to unset.
-
- This event is triggered when a friend starts or stops typing.
-
- :param callback: Python function. Should take pointer (c_void_p) to Tox object,
- The friend number (c_uint32) of the friend who started or stopped typing,
- The result of calling tox_friend_get_typing (c_bool) on the passed friend_number,
- pointer (c_void_p) to user_data
- :param user_data: pointer (c_void_p) to user data
- """
- c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_bool, c_void_p)
- self.friend_typing_cb = c_callback(callback)
- Tox.libtoxcore.tox_callback_friend_typing(self._tox_pointer, self.friend_typing_cb, c_void_p(user_data))
-
- # -----------------------------------------------------------------------------------------------------------------
- # Sending private messages
- # -----------------------------------------------------------------------------------------------------------------
-
- def self_set_typing(self, friend_number, typing):
- """
- Set the client's typing status for a friend.
-
- The client is responsible for turning it on or off.
-
- :param friend_number: The friend to which the client is typing a message.
- :param typing: The typing status. True means the client is typing.
- :return: True on success.
- """
- tox_err_set_typing = c_int()
- result = Tox.libtoxcore.tox_self_set_typing(self._tox_pointer, c_uint32(friend_number),
- c_bool(typing), byref(tox_err_set_typing))
- tox_err_set_typing = tox_err_set_typing.value
- if tox_err_set_typing == TOX_ERR_SET_TYPING['OK']:
- return bool(result)
- elif tox_err_set_typing == TOX_ERR_SET_TYPING['FRIEND_NOT_FOUND']:
- raise ArgumentError('The friend number did not designate a valid friend.')
-
- def friend_send_message(self, friend_number, message_type, message):
- """
- Send a text chat message to an online friend.
-
- This function creates a chat message packet and pushes it into the send queue.
-
- The message length may not exceed TOX_MAX_MESSAGE_LENGTH. Larger messages must be split by the client and sent
- as separate messages. Other clients can then reassemble the fragments. Messages may not be empty.
-
- The return value of this function is the message ID. If a read receipt is received, the triggered
- `friend_read_receipt` event will be passed this message ID.
-
- Message IDs are unique per friend. The first message ID is 0. Message IDs are incremented by 1 each time a
- message is sent. If UINT32_MAX messages were sent, the next message ID is 0.
-
- :param friend_number: The friend number of the friend to send the message to.
- :param message_type: Message type (TOX_MESSAGE_TYPE).
- :param message: A non-None message text.
- :return: message ID
- """
- tox_err_friend_send_message = c_int()
- result = Tox.libtoxcore.tox_friend_send_message(self._tox_pointer, c_uint32(friend_number),
- c_int(message_type), c_char_p(message), c_size_t(len(message)),
- byref(tox_err_friend_send_message))
- tox_err_friend_send_message = tox_err_friend_send_message.value
- if tox_err_friend_send_message == TOX_ERR_FRIEND_SEND_MESSAGE['OK']:
- return result
- elif tox_err_friend_send_message == TOX_ERR_FRIEND_SEND_MESSAGE['NULL']:
- raise ArgumentError('One of the arguments to the function was NULL when it was not expected.')
- elif tox_err_friend_send_message == TOX_ERR_FRIEND_SEND_MESSAGE['FRIEND_NOT_FOUND']:
- raise ArgumentError('The friend number did not designate a valid friend.')
- elif tox_err_friend_send_message == TOX_ERR_FRIEND_SEND_MESSAGE['FRIEND_NOT_CONNECTED']:
- raise ArgumentError('This client is currently not connected to the friend.')
- elif tox_err_friend_send_message == TOX_ERR_FRIEND_SEND_MESSAGE['SENDQ']:
- raise MemoryError('An allocation error occurred while increasing the send queue size.')
- elif tox_err_friend_send_message == TOX_ERR_FRIEND_SEND_MESSAGE['TOO_LONG']:
- raise ArgumentError('Message length exceeded TOX_MAX_MESSAGE_LENGTH.')
- elif tox_err_friend_send_message == TOX_ERR_FRIEND_SEND_MESSAGE['EMPTY']:
- raise ArgumentError('Attempted to send a zero-length message.')
-
- def callback_friend_read_receipt(self, callback, user_data):
- """
- Set the callback for the `friend_read_receipt` event. Pass None to unset.
-
- This event is triggered when the friend receives the message sent with tox_friend_send_message with the
- corresponding message ID.
-
- :param callback: Python function. Should take pointer (c_void_p) to Tox object,
- The friend number (c_uint32) of the friend who received the message,
- The message ID (c_uint32) as returned from tox_friend_send_message corresponding to the message sent,
- pointer (c_void_p) to user_data
- :param user_data: pointer (c_void_p) to user data
- """
- c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_void_p)
- self.friend_read_receipt_cb = c_callback(callback)
- Tox.libtoxcore.tox_callback_friend_read_receipt(self._tox_pointer,
- self.friend_read_receipt_cb, c_void_p(user_data))
-
- # -----------------------------------------------------------------------------------------------------------------
- # Receiving private messages and friend requests
- # -----------------------------------------------------------------------------------------------------------------
-
- def callback_friend_request(self, callback, user_data):
- """
- Set the callback for the `friend_request` event. Pass None to unset.
-
- This event is triggered when a friend request is received.
-
- :param callback: Python function. Should take pointer (c_void_p) to Tox object,
- The Public Key (c_uint8 array) of the user who sent the friend request,
- The message (c_char_p) they sent along with the request,
- The size (c_size_t) of the message byte array,
- pointer (c_void_p) to user_data
- :param user_data: pointer (c_void_p) to user data
- """
- c_callback = CFUNCTYPE(None, c_void_p, POINTER(c_uint8), c_char_p, c_size_t, c_void_p)
- self.friend_request_cb = c_callback(callback)
- Tox.libtoxcore.tox_callback_friend_request(self._tox_pointer, self.friend_request_cb, c_void_p(user_data))
-
- def callback_friend_message(self, callback, user_data):
- """
- Set the callback for the `friend_message` event. Pass None to unset.
-
- This event is triggered when a message from a friend is received.
-
- :param callback: Python function. Should take pointer (c_void_p) to Tox object,
- The friend number (c_uint32) of the friend who sent the message,
- Message type (TOX_MESSAGE_TYPE),
- The message data (c_char_p) they sent,
- The size (c_size_t) of the message byte array.
- pointer (c_void_p) to user_data
- :param user_data: pointer (c_void_p) to user data
- """
- c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_int, c_char_p, c_size_t, c_void_p)
- self.friend_message_cb = c_callback(callback)
- Tox.libtoxcore.tox_callback_friend_message(self._tox_pointer, self.friend_message_cb, c_void_p(user_data))
-
- # -----------------------------------------------------------------------------------------------------------------
- # File transmission: common between sending and receiving
- # -----------------------------------------------------------------------------------------------------------------
-
- @staticmethod
- def hash(data, hash=None):
- """
- Generates a cryptographic hash of the given data.
-
- This function may be used by clients for any purpose, but is provided primarily for validating cached avatars.
- This use is highly recommended to avoid unnecessary avatar updates.
-
- If hash is NULL or data is NULL while length is not 0 the function returns false, otherwise it returns true.
-
- This function is a wrapper to internal message-digest functions.
-
- :param hash: A valid memory location the hash data. It must be at least TOX_HASH_LENGTH bytes in size.
- :param data: Data to be hashed or NULL.
- :return: true if hash was not NULL.
- """
- if hash is None:
- hash = create_string_buffer(TOX_HASH_LENGTH)
- Tox.libtoxcore.tox_hash(hash, c_char_p(data), len(data))
- return bin_to_string(hash, TOX_HASH_LENGTH)
-
- def file_control(self, friend_number, file_number, control):
- """
- Sends a file control command to a friend for a given file transfer.
-
- :param friend_number: The friend number of the friend the file is being transferred to or received from.
- :param file_number: The friend-specific identifier for the file transfer.
- :param control: The control (TOX_FILE_CONTROL) command to send.
- :return: True on success.
- """
- tox_err_file_control = c_int()
- result = Tox.libtoxcore.tox_file_control(self._tox_pointer, c_uint32(friend_number), c_uint32(file_number),
- c_int(control), byref(tox_err_file_control))
- tox_err_file_control = tox_err_file_control.value
- if tox_err_file_control == TOX_ERR_FILE_CONTROL['OK']:
- return bool(result)
- elif tox_err_file_control == TOX_ERR_FILE_CONTROL['FRIEND_NOT_FOUND']:
- raise ArgumentError('The friend_number passed did not designate a valid friend.')
- elif tox_err_file_control == TOX_ERR_FILE_CONTROL['FRIEND_NOT_CONNECTED']:
- raise ArgumentError('This client is currently not connected to the friend.')
- elif tox_err_file_control == TOX_ERR_FILE_CONTROL['NOT_FOUND']:
- raise ArgumentError('No file transfer with the given file number was found for the given friend.')
- elif tox_err_file_control == TOX_ERR_FILE_CONTROL['NOT_PAUSED']:
- raise RuntimeError('A RESUME control was sent, but the file transfer is running normally.')
- elif tox_err_file_control == TOX_ERR_FILE_CONTROL['DENIED']:
- raise RuntimeError('A RESUME control was sent, but the file transfer was paused by the other party. Only '
- 'the party that paused the transfer can resume it.')
- elif tox_err_file_control == TOX_ERR_FILE_CONTROL['ALREADY_PAUSED']:
- raise RuntimeError('A PAUSE control was sent, but the file transfer was already paused.')
- elif tox_err_file_control == TOX_ERR_FILE_CONTROL['SENDQ']:
- raise RuntimeError('Packet queue is full.')
-
- def callback_file_recv_control(self, callback, user_data):
- """
- Set the callback for the `file_recv_control` event. Pass NULL to unset.
-
- This event is triggered when a file control command is received from a friend.
-
- :param callback: Python function.
- When receiving TOX_FILE_CONTROL_CANCEL, the client should release the resources associated with the file number
- and consider the transfer failed.
-
- Should take pointer (c_void_p) to Tox object,
- The friend number (c_uint32) of the friend who is sending the file.
- The friend-specific file number (c_uint32) the data received is associated with.
- The file control (TOX_FILE_CONTROL) command received.
- pointer (c_void_p) to user_data
- :param user_data: pointer (c_void_p) to user data
- """
- c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_int, c_void_p)
- self.file_recv_control_cb = c_callback(callback)
- Tox.libtoxcore.tox_callback_file_recv_control(self._tox_pointer,
- self.file_recv_control_cb, user_data)
-
- def file_seek(self, friend_number, file_number, position):
- """
- Sends a file seek control command to a friend for a given file transfer.
-
- This function can only be called to resume a file transfer right before TOX_FILE_CONTROL_RESUME is sent.
-
- :param friend_number: The friend number of the friend the file is being received from.
- :param file_number: The friend-specific identifier for the file transfer.
- :param position: The position that the file should be seeked to.
- :return: True on success.
- """
- tox_err_file_seek = c_int()
- result = Tox.libtoxcore.tox_file_control(self._tox_pointer, c_uint32(friend_number), c_uint32(file_number),
- c_uint64(position), byref(tox_err_file_seek))
- tox_err_file_seek = tox_err_file_seek.value
- if tox_err_file_seek == TOX_ERR_FILE_SEEK['OK']:
- return bool(result)
- elif tox_err_file_seek == TOX_ERR_FILE_SEEK['FRIEND_NOT_FOUND']:
- raise ArgumentError('The friend_number passed did not designate a valid friend.')
- elif tox_err_file_seek == TOX_ERR_FILE_SEEK['FRIEND_NOT_CONNECTED']:
- raise ArgumentError('This client is currently not connected to the friend.')
- elif tox_err_file_seek == TOX_ERR_FILE_SEEK['NOT_FOUND']:
- raise ArgumentError('No file transfer with the given file number was found for the given friend.')
- elif tox_err_file_seek == TOX_ERR_FILE_SEEK['SEEK_DENIED']:
- raise IOError('File was not in a state where it could be seeked.')
- elif tox_err_file_seek == TOX_ERR_FILE_SEEK['INVALID_POSITION']:
- raise ArgumentError('Seek position was invalid')
- elif tox_err_file_seek == TOX_ERR_FILE_SEEK['SENDQ']:
- raise RuntimeError('Packet queue is full.')
-
- def file_get_file_id(self, friend_number, file_number, file_id=None):
- """
- Copy the file id associated to the file transfer to a byte array.
-
- :param friend_number: The friend number of the friend the file is being transferred to or received from.
- :param file_number: The friend-specific identifier for the file transfer.
- :param file_id: A pointer (c_char_p) to memory region of at least TOX_FILE_ID_LENGTH bytes. If this parameter is
- None, this function has no effect.
- :return: file id.
- """
- if file_id is None:
- file_id = create_string_buffer(TOX_FILE_ID_LENGTH)
- tox_err_file_get = c_int()
- Tox.libtoxcore.tox_file_get_file_id(self._tox_pointer, c_uint32(friend_number), c_uint32(file_number), file_id,
- byref(tox_err_file_get))
- tox_err_file_get = tox_err_file_get.value
- if tox_err_file_get == TOX_ERR_FILE_GET['OK']:
- return bin_to_string(file_id, TOX_FILE_ID_LENGTH)
- elif tox_err_file_get == TOX_ERR_FILE_GET['NULL']:
- raise ArgumentError('One of the arguments to the function was NULL when it was not expected.')
- elif tox_err_file_get == TOX_ERR_FILE_GET['FRIEND_NOT_FOUND']:
- raise ArgumentError('The friend_number passed did not designate a valid friend.')
- elif tox_err_file_get == TOX_ERR_FILE_GET['NOT_FOUND']:
- raise ArgumentError('No file transfer with the given file number was found for the given friend.')
-
- # -----------------------------------------------------------------------------------------------------------------
- # File transmission: sending
- # -----------------------------------------------------------------------------------------------------------------
-
- def file_send(self, friend_number, kind, file_size, file_id, filename):
- """
- Send a file transmission request.
-
- Maximum filename length is TOX_MAX_FILENAME_LENGTH bytes. The filename should generally just be a file name, not
- a path with directory names.
-
- If a non-UINT64_MAX file size is provided, it can be used by both sides to determine the sending progress. File
- size can be set to UINT64_MAX for streaming data of unknown size.
-
- File transmission occurs in chunks, which are requested through the `file_chunk_request` event.
-
- When a friend goes offline, all file transfers associated with the friend are purged from core.
-
- If the file contents change during a transfer, the behaviour is unspecified in general. What will actually
- happen depends on the mode in which the file was modified and how the client determines the file size.
-
- - If the file size was increased
- - and sending mode was streaming (file_size = UINT64_MAX), the behaviour will be as expected.
- - and sending mode was file (file_size != UINT64_MAX), the file_chunk_request callback will receive length =
- 0 when Core thinks the file transfer has finished. If the client remembers the file size as it was when
- sending the request, it will terminate the transfer normally. If the client re-reads the size, it will think
- the friend cancelled the transfer.
- - If the file size was decreased
- - and sending mode was streaming, the behaviour is as expected.
- - and sending mode was file, the callback will return 0 at the new (earlier) end-of-file, signalling to the
- friend that the transfer was cancelled.
- - If the file contents were modified
- - at a position before the current read, the two files (local and remote) will differ after the transfer
- terminates.
- - at a position after the current read, the file transfer will succeed as expected.
- - In either case, both sides will regard the transfer as complete and successful.
-
- :param friend_number: The friend number of the friend the file send request should be sent to.
- :param kind: The meaning of the file to be sent.
- :param file_size: Size in bytes of the file the client wants to send, UINT64_MAX if unknown or streaming.
- :param file_id: A file identifier of length TOX_FILE_ID_LENGTH that can be used to uniquely identify file
- transfers across core restarts. If NULL, a random one will be generated by core. It can then be obtained by
- using tox_file_get_file_id().
- :param filename: Name of the file. Does not need to be the actual name. This name will be sent along with the
- file send request.
- :return: A file number used as an identifier in subsequent callbacks. This number is per friend. File numbers
- are reused after a transfer terminates. On failure, this function returns UINT32_MAX. Any pattern in file
- numbers should not be relied on.
- """
- tox_err_file_send = c_int()
- result = self.libtoxcore.tox_file_send(self._tox_pointer, c_uint32(friend_number), c_uint32(kind),
- c_uint64(file_size),
- string_to_bin(file_id),
- c_char_p(filename),
- c_size_t(len(filename)), byref(tox_err_file_send))
- tox_err_file_send = tox_err_file_send.value
- if tox_err_file_send == TOX_ERR_FILE_SEND['OK']:
- return result
- elif tox_err_file_send == TOX_ERR_FILE_SEND['NULL']:
- raise ArgumentError('One of the arguments to the function was NULL when it was not expected.')
- elif tox_err_file_send == TOX_ERR_FILE_SEND['FRIEND_NOT_FOUND']:
- raise ArgumentError('The friend_number passed did not designate a valid friend.')
- elif tox_err_file_send == TOX_ERR_FILE_SEND['FRIEND_NOT_CONNECTED']:
- raise ArgumentError('This client is currently not connected to the friend.')
- elif tox_err_file_send == TOX_ERR_FILE_SEND['NAME_TOO_LONG']:
- raise ArgumentError('Filename length exceeded TOX_MAX_FILENAME_LENGTH bytes.')
- elif tox_err_file_send == TOX_ERR_FILE_SEND['TOO_MANY']:
- raise RuntimeError('Too many ongoing transfers. The maximum number of concurrent file transfers is 256 per'
- 'friend per direction (sending and receiving).')
-
- def file_send_chunk(self, friend_number, file_number, position, data):
- """
- Send a chunk of file data to a friend.
-
- This function is called in response to the `file_chunk_request` callback. The length parameter should be equal
- to the one received though the callback. If it is zero, the transfer is assumed complete. For files with known
- size, Core will know that the transfer is complete after the last byte has been received, so it is not necessary
- (though not harmful) to send a zero-length chunk to terminate. For streams, core will know that the transfer is
- finished if a chunk with length less than the length requested in the callback is sent.
-
- :param friend_number: The friend number of the receiving friend for this file.
- :param file_number: The file transfer identifier returned by tox_file_send.
- :param position: The file or stream position from which to continue reading.
- :param data: Chunk of file data
- :return: true on success.
- """
- tox_err_file_send_chunk = c_int()
- result = self.libtoxcore.tox_file_send_chunk(self._tox_pointer, c_uint32(friend_number), c_uint32(file_number),
- c_uint64(position), c_char_p(data), c_size_t(len(data)),
- byref(tox_err_file_send_chunk))
- tox_err_file_send_chunk = tox_err_file_send_chunk.value
- if tox_err_file_send_chunk == TOX_ERR_FILE_SEND_CHUNK['OK']:
- return bool(result)
- elif tox_err_file_send_chunk == TOX_ERR_FILE_SEND_CHUNK['NULL']:
- raise ArgumentError('The length parameter was non-zero, but data was NULL.')
- elif tox_err_file_send_chunk == TOX_ERR_FILE_SEND_CHUNK['FRIEND_NOT_FOUND']:
- ArgumentError('The friend_number passed did not designate a valid friend.')
- elif tox_err_file_send_chunk == TOX_ERR_FILE_SEND_CHUNK['FRIEND_NOT_CONNECTED']:
- raise ArgumentError('This client is currently not connected to the friend.')
- elif tox_err_file_send_chunk == TOX_ERR_FILE_SEND_CHUNK['NOT_FOUND']:
- raise ArgumentError('No file transfer with the given file number was found for the given friend.')
- elif tox_err_file_send_chunk == TOX_ERR_FILE_SEND_CHUNK['NOT_TRANSFERRING']:
- raise ArgumentError('File transfer was found but isn\'t in a transferring state: (paused, done, broken, '
- 'etc...) (happens only when not called from the request chunk callback).')
- elif tox_err_file_send_chunk == TOX_ERR_FILE_SEND_CHUNK['INVALID_LENGTH']:
- raise ArgumentError('Attempted to send more or less data than requested. The requested data size is '
- 'adjusted according to maximum transmission unit and the expected end of the file. '
- 'Trying to send less or more than requested will return this error.')
- elif tox_err_file_send_chunk == TOX_ERR_FILE_SEND_CHUNK['SENDQ']:
- raise RuntimeError('Packet queue is full.')
- elif tox_err_file_send_chunk == TOX_ERR_FILE_SEND_CHUNK['WRONG_POSITION']:
- raise ArgumentError('Position parameter was wrong.')
-
- def callback_file_chunk_request(self, callback, user_data):
- """
- Set the callback for the `file_chunk_request` event. Pass None to unset.
-
- This event is triggered when Core is ready to send more file data.
-
- :param callback: Python function.
- If the length parameter is 0, the file transfer is finished, and the client's resources associated with the file
- number should be released. After a call with zero length, the file number can be reused for future file
- transfers.
-
- If the requested position is not equal to the client's idea of the current file or stream position, it will need
- to seek. In case of read-once streams, the client should keep the last read chunk so that a seek back can be
- supported. A seek-back only ever needs to read from the last requested chunk. This happens when a chunk was
- requested, but the send failed. A seek-back request can occur an arbitrary number of times for any given chunk.
-
- In response to receiving this callback, the client should call the function `tox_file_send_chunk` with the
- requested chunk. If the number of bytes sent through that function is zero, the file transfer is assumed
- complete. A client must send the full length of data requested with this callback.
-
- Should take pointer (c_void_p) to Tox object,
- The friend number (c_uint32) of the receiving friend for this file.
- The file transfer identifier (c_uint32) returned by tox_file_send.
- The file or stream position (c_uint64) from which to continue reading.
- The number of bytes (c_size_t) requested for the current chunk.
- pointer (c_void_p) to user_data
- :param user_data: pointer (c_void_p) to user data
- """
- c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_uint64, c_size_t, c_void_p)
- self.file_chunk_request_cb = c_callback(callback)
- self.libtoxcore.tox_callback_file_chunk_request(self._tox_pointer, self.file_chunk_request_cb, user_data)
-
- # -----------------------------------------------------------------------------------------------------------------
- # File transmission: receiving
- # -----------------------------------------------------------------------------------------------------------------
-
- def callback_file_recv(self, callback, user_data):
- """
- Set the callback for the `file_recv` event. Pass None to unset.
-
- This event is triggered when a file transfer request is received.
-
- :param callback: Python function.
- The client should acquire resources to be associated with the file transfer. Incoming file transfers start in
- the PAUSED state. After this callback returns, a transfer can be rejected by sending a TOX_FILE_CONTROL_CANCEL
- control command before any other control commands. It can be accepted by sending TOX_FILE_CONTROL_RESUME.
-
- Should take pointer (c_void_p) to Tox object,
- The friend number (c_uint32) of the friend who is sending the file transfer request.
- The friend-specific file number (c_uint32) the data received is associated with.
- The meaning of the file (c_uint32) to be sent.
- Size in bytes (c_uint64) of the file the client wants to send, UINT64_MAX if unknown or streaming.
- Name of the file (c_char_p). Does not need to be the actual name. This name will be sent along with the file
- send request.
- Size in bytes (c_size_t) of the filename.
- pointer (c_void_p) to user_data
- :param user_data: pointer (c_void_p) to user data
- """
- c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_uint32, c_uint64, c_char_p, c_size_t, c_void_p)
- self.file_recv_cb = c_callback(callback)
- self.libtoxcore.tox_callback_file_recv(self._tox_pointer, self.file_recv_cb, user_data)
-
- def callback_file_recv_chunk(self, callback, user_data):
- """
- Set the callback for the `file_recv_chunk` event. Pass NULL to unset.
-
- This event is first triggered when a file transfer request is received, and subsequently when a chunk of file
- data for an accepted request was received.
-
- :param callback: Python function.
- When length is 0, the transfer is finished and the client should release the resources it acquired for the
- transfer. After a call with length = 0, the file number can be reused for new file transfers.
-
- If position is equal to file_size (received in the file_receive callback) when the transfer finishes, the file
- was received completely. Otherwise, if file_size was UINT64_MAX, streaming ended successfully when length is 0.
-
- Should take pointer (c_void_p) to Tox object,
- The friend number (c_uint32) of the friend who is sending the file.
- The friend-specific file number (c_uint32) the data received is associated with.
- The file position (c_uint64) of the first byte in data.
- A byte array (c_char_p) containing the received chunk.
- The length (c_size_t) of the received chunk.
- pointer (c_void_p) to user_data
- :param user_data: pointer (c_void_p) to user data
- """
- c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_uint64, POINTER(c_uint8), c_size_t, c_void_p)
- self.file_recv_chunk_cb = c_callback(callback)
- self.libtoxcore.tox_callback_file_recv_chunk(self._tox_pointer, self.file_recv_chunk_cb, user_data)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Low-level custom packet sending and receiving
- # -----------------------------------------------------------------------------------------------------------------
-
- def friend_send_lossy_packet(self, friend_number, data):
- """
- Send a custom lossy packet to a friend.
- The first byte of data must be in the range 200-254. Maximum length of a
- custom packet is TOX_MAX_CUSTOM_PACKET_SIZE.
-
- Lossy packets behave like UDP packets, meaning they might never reach the
- other side or might arrive more than once (if someone is messing with the
- connection) or might arrive in the wrong order.
-
- Unless latency is an issue, it is recommended that you use lossless custom packets instead.
-
- :param friend_number: The friend number of the friend this lossy packet
- :param data: python string containing the packet data
- :return: True on success.
- """
- tox_err_friend_custom_packet = c_int()
- result = self.libtoxcore.tox_friend_send_lossy_packet(self._tox_pointer, c_uint32(friend_number),
- c_char_p(data), c_size_t(len(data)),
- byref(tox_err_friend_custom_packet))
- tox_err_friend_custom_packet = tox_err_friend_custom_packet.value
- if tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['OK']:
- return bool(result)
- elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['NULL']:
- raise ArgumentError('One of the arguments to the function was NULL when it was not expected.')
- elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['FRIEND_NOT_FOUND']:
- raise ArgumentError('The friend number did not designate a valid friend.')
- elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['FRIEND_NOT_CONNECTED']:
- raise ArgumentError('This client is currently not connected to the friend.')
- elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['INVALID']:
- raise ArgumentError('The first byte of data was not in the specified range for the packet type.'
- 'This range is 200-254 for lossy, and 160-191 for lossless packets.')
- elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['EMPTY']:
- raise ArgumentError('Attempted to send an empty packet.')
- elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['TOO_LONG']:
- raise ArgumentError('Packet data length exceeded TOX_MAX_CUSTOM_PACKET_SIZE.')
- elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['SENDQ']:
- raise RuntimeError('Packet queue is full.')
-
- def friend_send_lossless_packet(self, friend_number, data):
- """
- Send a custom lossless packet to a friend.
- The first byte of data must be in the range 160-191. Maximum length of a
- custom packet is TOX_MAX_CUSTOM_PACKET_SIZE.
-
- Lossless packet behaviour is comparable to TCP (reliability, arrive in order)
- but with packets instead of a stream.
-
- :param friend_number: The friend number of the friend this lossless packet
- :param data: python string containing the packet data
- :return: True on success.
- """
- tox_err_friend_custom_packet = c_int()
- result = self.libtoxcore.tox_friend_send_lossless_packet(self._tox_pointer, c_uint32(friend_number),
- c_char_p(data), c_size_t(len(data)),
- byref(tox_err_friend_custom_packet))
- tox_err_friend_custom_packet = tox_err_friend_custom_packet.value
- if tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['OK']:
- return bool(result)
- elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['NULL']:
- raise ArgumentError('One of the arguments to the function was NULL when it was not expected.')
- elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['FRIEND_NOT_FOUND']:
- raise ArgumentError('The friend number did not designate a valid friend.')
- elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['FRIEND_NOT_CONNECTED']:
- raise ArgumentError('This client is currently not connected to the friend.')
- elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['INVALID']:
- raise ArgumentError('The first byte of data was not in the specified range for the packet type.'
- 'This range is 200-254 for lossy, and 160-191 for lossless packets.')
- elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['EMPTY']:
- raise ArgumentError('Attempted to send an empty packet.')
- elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['TOO_LONG']:
- raise ArgumentError('Packet data length exceeded TOX_MAX_CUSTOM_PACKET_SIZE.')
- elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['SENDQ']:
- raise RuntimeError('Packet queue is full.')
-
- def callback_friend_lossy_packet(self, callback, user_data):
- """
- Set the callback for the `friend_lossy_packet` event. Pass NULL to unset.
-
- :param callback: Python function.
- Should take pointer (c_void_p) to Tox object,
- friend_number (c_uint32) - The friend number of the friend who sent a lossy packet,
- A byte array (c_uint8 array) containing the received packet data,
- length (c_size_t) - The length of the packet data byte array,
- pointer (c_void_p) to user_data
- :param user_data: pointer (c_void_p) to user data
- """
- c_callback = CFUNCTYPE(None, c_void_p, c_uint32, POINTER(c_uint8), c_size_t, c_void_p)
- self.friend_lossy_packet_cb = c_callback(callback)
- self.libtoxcore.tox_callback_friend_lossy_packet(self._tox_pointer, self.friend_lossy_packet_cb, user_data)
-
- def callback_friend_lossless_packet(self, callback, user_data):
- """
- Set the callback for the `friend_lossless_packet` event. Pass NULL to unset.
-
- :param callback: Python function.
- Should take pointer (c_void_p) to Tox object,
- friend_number (c_uint32) - The friend number of the friend who sent a lossless packet,
- A byte array (c_uint8 array) containing the received packet data,
- length (c_size_t) - The length of the packet data byte array,
- pointer (c_void_p) to user_data
- :param user_data: pointer (c_void_p) to user data
- """
- c_callback = CFUNCTYPE(None, c_void_p, c_uint32, POINTER(c_uint8), c_size_t, c_void_p)
- self.friend_lossless_packet_cb = c_callback(callback)
- self.libtoxcore.tox_callback_friend_lossless_packet(self._tox_pointer, self.friend_lossless_packet_cb,
- user_data)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Low-level network information
- # -----------------------------------------------------------------------------------------------------------------
-
- def self_get_dht_id(self, dht_id=None):
- """
- Writes the temporary DHT public key of this instance to a byte array.
-
- This can be used in combination with an externally accessible IP address and the bound port (from
- tox_self_get_udp_port) to run a temporary bootstrap node.
-
- Be aware that every time a new instance is created, the DHT public key changes, meaning this cannot be used to
- run a permanent bootstrap node.
-
- :param dht_id: pointer (c_char_p) to a memory region of at least TOX_PUBLIC_KEY_SIZE bytes. If this parameter is
- None, this function allocates memory for dht_id.
- :return: dht_id
- """
- if dht_id is None:
- dht_id = create_string_buffer(TOX_PUBLIC_KEY_SIZE)
- Tox.libtoxcore.tox_self_get_dht_id(self._tox_pointer, dht_id)
- return bin_to_string(dht_id, TOX_PUBLIC_KEY_SIZE)
-
- def self_get_udp_port(self):
- """
- Return the UDP port this Tox instance is bound to.
- """
- tox_err_get_port = c_int()
- result = Tox.libtoxcore.tox_self_get_udp_port(self._tox_pointer, byref(tox_err_get_port))
- tox_err_get_port = tox_err_get_port.value
- if tox_err_get_port == TOX_ERR_GET_PORT['OK']:
- return result
- elif tox_err_get_port == TOX_ERR_GET_PORT['NOT_BOUND']:
- raise RuntimeError('The instance was not bound to any port.')
-
- def self_get_tcp_port(self):
- """
- Return the TCP port this Tox instance is bound to. This is only relevant if the instance is acting as a TCP
- relay.
- """
- tox_err_get_port = c_int()
- result = Tox.libtoxcore.tox_self_get_tcp_port(self._tox_pointer, byref(tox_err_get_port))
- tox_err_get_port = tox_err_get_port.value
- if tox_err_get_port == TOX_ERR_GET_PORT['OK']:
- 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)
diff --git a/toxygen/tox_dns.py b/toxygen/tox_dns.py
deleted file mode 100644
index 26b9619..0000000
--- a/toxygen/tox_dns.py
+++ /dev/null
@@ -1,59 +0,0 @@
-import json
-import urllib.request
-from util import log
-import settings
-from PyQt5 import QtNetwork, QtCore
-
-
-def tox_dns(email):
- """
- TOX DNS 4
- :param email: data like 'groupbot@toxme.io'
- :return: tox id on success else None
- """
- site = email.split('@')[1]
- data = {"action": 3, "name": "{}".format(email)}
- urls = ('https://{}/api'.format(site), 'http://{}/api'.format(site))
- s = settings.Settings.get_instance()
- if not s['proxy_type']: # no proxy
- for url in urls:
- try:
- return send_request(url, data)
- except Exception as ex:
- log('TOX DNS ERROR: ' + str(ex))
- else: # proxy
- netman = QtNetwork.QNetworkAccessManager()
- proxy = QtNetwork.QNetworkProxy()
- proxy.setType(QtNetwork.QNetworkProxy.Socks5Proxy if s['proxy_type'] == 2 else QtNetwork.QNetworkProxy.HttpProxy)
- proxy.setHostName(s['proxy_host'])
- proxy.setPort(s['proxy_port'])
- netman.setProxy(proxy)
- for url in urls:
- try:
- request = QtNetwork.QNetworkRequest()
- request.setUrl(QtCore.QUrl(url))
- request.setHeader(QtNetwork.QNetworkRequest.ContentTypeHeader, "application/json")
- reply = netman.post(request, bytes(json.dumps(data), 'utf-8'))
-
- while not reply.isFinished():
- QtCore.QThread.msleep(1)
- QtCore.QCoreApplication.processEvents()
- data = bytes(reply.readAll().data())
- result = json.loads(str(data, 'utf-8'))
- if not result['c']:
- return result['tox_id']
- except Exception as ex:
- log('TOX DNS ERROR: ' + str(ex))
-
- return None # error
-
-
-def send_request(url, data):
- req = urllib.request.Request(url)
- req.add_header('Content-Type', 'application/json')
- response = urllib.request.urlopen(req, bytes(json.dumps(data), 'utf-8'))
- res = json.loads(str(response.read(), 'utf-8'))
- if not res['c']:
- return res['tox_id']
- else:
- raise LookupError()
diff --git a/toxygen/toxav.py b/toxygen/toxav.py
deleted file mode 100644
index 0ab891c..0000000
--- a/toxygen/toxav.py
+++ /dev/null
@@ -1,362 +0,0 @@
-from ctypes import c_int, POINTER, c_void_p, byref, ArgumentError, c_uint32, CFUNCTYPE, c_size_t, c_uint8, c_uint16
-from ctypes import c_char_p, c_int32, c_bool, cast
-from libtox import LibToxAV
-from toxav_enums import *
-
-
-class ToxAV:
- """
- The ToxAV instance type. Each ToxAV instance can be bound to only one Tox instance, and Tox instance can have only
- one ToxAV instance. One must make sure to close ToxAV instance prior closing Tox instance otherwise undefined
- behaviour occurs. Upon closing of ToxAV instance, all active calls will be forcibly terminated without notifying
- peers.
- """
-
- # -----------------------------------------------------------------------------------------------------------------
- # Creation and destruction
- # -----------------------------------------------------------------------------------------------------------------
-
- def __init__(self, tox_pointer):
- """
- Start new A/V session. There can only be only one session per Tox instance.
-
- :param tox_pointer: pointer to Tox instance
- """
- self.libtoxav = LibToxAV()
- toxav_err_new = c_int()
- self.libtoxav.toxav_new.restype = POINTER(c_void_p)
- self._toxav_pointer = self.libtoxav.toxav_new(tox_pointer, byref(toxav_err_new))
- toxav_err_new = toxav_err_new.value
- if toxav_err_new == TOXAV_ERR_NEW['NULL']:
- raise ArgumentError('One of the arguments to the function was NULL when it was not expected.')
- elif toxav_err_new == TOXAV_ERR_NEW['MALLOC']:
- raise MemoryError('Memory allocation failure while trying to allocate structures required for the A/V '
- 'session.')
- elif toxav_err_new == TOXAV_ERR_NEW['MULTIPLE']:
- raise RuntimeError('Attempted to create a second session for the same Tox instance.')
-
- self.call_state_cb = None
- self.audio_receive_frame_cb = None
- self.video_receive_frame_cb = None
- self.call_cb = None
-
- def __del__(self):
- """
- Releases all resources associated with the A/V session.
-
- If any calls were ongoing, these will be forcibly terminated without notifying peers. After calling this
- function, no other functions may be called and the av pointer becomes invalid.
- """
- self.libtoxav.toxav_kill(self._toxav_pointer)
-
- def get_tox_pointer(self):
- """
- Returns the Tox instance the A/V object was created for.
-
- :return: pointer to the Tox instance
- """
- self.libtoxav.toxav_get_tox.restype = POINTER(c_void_p)
- return self.libtoxav.toxav_get_tox(self._toxav_pointer)
-
- # -----------------------------------------------------------------------------------------------------------------
- # A/V event loop
- # -----------------------------------------------------------------------------------------------------------------
-
- def iteration_interval(self):
- """
- Returns the interval in milliseconds when the next toxav_iterate call should be. If no call is active at the
- moment, this function returns 200.
-
- :return: interval in milliseconds
- """
- return self.libtoxav.toxav_iteration_interval(self._toxav_pointer)
-
- def iterate(self):
- """
- Main loop for the session. This function needs to be called in intervals of toxav_iteration_interval()
- milliseconds. It is best called in the separate thread from tox_iterate.
- """
- self.libtoxav.toxav_iterate(self._toxav_pointer)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Call setup
- # -----------------------------------------------------------------------------------------------------------------
-
- def call(self, friend_number, audio_bit_rate, video_bit_rate):
- """
- Call a friend. This will start ringing the friend.
-
- It is the client's responsibility to stop ringing after a certain timeout, if such behaviour is desired. If the
- client does not stop ringing, the library will not stop until the friend is disconnected. Audio and video
- receiving are both enabled by default.
-
- :param friend_number: The friend number of the friend that should be called.
- :param audio_bit_rate: Audio bit rate in Kb/sec. Set this to 0 to disable audio sending.
- :param video_bit_rate: Video bit rate in Kb/sec. Set this to 0 to disable video sending.
- :return: True on success.
- """
- toxav_err_call = c_int()
- result = self.libtoxav.toxav_call(self._toxav_pointer, c_uint32(friend_number), c_uint32(audio_bit_rate),
- c_uint32(video_bit_rate), byref(toxav_err_call))
- toxav_err_call = toxav_err_call.value
- if toxav_err_call == TOXAV_ERR_CALL['OK']:
- return bool(result)
- elif toxav_err_call == TOXAV_ERR_CALL['MALLOC']:
- raise MemoryError('A resource allocation error occurred while trying to create the structures required for '
- 'the call.')
- elif toxav_err_call == TOXAV_ERR_CALL['SYNC']:
- raise RuntimeError('Synchronization error occurred.')
- elif toxav_err_call == TOXAV_ERR_CALL['FRIEND_NOT_FOUND']:
- raise ArgumentError('The friend number did not designate a valid friend.')
- elif toxav_err_call == TOXAV_ERR_CALL['FRIEND_NOT_CONNECTED']:
- raise ArgumentError('The friend was valid, but not currently connected.')
- elif toxav_err_call == TOXAV_ERR_CALL['FRIEND_ALREADY_IN_CALL']:
- raise ArgumentError('Attempted to call a friend while already in an audio or video call with them.')
- elif toxav_err_call == TOXAV_ERR_CALL['INVALID_BIT_RATE']:
- raise ArgumentError('Audio or video bit rate is invalid.')
-
- def callback_call(self, callback, user_data):
- """
- Set the callback for the `call` event. Pass None to unset.
-
- :param callback: The function for the call callback.
-
- Should take pointer (c_void_p) to ToxAV object,
- The friend number (c_uint32) from which the call is incoming.
- True (c_bool) if friend is sending audio.
- True (c_bool) if friend is sending video.
- pointer (c_void_p) to user_data
- :param user_data: pointer (c_void_p) to user data
- """
- c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_bool, c_bool, c_void_p)
- self.call_cb = c_callback(callback)
- self.libtoxav.toxav_callback_call(self._toxav_pointer, self.call_cb, user_data)
-
- def answer(self, friend_number, audio_bit_rate, video_bit_rate):
- """
- Accept an incoming call.
-
- If answering fails for any reason, the call will still be pending and it is possible to try and answer it later.
- Audio and video receiving are both enabled by default.
-
- :param friend_number: The friend number of the friend that is calling.
- :param audio_bit_rate: Audio bit rate in Kb/sec. Set this to 0 to disable audio sending.
- :param video_bit_rate: Video bit rate in Kb/sec. Set this to 0 to disable video sending.
- :return: True on success.
- """
- toxav_err_answer = c_int()
- result = self.libtoxav.toxav_answer(self._toxav_pointer, c_uint32(friend_number), c_uint32(audio_bit_rate),
- c_uint32(video_bit_rate), byref(toxav_err_answer))
- toxav_err_answer = toxav_err_answer.value
- if toxav_err_answer == TOXAV_ERR_ANSWER['OK']:
- return bool(result)
- elif toxav_err_answer == TOXAV_ERR_ANSWER['SYNC']:
- raise RuntimeError('Synchronization error occurred.')
- elif toxav_err_answer == TOXAV_ERR_ANSWER['CODEC_INITIALIZATION']:
- raise RuntimeError('Failed to initialize codecs for call session. Note that codec initiation will fail if '
- 'there is no receive callback registered for either audio or video.')
- elif toxav_err_answer == TOXAV_ERR_ANSWER['FRIEND_NOT_FOUND']:
- raise ArgumentError('The friend number did not designate a valid friend.')
- elif toxav_err_answer == TOXAV_ERR_ANSWER['FRIEND_NOT_CALLING']:
- raise ArgumentError('The friend was valid, but they are not currently trying to initiate a call. This is '
- 'also returned if this client is already in a call with the friend.')
- elif toxav_err_answer == TOXAV_ERR_ANSWER['INVALID_BIT_RATE']:
- raise ArgumentError('Audio or video bit rate is invalid.')
-
- # -----------------------------------------------------------------------------------------------------------------
- # Call state graph
- # -----------------------------------------------------------------------------------------------------------------
-
- def callback_call_state(self, callback, user_data):
- """
- Set the callback for the `call_state` event. Pass None to unset.
-
- :param callback: Python function.
- The function for the call_state callback.
-
- Should take pointer (c_void_p) to ToxAV object,
- The friend number (c_uint32) for which the call state changed.
- The bitmask of the new call state which is guaranteed to be different than the previous state. The state is set
- to 0 when the call is paused. The bitmask represents all the activities currently performed by the friend.
- pointer (c_void_p) to user_data
- :param user_data: pointer (c_void_p) to user data
- """
- c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_void_p)
- self.call_state_cb = c_callback(callback)
- self.libtoxav.toxav_callback_call_state(self._toxav_pointer, self.call_state_cb, user_data)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Call control
- # -----------------------------------------------------------------------------------------------------------------
-
- def call_control(self, friend_number, control):
- """
- Sends a call control command to a friend.
-
- :param friend_number: The friend number of the friend this client is in a call with.
- :param control: The control command to send.
- :return: True on success.
- """
- toxav_err_call_control = c_int()
- result = self.libtoxav.toxav_call_control(self._toxav_pointer, c_uint32(friend_number), c_int(control),
- byref(toxav_err_call_control))
- toxav_err_call_control = toxav_err_call_control.value
- if toxav_err_call_control == TOXAV_ERR_CALL_CONTROL['OK']:
- return bool(result)
- elif toxav_err_call_control == TOXAV_ERR_CALL_CONTROL['SYNC']:
- raise RuntimeError('Synchronization error occurred.')
- elif toxav_err_call_control == TOXAV_ERR_CALL_CONTROL['FRIEND_NOT_FOUND']:
- raise ArgumentError('The friend_number passed did not designate a valid friend.')
- elif toxav_err_call_control == TOXAV_ERR_CALL_CONTROL['FRIEND_NOT_IN_CALL']:
- raise RuntimeError('This client is currently not in a call with the friend. Before the call is answered, '
- 'only CANCEL is a valid control.')
- elif toxav_err_call_control == TOXAV_ERR_CALL_CONTROL['INVALID_TRANSITION']:
- raise RuntimeError('Happens if user tried to pause an already paused call or if trying to resume a call '
- 'that is not paused.')
-
- # -----------------------------------------------------------------------------------------------------------------
- # TODO Controlling bit rates
- # -----------------------------------------------------------------------------------------------------------------
-
- # -----------------------------------------------------------------------------------------------------------------
- # A/V sending
- # -----------------------------------------------------------------------------------------------------------------
-
- def audio_send_frame(self, friend_number, pcm, sample_count, channels, sampling_rate):
- """
- Send an audio frame to a friend.
-
- The expected format of the PCM data is: [s1c1][s1c2][...][s2c1][s2c2][...]...
- Meaning: sample 1 for channel 1, sample 1 for channel 2, ...
- For mono audio, this has no meaning, every sample is subsequent. For stereo, this means the expected format is
- LRLRLR... with samples for left and right alternating.
-
- :param friend_number: The friend number of the friend to which to send an audio frame.
- :param pcm: An array of audio samples. The size of this array must be sample_count * channels.
- :param sample_count: Number of samples in this frame. Valid numbers here are
- ((sample rate) * (audio length) / 1000), where audio length can be 2.5, 5, 10, 20, 40 or 60 milliseconds.
- :param channels: Number of audio channels. Sulpported values are 1 and 2.
- :param sampling_rate: Audio sampling rate used in this frame. Valid sampling rates are 8000, 12000, 16000,
- 24000, or 48000.
- """
- toxav_err_send_frame = c_int()
- result = self.libtoxav.toxav_audio_send_frame(self._toxav_pointer, c_uint32(friend_number),
- cast(pcm, c_void_p),
- c_size_t(sample_count), c_uint8(channels),
- c_uint32(sampling_rate), byref(toxav_err_send_frame))
- toxav_err_send_frame = toxav_err_send_frame.value
- if toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['OK']:
- return bool(result)
- elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['NULL']:
- raise ArgumentError('The samples data pointer was NULL.')
- elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['FRIEND_NOT_FOUND']:
- raise ArgumentError('The friend_number passed did not designate a valid friend.')
- elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['FRIEND_NOT_IN_CALL']:
- raise RuntimeError('This client is currently not in a call with the friend.')
- elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['SYNC']:
- raise RuntimeError('Synchronization error occurred.')
- elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['INVALID']:
- raise ArgumentError('One of the frame parameters was invalid. E.g. the resolution may be too small or too '
- 'large, or the audio sampling rate may be unsupported.')
- elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['PAYLOAD_TYPE_DISABLED']:
- raise RuntimeError('Either friend turned off audio or video receiving or we turned off sending for the said'
- 'payload.')
- elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['RTP_FAILED']:
- RuntimeError('Failed to push frame through rtp interface.')
-
- def video_send_frame(self, friend_number, width, height, y, u, v):
- """
- Send a video frame to a friend.
-
- Y - plane should be of size: height * width
- U - plane should be of size: (height/2) * (width/2)
- V - plane should be of size: (height/2) * (width/2)
-
- :param friend_number: The friend number of the friend to which to send a video frame.
- :param width: Width of the frame in pixels.
- :param height: Height of the frame in pixels.
- :param y: Y (Luminance) plane data.
- :param u: U (Chroma) plane data.
- :param v: V (Chroma) plane data.
- """
- toxav_err_send_frame = c_int()
- result = self.libtoxav.toxav_video_send_frame(self._toxav_pointer, c_uint32(friend_number), c_uint16(width),
- c_uint16(height), c_char_p(y), c_char_p(u), c_char_p(v),
- byref(toxav_err_send_frame))
- toxav_err_send_frame = toxav_err_send_frame.value
- if toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['OK']:
- return bool(result)
- elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['NULL']:
- raise ArgumentError('One of Y, U, or V was NULL.')
- elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['FRIEND_NOT_FOUND']:
- raise ArgumentError('The friend_number passed did not designate a valid friend.')
- elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['FRIEND_NOT_IN_CALL']:
- raise RuntimeError('This client is currently not in a call with the friend.')
- elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['SYNC']:
- raise RuntimeError('Synchronization error occurred.')
- elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['INVALID']:
- raise ArgumentError('One of the frame parameters was invalid. E.g. the resolution may be too small or too '
- 'large, or the audio sampling rate may be unsupported.')
- elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['PAYLOAD_TYPE_DISABLED']:
- raise RuntimeError('Either friend turned off audio or video receiving or we turned off sending for the said'
- 'payload.')
- elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['RTP_FAILED']:
- RuntimeError('Failed to push frame through rtp interface.')
-
- # -----------------------------------------------------------------------------------------------------------------
- # A/V receiving
- # -----------------------------------------------------------------------------------------------------------------
-
- def callback_audio_receive_frame(self, callback, user_data):
- """
- Set the callback for the `audio_receive_frame` event. Pass None to unset.
-
- :param callback: Python function.
- Function for the audio_receive_frame callback. The callback can be called multiple times per single
- iteration depending on the amount of queued frames in the buffer. The received format is the same as in send
- function.
-
- Should take pointer (c_void_p) to ToxAV object,
- The friend number (c_uint32) of the friend who sent an audio frame.
- An array (c_uint8) of audio samples (sample_count * channels elements).
- The number (c_size_t) of audio samples per channel in the PCM array.
- Number (c_uint8) of audio channels.
- Sampling rate (c_uint32) used in this frame.
- pointer (c_void_p) to user_data
- :param user_data: pointer (c_void_p) to user data
- """
- c_callback = CFUNCTYPE(None, c_void_p, c_uint32, POINTER(c_uint8), c_size_t, c_uint8, c_uint32, c_void_p)
- self.audio_receive_frame_cb = c_callback(callback)
- self.libtoxav.toxav_callback_audio_receive_frame(self._toxav_pointer, self.audio_receive_frame_cb, user_data)
-
- def callback_video_receive_frame(self, callback, user_data):
- """
- Set the callback for the `video_receive_frame` event. Pass None to unset.
-
- :param callback: Python function.
- The function type for the video_receive_frame callback.
-
- Should take
- toxAV pointer (c_void_p) to ToxAV object,
- friend_number The friend number (c_uint32) of the friend who sent a video frame.
- width Width (c_uint16) of the frame in pixels.
- height Height (c_uint16) of the frame in pixels.
- y
- u
- v Plane data (POINTER(c_uint8)).
- The size of plane data is derived from width and height where
- Y = MAX(width, abs(ystride)) * height,
- U = MAX(width/2, abs(ustride)) * (height/2) and
- V = MAX(width/2, abs(vstride)) * (height/2).
- ystride
- ustride
- vstride Strides data (c_int32). Strides represent padding for each plane that may or may not be present. You must
- handle strides in your image processing code. Strides are negative if the image is bottom-up
- hence why you MUST abs() it when calculating plane buffer size.
- user_data pointer (c_void_p) to user_data
- :param user_data: pointer (c_void_p) to user data
- """
- c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint16, c_uint16, POINTER(c_uint8), POINTER(c_uint8),
- POINTER(c_uint8), c_int32, c_int32, c_int32, c_void_p)
- self.video_receive_frame_cb = c_callback(callback)
- self.libtoxav.toxav_callback_video_receive_frame(self._toxav_pointer, self.video_receive_frame_cb, user_data)
diff --git a/toxygen/toxav_enums.py b/toxygen/toxav_enums.py
deleted file mode 100644
index 3f3977a..0000000
--- a/toxygen/toxav_enums.py
+++ /dev/null
@@ -1,131 +0,0 @@
-TOXAV_ERR_NEW = {
- # The function returned successfully.
- 'OK': 0,
- # One of the arguments to the function was NULL when it was not expected.
- 'NULL': 1,
- # Memory allocation failure while trying to allocate structures required for the A/V session.
- 'MALLOC': 2,
- # Attempted to create a second session for the same Tox instance.
- 'MULTIPLE': 3,
-}
-
-TOXAV_ERR_CALL = {
- # The function returned successfully.
- 'OK': 0,
- # A resource allocation error occurred while trying to create the structures required for the call.
- 'MALLOC': 1,
- # Synchronization error occurred.
- 'SYNC': 2,
- # The friend number did not designate a valid friend.
- 'FRIEND_NOT_FOUND': 3,
- # The friend was valid, but not currently connected.
- 'FRIEND_NOT_CONNECTED': 4,
- # Attempted to call a friend while already in an audio or video call with them.
- 'FRIEND_ALREADY_IN_CALL': 5,
- # Audio or video bit rate is invalid.
- 'INVALID_BIT_RATE': 6,
-}
-
-TOXAV_ERR_ANSWER = {
- # The function returned successfully.
- 'OK': 0,
- # Synchronization error occurred.
- 'SYNC': 1,
- # Failed to initialize codecs for call session. Note that codec initiation will fail if there is no receive callback
- # registered for either audio or video.
- 'CODEC_INITIALIZATION': 2,
- # The friend number did not designate a valid friend.
- 'FRIEND_NOT_FOUND': 3,
- # The friend was valid, but they are not currently trying to initiate a call. This is also returned if this client
- # is already in a call with the friend.
- 'FRIEND_NOT_CALLING': 4,
- # Audio or video bit rate is invalid.
- 'INVALID_BIT_RATE': 5,
-}
-
-TOXAV_FRIEND_CALL_STATE = {
- # Set by the AV core if an error occurred on the remote end or if friend timed out. This is the final state after
- # which no more state transitions can occur for the call. This call state will never be triggered in combination
- # with other call states.
- 'ERROR': 1,
- # The call has finished. This is the final state after which no more state transitions can occur for the call. This
- # call state will never be triggered in combination with other call states.
- 'FINISHED': 2,
- # The flag that marks that friend is sending audio.
- 'SENDING_A': 4,
- # The flag that marks that friend is sending video.
- 'SENDING_V': 8,
- # The flag that marks that friend is receiving audio.
- 'ACCEPTING_A': 16,
- # The flag that marks that friend is receiving video.
- 'ACCEPTING_V': 32,
-}
-
-TOXAV_CALL_CONTROL = {
- # Resume a previously paused call. Only valid if the pause was caused by this client, if not, this control is
- # ignored. Not valid before the call is accepted.
- 'RESUME': 0,
- # Put a call on hold. Not valid before the call is accepted.
- 'PAUSE': 1,
- # Reject a call if it was not answered, yet. Cancel a call after it was answered.
- 'CANCEL': 2,
- # Request that the friend stops sending audio. Regardless of the friend's compliance, this will cause the
- # audio_receive_frame event to stop being triggered on receiving an audio frame from the friend.
- 'MUTE_AUDIO': 3,
- # Calling this control will notify client to start sending audio again.
- 'UNMUTE_AUDIO': 4,
- # Request that the friend stops sending video. Regardless of the friend's compliance, this will cause the
- # video_receive_frame event to stop being triggered on receiving a video frame from the friend.
- 'HIDE_VIDEO': 5,
- # Calling this control will notify client to start sending video again.
- 'SHOW_VIDEO': 6,
-}
-
-TOXAV_ERR_CALL_CONTROL = {
- # The function returned successfully.
- 'OK': 0,
- # Synchronization error occurred.
- 'SYNC': 1,
- # The friend_number passed did not designate a valid friend.
- 'FRIEND_NOT_FOUND': 2,
- # This client is currently not in a call with the friend. Before the call is answered, only CANCEL is a valid
- # control.
- 'FRIEND_NOT_IN_CALL': 3,
- # Happens if user tried to pause an already paused call or if trying to resume a call that is not paused.
- 'INVALID_TRANSITION': 4,
-}
-
-TOXAV_ERR_BIT_RATE_SET = {
- # The function returned successfully.
- 'OK': 0,
- # Synchronization error occurred.
- 'SYNC': 1,
- # The audio bit rate passed was not one of the supported values.
- 'INVALID_AUDIO_BIT_RATE': 2,
- # The video bit rate passed was not one of the supported values.
- 'INVALID_VIDEO_BIT_RATE': 3,
- # The friend_number passed did not designate a valid friend.
- 'FRIEND_NOT_FOUND': 4,
- # This client is currently not in a call with the friend.
- 'FRIEND_NOT_IN_CALL': 5,
-}
-
-TOXAV_ERR_SEND_FRAME = {
- # The function returned successfully.
- 'OK': 0,
- # In case of video, one of Y, U, or V was NULL. In case of audio, the samples data pointer was NULL.
- 'NULL': 1,
- # The friend_number passed did not designate a valid friend.
- 'FRIEND_NOT_FOUND': 2,
- # This client is currently not in a call with the friend.
- 'FRIEND_NOT_IN_CALL': 3,
- # Synchronization error occurred.
- 'SYNC': 4,
- # One of the frame parameters was invalid. E.g. the resolution may be too small or too large, or the audio sampling
- # rate may be unsupported.
- 'INVALID': 5,
- # Either friend turned off audio or video receiving or we turned off sending for the said payload.
- 'PAYLOAD_TYPE_DISABLED': 6,
- # Failed to push frame through rtp interface.
- 'RTP_FAILED': 7,
-}
diff --git a/toxygen/toxcore_enums_and_consts.py b/toxygen/toxcore_enums_and_consts.py
deleted file mode 100644
index a17d93e..0000000
--- a/toxygen/toxcore_enums_and_consts.py
+++ /dev/null
@@ -1,220 +0,0 @@
-TOX_USER_STATUS = {
- 'NONE': 0,
- 'AWAY': 1,
- 'BUSY': 2,
-}
-
-TOX_MESSAGE_TYPE = {
- 'NORMAL': 0,
- 'ACTION': 1,
-}
-
-TOX_PROXY_TYPE = {
- 'NONE': 0,
- 'HTTP': 1,
- 'SOCKS5': 2,
-}
-
-TOX_SAVEDATA_TYPE = {
- 'NONE': 0,
- 'TOX_SAVE': 1,
- 'SECRET_KEY': 2,
-}
-
-TOX_ERR_OPTIONS_NEW = {
- 'OK': 0,
- 'MALLOC': 1,
-}
-
-TOX_ERR_NEW = {
- 'OK': 0,
- 'NULL': 1,
- 'MALLOC': 2,
- 'PORT_ALLOC': 3,
- 'PROXY_BAD_TYPE': 4,
- 'PROXY_BAD_HOST': 5,
- 'PROXY_BAD_PORT': 6,
- 'PROXY_NOT_FOUND': 7,
- 'LOAD_ENCRYPTED': 8,
- 'LOAD_BAD_FORMAT': 9,
-}
-
-TOX_ERR_BOOTSTRAP = {
- 'OK': 0,
- 'NULL': 1,
- 'BAD_HOST': 2,
- 'BAD_PORT': 3,
-}
-
-TOX_CONNECTION = {
- 'NONE': 0,
- 'TCP': 1,
- 'UDP': 2,
-}
-
-TOX_ERR_SET_INFO = {
- 'OK': 0,
- 'NULL': 1,
- 'TOO_LONG': 2,
-}
-
-TOX_ERR_FRIEND_ADD = {
- 'OK': 0,
- 'NULL': 1,
- 'TOO_LONG': 2,
- 'NO_MESSAGE': 3,
- 'OWN_KEY': 4,
- 'ALREADY_SENT': 5,
- 'BAD_CHECKSUM': 6,
- 'SET_NEW_NOSPAM': 7,
- 'MALLOC': 8,
-}
-
-TOX_ERR_FRIEND_DELETE = {
- 'OK': 0,
- 'FRIEND_NOT_FOUND': 1,
-}
-
-TOX_ERR_FRIEND_BY_PUBLIC_KEY = {
- 'OK': 0,
- 'NULL': 1,
- 'NOT_FOUND': 2,
-}
-
-TOX_ERR_FRIEND_GET_PUBLIC_KEY = {
- 'OK': 0,
- 'FRIEND_NOT_FOUND': 1,
-}
-
-TOX_ERR_FRIEND_GET_LAST_ONLINE = {
- 'OK': 0,
- 'FRIEND_NOT_FOUND': 1,
-}
-
-TOX_ERR_FRIEND_QUERY = {
- 'OK': 0,
- 'NULL': 1,
- 'FRIEND_NOT_FOUND': 2,
-}
-
-TOX_ERR_SET_TYPING = {
- 'OK': 0,
- 'FRIEND_NOT_FOUND': 1,
-}
-
-TOX_ERR_FRIEND_SEND_MESSAGE = {
- 'OK': 0,
- 'NULL': 1,
- 'FRIEND_NOT_FOUND': 2,
- 'FRIEND_NOT_CONNECTED': 3,
- 'SENDQ': 4,
- 'TOO_LONG': 5,
- 'EMPTY': 6,
-}
-
-TOX_FILE_KIND = {
- 'DATA': 0,
- 'AVATAR': 1,
-}
-
-TOX_FILE_CONTROL = {
- 'RESUME': 0,
- 'PAUSE': 1,
- 'CANCEL': 2,
-}
-
-TOX_ERR_FILE_CONTROL = {
- 'OK': 0,
- 'FRIEND_NOT_FOUND': 1,
- 'FRIEND_NOT_CONNECTED': 2,
- 'NOT_FOUND': 3,
- 'NOT_PAUSED': 4,
- 'DENIED': 5,
- 'ALREADY_PAUSED': 6,
- 'SENDQ': 7,
-}
-
-TOX_ERR_FILE_SEEK = {
- 'OK': 0,
- 'FRIEND_NOT_FOUND': 1,
- 'FRIEND_NOT_CONNECTED': 2,
- 'NOT_FOUND': 3,
- 'DENIED': 4,
- 'INVALID_POSITION': 5,
- 'SENDQ': 6,
-}
-
-TOX_ERR_FILE_GET = {
- 'OK': 0,
- 'NULL': 1,
- 'FRIEND_NOT_FOUND': 2,
- 'NOT_FOUND': 3,
-}
-
-TOX_ERR_FILE_SEND = {
- 'OK': 0,
- 'NULL': 1,
- 'FRIEND_NOT_FOUND': 2,
- 'FRIEND_NOT_CONNECTED': 3,
- 'NAME_TOO_LONG': 4,
- 'TOO_MANY': 5,
-}
-
-TOX_ERR_FILE_SEND_CHUNK = {
- 'OK': 0,
- 'NULL': 1,
- 'FRIEND_NOT_FOUND': 2,
- 'FRIEND_NOT_CONNECTED': 3,
- 'NOT_FOUND': 4,
- 'NOT_TRANSFERRING': 5,
- 'INVALID_LENGTH': 6,
- 'SENDQ': 7,
- 'WRONG_POSITION': 8,
-}
-
-TOX_ERR_FRIEND_CUSTOM_PACKET = {
- 'OK': 0,
- 'NULL': 1,
- 'FRIEND_NOT_FOUND': 2,
- 'FRIEND_NOT_CONNECTED': 3,
- 'INVALID': 4,
- 'EMPTY': 5,
- 'TOO_LONG': 6,
- 'SENDQ': 7,
-}
-
-TOX_ERR_GET_PORT = {
- 'OK': 0,
- '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
-
-TOX_MAX_FRIEND_REQUEST_LENGTH = 1016
-
-TOX_MAX_MESSAGE_LENGTH = 1372
-
-TOX_MAX_NAME_LENGTH = 128
-
-TOX_MAX_STATUS_MESSAGE_LENGTH = 1007
-
-TOX_SECRET_KEY_SIZE = 32
-
-TOX_FILE_ID_LENGTH = 32
-
-TOX_HASH_LENGTH = 32
-
-TOX_MAX_CUSTOM_PACKET_SIZE = 1373
diff --git a/toxygen/toxencryptsave.py b/toxygen/toxencryptsave.py
deleted file mode 100644
index b579e21..0000000
--- a/toxygen/toxencryptsave.py
+++ /dev/null
@@ -1,74 +0,0 @@
-import libtox
-from ctypes import c_size_t, create_string_buffer, byref, c_int, ArgumentError, c_char_p, c_bool
-from toxencryptsave_enums_and_consts import *
-
-
-class ToxEncryptSave:
-
- def __init__(self):
- self.libtoxencryptsave = libtox.LibToxEncryptSave()
-
- def is_data_encrypted(self, data):
- """
- Checks if given data is encrypted
- """
- func = self.libtoxencryptsave.tox_is_data_encrypted
- func.restype = c_bool
- result = func(c_char_p(bytes(data)))
- return result
-
- def pass_encrypt(self, data, password):
- """
- Encrypts the given data with the given password.
-
- :return: output array
- """
- out = create_string_buffer(len(data) + TOX_PASS_ENCRYPTION_EXTRA_LENGTH)
- tox_err_encryption = c_int()
- self.libtoxencryptsave.tox_pass_encrypt(c_char_p(data),
- c_size_t(len(data)),
- c_char_p(bytes(password, 'utf-8')),
- c_size_t(len(password)),
- out,
- byref(tox_err_encryption))
- tox_err_encryption = tox_err_encryption.value
- if tox_err_encryption == TOX_ERR_ENCRYPTION['OK']:
- return out[:]
- elif tox_err_encryption == TOX_ERR_ENCRYPTION['NULL']:
- raise ArgumentError('Some input data, or maybe the output pointer, was null.')
- elif tox_err_encryption == TOX_ERR_ENCRYPTION['KEY_DERIVATION_FAILED']:
- raise RuntimeError('The crypto lib was unable to derive a key from the given passphrase, which is usually a'
- ' lack of memory issue. The functions accepting keys do not produce this error.')
- elif tox_err_encryption == TOX_ERR_ENCRYPTION['FAILED']:
- raise RuntimeError('The encryption itself failed.')
-
- def pass_decrypt(self, data, password):
- """
- Decrypts the given data with the given password.
-
- :return: output array
- """
- out = create_string_buffer(len(data) - TOX_PASS_ENCRYPTION_EXTRA_LENGTH)
- tox_err_decryption = c_int()
- self.libtoxencryptsave.tox_pass_decrypt(c_char_p(bytes(data)),
- c_size_t(len(data)),
- c_char_p(bytes(password, 'utf-8')),
- c_size_t(len(password)),
- out,
- byref(tox_err_decryption))
- tox_err_decryption = tox_err_decryption.value
- if tox_err_decryption == TOX_ERR_DECRYPTION['OK']:
- return out[:]
- elif tox_err_decryption == TOX_ERR_DECRYPTION['NULL']:
- raise ArgumentError('Some input data, or maybe the output pointer, was null.')
- elif tox_err_decryption == TOX_ERR_DECRYPTION['INVALID_LENGTH']:
- raise ArgumentError('The input data was shorter than TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes')
- elif tox_err_decryption == TOX_ERR_DECRYPTION['BAD_FORMAT']:
- raise ArgumentError('The input data is missing the magic number (i.e. wasn\'t created by this module, or is'
- ' corrupted)')
- elif tox_err_decryption == TOX_ERR_DECRYPTION['KEY_DERIVATION_FAILED']:
- raise RuntimeError('The crypto lib was unable to derive a key from the given passphrase, which is usually a'
- ' lack of memory issue. The functions accepting keys do not produce this error.')
- elif tox_err_decryption == TOX_ERR_DECRYPTION['FAILED']:
- raise RuntimeError('The encrypted byte array could not be decrypted. Either the data was corrupt or the '
- 'password/key was incorrect.')
diff --git a/toxygen/toxencryptsave_enums_and_consts.py b/toxygen/toxencryptsave_enums_and_consts.py
deleted file mode 100644
index cf795f8..0000000
--- a/toxygen/toxencryptsave_enums_and_consts.py
+++ /dev/null
@@ -1,29 +0,0 @@
-TOX_ERR_ENCRYPTION = {
- # The function returned successfully.
- 'OK': 0,
- # Some input data, or maybe the output pointer, was null.
- 'NULL': 1,
- # The crypto lib was unable to derive a key from the given passphrase, which is usually a lack of memory issue. The
- # functions accepting keys do not produce this error.
- 'KEY_DERIVATION_FAILED': 2,
- # The encryption itself failed.
- 'FAILED': 3
-}
-
-TOX_ERR_DECRYPTION = {
- # The function returned successfully.
- 'OK': 0,
- # Some input data, or maybe the output pointer, was null.
- 'NULL': 1,
- # The input data was shorter than TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes
- 'INVALID_LENGTH': 2,
- # The input data is missing the magic number (i.e. wasn't created by this module, or is corrupted)
- 'BAD_FORMAT': 3,
- # The crypto lib was unable to derive a key from the given passphrase, which is usually a lack of memory issue. The
- # functions accepting keys do not produce this error.
- 'KEY_DERIVATION_FAILED': 4,
- # The encrypted byte array could not be decrypted. Either the data was corrupt or the password/key was incorrect.
- 'FAILED': 5,
-}
-
-TOX_PASS_ENCRYPTION_EXTRA_LENGTH = 80
diff --git a/toxygen/toxes.py b/toxygen/toxes.py
deleted file mode 100644
index 5b7282f..0000000
--- a/toxygen/toxes.py
+++ /dev/null
@@ -1,28 +0,0 @@
-import util
-import toxencryptsave
-
-
-class ToxES(util.Singleton):
-
- def __init__(self):
- super().__init__()
- self._toxencryptsave = toxencryptsave.ToxEncryptSave()
- self._passphrase = None
-
- def set_password(self, passphrase):
- self._passphrase = passphrase
-
- def has_password(self):
- return bool(self._passphrase)
-
- def is_password(self, password):
- return self._passphrase == password
-
- def is_data_encrypted(self, data):
- return len(data) > 0 and self._toxencryptsave.is_data_encrypted(data)
-
- def pass_encrypt(self, data):
- return self._toxencryptsave.pass_encrypt(data, self._passphrase)
-
- def pass_decrypt(self, data):
- return self._toxencryptsave.pass_decrypt(data, self._passphrase)
diff --git a/toxygen/updater.py b/toxygen/updater.py
deleted file mode 100644
index 762892a..0000000
--- a/toxygen/updater.py
+++ /dev/null
@@ -1,110 +0,0 @@
-import util
-import os
-import settings
-import platform
-import urllib
-from PyQt5 import QtNetwork, QtCore
-import subprocess
-
-
-def connection_available():
- try:
- urllib.request.urlopen('http://216.58.192.142', timeout=1) # google.com
- return True
- except:
- return False
-
-
-def updater_available():
- if is_from_sources():
- return os.path.exists(util.curr_directory() + '/toxygen_updater.py')
- elif platform.system() == 'Windows':
- return os.path.exists(util.curr_directory() + '/toxygen_updater.exe')
- else:
- return os.path.exists(util.curr_directory() + '/toxygen_updater')
-
-
-def check_for_updates():
- current_version = util.program_version
- major, minor, patch = list(map(lambda x: int(x), current_version.split('.')))
- versions = generate_versions(major, minor, patch)
- for version in versions:
- if send_request(version):
- return version
- return None # no new version was found
-
-
-def is_from_sources():
- return __file__.endswith('.py')
-
-
-def test_url(version):
- return 'https://github.com/toxygen-project/toxygen/releases/tag/v' + version
-
-
-def get_url(version):
- if is_from_sources():
- return 'https://github.com/toxygen-project/toxygen/archive/v' + version + '.zip'
- else:
- if platform.system() == 'Windows':
- name = 'toxygen_windows.zip'
- elif util.is_64_bit():
- name = 'toxygen_linux_64.tar.gz'
- else:
- name = 'toxygen_linux.tar.gz'
- return 'https://github.com/toxygen-project/toxygen/releases/download/v{}/{}'.format(version, name)
-
-
-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]
- else:
- return ['./toxygen_updater', url, version]
-
-
-def download(version):
- os.chdir(util.curr_directory())
- url = get_url(version)
- params = get_params(url, version)
- print('Updating Toxygen')
- util.log('Updating Toxygen')
- try:
- subprocess.Popen(params)
- except Exception as ex:
- util.log('Exception: running updater failed with ' + str(ex))
-
-
-def send_request(version):
- s = settings.Settings.get_instance()
- netman = QtNetwork.QNetworkAccessManager()
- proxy = QtNetwork.QNetworkProxy()
- if s['proxy_type']:
- proxy.setType(QtNetwork.QNetworkProxy.Socks5Proxy if s['proxy_type'] == 2 else QtNetwork.QNetworkProxy.HttpProxy)
- proxy.setHostName(s['proxy_host'])
- proxy.setPort(s['proxy_port'])
- netman.setProxy(proxy)
- url = test_url(version)
- try:
- request = QtNetwork.QNetworkRequest()
- request.setUrl(QtCore.QUrl(url))
- reply = netman.get(request)
- while not reply.isFinished():
- QtCore.QThread.msleep(1)
- QtCore.QCoreApplication.processEvents()
- attr = reply.attribute(QtNetwork.QNetworkRequest.HttpStatusCodeAttribute)
- return attr is not None and 200 <= attr < 300
- except Exception as ex:
- util.log('TOXYGEN UPDATER ERROR: ' + str(ex))
- return False
-
-
-def generate_versions(major, minor, patch):
- new_major = '.'.join([str(major + 1), '0', '0'])
- new_minor = '.'.join([str(major), str(minor + 1), '0'])
- new_patch = '.'.join([str(major), str(minor), str(patch + 1)])
- return new_major, new_minor, new_patch
diff --git a/toxygen/util.py b/toxygen/util.py
deleted file mode 100644
index 6157775..0000000
--- a/toxygen/util.py
+++ /dev/null
@@ -1,107 +0,0 @@
-import os
-import time
-import shutil
-import sys
-import re
-
-
-program_version = '0.4.4'
-
-
-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__))
-
-
-def curr_time():
- return time.strftime('%H:%M')
-
-
-def copy(src, dest):
- if not os.path.exists(dest):
- os.makedirs(dest)
- src_files = os.listdir(src)
- for file_name in src_files:
- full_file_name = os.path.join(src, file_name)
- if os.path.isfile(full_file_name):
- shutil.copy(full_file_name, dest)
- else:
- copy(full_file_name, os.path.join(dest, file_name))
-
-
-def remove(folder):
- if os.path.isdir(folder):
- shutil.rmtree(folder)
-
-
-def convert_time(t):
- offset = time.timezone + time_offset() * 60
- sec = int(t) - offset
- m, s = divmod(sec, 60)
- h, m = divmod(m, 60)
- d, h = divmod(h, 24)
- return '%02d:%02d' % (h, m)
-
-
-@cached
-def time_offset():
- hours = int(time.strftime('%H'))
- minutes = int(time.strftime('%M'))
- sec = int(time.time()) - time.timezone
- m, s = divmod(sec, 60)
- h, m = divmod(m, 60)
- d, h = divmod(h, 24)
- result = hours * 60 + minutes - h * 60 - m
- return result
-
-
-def append_slash(s):
- if len(s) and s[-1] not in ('\\', '/'):
- s += '/'
- return s
-
-
-@cached
-def is_64_bit():
- return sys.maxsize > 2 ** 32
-
-
-def is_re_valid(regex):
- try:
- re.compile(regex)
- except re.error:
- return False
- else:
- return True
-
-
-class Singleton:
- _instance = None
-
- def __init__(self):
- self.__class__._instance = self
-
- @classmethod
- def get_instance(cls):
- return cls._instance
diff --git a/toxygen/widgets.py b/toxygen/widgets.py
deleted file mode 100644
index b63deb0..0000000
--- a/toxygen/widgets.py
+++ /dev/null
@@ -1,166 +0,0 @@
-from PyQt5 import QtCore, QtGui, QtWidgets
-
-
-class DataLabel(QtWidgets.QLabel):
- """
- Label with elided text
- """
- def setText(self, text):
- text = ''.join('\u25AF' if len(bytes(c, 'utf-8')) >= 4 else c for c in text)
- metrics = QtGui.QFontMetrics(self.font())
- text = metrics.elidedText(text, QtCore.Qt.ElideRight, self.width())
- super().setText(text)
-
-
-class ComboBox(QtWidgets.QComboBox):
-
- def __init__(self, *args):
- super().__init__(*args)
- self.view().setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding)
-
-
-class CenteredWidget(QtWidgets.QWidget):
-
- def __init__(self):
- super(CenteredWidget, self).__init__()
- self.center()
-
- def center(self):
- qr = self.frameGeometry()
- cp = QtWidgets.QDesktopWidget().availableGeometry().center()
- qr.moveCenter(cp)
- self.move(qr.topLeft())
-
-
-class LineEdit(QtWidgets.QLineEdit):
-
- def __init__(self, parent=None):
- super(LineEdit, self).__init__(parent)
-
- def contextMenuEvent(self, event):
- menu = create_menu(self.createStandardContextMenu())
- menu.exec_(event.globalPos())
- del menu
-
-
-class QRightClickButton(QtWidgets.QPushButton):
- """
- Button with right click support
- """
-
- rightClicked = QtCore.pyqtSignal()
-
- def __init__(self, parent):
- super(QRightClickButton, self).__init__(parent)
-
- def mousePressEvent(self, event):
- if event.button() == QtCore.Qt.RightButton:
- self.rightClicked.emit()
- else:
- super(QRightClickButton, self).mousePressEvent(event)
-
-
-class RubberBand(QtWidgets.QRubberBand):
-
- def __init__(self):
- super(RubberBand, self).__init__(QtWidgets.QRubberBand.Rectangle, None)
- self.setPalette(QtGui.QPalette(QtCore.Qt.transparent))
- self.pen = QtGui.QPen(QtCore.Qt.blue, 4)
- self.pen.setStyle(QtCore.Qt.SolidLine)
- self.painter = QtGui.QPainter()
-
- def paintEvent(self, event):
-
- self.painter.begin(self)
- self.painter.setPen(self.pen)
- self.painter.drawRect(event.rect())
- 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
- """
- for action in menu.actions():
- text = action.text()
- if 'Link Location' in text:
- text = text.replace('Copy &Link Location',
- QtWidgets.QApplication.translate("MainWindow", "Copy link location"))
- elif '&Copy' in text:
- text = text.replace('&Copy', QtWidgets.QApplication.translate("MainWindow", "Copy"))
- elif 'All' in text:
- text = text.replace('Select All', QtWidgets.QApplication.translate("MainWindow", "Select all"))
- elif 'Delete' in text:
- text = text.replace('Delete', QtWidgets.QApplication.translate("MainWindow", "Delete"))
- elif '&Paste' in text:
- text = text.replace('&Paste', QtWidgets.QApplication.translate("MainWindow", "Paste"))
- elif 'Cu&t' in text:
- text = text.replace('Cu&t', QtWidgets.QApplication.translate("MainWindow", "Cut"))
- elif '&Undo' in text:
- text = text.replace('&Undo', QtWidgets.QApplication.translate("MainWindow", "Undo"))
- elif '&Redo' in text:
- text = text.replace('&Redo', QtWidgets.QApplication.translate("MainWindow", "Redo"))
- else:
- menu.removeAction(action)
- continue
- action.setText(text)
- return menu
-
-
-class MultilineEdit(CenteredWidget):
-
- def __init__(self, title, text, save):
- super(MultilineEdit, self).__init__()
- self.resize(350, 200)
- self.setMinimumSize(QtCore.QSize(350, 200))
- self.setMaximumSize(QtCore.QSize(350, 200))
- self.setWindowTitle(title)
- self.edit = QtWidgets.QTextEdit(self)
- self.edit.setGeometry(QtCore.QRect(0, 0, 350, 150))
- self.edit.setText(text)
- self.button = QtWidgets.QPushButton(self)
- self.button.setGeometry(QtCore.QRect(0, 150, 350, 50))
- self.button.setText(QtWidgets.QApplication.translate("MainWindow", "Save"))
- self.button.clicked.connect(self.button_click)
- self.center()
- self.save = save
-
- def button_click(self):
- self.save(self.edit.toPlainText())
- self.close()